How to Make a Strategy Game in Godot 4

Aspiring to create a strategic game with character units that respond to player input? Then this guide is your ultimate resource for building Godot strategy games.

We’ll delve into how to implement a Game Manager script, control player units, and manage enemy units in a Godot-based game environment. From detecting units to handling player inputs, we’ll cover all the necessary aspects to get your characters ready for action-packed gameplay.

If you want to learn these topics in detail, you can review our full course Real-Time Strategy Game with Godot 4 which covers these concepts more in-depth.

Let’s dive into our Godot strategy game tutorial without delay!

Project Files

We have included the assets showcased in this tutorial to use for your own Godot strategy game project.

Download Project Files Here

CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Game Manager – Part 1

In this first part of our Godot strategy game tutorial, we will be working on the Game Manager script. This script will handle everything from detecting units to the player inputs, allowing the player to select and command their Units. We will be attaching this script to the root node of the Main scene. This script will be called “GameManager.gd”.

Game Manager script

This script won’t make use of the _ready or _process functions, so we can remove these, leaving just the extends line.

extends Node2D

Game Manager Variables

This script will only need three variables to keep track of the Godot strategy game. The first will track our selected_unit, which will have a type of CharacterBody2D. This variable will keep track of the unit the player currently has selected.

var selected_unit : CharacterBody2D

We also need two variables to keep track of every Player Unit, and another to keep track of every Enemy Unit. For this, we will be using a type of Array, which is a variable type that can hold multiple values in a list format. To define the type of data the Array will store, we will use square brackets containing CharacterBody2D. We will call these variables players and enemies.

var players : Array[CharacterBody2D]
var enemies : Array[CharacterBody2D]

Get Selected Unit Function

The first function we will create is the _get_selected_unit function. This function will find if a Unit is currently under the mouse cursor, and return that Unit if one is available. To do this we will be sending a query to Godot’s Space State, telling it we are looking for a Unit object and where we are looking for it. This sounds a bit complex at first, but it can be broken down into multiple steps.

The first step is to get the Space State and store it in a variable, called space.

func _get_selected_unit():
    var space = get_world_2d().direct_space_state

Next, we want to create a query variable which will be a PhysicsPointQueryParameters2D object. This will allow us to pass the query a position value, which means Godot will search the scene for an object at a given position.

func _get_selected_unit():
    ...
    var query = PhysicsPointQueryParameters2D.new()

For the position value, we will supply the global mouse position, as we want to check what object is below the mouse cursor.

func _get_selected_unit():
    ...
    query.position = get_global_mouse_position()

The next step is to create a variable called intersection, which we will get using the space object we defined above. This function will take our query value, therefore giving the mouse position, and a value of how many objects to find, which we will pass as object.

func _get_selected_unit():
    ...
    var intersection = space.intersect_point(query, 1)

This code will find if an object is below the cursor in the scene. The next step is to find if an object was found, which we can do using the is_empty function and an to check that it is not empty.

func _get_selected_unit():
    ...
    if !intersection.is_empty():

If the intersection value is not empty, we will return the collider of the first object found (at the 0th position in the list).

func _get_selected_unit():
    ...
    if !intersection.is_empty():
        return intersection[0].collider

Finally, if this code doesn’t run, we can return a value of null at the end of the function.

func _get_selected_unit():
    ...
    return null

General Game Manager Functions

With this function complete, we can begin implementing the other functions in our Godot strategy game. To begin with, we will create each function using the pass keyword, so that we can come back and add the functionality afterwards. The first function will be called _try_select_unit, and will call the _get_selected_unit to try and find a Unit object to select.

func _try_select_unit():
    pass

The next function will be called _select_unit, which will handle the functionality behind selecting a Unit object when one is found. This function will have a parameter of unit, which will be the Unit object found below the cursor.

func _select_unit(unit):
    pass

We will need a similar function called _unselect_unit, which will unselect the Unit stored in the selected_unit variable.

func _unselect_unit():
    pass

_try_command_unit will be a function that is called when the player right-clicks on the ground or an Enemy Unit.

func _try_command_unit():
    pass

Finally, we will use the _input function provided by Godot to handle input events. This function requires a parameter of event which will give us the information about the player’s input.

func _input(event):

We will start by checking if the event property is a mouse-click event.

func _input(event):
    if event is InputEventMouseButton and event.pressed:

This code checks if the event property is a mouse button event, and then checks if the event is about a mouse button being pressed. Next, we want to check if the button is the left mouse button as this will be used to call the try_select_unit function.

func _input(event):
    if event is InputEventMouseButton and event.pressed:
        if event.button_index == MOUSE_BUTTON_LEFT:
            _try_select_unit()

On the other hand, if the right mouse button is pressed, we will call the _try_command_unit function.

func _input(event):
    if event is InputEventMouseButton and event.pressed:
        ...
        elif event.button_index == MOUSE_BUTTON_RIGHT:
            _try_command_unit()

