
I started messing with Gadot (https://godotengine.org/) to fulfill the free-form open source activist in me (also I have a friend making stuff in Gadot).
So I started messing with the 3d side but the goal is for a more metroidvania type game. So I moved into understanding the 2d asset pipelines a bit better. The way I want to handle this is to:
- Make a spritesheet(s) with four different animations: Idle, walk, jump, land
- Make a character using the sprite-sheet with simple controls
- Stretch goal- Add landing and walking smoke effects
Making spritesheets has gotten so easily since I was making simple games in XNA (15ish years ago damn…) now you can do everything in browser. I’m using https://www.piskelapp.com which is very fast and reliable for dumping a .png for a sprite sheet.

I’m going with a stick figure. I’m cool with jank walking but as long as you get four frames I you’re good (https://en.wikipedia.org/wiki/Walk_cycle). My finalized sprite sheet:

First three two frames are idle (just a simple bob). The next four are the jump/land cycle, the rest are walking (which I can invert for each direction in gadot).
Gadot has a REALLY good sprite sheet importer:

It lets you chop, then select which frames to import. In that way you can keep 100% of the sprites in one huge sprite-sheet and not worry about flipping between files at runtime (which in reality isn’t a big issue but I imagine you’re going to hit performance on low ram platforms with 1000+ pngs loaded in memory).
The animation editor once you get frames in place is SUPER easy and lets you quickly edit and test spritesheets as needed:

All of these get stored inside of your “AnimatedSprite2d” instance that can be selected via sprite frames property:

Now the scripting in gadot mirrors unity (and unreal kinda) where everything has a setup and loop function that you edit to handle internal logic to your “Nodes” (which are called actor and gameobject in unreal and unity respectively). Gadot doesn’t have a nice state machine editor like unity or unreal so you need to program it up yourself. The design of the simple character is actually expecting you to do this so there’s there’s state and utility funcitons in both the character and sprite classes that let you do this easily:
extends CharacterBody2D
const SPEED = 300.0
const JUMP_VELOCITY = -400.0
@onready var _animated_sprite = $AnimatedSprite2D
#AnimationStateMachine
var startJump = false
var isFalling = false
var endJump = false
var isWalking = false
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
startJump = false
isFalling = true
elif isFalling and is_on_floor():
endJump = true
isFalling = false
elif endJump and is_on_floor():
endJump = false
else:
endJump = false
isFalling = false
# Handle jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
startJump = true
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var direction := Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
if(absf(velocity.x) > 0):
isWalking = true
else:
isWalking = false
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
if(absf(velocity.x) > 0):
isWalking = true
else:
isWalking = false
move_and_slide()
handleAnimationStateMachine()
func handleAnimationStateMachine():
if startJump:
_animated_sprite.play("Jump_start")
elif isFalling and _animated_sprite.animation_finished:
_animated_sprite.play("Jump_loop")
startJump = false
elif endJump and _animated_sprite.animation_finished:
_animated_sprite.play("Jump_end")
elif isWalking and _animated_sprite.animation_finished:
_animated_sprite.play("Walk")
else:
_animated_sprite.play("default")
endJump = false
isWalking = false
If you have no idea what this is doing essentially its:

Final result:
So I’m pretty confident here that if someone threw me a bunch of sprites/2d art I could go ahead and make a game. Gadot has the same issues as unity where the structure is much less defined than Unreal. Therefore its crazy easy to prototype but scaling up will take more developer discipline to prevent Node spanning bugs and state issues. In my head the rough hierarchical structure of any 2d sidescroller would be:

The way to read this is that the controller points to the controlled. So the game master controls everything the scene master controls characters and cut-scenes etc. With this structure way you don’t get confused with race conditions etc. If you’re a lower level object that wants to initiate a high level event you’ll be forced to request up the chain (i.e. an npc that the player presses use on will send a request to the scene controller to start a cut-scene, which will then send a request to the game controller which then can trigger the cut-scene player). It seems confusing and over-engineered but if you consider the alternative you would need everyone to align to some other kind of mental model, which in my opinion can be crazy painful to handle and leads to a BUNCH of crunch.