Compare commits

...

5 Commits

  1. 5
      .editorconfig
  2. 22
      README.md
  3. 0
      docs/.gdignore
  4. 66
      docs/abilities.md
  5. 74
      docs/characters.md
  6. 53
      docs/forms.md
  7. BIN
      docs/images/2024-10-04.png
  8. BIN
      docs/images/inspiration/adventurers_glade_by_sully.png
  9. BIN
      docs/images/inspiration/hocu_by_max_hancock.png
  10. 40
      docs/overview.md
  11. 13
      docs/quests.md
  12. 31
      docs/story.md
  13. 78
      docs/zones.md
  14. 68
      player/CameraController.cs
  15. 21
      player/character.tscn
  16. 1
      terrain/Terrain+Editing.cs
  17. 15
      terrain/Terrain.cs
  18. 6
      terrain/TerrainChunk.cs
  19. 3
      terrain/Tile.cs
  20. 20
      utility/GodotExtensions.cs
  21. 12
      utility/MathExtensions.cs

@ -7,3 +7,8 @@ indent_size = 4
indent_style = tab indent_style = tab
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
[*.md]
indent_size = 2
indent_style = space
trim_trailing_whitespace = false

@ -0,0 +1,22 @@
# SlimeDream
.. is a yet to be properly named game written in Godot / C# that is inspired by a few actual dreams I've had. You get to play as a human / slime-kin hybrid to rescue your mother from prison, exploring 7 unique zones, unlocking abilities, different forms, making friends / enemies, and more, on the way.
Development is still in the very early stages, with a lot of mechanics to be figured out, characters to develop, progression to be nailed down, assets to be made, etc.
If you're interested to learn more, check out [the overview](docs/overview.md).
![Screenshot](docs/images/2024-10-04.png)
If you want to follow the development, here's how:
- Follow me on fediverse for updates:
[@copygirl @ fedi.anarchy.moe] for infrequent but more development focused updates.
[@copygirl @ vt.social] for stream announcements and summaries.
(But also lots more unrelated posts related to VTubing.)
- Follow me on [Twitch] for those gamedev streams.
(I'm also known to sometimes play games, however.)
[@copygirl @ fedi.anarchy.moe]: https://fedi.anarchy.moe/copygirl
[@copygirl @ vt.social]: https://vt.social/copygirl
[Twitch]: https://twitch.tv/copygirl

@ -0,0 +1,66 @@
# Abilities
This document is just going to be for a bunch of ideas that we're gonna throw at the wall and see what sticks. Ideally each ability will be unique enough, perhaps have multiple uses both inside and outside of combat, and be used for progression.
## Dissolve food
(Unlocked by default.)
Digests [held](#hold) item over time to regain health and possibly gain temporary effects.
### Upgrade: Dissolve materials
- Can digest various materials to gain temporary effects.
- Unlocks melting down wooden barricades, doors and similar.
### Upgrade: Dissolve metals
- Can digest precious metals to gain temporary effects, such as higher defense.
- Unlocks melting down metal locks.
## Hold
(Unlocked by default.)
Allows you to hold onto a single item at a time, using or dropping it later. Any items held onto like this will be dropped when squeezing through tight spots in [blob form](forms.md#blob).
With enough body mass, the amount of items that can be hold onto, increases, unlocking access to the inventory system. Some equipment such as clothes in [humanoid](forms.md#humanoid) or [human](forms.md#human) forms can also increase the maximum inventory size.
### Upgrade: Memory
- Allows certain items to be assimilated into your body, depending on [dissolve ability](#dissolve-food).
- They can be re-created at any time, essentially increasing your inventory dramatically.
- These items don't get left behind when squeezing through tight spots in [blob form](forms.md#blob).
- (Unsure about the name of this ability.)
## Splash
Ejects some of your mass to fire out a ball of slime that can hit, damage and push back enemies, or objects. This reduces your health and requires you to pick it back up, or eat food to get it back. May also be used to lure a target away by making a distracting sound?
### Upgrade: Shard
- Requires you to have recently eaten metal, or have the ice upgrade?
- Allows you to cut soft material such as ropes.
- Chance to cause enemies to bleed, causing damage over time. Can stack.
- Can kill if used excessively on a low-health enemy.
## Heal
Allows you to heal other creatures, and humans.
- Similar to [poison](#poison), accumulate healing to be able to use this?
- If they have been recently knocked out, they typically stay that way.
## Poison
The player is resistant to poisons by default, but this ability will passively make them completely invulnurable. Poison can be accumulated by eating poisonous plants, foods, or other items. This is required to use the active part of this ability.
- When active, your next attack, melee or ranged, will poison the enemy.
- Can be cancelled by using the ability a second time.
- Poison deals damage over time.
- Can kill the target if not treated.
## Drain
Allows the player to drain energy from downed or killed enemies to regain health.
- This is considered a "dark" ability, affecting the player's status.
More than just regularly killing and enemy, that is.
- Can also be used to remove poison without doing harm?

@ -0,0 +1,74 @@
# Characters
## Player Character
- Slime-kin hybrid born from a human mother.
- Born without slime core, and therefore has human appearance.
- Developed much more quickly, may be 2 years of age but may look 12.
- Adopted by somewhat abusive family. Only parents know of the real circumstances.
- Doesn't know they are a slime-kin, only that they are treated differently by many.
- Being gaslit into believing to be as old as they appear.
- Being brought up as a boy despite being technically sexless.
- Has a hazy memory of their past due to trauma.
- Unlike the other kids, does not get to celebrate their birthday.
- Not allowed in school, has not been taught to read or write, with small exceptions.
- Takes care of the house, doing chores, while everyone else is gone.
- Since recently, is also sent to the market to get groceries.
### Mother
- Used to be a skilled (monster) huntress.
- Stopped the senseless killing, since learning many monsters are actually pretty intelligent.
- Settled down with a slime-kin, who's disguised as a human to be able to spend time with her in her village.
- Didn't know they could have a child, as human-monster offspring were more of a thing of fairy tales.
(Such a fairy tale could be told as a cutscene, or maybe be an optional book the player could read.)
- Other parent fled, unsure what'll happen, due to fears of being found out and attacked by humans.
- She decided to stick around, believing in her community, but they grew very suspicious of her.
- After birth, is taken captive and punished with prison for life, and her monster child sentenced to death.
- Those in charge of child's fate couldn't go that far, feeling remorse for what happened to the mother.
## Adoptive Family
### Adoptive Mother
- Strict, bossy
- Works in the townhall, helping out the major.
- Pushes back any attempt of PC to build emotional bond.
- Secretly scared of the PC, keeps them away from her children.
### Adoptive Father
- Sensible, busy
- Works as a woodcutter, but is available for any odd job.
- Tries to include PC in family activities.
### Children
- Told not to speak about their adoptive sibling to anyone.
- Two youngest spend most time at a school during the day.
#### Youngest
- Boy, 8
- Jealous of older siblings.
- Especially jealous of PC for growing up so fast.
- Will get physical, especially now that PC is bigger than him.
#### Middle Child
- Girl, 14
- Rebellious
- Protective of PC but in a very childish way.
- Plays dress-up with PC, especially since they fit into her own (older) clothes.
- Gets PC into trouble, for example by inviting them outside when they're not supposed to.
#### Oldest
- Girl, 17
- Wants to become a legendary monster huntress.
- Thinks she understands what's going on, having heard the rumors about.
- Knows about PC's mother and is disappointed in the "famous" huntress from her town.
- Straight up ignores the PC's existance when forced to spend family time together
- Barely hangs around family, focuses on training and helping out with monster hunts.
- Is frustrated that barely anyone thinks she's capable. (She might not be.)
- Likely will be an enemy the PC will face in the future.

@ -0,0 +1,53 @@
# Forms
As the player progresses through the game, they unlock differents forms, available to them due to their slimekin nature. Different forms may have different natural abilities or movement techniques only available to them in certain forms.
- Greater forms require a certain amount of health / mass to maintain?
- Some forms may be entered on low health, but aren't maintainable for long.
## Blob
You start out in this form, after being chased out of the starting village.
- High friction and sticky. Can't climb, though.
- Can get through tight areas or grates with some effort.
- No inherent combat abilities?
## Bouncy
- Can bounce up to higher ledges.
- Higher maximum movement speed, though difficult to control.
- Used to repel yourself after attacking an enemy, possibly staggering them.
## Humanoid
- Can use human-made items and equipment.
- Loses equipment when switching to non-human(oid) form, unless [Memory](abilities.md#memory) is available.
- Can communicate with humans instead of just monsters.
- Can freely switch between fem, masc and maybe androgynous appearance.
## Monster
- Do monster quests to unlock different appearances?
- Another way to get these could be killing and [draining](abilities.md#drain) a champion beast.
- Once [Human form](#human) is unlocked, can also pick anthro forms of these.
- Different forms can have unique ability or combat moves.
- May be used to intimidate.
## Human
- Grants access to full customization, change appearance at any time.
- Switch between child, teen and adult sizes.
- Choose colors for skin, hair and eyes.
- Choose from any NPCs hair style.
- Choose minor details like freckles, blush, scar, moles.
- Appearance presets can be saved and switched out easily.
- Appears to everyone as a human, therefore:
- Humans will by default be friendly towards the PC.
- Monsters by default will attack or flee from them.
Exactly match an NPC's appearance and outfit to impersonate them. Other than the [imposter quest](quests.md#imposter), this mechanic should be optional and the player themself should realize that the mechanic can be applied in other situations, hopefully causing them to react in a "woah this works?!" sort of way.
- Could grant you access to areas you would otherwise be barred from.
- Imperfect impersonations might still work, but can attract suspicion.
- Can't impersonate voice, so having a real conversation is out of the question.

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

@ -0,0 +1,40 @@
# Overview
Please note that anything outside of this overview obviously includes **MASSIVE SPOILERS** as these will be used to keep track of goals, abilities, progression and more for the involved team members and people who may potentially be interested in contributing (or are just nosy).
- [Zones](zones.md)
- [Forms](forms.md)
- [Abilities](abilities.md)
- [Characters](characters.md)
- [Story](story.md)
- [Quests](quests.md)
- Progression
## Gameplay
- Third person view with ability to go first person to look around.
- There's 7 main [zones](zones.md) to explore, each with their sub-areas.
- Travel between zones at the beginning is limited, either by movement ability checks, combat checks or quest checks. There should however be multiple ways to progress.
- Health is, in a way, correlated to "usable mass".
- Other than combat, also affects certain abilities.
- For example, some forms require a certain mass to maintain.
- Metroidvania-style progression? Acquire abilities and key items in key areas, through exploration, combat, and interaction with NPCs. Will have to go through things with the export in the field, NolamiAmada.
- Can be a pacifist, align with the human or monster faction, or become the ultimate evil!
- As such, certain actions and choices can affect your reputation, and "karma".
- Different endings depending on these choices?
### Combat
Enemies that are defeated don't automatically die. But the player has the ability to finish them off if they so choose. Certain abilities can kill enemies after a battle, such as bleed and poison.
## Style Inspiration
I would like to go for a simplistic low-poly style with pixelated textures. Hopefully this will allow us to create enough assets and variation without too too much effort, and allow the less artistic people to make adjustments?
`TODO:` Find some inspiration for the different zones and set some style guidelines.
![](images/inspiration/adventurers_glade_by_sully.png)
Credit: ["Adventurer's Glade" by Brendan 'Sully' Sullivan](https://sketchfab.com/3d-models/adventurers-glade-lowpoly-pixel-scene-cdcdeaaa233f4a73a8bafcd1829a4e6e)
![](images/inspiration/hocu_by_max_hancock.png)
Credit: ["Hocu" by Max Hancock](http://www.maxhancock.net/3d.html)

@ -0,0 +1,13 @@
# Quests
## Imposter
Optional side quest in which you are introduced to the impersonation mechanic, that unlocks along with the appearance customization that comes with your [human form](forms.md#human).
An NPC asks you to mimic a friend or relative. They point out to you any mistake you make, ask you to fetch (likely steal) the same outfit they're wearing, note that you're not able to imitate their voice well enough, and then send you off to impersonate them, likely to get an item from another NPC to finish the quest.
## Birthday
Bit of a joke quest, but could also reveal some information about the PC's lack of birthdays, maybe their real age as they try to remember how many of their adoptive family's birthdays they remember experiencing.
You're invited to a kid's birthday party. As they blow out the birthday candles, their model changes to that of a teen. They probably say something funny, maybe along the lines of they're now allowed to do or eat / drink something they weren't before. (Even better if it's a game mechanic or behavior children are never seen doing.)

@ -0,0 +1,31 @@
# Story
See [Player Character](characters.md#player-character) for background.
## Prologue
- Starts out in [village](zones.md#village) with adoptive family.
- PC is told to get groceries for dinner that day.
- After finishing chores, they pick up grocery list, barely recognizing the words.
- Piece of candy is drawn at the bottom, with "for you" in different handwriting (by adoptive father).
- Excited that they can not only have candy, but pick it out themself, the PC heads off.
- Store that usually has the candy is not present today.
- Monster shop sells dead slime cores that look like candy, so not knowing what it is, PC picks one.
(The text of anything the PC doesn't understand should be unreadable / glitchy to the player.)
- PC returns home, starts preparing dinner.
(Cooking minigame introduction? That is, if cooking is going to be a mechanic.)
- They finish the meal and leave it on the stove on low heat.
- Curious about their "candy", they try it. Biting does't work. Putting into mouth to dissolve doesn't either.
- Something happens that surprises the PC, and they reflexively swallow.
- Their body realizes it's been missing this "core" piece, starts adapting it.
- PC starts to feel sick and dizzy.
- ???
- PC turns into large-ish, colored slime blob.
- Is assumed to be a monster that somehow got into town.
- Villagers hunt them down and out of town, trying to kill them.
- Only way to escape is to jump into a small but fast flowing river.
- Villagers let go because slimes don't fare well in water.
- PC is partially resistant to the effects of water due to being a hybrid.
- Managed to escape river downstream, finally being able to regain conciousness proper.
- Ends up at the [farms zone](zones.md#farms), where they'll be able to regain their strength.
- Has lost a good amount of mass. Initially stuck in [blob form](forms.md#blob).

@ -0,0 +1,78 @@
# Zones
The game is split up into multiple distinct thematical zones, each with their own sub-areas. Freely traveling between areas, but especially zones, may require you to have unlocked an ability or finished a quest. Ideally there will be different available methods to move between zones.
- 🏘 [Village](#village)
- 🥕 [Farms](#farms)
- 🌲 [Forest](#forest)
- 🏖 [Beach](#beach)
- 💧 [Swamp](#swamp)
- ⛰ [Mountain](#mountain)
- 🏰 [Capital](#capital)
```
🏰-----⛰
#### /#\ # = River
/ \## # \ | = Connection
🥕---🌲-#--🏘
\ #### /
\#/ \ /
🏖-----💧
```
For each "animal" in a zone, which is essentially just a low-level or domesticated version of a monster, there can also exist higher-level variants that can have humanoid, anthropomorphic forms, similar to the player character.
## Village
This is where you start out in the beginning of the game, before getting chased out. When returning here, should be a mid-game zone focusing on learning more about the story and end-game goal. The player has the option of reconnecting with the villagers and their adoptive family.
- Home to humans, pets.
## Farms
This is where you end up after getting chased out of the village, allowing you to regain your strength by sneaking around farms, stealing crops. The goal here is to eat enough varied crops to unlock [bouncy form](forms.md#bouncy). Farmers then request help from the capital, causing guards to patrol the area, making it more dangerous to stay.
- Home to humans, sheep, cows, cats.
- Sneak around and into farmers' buildings for shenanigans.
## Forest
Central connecting zone. Lots of progression checks to see if you can enter the other zones from here.
- Home to wolves, wild pigs, rabbits, deer, owls.
- Low to mid level monsters in the wild that get hunted by humans.
- Occasional camps of monster hunters, some abandoned.
- Trading outpost with decent security.
- Monster camps with champion beasts (anthro variants) defending key areas.
Very territorial, defensive, but typically don't directly go after humans.
## Beach
Laid back zone, most humans are friendly towards harmless monsters.
- Home to humans, squids, sharks.
- The squids' design miiight be a little inspired by Splatoon?
- Unlock ranged [splat ability](abilities.md#splat) here?
- Beach episode with campfire party at night!
- Unlock [humanoid form](forms.md#humanoid) here?
- Befriend either a fisher or shark to get you to an island?
## Swamp
- Home to slimes, frogs.
- Witch character?
- Could unlock [poison ability](abilities.md#poison) here?
- Potion brewing? Or just buy them?
## Mountain
End-game zone that has a lot of stronger monsters and hunters to keep them in check.
- Home to foxes, goats, lizards.
- Features cave(s).
## Capital
End-game zone requiring lots of abilities, quests, or combat, to achieve the end game goal.
- Home to humans.

@ -1,17 +1,31 @@
public partial class CameraController : SpringArm3D public partial class CameraController : Camera3D
{ {
[ExportCategory("Follow")]
[Export] public float FollowDistance { get; set; } = 1.2f;
[Export] public float FollowYOffset { get; set; } = 1.0f;
[Export] public float FollowSmoothing { get; set; } = 12.0f;
[ExportCategory("Rotation")]
[Export] public Vector2 MouseSensitivity { get; set; } = new(0.2f, 0.2f); // Degrees per pixel. [Export] public Vector2 MouseSensitivity { get; set; } = new(0.2f, 0.2f); // Degrees per pixel.
[Export] public float PitchMinimum { get; set; } = -90; [Export] public float PitchMinimum { get; set; } = 20;
[Export] public float PitchMaximum { get; set; } = -25; [Export] public float PitchMaximum { get; set; } = 65;
[Export] public float PitchSmoothing { get; set; } = 12.0f;
// FIXME: Fix the "snappyness" when moving slowly.
// TODO: Add a "soft" minimum / maximum for pitch.
// TODO: Gradually return to maximum spring length. // TODO: Gradually return to maximum spring length.
// TODO: Turn player transparent as the camera moves closer. // TODO: Turn player transparent as the camera moves closer.
public static bool IsMouseCaptured public static bool IsMouseCaptured
=> Input.MouseMode == Input.MouseModeEnum.Captured; => Input.MouseMode == Input.MouseModeEnum.Captured;
Node3D _player;
Vector3 _smoothPlayerPos;
public override void _Ready()
{
_player = this.GetParentOrThrow<Node3D>();
_smoothPlayerPos = _player.GlobalPosition;
Transform = _player.GlobalTransform.Translated(new(0.0f, FollowYOffset, 0.0f));
}
public override void _Input(InputEvent ev) public override void _Input(InputEvent ev)
{ {
if (IsMouseCaptured && ev.IsActionPressed("ui_cancel")) { if (IsMouseCaptured && ev.IsActionPressed("ui_cancel")) {
@ -37,11 +51,45 @@ public partial class CameraController : SpringArm3D
void ApplyRotation(Vector2 delta) void ApplyRotation(Vector2 delta)
{ {
delta *= Tau / 360; // degrees to radians var (pitch, yaw, _) = RotationDegrees;
var (pitch, yaw, _) = Rotation;
yaw += delta.X;
pitch += delta.Y; pitch += delta.Y;
pitch = Clamp(pitch, DegToRad(PitchMinimum), DegToRad(PitchMaximum)); yaw += delta.X;
Rotation = new(pitch, yaw, 0); RotationDegrees = new(pitch, yaw, 0);
}
public override void _PhysicsProcess(double delta)
{
_smoothPlayerPos = _smoothPlayerPos.Damp(_player.GlobalPosition, FollowSmoothing, delta);
var target = _smoothPlayerPos
+ Basis.Z * FollowDistance
+ Vector3.Up * FollowYOffset;
Position = OffsetRayIntersection(_smoothPlayerPos, target, 0.2f);
}
Vector3 OffsetRayIntersection(Vector3 from, Vector3 to, float margin)
{
const PhysicsLayer CollisionMask = PhysicsLayer.Terrain
| PhysicsLayer.Objects;
var query = PhysicsRayQueryParameters3D.Create(from, to, (uint)CollisionMask);
var result = GetWorld3D().DirectSpaceState.IntersectRay(query);
if (result.Count > 0) {
var hit = (Vector3)result["position"];
var safeDistance = Max(0, from.DistanceTo(hit) - margin);
return from + (to - from).Normalized() * safeDistance;
} else {
// No intersection occured,
return to;
}
}
public override void _Process(double delta)
{
var pitch = RotationDegrees.X;
var (min, max) = (-PitchMaximum, -PitchMinimum);
if (pitch < min) pitch = pitch.Damp(min, PitchSmoothing, delta);
if (pitch > max) pitch = pitch.Damp(max, PitchSmoothing, delta);
RotationDegrees = RotationDegrees with { X = pitch };
} }
} }

@ -20,25 +20,18 @@ collision_mask = 3
floor_max_angle = 0.698132 floor_max_angle = 0.698132
[node name="MeshInstance3D" type="MeshInstance3D" parent="."] [node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.15, 0)
mesh = SubResource("SphereMesh_7ljg8") mesh = SubResource("SphereMesh_7ljg8")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."] [node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.2, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0)
shape = SubResource("SphereShape3D_6qbb2") shape = SubResource("SphereShape3D_6qbb2")
[node name="MovementController" type="Node" parent="."] [node name="Camera" type="Camera3D" parent="."]
script = ExtResource("1_akh08") top_level = true
[node name="CameraArm" type="SpringArm3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.766044, 0.642788, 0, -0.642788, 0.766044, 0, 0.1, 0)
collision_mask = 3
spring_length = 2.0
margin = 0.05
script = ExtResource("2_2din1")
[node name="Camera" type="Camera3D" parent="CameraArm"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
current = true current = true
size = 4.5 size = 4.5
far = 500.0 far = 500.0
script = ExtResource("2_2din1")
[node name="MovementController" type="Node" parent="."]
script = ExtResource("1_akh08")

@ -1,5 +1,4 @@
using System.IO; using System.IO;
using System.Runtime.InteropServices;
public partial class Terrain public partial class Terrain
{ {

@ -62,12 +62,19 @@ public partial class Terrain
SetTexture(tile.TexturePrimary, tile.TextureSecondary, tile.TextureBlend); SetTexture(tile.TexturePrimary, tile.TextureSecondary, tile.TextureBlend);
var sorted = new (Corner Corner, float Height)[] {
(Corner.TopLeft , tile.Height.TopLeft ),
(Corner.TopRight , tile.Height.TopRight ),
(Corner.BottomRight, tile.Height.BottomRight),
(Corner.BottomLeft , tile.Height.BottomLeft ),
};
Array.Sort(sorted, (a, b) => a.Height.CompareTo(b.Height));
// Find the "ideal way" to split the quad for the tile into two triangles. // Find the "ideal way" to split the quad for the tile into two triangles.
// This is done by finding the corner with the least variance between its neighboring corners. // This is done by finding the corner with the least variance between its neighboring corners.
var sorted = tile.Height.ToArray(); Array.Sort(sorted); var minDiff = Abs(sorted[0].Height - sorted[2].Height); // Difference between lowest and 3rd lowest point.
var minDiff = Abs(sorted[0] - sorted[2]); // Difference between lowest and 3rd lowest point. var maxDiff = Abs(sorted[3].Height - sorted[1].Height); // Difference between highest and 3rd highest point.
var maxDiff = Abs(sorted[3] - sorted[1]); // Difference between highest and 3rd highest point. var first = sorted[(minDiff > maxDiff) ? 0 : 3].Corner;
var first = (Corner)sorted[(minDiff > maxDiff) ? 0 : 3];
if (first is Corner.TopLeft or Corner.BottomRight) { if (first is Corner.TopLeft or Corner.BottomRight) {
AddTriangle(corners.TopLeft , new(0.0f, 0.0f), AddTriangle(corners.TopLeft , new(0.0f, 0.0f),

@ -15,6 +15,12 @@ public partial class TerrainChunk
[Export] public byte[] Data { get; set; } = new byte[SizeInBytes]; [Export] public byte[] Data { get; set; } = new byte[SizeInBytes];
public bool IsEmpty { get {
foreach (var b in Data)
if (b != 0) return false;
return true;
} }
public ref Tile this[TilePos pos] { get { public ref Tile this[TilePos pos] { get {
var tiles = MemoryMarshal.Cast<byte, Tile>(Data); var tiles = MemoryMarshal.Cast<byte, Tile>(Data);
return ref tiles[GetIndex(pos)]; return ref tiles[GetIndex(pos)];

@ -61,9 +61,6 @@ public struct Corners<T>(T topLeft, T topRight, T bottomRight, T bottomLeft)
} } } }
} }
public readonly T[] ToArray()
=> [ TopLeft, TopRight, BottomRight, BottomLeft ];
public readonly bool Equals(Corners<T> other) public readonly bool Equals(Corners<T> other)
=> TopLeft .Equals(other.TopLeft ) => TopLeft .Equals(other.TopLeft )
&& TopRight .Equals(other.TopRight ) && TopRight .Equals(other.TopRight )

@ -1,7 +1,23 @@
public static class GodotExtensions public static class GodotExtensions
{ {
public static Vector2I RoundToVector2I(this Vector2 vector) public static T GetParentOrThrow<T>(this Node node)
=> new(RoundToInt(vector.X), RoundToInt(vector.Y)); where T : class
{
var parent = node.GetParent();
if (parent == null) throw new InvalidOperationException($"Parent of {node} is null");
if (parent is not T result) throw new InvalidCastException($"Parent of {node} is {node.GetType()}, not {typeof(T)}");
return result;
}
public static T GetNodeOrThrow<T>(this Node parent, NodePath path)
where T : class
{
var node = parent.GetNodeOrNull(path);
if (node == null) throw new InvalidOperationException($"Could not find node {path} from {parent}");
if (node is not T result) throw new InvalidCastException($"Node {path} from {parent} is {node.GetType()}, not {typeof(T)}");
return result;
}
public static (Corner, Corner) GetCorners(this Side side) public static (Corner, Corner) GetCorners(this Side side)
=> side switch { => side switch {

@ -0,0 +1,12 @@
public static class MathExtensions
{
// Framerate independent dampening functions, similar to lerp.
// https://rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/
public static float Damp(this float from, float to, float lambda, double delta)
=> Lerp(from, to, 1 - Exp(-lambda * (float)delta));
public static Vector3 Damp(this Vector3 from, Vector3 to, float lambda, double delta)
=> from.Lerp(to, 1 - Exp(-lambda * (float)delta));
public static Vector2I RoundToVector2I(this Vector2 vector)
=> new(RoundToInt(vector.X), RoundToInt(vector.Y));
}
Loading…
Cancel
Save