We can now implement the _try_select_unit function, as this will be the first to be called. Inside this function, we will check if the mouse is over a unit, which we will do using the _get_selected_unit function.

func _try_select_unit():
    var unit = _get_selected_unit()

This function will either return a collider object or return null, depending on if an object is found or not. The first thing to check is whether an object is found (!= null) and then check if the object is actually a Player Unit object. We want to check the is_player variable here, as we don’t want to try and select an Enemy Unit.

func _try_select_unit():
    ...
    if unit != null and unit.is_player:

If this is the case, we will call the _select_unit function, while sending over the unit variable.

func _try_select_unit():
    ...
    if unit != null and unit.is_player:
        _select_unit(unit)

In the case a Unit object isn’t found, we want to call the _unselect_unit function, as the player is clicking in empty space.

func _try_select_unit():
    ...
    else:
        _unselect_unit()

Continuing the selection code, we first want to call the _unselect_unit function in the _select_unit function, so that before a new unit is selected, the last one is unselected.

func _select_unit(unit):
    _unselect_unit()

We can then update the selected_unit variable with the new unit parameter. We can also call the toggle_selected_visual function on the unit, to make it visible to the player that the Unit has been selected.

func _select_unit(unit):
    ...
    selected_unit = unit
    selected_unit.toggle_selected_visual(true)

In the next section of this Godot strategy game tutorial, we will continue implementing the other functions in this script.

Game Manager – Part 2

To continue on from the previous section in this Godot strategy game tutorial, we will begin by implementing the _unselect_unit function. The first thing to do here is to check if there is currently a selected unit, as we don’t want to try and unselect nothing.

func _unselect_unit():
    if selected_unit != null:

In the case that there is a selected_unit value, we will toggle the selection visual on this Unit.

func _unselect_unit():
    if selected_unit != null:
        selected_unit.toggle_selection_visual(false)

We will then update the selected_unit value to null to show there is no currently selected Unit object.

func _unselect_unit():
    ...
    selected_unit = null

The final function to implement is the _try_command_unit function. We don’t want this function to run if no selected_unit exists, so this is the first thing to check.

func _try_command_unit():
    if selected_unit == null:
        return

The next thing this function needs to do is find out whether the mouse is currently over an Enemy Unit. This means we can reuse the _get_selected_unit() and store it in a variable called target.

func _try_command_unit():
    ...
    var target = _get_selected_unit()

To check if this is an Enemy Unit we first need to check the target value is not null, and then check the is_player property is false.

func _try_command_unit():
    ...
    if target != null and target.is_player == false:

If the player has clicked on an Enemy Unit, we can direct the selected_unit towards it, using the set_target function.

func _try_command_unit():
    ...
    if target != null and target.is_player == false:
        selected_unit.set_target(target)

In the case that an Enemy Unit isn’t found, we can call the move_to_location function to make the selected_unit move towards the global mouse position.

func _try_command_unit():
    ...
    else:
        selected_unit.move_to_location(get_global_mouse_position())

The final thing to do is make sure Is Player is checked on the Player Unit root node of the PlayerUnit.tscn scene. Make sure to save (CTRL+S) this to update the scene. This should be unchecked on the Enemy Unit scene.

Is Player checked

Testing the Functions

We are now ready to begin testing the functions we have set up for our Godot strategy game. To do this, press play and try left-clicking on the Player Unit object, this should make the selection visual appear.

testing the functions

In case this isn’t working, you can try changing “$Sprite2D” to “$Sprite” in the _ready function of the Unit.gd script. You should be able to right-click anywhere on the ground and the Player Unit will move to that location. If you left-click off the agent, it will deselect it.

You might notice that the Unit often stops short of where you click to move, to fix that we will set the Path Desired Distance and Target Desired Distance properties of the NavigationAgent2D in the Unit scene to 1. We will also change the Path Max Distance property to 10.

NavigationAgent2D properties

The final thing to test is right-clicking on the enemy, this will make the Player Unit move towards the Enemy Unit and attack the enemy. This is a bit difficult to see, as there is no visual representation of the attacks, but you can easily set up some sprite flashes to showcase this.

Godot Strategy Game Wrap-Up

Congratulations, you’ve successfully completed this Godot strategy game tutorial and now possess the skills required to handle and manage game characters in Godot. The Game Manager script you’ve written is an essential asset when controlling character units in any strategy-based game.

However, the journey doesn’t stop here. You can refine this project by introducing more complex scenarios, adding visual effects, or incorporating audio elements for an enhanced gameplay experience. No matter what additions you make, this project is a valuable piece of work to showcase in your development portfolio!

You can also consider exploring more about Godot and game development with other tutorials – as there are many strategy sub-genres as well as RPGS and more.

We wish you good luck with your future game projects and anticipate your creations to create commendable strides in the gaming industry!

Want to learn more? Try our complete REAL-TIME STRATEGY GAME WITH GODOT 4 course.

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.