For anyone keen on designing their own RPG in Godot, you’ve come to the right Godot RPG tutorial.
Whether RPGs or strategy games, implementing strategic combat is a fundamental part of the process. This tutorial breaks down the creation of battle mechanisms, focusing on implementing various combat actions, creating a user interface (UI), and assigning different combat actions to individual characters.
By the end of this tutorial, you will have developed a simple yet effective combat system complete with player and enemy turns and actions. Note that this tutorial is intermediate level and expects familiarity with Godot engine and GDScript language. However, you can also review our full course, Build a Micro Turn-Based RPG with Godot 4 which covers these concepts more in-depth.
Without delay, let’s jump into it.
Table of contents
Feel free to download the assets used in this Godot RPG tutorial to support your learning. You can modify these files as per your needs to try out different combat scenarios.
Download Project Files Here
Combat Actions – Part 1
To start our Godot RPG tutorial, we will begin implementing the combat actions that will make up a major part of our game. In our game, a Combat Action will be an action the player or the enemy can do when it is their turn. These actions will be used to deal damage to the other Character or heal the current Character, however, we will be making the system in such a way you can expand it to add more features if you would like. Our Godot RPG tutorial game will include three basic combat actions:
- Basic Attack, which deals damage.
- Heavy Attack, which deals more damage than the basic attack.
- Heal, which heals the character who is using the action.
All of these actions also need to be interchangeable so that we can give the player different actions to the enemy. The system we design will also be fairly modular – so when you’re done with this Godot RPG tutorial, you can makes up your own attacks as needed.
Because we want to have multiple, interchangeable combat actions, we don’t want to hard code each action into the Character script. This is where Resources come into play. A Resource in Godot is effectively a custom asset that will allow us to store the information needed for each combat action. To create a new custom Resource, we first need to create a New Script by right-clicking in the FileSystem window.
We will call this “CombatAction”.
This script also needs to inherit from the Resource class by selecting the tree structure button.
Select the Resource class as the class to inherit from.
With these values selected, we are ready to choose Create and open up the script for editing. Before we can begin adding information to the new resource, we first need to give the script a class_name definition. This will make it possible to create the Resource instance in the FileSystem window along with identifying it in scripts. For this class name, we will use the name CombatAction.
Creating the Variables
We are now ready to set up the properties for this Resource in our Godot RPG tutorial. Each of these variables will be exported so that we can edit them across each instance of the CombatAction resource. The first variable will be called display_name. This will define the name that is shown to the player, so we can put a placeholder of “Action (x DMG)” in place as a default value.
@export var display_name = "Action (x DMG)"
Next, we will create a variable called damage, which will have a type of int and have a default value of 0. This will be the damage that the Character applies to their opponent when the action is used.
@export var damage : int = 0
We can copy this variable for the heal property.
@export var heal : int = 0
Creating Combat Actions
With the variables in place, we can begin creating each instance of the new CombatAction resource. We will do this in a new folder inside of the FileSystem window to keep the project organized.
Inside this folder, we can create our Resources. To do this select the new -> Resource option in the right-click menu.
A Resource is just another name for an asset in our project. Although we often won’t access assets like scenes and sprites from this menu, you can create any type of Resource here. Because our CombatAction inherits from Resource, we are able to create them as resources within the FileSystem. This means that we don’t need to instantiate the CombatAction as a node inside the scene to use it. Instead, we can access it directly from the asset. To create the resource, simply select CombatAction from the resource tree.
We can name this “Slash.tres” as that will be the name for our base attack action.
With the Slash.tres resource select in the FileSystem, you will notice the properties we have created are editable in the Inspector, similar to node instances in a scene. For our Slash resource, we want the following values:
- Display Name: Slash (5 DMG)
- Damage: 5
- Heal: 0
We can now easily create a second CombatAction resource with different values. This can be called “Heal.tres”.
For the Heal resource, we will use the following values:
- Display Name: Heal (5 HP)
- Damage: 0
- Heal: 5
As a challenge, before the next section of our Godot RPG tutorial, try creating another combat action called Stomp. This action will deal 8 damage to the opponent.
Combat Actions – Part 2
Hopefully, you had a good go at the challenge set above for our Godot RPG tutorial! To create our Stomp combat action, we will begin by creating another CombatAction resource.
We can call this “Stomp.tres” to follow the naming scheme set up in the previous lesson. For the properties of this action, we will use the following:
- Display Name: Stomp (8 DMG)
- Damage: 8
- Health: 0
Modifying the Character Script
Currently, we have no way to assign our combat actions to our Character. Luckily, as we have created them as Resources this will be no problem. Previously, we created a variable called combat_action in the Character script. This should be renamed to combat_actions to better show there are multiple elements in this variable.
@export var combat_actions : Array
The combat_actions variable is given a type of Array. This type allows us to store multiple items inside one variable in a list format. With this in mind, we can select the Player node and begin adding elements to our Combat Actions array from the Inpector window. Press the Add Element button to add room for new properties in the array.
By default, Godot does not know we want our Array to contain CombatActions. To fix this, select the pencil button to the right of the new element and select the Object type.
This will then allow us to drag our combat action into the new property.
This process can then be repeated for both the Heal and the Stomp combat actions.
We can then repeat this process for the Enemy character. However, to make the game more playable, we don’t want to give our enemy the Stomp combat action, so we can simply not add this Resource.
This means we now have all the code in place to create the gameplay systems. For the next step of our Godot RPG tutorial, we will begin creating the Player’s UI for selecting the combat actions.
Player UI – Part 1
Our Godot RPG tutorial game now needs a user interface to allow our players to select the combat action they want to use. The idea is to create a list of buttons that appear below the Player character instance whenever it is their turn, when the button is clicked, this will trigger the combat action to be used.
Creating the UI
To begin with, we will create a new VBoxContainer node in the BattleScene. This will act as a container for our UI, that automatically arranges our buttons into a vertical list.
We can rename this node to “PlayerCombatActionsUI”.
We can then add a Button as a child node to this container.
As you will see, because we use a VBoxContainer to handle the UI positioning, the button is automatically scaled to the width of the container.
We probably want to make this a bit taller. The best way to do that is to enter some default text on our button, such as “Slash (5 DMG)”.
This will allow us to visualize the buttons in the scene. However, we will be changing this text through the code later on in our Godot RPG tutorial.
We can now implement some of the functionality for our button. The best way to do this will be to add a new script directly to the Button node. We can name this CombatActionButton, ensure it inherits from Button and press Create.
Inside this script, we can remove the _ready and _process functions as we won’t be needing them. Our first variable will be called combat_action and have a type of CombatAction. This will allow us to assign the button with a specific CombatAction resource, allowing our script to know the effects that should be applied when the button is pressed. Our buttons will be passed the combat action when it is the player’s turn, so we don’t need to give a default value.
var combat_action : CombatAction
Before handling the click events on our button, we first want to set up a function in our Character script that can be called when the button is clicked. This will be called cast_combat_action and have a parameter of action, which will be the combat_action value of the button that was pressed.
func cast_combat_action (action): pass
We can now handle button clicks inside the CombatActionButton script. To listen for the button being clicked, we can add a signal to the node. We will use the pressed signal for this.
Make sure to assign the pressed signal to the Button node before clicking Connect.
This will generate a function called _on_pressed inside our button script, which will be automatically called whenever the button is pressed by the player. Here, we can call the cast_combat_action function on the current character.
func _on_pressed(): get_node("/root/BattleScene").cur_char.cast_combat_action(combat_action)
This is a relatively long line, however, it can easily be broken down into multiple steps:
- Firstly, we access the BattleScene root node using the get_node method.
- Then, we get the cur_char variable from the TurnManager script attached to the BattleScene node. Because the player’s buttons are visible, we know this is our Player character instance.
- Finally, we call the cast_combat_action function on the current character, with a parameter of combat_action.
We can now rename the Button node to CombatAction so that it can be identified easily.
From here, duplicate (CTRL+D) the node a few times to create four buttons in total. These will automatically resize and position into a list, because of the VBoxContainer parent node.
We now need to create a new script to handle connecting the Player character instance to the individual buttons in our Godot RPG tutorial game. This script can be attached to the PlayerCombatActionsUI node and will be called PlayerUI.
In this script, we won’t need the _process function, so this can be removed. After this, we can create a new variable called buttons, with a type of Array. This can be exported so that we can assign the buttons in the Inspector window.
@export var buttons : Array
This script will make use of our character_begin_turn and character_end_turn signals that we created previously. To do this, we will need to create two functions that can listen to these signals. The first will be called _on_character_begin_turn, with a parameter of character.
func _on_character_begin_turn(character): pass
We can then copy this function format for the on_character_end_turn function.
func _on_character_end_turn(character): pass
Next, we can connect these functions to the relevant TurnManager signals. This can be done by accessing the TurnManager script from our BattleScene root node and assigning the individual signals, inside the _ready function.
func _ready(): get_node("/root/BattleScene/").character_begin_turn.connect(_on_character_begin_turn) get_node("/root/BattleScene/").character_end_turn.connect(_on_character_end_turn)
The next step is to toggle the PlayerCombatActionUI’s visibility based on whether it is the player’s turn or not. This can be done in the _on_character_begin_turn function.
func _on_character_begin_turn(character): visible = character.is_player
Here we assign the visible variable, supplied by Godot on the VBoxContainer node, to the is_player value of the currently active Character. This means that if it is our player’s turn, the UI will be visible, and if it is not, the UI will be hidden. To make sure the player can’t run multiple actions, we will also disable the visibility in the _on_character_end_turn function, no matter which character it is.
func _on_character_end_turn(character): visible = false
In the part of our Godot RPG tutorial, we will look at populating the buttons and giving them the correct information.
Player UI – Part 2
In this part, we are going to continue creating our player’s combat UI (and finish the UI for this Godot RPG tutorial).
Previously, we set up our buttons and the scripts required to make them work. The next stage is to implement the systems to pass the combat action information from the Player character to the buttons in the UI. We will begin by creating a new _display_combat_actions function in the PlayerUI script, with a parameter of combat_actions, which will be the list of combat actions to apply to the buttons.
In this function, we can loop through each button in our buttons list using a for loop.
func _display_combat_actions(combat_actions): for i in len(buttons):
From here, we can grab the button node instance using the get_node function.
func _display_combat_actions(combat_actions): for i in len(buttons): var button = get_node(buttons[i])
We can now check to see whether we need to give this button a combat action. We can do this by checking whether i is larger than the combat_actions array’s length. If the button is within the combat_actions length, we can make the button visible.
func _display_combat_actions(combat_actions): for i in len(buttons): ... if i < len(combat_actions): button.visible = true
Then, we can update the text property of the button based on the display_name value of the CombatAction at this index (i), along with assigning the combat_action property of the button.
func _display_combat_actions(combat_actions): for i in len(buttons): ... if i < len(combat_actions): ... button.text = combat_actions[i].display_name button.combat_action = combat_actions[i]
If the button is outside the bounds of the combat_actions array, we want to hide it.
func _display_combat_actions(combat_actions): for i in len(buttons): ... if i < len(combat_actions): ... else: button.visible = false
We want to call the _display_combat_actions function from the _on_character_begin_turn. However, we only want to do this when it is the player’s turn, as we don’t want the player to be able to choose the enemy’s actions.
func _on_character_begin_turn(character): ... if character.is_player: _display_combat_actions(character.combat_actions)
Finally, we also need to fix a small error in the previous lesson. In the _on_pressed function of the CombatActionButton script, we use a variable called cur_character. This should be cur_char to allow our buttons to work properly, well done if you caught this!
func _on_pressed(): get_node("/root/BattleScene").cur_char.cast_combat_action(combat_action)
We now need to fill the buttons property of the PlayerCombatActionsUI in the Inspector window. For this Array, the element type (selected using the pencil icon) will be a Node Path.
We can drag each CombatAction button node into this Array.
We also need to assign the Player Char and Enemy Char properties of the BattleScene node, inside the Inspector window.
We can now press the play button in the editor to try the game. With everything set up, you will see the buttons appear as expected.
You can also try clicking the buttons. However, nothing will happen as we haven’t yet set up the ability to cast the combat actions, so we will cover this next in our Godot RPG tutorial.
Casting Combat Actions
The final step to finishing the player’s UI and our Godot RPG tutorial game is to set up the systems to handle our Characters casting combat actions.
We will expand the cast_combat_action function, which is called both by our enemy AI (that we will create in the next lesson), and the player’s onscreen buttons. The first step is to find out whether the action property is a combat action that causes damage or a combat action that heals the character. This can be done by checking if the damage property is more than zero.
func cast_combat_action(action): if action. Damage > 0:
If the damage property is more than zero, this means the action property is aimed at causing damage to our opponent. Luckily, we have already set up the take_damage function on our Character script, so we can simply call this function on the opponent variable.
func cast_combat_action(action): if action.damage > 0: opponent.take_damage(action.damage)
If the damage property is zero, but the heal property is more than zero, this means the action property is aimed at healing the current character. Because we want to heal the current character instance, we can simply call the heal function.
func cast_combat_action(action): if action.damage > 0: ... else if action.heal > 0: heal(action.heal)
With the combat action processed, we then want to end the character’s turn. To do this we want to call the end_current_turn function on the BattleScene root node.
func cast_combat_action(action): ... get_node("/root/BattleScene").end_current_turn()
Make sure to assign the Opponent variable for both the Player and the Enemy characters, if you haven’t previously.
We should now be able to press play and test the combat actions. If you try pressing the Slash or Stomp buttons, you will see the damage is applied to the enemy and the UI disappears as the turn changes, just as expected.
Currently, our Enemy character can’t fight back, but enemy AI is a lesson for another day and another Godot RPG tutorial.
Godot RPG Tutorial Wrap Up
This Godot RPG tutorial is done! You’ve successfully created an interactive combat system into a game, adding weight and strategy to your gameplay. Feel free to further expand the concepts taught here – add more combat actions, create stronger enemies, or introduce magic and skills. The scope is endless!
Want to learn more? Try our complete BUILD A MICRO TURN-BASED RPG WITH GODOT 4 course.
FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.