Final improvements to the FlappyBird clone and Android support devlog 3

Decided to conclude my FlappyBird journey with one last set of improvements, following up on devlogs 1 and 2. Focusing on refactoring, better UI, sprite selection and Android support.

I missed some features that I really wanted to get in but I’m already tired of working on this toy project and already eager to move to another one. Most of the features I wanted to add are just QoL UI enhancements and extra buttons basically.

The source code can be found at luevano/flappybirdgodot. Playable at

Table of contents


The first part for my refactor was to move everything out of the src/ directory into the root directory of the git repository, organizing it a tiny bit better, personal preference from what I’ve learned so far. I also decided to place all the raw aseprite assets next to the imported one, this way its easier to make modifications and then save directly in the same directory. Also, a list of other refactoring done:

func _ready():

func _on_game_pause(pause: bool):

Just connecting to set_process is enough:

func _ready():
    # and when the signal doesn't send anything:


Background parallax

First thing was to add a moving background functionality, by adding 2 of the same Sprite2D’s one after another and everytime the first sprite moves out of the screen, position it right after the second sprite. Some sample code to accomplish this:

func _ready():
   # Sprite2D and CompressedTexture2D nodes
   background_orig.texture = background_texture
   texture_size = background_orig.texture.get_size()

   backgrounds[1].position = background_orig.position + Vector2(texture_size.x, 0.0)

   background_orig.visible = false

# ifirst (index first) it's a boolean value starting with false and
#   its a hacky way of tracking the first sprites
#   (the one closest to the left of the screen) in the array
func _process(delta: float):
    for background in backgrounds:
        background.move_local_x(- SPEED * delta)

    # moves the sprite that just exited the screen to the right of the upcoming sprite
    if backgrounds[int(ifirst)].position.x <= - background_orig.position.x:
        backgrounds[int(ifirst)].position.x = backgrounds[int(!ifirst)].position.x + texture_size.x
        ifirst = !ifirst

Then I added background parallax by separating the background sprites in two: background and “foreground” (the buildings in the original sprites). And to move them separately just applied the same logic described above with 2 different speeds.

Sprite switcher

Also added a way to select between the bird sprites and the backgrounds, currently pretty primitive but functional. Accomplished this by holding textures in an exported array, then added a bit of logic to cycle between them (example for the background):

func _get_new_sprite_index(index: int) -> int:
    return clampi(index, 0, background_textures.size() - 1)

func _set_sprites_index(index: int) -> int:
    var new_index: int = _get_new_sprite_index(index)
    if new_index == itexture:
        return new_index
    for bg in backgrounds:
        bg.texture = background_textures[new_index]
    for fg in foregrounds:
        fg.texture = foreground_textures[new_index]
    itexture = new_index
    return new_index

Then, in custom signals I just call _set_sprites_index with a texture_index +/- 1.

Save data

Moved from manual ConfigFile (which is an .ini file basically) to Resource which is easier to work with and faster to implement.

Accomplished by defining a new

class_name DataResource
extends Resource

@export var high_score: int
@export var volume: float
@export var mute: bool
@export var bird: int
@export var background: int

func _init():
    high_score = 0
    volume = 0.5
    mute = false
    bird = 0
    background = 0

Where the @exports are not needed unless you need to manage the .tres resource files for testing in-editor.

Then, the script needs to be changed accordingly, most notably:

func save():
    var err: int =, DATA_PATH)
    if err != OK:
        print("[ERROR] Couldn't save data.")
func _load_data():
    if ResourceLoader.exists(DATA_PATH):
        _data = load(DATA_PATH)
        _data =

Compared to the 3.x version it is a lot more simple. Though I still have setters and getters for each attribute/config (I’ll se how to change this in the future).


I did add android support but it’s been so long since I did it that I actually don’t remember (this entry has been sitting in a draft for months). In general I followed the official guide for Exporting for Android, setting up Android studio and remotely debugging with my personal phone; it does take a while to setup but after that it’s as simple as doing “one click deploys”.

Most notably, I had to enable touch screen support and make the buttons clickable either by an actual mouse click or touch input. Some of the Project Settings that I remember that needs changes are:


Found a bug on the ScoreDetector where it would collide with the Ceiling. While this is really not a problem outside of me doing tests I fixed it by applying the correct layer/mask.

By David Luévano

Created: Fri, Mar 01, 2024 @ 10:00 UTC

Modified: Fri, Mar 01, 2024 @ 10:11 UTC