
Snow_Shinobi’s 2024 Summer Jam was the second jam under my belt and I felt way more prepared heading into it. I also felt a lot less confident about the subject matter as this game jam had an enforced genre: Souls-like. This genre is not one that I feel a lot of connection to, as I’ve never really been drawn into any of the games in the genre, nor do I have any experience with 3D, which I feel like is characteristic of most soulsian games. That being said, 3D was not a requirement of the jam, so I just had to figure out how to take a genre I’m not particularly familiar with and adapt it to a format that it’s not often found in. Sounds easy, right?
Custom resources
I won’t go in depth too much here in this post, because I have a post for demonstrating this exact concept. But this is where I first implemented this pattern in a game and it made life so much easier not having to worry about connecting nodes to signals all over the place.
AnimationPlayer is my new favorite node


AnimationPlayer is absolutely my favorite node of all time now. It is so useful for scheduling specific sequences of things, far beyond just animating sprites and models. You can make it so damage gets registered on the exact frame the enemy attack strikes, automatically play audio streams when animations play, or even call queue_free()
at the end of the death animation so you don’t have to worry about remembering to call it in code. But I’ve found other amazing uses for the AnimationPlayer node, too. I used it for my custom cutscene engine (which I talk about in the next section) and animating the UI when you rest at a campfire:

Finite state machines are still amazing (feat. custom cutscene engine)
Every time I use a finite state machine, it seems like I find more and more reasons that they are amazing. In this project, I tried implementing a custom cutscene engine and was incredibly pleased with what I ended up with. I was able to manipulate the player by setting which state the player was in from the code and I’d end up with the correct animation along with all the other logic stored in the state.

My cutscenes manipulated other nodes in the scene through a series of signals it could emit. The basic functionality I built in was to control player movement, camera tweening, pausing enemies (so they couldn’t attack you during a cutscene), adding a node to the world (a node that would persist even when the cutscene node was discarded), and the ability to set animation speed of the player (this was an aesthetic choice as it looked better if the player idle animation during the cutscene was run at a slower framerate).
class_name BasicCutscene extends AnimationPlayer
signal play_animation(animation_name: String)
signal start_walking(walk_left: bool)
signal stop_walking()
signal pause_enemies()
signal resume_enemies()
signal tween_camera(offset: Vector2, time: float)
signal end_cutscene()
signal add_node_to_world(node: Node)
signal set_animation_speed_scale(scale: float)
func play_cutscene() -> void:
pass
func end() -> void:
end_cutscene.emit()
func _pause_enemies() -> void:
pause_enemies.emit()
func _resume_enemies() -> void:
resume_enemies.emit()
Don’t fear daisy-chained signals
I’ve always been afraid of chaining long strings of signals to emit a change that is buried deep within packed scenes, inside packed scenes, inside packed scenes. I haven’t found any better methods to emit buried signals. While chaining signals together felt like the logical thing to do, there have always been a few things about it that make me hesitate. It always felt super tedious making a node connect to a child’s signal, just to emit the exact same signal itself, not actually processing any logic. It also makes refactoring and making changes to signals obnoxious as you need to update every node in the chain each time you change the name or parameters of a signal.
Rather than connecting the signal at runtime when the nodes are all setup, which I find potentially problematic as the node structure can change and visibility to nodes that are children of packed scenes is not great in the editor, I preferred to chain signals up the tree. This solution, while tedious, feels safer to me as each packed scene can be in charge of emitting a signal when it’s target emits that node and is responsible for tracking if a node gets disposed of or replaced.
It may feel somewhat janky, but I saw no performance hit (though my benchmarking was pretty surface level and minimal) and it, ultimately, felt safer. In my project, for example, whenever you triggered a cutscene, it would instantiate a PackedScene
containing my cutscene code. The CutsceneHandler
attached the appropriate signals when it instantiated a new cutscene. Then the player node only had to worry about connecting to the signals on the handler, once. Furthermore, some signals needed to bubble all the way up to the world node to handle things like pausing or slowing the game.

Mirroring off-centered nodes the easy way
While working on this jam project, I discovered a rather easy way to mirror nodes about an axis, even if they are not centered on the axis they’re being mirrored over. In the example below, the Area2D
that registers attack damage of the bat (the currently selected node that is called AttackArea
) is centered on the origin, despite the actual CollisionShape2D
not being centered (the node called AttackCollision
that is colored red in the editor). With this setup, you simply have to take the parent node, AttackArea
in this case, and set it’s scale.x
to -1
and it will mirror it about the y-axis (make sure you unlink the x and y scale, or it will flip it upside-down, too)


Don’t forget super()
Not much to say here, but basically make sure that if you’re inheriting a parent class and overriding one of it’s functions, make sure you call super()
if you still want to run all the code in the same function of the parent class. This isn’t a crazy revelation or anything, I’ve been a developer for 10 years, this is a common language feature, but I’d still forget it nonetheless and it caused so many bugs in the development of my jam game. Especially when it came to the state machine. If you’re asking “what’s this super function and what does it do?”:
class_name A
func say():
print("A")
class_name B extends A
func say():
super() # will call A.say()
print("B")
Anytime you extend another class, you have the option of redefining existing methods that you’re inheriting from the base class. When you do this, the inherited version of the function is ignored, but you may call it by using super()
which will run the inherited version inside an overriding function.
class_name BasicEnemyState extends Node
var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")
@export var animation_name: String
@export var enemy: BasicEnemy
func Enter() -> void:
enemy.animation_player.play(animation_name)
func Exit() -> void:
pass
func Process(_delta: float) -> BasicEnemyState:
return null
func Physics(_delta: float) -> BasicEnemyState:
return null
class_name EnemyAttackState extends BasicEnemyState
@export var idle_state: EnemyIdleState
func Enter() -> void:
enemy.is_attack_windup = true
super()
func Process(_delta: float) -> BasicEnemyState:
if not enemy.animation_player.is_playing():
if enemy.can_attack_player():
Enter()
else:
return idle_state
return null
func Exit() -> void:
enemy.is_attack_windup = false
I can’t tell you how many times I was working on my state machine and then all of the sudden, a state would stop playing the relevant animation out of nowhere. I kept overriding func Enter() -> void
and not remembering to call super()
, therefore I never ended up calling enemy.animation_player.play(animation_name)
. Sometimes even senior developers can forget about simple things like that. And if you didn’t know about super before, now you do.
Partial tree pausing
During my cutscenes I didn’t want enemies to be able to attack the helpless player, so I realized that if you put all the enemies under a parent node, you can simply set enemies.process_mode = Node.PROCESS_MODE_DISABLED
to pause all the enemies, and only the enemies, and prevent their scripts from running (as opposed to using get_tree().paused
to pause the whole game). Just make sure to set it back when the cutscene is done enemies.process_mode = Node.PROCESS_MODE_INHERIT
(I also do this, when the player is resting).
This was an incredibly fun game jam, however, I ended up getting COVID halfway through the jam, so I while I implemented all the mechanics I wanted to, I wasn’t able to finish the level design in order to highlight the use of all the mechanics I developed. Overall, I still learned a ton during this jam and don’t regret participating!