More sprites, GameManager and refactoring
Source: Dev.to
More Sprites
We’re making real progress now. In the last post, I showed the fish and the boat. But that’s not all—I’ve also added a cloud and some water… or rather, the waves.
I pushed the boat down slightly so it looks like it’s actually sitting in the water instead of hovering awkwardly above it.
Boat bobbing animation
To make things feel more alive, I added a subtle bobbing effect to the ship. I did this by adding an AnimationPlayer to the Player scene, creating a new animation, and animating the Y position from 0 → -2 → 0. Then I set it to loop.
# Get the animation reference
@onready var animation = $AnimationPlayer
func _ready() -> void:
animation.play("bopp") # Note: the animation is intentionally named "bopp"
It’s a tiny movement, but it makes a big difference. The boat no longer feels static; it gently floats, like it’s actually resting on water instead of glued to the screen.
Waves
For the waves, I added a TileMapLayer as a child node in the Main scene. I’m not sure if I should have made it its own scene and instantiated it instead, but I’ll figure that out later.
I created a spritesheet specifically for the wave tiles. Going forward, I plan to move everything into spritesheets instead of having single images scattered across the project.
(Spritesheet preview omitted)
GameManager Refactoring
I made some major changes to the code. Instead of having important logic scattered across multiple places, I centralized most of what I need into a single script called GameManager. It handles shared data and global behavior such as score, lives, and game‑over logic.
extends Node
signal lives_changed(new_lives)
signal score_changed(new_score)
var lives = 3
var current_score = 0
# ---- Score handling ----
func _update_score(fish_score):
current_score += fish_score
score_changed.emit(current_score)
# ---- Health handling ----
func _decrease_life():
if lives > 0:
lives -= 1
lives_changed.emit(lives)
if lives == 0:
_game_over()
# ---- Game over UI ----
func _game_over():
var game_over_scene = preload("uid://behejrhw6wlo7")
var game_over_instance = game_over_scene.instantiate()
var GameOverCanvas = get_node("/root/MainScene/GameOverCanvas")
GameOverCanvas.add_child(game_over_instance)
get_tree().paused = true
The health and game‑over systems are still a work in progress, but the score system is functional.
Fish script interaction
@export var fish_score = 1
func _on_body_entered(body):
if body.is_in_group("Player"):
GameManager._update_score(fish_score)
queue_free()
When a fish collides with the player, it calls _update_score in the GameManager singleton. The function updates the total score and emits a signal, which the UI listens to in order to update the score counter.
Flow:
Player catches fish → call _update_score in GameManager → current score (starting at 0) increases by the fish’s value → score_changed signal emitted → UI updates.
It took a while to figure out because I hadn’t worked with singletons in Godot before. After watching a short video and experimenting, it clicked.
What’s next?
In the next post I’ll dive deeper into the GameManager, show how it’s connected to the UI, flesh out the health system, and introduce a bomb mechanic.
Feel free to leave comments or feedback. I’m still learning and may make mistakes.