Making an RTS Unit That Moves

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:

RTS military unit godot
  • Make sure the NavigationSystem & RaycastSystem we developed earlier are singleton (auto-load). TO make them singleton (if you didn’t already make them, go to Project 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

  1. 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 the y-component of target position and player position to be 0.0 during calculation.
  2. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *