DEV Community

Cover image for Let’s Learn Godot 4 by Making an RPG — Part 23: Testing, Debugging, & Exporting🤠
christine
christine

Posted on • Edited on

Let’s Learn Godot 4 by Making an RPG — Part 23: Testing, Debugging, & Exporting🤠

Congratulations on making it to the end of our 2D RPG series! It might have taken you a long to get here, but you persisted and hopefully by now you have a working game and you understand all of the concepts that we went over throughout this tutorial. With our game created, you need to go back and test it to make sure that it is as bug-free as possible.


WHAT YOU WILL LEARN IN THIS PART:
· How to install Export Templates.
· How to export projects as Windows Executables.
· How to test and debug your game


For this, you need to delve into the world of gameplay testing. Since this is a small-scale game, we’ll focus on the aspects of manual testing, which includes testing the game’s mechanics by playing it and trying to break it. Please note, that everybody’s testing approach is different, and the following section just contains basic guidelines.

Here’s a general guideline for manual testing:

  • Gameplay Mechanics: This involves testing all of the game’s mechanics to ensure they work correctly. In our game, the mechanics to test would include moving, shooting, interacting, damaging entities, consuming pickups, and the ability to progress and end the game. The interaction of the player’s character with enemie should also be tested. Are they colliding properly? Do the animations play correctly? Make sure to separate each factor of the game into a checklist which you can then test individually.

  • Levels: Each level should be tested thoroughly. This includes testing the map generation to make sure that the tiles are generating correctly. Spawn locations of entities and other obstacles should also be tested to see if they are not out of bounds/unreachable or spawning beyond the game map.

  • User Interface (UI) and Controls: You should test that the game’s controls work correctly and that the UI displays the right information. For example, you might check that the stamina and health progress bar regenerates correctly.

  • Difficulty and Progression: Check that the game gets harder as you progress through the levels. For example, the payer’s health increase each time the level progresses — but the enemies health remains the same. Make sure that this progression feels fair and balanced.

  • Audio and Visuals: Test the game’s sound effects, music, and graphics. This would involve testing things like the sounds of picking up items, shooting, and taking damage, as well as the animations for these actions.

  • Performance: Check the game’s performance. It should run smoothly without lagging or stuttering, even when there are lots of entities on the screen.

  • Bugs and Glitches: Play the game while actively trying to cause bugs and glitches. This might involve things like trying to move into walls, pausing and unpausing the game, or trying to interact with objects in unexpected ways.

  • Edge Cases: Test unusual or extreme situations. For example, what happens if the player doesn’t move at all? What if they try to run in place whilst shooting?

  • Player Experience: Lastly, test the overall player experience. Is the game fun to play? Are there any frustrating parts? This will be subjective, so it can be useful to get multiple people to playtest the game.

You can use the debugger to see which functions or methods are returning errors. My debug console returned a lot of warnings (yellow errors) which won’t affect my game in any way but instead are giving us suggestions for code optimizations. It also returned a lot of red errors, which might affect our game. Let’s go ahead and fix these.

Godot RPG

DEBUGGING

Error calling from signal ‘health_updated’ and ‘stamina_updated’ to callable.

This error will occur whenever our game tries to update our health and stamina via the signals. Just like the error above, this is due to a mismatch in argument numbers. When we emit the ‘health_updated’ signal in the Player.gd script, we are passing in an argument. Then, in the Health.gd script, the ‘update_health’ method is connected to this signal, and is expecting to receive this argument. However, our method ‘update_health’ is defined without any parameters, hence the mismatch and the error.

Godot RPG

To fix this, we just need to emit both our x and max_x variables in our health and stamina signals.



    ### Player.gd
    # ----------------- Level & XP ------------------------------
    #updates player xp
    func update_xp(value):
        xp += value
        #check if player leveled up after reaching xp requirements
        if xp >= xp_requirements:
            #stop background music
            background_music.stop()
            level_up_music.play()
            #allows input
            set_process_input(true)
            Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
            #pause the game
            get_tree().paused = true
            #make popup visible
            level_popup.visible = true
            #reset xp to 0
            xp = 0
            #increase the level and xp requirements
            level += 1
            xp_requirements *= 2

            #update their max health and stamina
            max_health += 10 
            max_stamina += 10 

            #give the player some ammo and pickups
            ammo_pickup += 10 
            health_pickup += 5
            stamina_pickup += 3

            #update signals for Label values
            health_updated.emit(health, max_health)
            stamina_updated.emit(stamina, max_stamina)
            ammo_pickups_updated.emit(ammo_pickup)
            health_pickups_updated.emit(health_pickup)
            stamina_pickups_updated.emit(stamina_pickup)
            xp_updated.emit(xp)
            level_updated.emit(level)


Enter fullscreen mode Exit fullscreen mode

Error Condition “p_scene && p_scene->get_parent() != root” is true.

If you load your game, this error might occur. This error typically occurs when we’re trying to set a current scene in Godot, and the scene we’re trying to set already has a parent node, which is not the scene tree root. When changing scenes, we usually want to add the new scene as a child of the root node and then set it as the current scene. However, if the scene already has a parent that isn’t the root, Godot won’t allow us to set it as the current scene, causing this error.

Godot RPG

We can fix this via the call_deferred object, which will allow us to set our current scene and change our scene when we load our game both in the same frame, avoiding the error we’ve encountered.



    ### Global.gd

    func load_game():
        if loading and FileAccess.file_exists(save_path):
            #older code
            # Change to the loaded scene
            get_tree().root.call_deferred("add_child", game)        
            get_tree().call_deferred("set_current_scene", game)
            current_scene_name = game.name
            #older code


Enter fullscreen mode Exit fullscreen mode

Error Node not found: “Player” (relative to “/root/MainMenu”).

If you load your game, this error might occur. This error is happening because we’re trying to access the “Player” node from the scene “MainScene”. It seems like the “ MainScene “ scene does not have a “Player” node.

Godot RPG

In our load_data() function, we’re trying to find the “Player” node from the current_scene, which in this case is “MainMenu”. Here is the problematic line:



    var player = current_scene.get_node("Player")


Enter fullscreen mode Exit fullscreen mode

If there’s no “Player” node in the “MainMenu” scene, we’ll get a “Node not found” error. To avoid this error, we can first check if the “Player” node exists in the current_scene before trying to access it:



    ### Global.gd

    #player data to load when changing scenes
    func load_data():
        var current_scene = get_tree().get_current_scene()
        if current_scene and FileAccess.file_exists(save_path):
            print("Save file found!")
            var file = FileAccess.open(save_path, FileAccess.READ)
            var data = JSON.parse_string(file.get_as_text())
            file.close()

            # Now you can load data into the nodes
            if current_scene.has_node("Player"):
                var player = current_scene.get_node("Player")
                if player and data.has("player"):
                    player.values_to_load(data["player"])
        else:
            print("Save file not found!")


Enter fullscreen mode Exit fullscreen mode

Error change_scene(): Can’t change this state while flushing queries.

This error happens when we’re trying to change a scene while some physics queries are still being resolved. Godot doesn’t allow direct change in some physics properties or change scenes during a physics process as it could lead to crashes or unexpected behavior.

Godot RPG

One solution is to use the call_deferred() function which schedules the method to be called during the idle time of the next frame. This can prevent race conditions that may arise if physics queries are being flushed while we’re trying to change the scene.



     # Change scene
    func change_scene(scene_path):
        save()
        # Get the current scene
        current_scene_name = scene_path.get_file().get_basename()
        var current_scene = get_tree().get_root().get_child(get_tree().get_root().get_child_count() - 1)
        # Free it for the new scene
        current_scene.queue_free()
        # Change the scene
        var new_scene = load(scene_path).instantiate()
        get_tree().get_root().call_deferred("add_child", new_scene) 
        get_tree().call_deferred("set_current_scene", new_scene)    
        call_deferred("post_scene_change_initialization")

    func post_scene_change_initialization():
        load_data()
        scene_changed.emit()


Enter fullscreen mode Exit fullscreen mode

With these issues, the red errors from our log should be removed. The remaining yellow warnings are of no concern, as removing some of the “shadowed” variables will result in us receiving actual errors. You can however go ahead and fix the warnings that say variable x is never used by adding the indentations to the unused parameters.

Godot RPG

TESTING: ENEMY DIFFICULTY

After testing, I realized that I want our level progression to be more fair. Each time we level up, the enemies’ health should also increase — making them harder to kill.

We can do this by increasing our enemy’s health in their ready function. We can then use the player’s level to adjust the enemy’s health. For example, for each level the player gains, the enemy’s health could increase by a certain percentage or a fixed amount.



    ### Enemy.gd

    func _ready():
        rng.randomize()
        # Reset color
        animation_sprite.modulate = Color(1,1,1,1)   
        # Adjust enemy health based on player's level
        health += player.level * 10  # Increase health by 10 for each player level
        max_health = health
        print("Enemy health:" , health)


Enter fullscreen mode Exit fullscreen mode

Now if we level up, our enemy’s health should increase by 10.

Godot RPG

