Now that we have a RTS camera, procedurally generated map, and a-star path finding set-up, we are just one step away from creating a unit that walks. In this post, I will be creating a simple unit with no state machine. State machine is common in RTS games, where we have different characters having different states (walking, idle, fighting, collecting, depositing, etc).
We will be having a simple script that uses navigation system to move the unit around.
Creating RTS Units
Start by creating a folder named “Units”. In this folder, create a script named “Unit.gd”. This is going to be the base class for all kinds of units in out game. This class will provide basic functionality such as selection, movement and other basic operations that are common to all kinds of units in an RTS game.
Now, create a scene for a test unit, named “Unit.tscn”. This is a temporary unit that we will delete after testing, as we are going to implement a state machine later in development, to manage complex states. This scene is just a means of testing our path finding module we developed earlier.
In Unit.tsn scene, root node must be CharacterBody3D
, and it must have following children: MeshInstance3D or Child Scene for the Character
and CollisionShape3D
. Attach “Unit.gd” script to this scene’s root. This is how the overall scene setup looks like:
- Make sure the
NavigationSystem
&RaycastSystem
we developed earlier are singleton (auto-load). TO make them singleton (if you didn’t already make them, go toProject Settings -> Globals
).
In Unit.gd:
extends CharacterBody3D
# Unit's current path (empty if idle)
var current_agent_path = []
var current_agent_path_index = 0
func _physics_process(delta):
# pathfinding code
if Input.is_action_just_pressed("left_mouse_button"):
var mouse_pos = RaycastSystem.get_mouse_world_position()
var from_pos = self.global_position
var to_pos = mouse_pos if mouse_pos else self.global_position
var path = NavigationSystem.get_shortest_path(from_pos, to_pos)
if not path.is_empty():
current_agent_path = path
current_agent_path_index = 0 # Reset path index
# Move agent along the path
if current_agent_path.size() > 0 and current_agent_path_index < current_agent_path.size():
var target_pos = current_agent_path[current_agent_path_index]
var direction = (target_pos - self.global_position).normalized()
var distance = self.global_position.distance_to(target_pos)
# Move the agent toward the target position
self.look_at(target_pos) # Look at target position
self.global_position += direction * min(distance, delta * 32) # Cap movement to the remaining distance
### animation_player.play("walk_animation") OPTIONAL
# Check if agent reached the current target position
if distance < 0.5:
current_agent_path_index += 1 # Move to the next point
print("Reached waypoint:", current_agent_path_index)
# Clear the path when the destination is reached
if current_agent_path_index >= current_agent_path.size():
current_agent_path.clear()
print("Destination reached")
# If current_agent_path is null, then it is idle
if current_agent_path.is_empty():
velocity = Vector3(0,0,0)
### animation_player.play("idle_animation") OPTIONAL
When you instantiate the above “Unit” scene in game world, you will see it moving wherever you click the mouse. You will have to define the left_mouse_button
input event in Project Settings -> Input Map
.
The above script does not take care of selection, so all units that are instanced in the world will move towards the mouse position. We already knew that it was a test for our NavigationSystem
& RaycastSystem
.
In the following post, we will add an RTS selection system to the unit, so the only unit which is selected, moves. After that, we will re-create the unit using a better state-machine based approach.
Problems in our RTS Unit
- If you see unit misbehaving during motion, it is likely due to
look_at
function forcing the character to look down or up, depending on the relative elevation of clicked position and the unit’s position. To solve this, make sure to keep they-component
of target position and player position to be0.0
during calculation. - There are tons of possible ways the unit can go wrong. Possible places to check for problems in are velocity, source/target positions for path finding, collision layers and so on.