In RTS games, buildings play a vital role. Town center in Age of Empires, and Keep in Stronghold act as the center of civilization. All the resources collected go to the town center, and all the villager units are created from the town center. Also, other buildings, such as barracks are used to spawn military units. This way, buildings are one of the core mechanics of RTS games.
Interestingly, I found that buildings are easier to make than I thought. They are static and require no state management so far.
Breakdown
Buildings in RTS games consist of a mesh, and physics collision shape. Additionally, some other custom attributes might be there for example name, health, etc of building. in Godot, it is easy since we have to make a scene for the building, as a StaticBody3D
, and we will add a MeshInstance3D
and CollisionShape3D
as its child.
Creating Buildings in Godot
Start by creating a folder named “Buildings”. In this, crease a script named “Building.gd”. This will be the base class for our building. The building script does not contain anything of interest. It only assigns the collision_layer
I set for buildings (it, too, is optional). Additionally, to display building name in 3D world, I added a Label3D
node in Godot. So, in code, this label is updated to reflect the name of scene for display:
extends StaticBody3D
class_name Building
@export var label3d: Label3D # To show name of building
func _ready():
add_to_group("buildings")
collision_layer = 0b00000000_00000000_00000000_00000100
label3d.text = self.name
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
if self in get_tree().get_nodes_in_group("selected_buildings"):
label3d.visible = true
else:
label3d.visible = false
Above label3d
is displayed only if building lies inside the selected_buildings
group. So, in order to let the buildings be selected/deselected, we need to add a _select_buildings
method in Selection Box system, similar to how we did selection of unit. The only difference is that, we need to pass the collision_mask
for the building, and not for the unit (so the raycast will detect building).
Creating the Scene
The building script we created earlier will be used only as a base class for the specialized buildings class. To create a real building, we will start by creating a scene for a specific building. Let’s say we are creating a town center. So, create a folder inside “Buildings” folder with name “TownCenter”. In this folder, create a scene named “TownCenter” with StaticBody3D
as its root. Add MeshInstance3D
and CollisionShape3D
as its child. overall setup looks like this:
Now that building is created, and it can be selected as well if we modify the selection system to select buildings as well (apart from only units). But the important thing is, what a particular building will do?
In age of empires, towers throw rocks or arrows, town centers receive resources, huts increase the population count of a civilization, docks allow to create new ships, and so on. This is exactly the logic we will put in each building’s unique script.
So, for this Town Center, attach a script named “TownCenter.gd”. This script will extend the base Building
, and will have all the TownCenter-specific logic in it. What is town center logic? It is a stockpile of resources, it allows for villager unit creation and so on. For now, as a minimal demo, I made town center to only be a place where we can deposit resources. I mean, it is a stockpile (as we call in Stronghold):
extends Building
# Resources stored in stockpile
var stockpile: Dictionary = {
# Format: "resource_type": amount
}
func _ready():
super()
add_to_group("town_centers")
func _process(delta):
super(delta)
# Function to deposit resource - resource name comes from RTSResourceTypes enum from RTSResource.gd
func deposit_resource(resource_name: String, amount: int):
if stockpile.has(resource_name):
stockpile[resource_name] += amount
else:
stockpile[resource_name] = amount
print("Deposited", amount, "of", resource_name, "to stockpile")
print("Stockpile:", stockpile)
That’s it. You can instance town center in the game world.
Important: Modify the A-star graph and exclude points that lie inside the placed Buildings
This is a very important step. We discussed earlier in our custom navigation system tutorial that we will need to modify the a-star graph in run-time, whenever we place buildings. We will have to remove all the points (nodes) that lies inside the space which the building covers. This will prevent the RTS agents (units) to take these nodes into consideration when calculating path.
For this, you can create an area of plane shape covering the entire base of you building. And whenever a building is placed, just check if any a-star point lies inside the area spanned by that plane. If it does just disable the point (so it will not be considered in a-star calculations).
Entirely removing points are not recommended, since later if your building destroys, the surface should again be walkable.