If the player levels up, we can also increase the max amount of enemies that can spawn. To do this, we need to define a new signal that we will emit when the player levels up.




    ### Player.gd

    # Custom signals
    signal health_updated
    signal stamina_updated
    signal ammo_pickups_updated
    signal health_pickups_updated
    signal stamina_pickups_updated
    signal xp_updated
    signal level_updated
    signal xp_requirements_updated
    signal coins_updated
    signal leveled_up

    # ----------------- Level & XP ------------------------------
    #updates player xp
    func update_xp(value):
        xp += value
        #check if player leveled up after reaching xp requirements
        if xp >= xp_requirements:
            # older code

            #update signals for Label values
            health_updated.emit(health, max_health)
            stamina_updated.emit(stamina, max_stamina)
            ammo_pickups_updated.emit(ammo_pickup)
            health_pickups_updated.emit(health_pickup)
            stamina_pickups_updated.emit(stamina_pickup)
            xp_updated.emit(xp)
            level_updated.emit(level)
            leveled_up.emit()


Enter fullscreen mode Exit fullscreen mode

Then, in our EnemySpawner, we will create a new function that will update our max_enemies + the current level if the signal is emitted.



    ###EnemySpawner.gd

    extends Node2D

    # Node refs
    @onready var player = get_tree().root.get_node("%s/Player" % Global.current_scene_name)
    @onready var spawned_enemies = $SpawnedEnemies
    @onready var tilemap = get_tree().root.get_node("%s/Map" % Global.current_scene_name)

    # Audio nodes
    @onready var death_sfx = $GameMusic/DeathMusic

    # Enemy stats
    @export var max_enemies = 9
    var enemy_count = 0 
    var rng = RandomNumberGenerator.new()

    # Inside the _ready() function
    func _ready():
        player.leveled_up.connect(_on_player_leveled_up)

    # The function that adjusts max_enemies based on player's level
    func _on_player_leveled_up():
        max_enemies += player.level * 1
        print("Max enemies adjusted to:", max_enemies)


Enter fullscreen mode Exit fullscreen mode

With these changes, the EnemySpawner will spawn more enemies and at a faster rate as the player levels up and the enemies’ health will increase, making the game progressively more challenging!

Godot RPG

TESTING: AUTOSAVE

I also want our game to have the functionality to autosave. We can do this with the help of a Timer node. We want our game to autosave every 5 minutes. In your Main and Main_2 scenes, add a new Timer node and set it’s wait-time to 300 (300 seconds = 5 minutes). Also, ensure that you enable autostart on load.

Godot RPG

Godot RPG

Connect your timer node’s timeout() signal to your Main script. In this function, we will simply just call our Global.save() function. Every five minutes, the timer will timeout, and save our game.



    ### Main_2.gd

    extends Node2D

    @onready var background_music = $Player/GameMusic/BackgroundMusic

    #connect signal to function 
    func _ready():
        background_music.stream = load("res://Assets/FX/Music/Free Retro SFX by @inertsongs/Imposter Syndrome (theme).wav")
        background_music.play()

    # Change scene
    func _on_trigger_area_body_entered(body):
        if body.is_in_group("player"):
            Global.change_scene("res://Scenes/Main.tscn")
            Global.scene_changed.connect(_on_scene_changed)

    #only after scene has been changed, do we free our resource     
    func _on_scene_changed():
        queue_free()

    # Autosave
    func _on_timer_timeout():
        Global.save()
        print("Game saved.")


Enter fullscreen mode Exit fullscreen mode



    ### Main.gd

    extends Node2D

    # Change scene
    func _on_trigger_area_body_entered(body):
        if body.is_in_group("player"):
            Global.change_scene("res://Scenes/Main_2.tscn")
            Global.scene_changed.connect(_on_scene_changed)

    #only after scene has been changed, do we free our resource     
    func _on_scene_changed():
        queue_free()

    # Autosave
    func _on_timer_timeout():
        Global.save()
        print("Game saved.")


Enter fullscreen mode Exit fullscreen mode

Now after five minutes have passed, your game should autosave!

Godot RPG

The final source code for this part should look like this.

EXPORTING

If you’ve gotten to the point in your game where you have fixed all of your major bugs and you have smoothened out your gameplay, then it might be time for you to export your game so that others can enjoy it as well! We’re going to be exporting our project for PC today. When we export our project, it will be compiled into an executable (.exe) program that we can launch at the click of a button!

Before we can export our project, we need to choose an export template. These templates will compile our binary files into a program file for the platform that we choose.

To choose a project template, we need to open our Export menu via our Project > Export property.

Godot RPG

This will open the Export window, and by default, you should have no export preset available to you because you do not have an export template installed.

Godot RPG

