DEV Community

Viktor de Pomian Sandell
Viktor de Pomian Sandell

Posted on

More sprites, GameManager and refactoring

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 made a cloud and some water… or rather, the waves.

Pixel image of a boat and some waves

I pushed the boat down slightly so it looks like it’s actually sitting in the water instead of hovering awkwardly above it.

I thought about animating the waves to move back and forth, but I ran into some weird bugs. Instead of fighting that battle, I parked it for later and moved on.

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.

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.

@onready var animation = $AnimationPlayer
Enter fullscreen mode Exit fullscreen mode

To get the animation reference.

func _ready() -> void:
    animation.play("bopp")
Enter fullscreen mode Exit fullscreen mode

This is just to make sure the animation loads and fires once when the game starts.

Yes, I misspelled the name, "bopp."

It works. It does its job. I ain't touching it right now.

Gif that shows a bobbing boat

For the waves, I added a TileMapLayer as a child node in my 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, though, I'm planning to move everything into spritesheets instead of having single images scattered all over the project.

This is the spritesheet for the waves(so far):

A spritesheet image of waves

GameManager and some refactoring

I made some major changes to the code.

Instead of having important logic scattered across multiple places, I decided to centralize most of what I need into a single script called GameManager.

It's very simple. One place to handle shared data and global behavior, things like score, game state, and other logic that multiple scenes depend on.

extends Node

signal lives_changed(new_lives)
signal score_changed(new_score)


var lives = 3
var current_score = 0

# KEEPING SCORE ---------------------------------

func _update_score(fish_score):
  current_score += fish_score
  score_changed.emit(current_score)

#------------------------------------------------

# HEALTH ----------------------------------------

func _decrease_life():
  if lives > 0:
    lives -=1
    lives_changed.emit(lives)

  if lives == 0:
    _game_over()

#------------------------------------------------

# GAME UI STUFF ---------------------------------

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

#------------------------------------------------
Enter fullscreen mode Exit fullscreen mode

The health and game-over systems will come in a future post, they're not fully finished yet, so no point pretending they are.

But the score system? That part is working.

To explain it properly, I need to show parts of the fish script again, specifically the section where the fish interacts with the GameManager.

@export var fish_score = 1

func _on_body_entered(body):
    if body.is_in_group("Player"):
        GameManager._update_score(fish_score)
        queue_free()
Enter fullscreen mode Exit fullscreen mode

I set it up so that when a fish comes into contact with the Player, it calls a function inside the GameManager to calculate the score.

Inside GameManager, the _update_score function updates the total and then emits a signal. That signal is picked up by the UI, which updates the score counter.

Flow looks like this:

Player catches fish -> call _update_score in the GameManager singleton -> current score (starting at 0) gets increased by the fish's value (normal or gold) -> signal is emitted.

That's it.

It took a while to figure out, mostly because I hadn't really worked with singletons in Godot before. Watched a short video, experimented a bit, and eventually it clicked.

In the next post, I'll go deeper into the GameManager, how I connected it to the UI, the health system, and the bomb I added to the game.

My dear readers. Remember that everything I write is not always 100% correct. I do mistakes and I'm a novice at this. So if you find a mistake or just have some random feedback on anything. Please comment!

Top comments (0)