We can add new presets by clicking on the “Add” option next to our Presets menu. This will open a dropdown to which we have to choose the platform that we want to export our project to. We want to export our project as a Windows Desktop application, so select that preset option.

Godot RPG

This will display a bunch of text that is written in red. This is because we still do not have our project template installed. Let’s install one by clicking on “Manage Export Templates”.

Godot RPG

From here you can download the most recent export template available to you. Click “Download and Install” to download the most recent one for your Godot version, or you can download one from the Godot website and install it from the file.

Godot RPG

Godot RPG

Once it’s finished installing, you can close the window and go back to your Exports window. The red warning messages will now be gone. In this window, you can change your game’s export name, save the path, and you can even set a password to it — amongst other things.

Godot RPG

In your resources menu, you can even set which assets/resources you want to export with the project. For this project, we will export all of our resources. You can read more about the properties in the Export window here.

Godot RPG

The default properties should be fine for our project — just change its name and save location. When you’ve added all your properties, you can click on “Export All” to export your executable program to your designated save location.

Godot RPG

Godot RPG

Now you can navigate to where you exported your project, and voila, your game runs! When you’ve created your own game and you are confident that you’ve created a smooth, engaging, and bug-free game, you’ll probably export your project so that it can be hosted on online marketplaces such as Steam, GOTM.io or itch.io.

Godot RPG

Godot RPG

Godot RPG

Congratulations, you’ve made a game in Godot 4 from start to finish. Hopefully, you’ve learned a lot on this journey, but you’ll only learn as much as you allow yourself to. A good thing to remember is that you’ll only really get good at Godot and game development in general if you practice.

If you’re wondering where to go from here, I will recommend the following guidelines:

  1. Do as many **tutorials **as possible on game development in Godot for beginners until you get bored. What I like to do, or what works for me when I’m stuck in this phase is that instead of following an entire course, I consume articles or videos that focus on singular topics. I’ll list some links to other tutorials at the end of this part.

  2. Practicing implementing simple mechanics that you’ve already learned to make. Let’s take our player’s movement code as an example. Try and implement it by yourself, and if you struggle, look back at how you did it and try again. This will help you to memorize simple things without getting overwhelmed by more complex problems.

  3. Move on to more complex mechanics. After you’ve done a few tutorials and practiced what you’ve learned, you can then challenge yourself to try out more advanced concepts, meaning try to implement things that scare or intimidate you. For example, try and implement a simple slot-based inventory system. If you struggle with it, try something else and come back to it later.

  4. Try making a basic game. Don’t shoot for an AAA game yet. Your first game will never meet the expectations that you’ve set for it in your head, so don’t shoot for the stars with this one. Instead, do something basic that is within your skill range. You can find the steps to a simple challenge Mario clone game below.

  5. Focus on one thing at a time. When you start out with game dev, don’t jump too quickly into other aspects of game dev such as character creation, art, music, etc. You’ll get lost if you focus on too many things at once. So, make sure you spend your time where it matters first — learning how to make games before you learn how to make games look and sound good. Once you know how to make your game, then go and learn the other aspects of game dev.

If you’re looking for something to do next, I’d recommend my other PDF’s in my “Learn Godot” series.

You can view them here.

Godot RPG

CHALLENGE PROJECT LINK:

GODOT TUTORIAL LINKS:

UNITY TUTORIAL LINKS:

UNREAL ENGINE TUTORIAL LINKS:

Good luck with the rest of your journey. Remember, this is not supposed to be a linear journey, so feel free to reach out to me if you need some help or advice. I have another tutorial series planned that I will be releasing soon, so once that is out you will be able to see it in my Gitbook updates. Remember to save your project, and I’ll see you in the next series!

Buy Me a Coffee at ko-fi.com


FULL TUTORIAL

Godot RPG

The tutorial series has 23 chapters. I’ll be releasing all of the chapters in sectional daily parts over the next couple of weeks.

If you like this series or want to skip the wait and access the offline, full version of the tutorial series, you can support me by buying the offline booklet for just $4 on Ko-fi!😊

You can find the updated list of the tutorial links for all 23 parts in this series here.

Top comments (5)

Collapse
 
alan504 profile image
Alan Kodzaev

Thanks! Just started your tutorial series and super excited.

Collapse
 
christinec_dev profile image
christine

I hope you have fun! Good luck on your game dev journey. 😊

Collapse
 
alan504 profile image
Alan Kodzaev

Thanks!

Thread Thread
 
phylis profile image
Phylis Jepchumba, MSc

Welcome to Dev Community

Collapse
 
christinec_dev profile image
christine • Edited

I have a new tutorial series coming on the 17th of July - so take a few days break, and I'll see you in the next one!