Kiefer Co:
@kieferandco
KieferAndCo
ChairmanCo

My Favourite Humans

Godot Unit Architecture

First Written    Tue Oct 12 04:12:18 2021
File Modified    Wed Feb 14 18:32:26 2024
Latest Upload    Thu Sep 19 03:09:54 2024

Pilum is a real time tactics game with many types of units from various civilizations from classical antiquity, as seen in our first gameplay test video!

To enable myself and Gunpreet to easily create and modify units, we decided on two key ideas:

  1. Units inherit from a basic unit class (in Godot, known as a scene)
  2. This basic unit is composed of distinct parts which handle specific responsibilities
Pilum unit architecture

Godot's scenes consist of trees of nodes, where parent nodes contain child nodes. The top-level node is called the root.

In Pilum's basic unit, above, you can see the script variables we've exposed on the root node. By changing the root node's colour and size variables, we can affect its appearance. Our root node acts as the glue, passing pieces to and from its child nodes. In this case, it passes colours to the sprites on its Visuals node.

We can also give the unit a starting position to march towards, which can be easily set to have a level begin with enemy units advancing towards the player's army. In this case, the root node sends those coordinates to its CommandSelection node, which is the unit's brain.

Some nodes in our hierarchy are generic, and meant to be used for all units:

  • The hitbox, which registers collisions with other units
  • The flank handler, which detects when allies or enemies are on a unit's flanks, protecting it or ambushing it from the side respectively
  • The motion handler, which applies smooth motion based on some stats (acceleration, turn speed, etc.)
  • The command selection node, which contains a state machine to describe where the unit should move to and what it should attack
  • The attack handler, which has three separate attacks:
    • A periodic blow against enemies in melee range represented by a cloud of dust
    • A powerful charge attack for when the unit makes first contact with a foe
    • (For units with ammunition), a ranged attack, such as a pilum or arrow

Other parts of the hierarchy are unit-specific. Different units have different sprites, such as a horseshoe for a cavalry unit, or a sword for a swordsman.

Units' stats are the most customizable part of our system, as they allow us to create completely different behaviours:

Pilum unit stats

Most of the other components have their behaviour defined by stats. This allows us to create a fast moving horseman unit with high speed and wide arcing turns, or a slow but easier to control archer just by altering stats. The motion handler doesn't need to be modified to create new styles of motion.

I learned about Godot's scenes and nodes system from jmbiv's Top-down Shooter Tutorial Series, which I highly recommend!

One of my biggest takeaways from his videos was to have nodes "call down, signal up".

Nodes can call methods directly on their children, because they will always have a reference to nodes they contain.

Children don't necessarily (and arguably, shouldn't) know who their parent nodes are, so they should rely on signals to send information back up the chain. Whether those signals get read or not is irrelevant for the child unit.

As an example, I use a similar system in Tributary:

Tributary unit architecture

In Tributary, units are split into key components similar to Pilum's units.

  • The BodyAppearance node controls all the sprites that make up a character's body, as well as how the entire unit is animated.

  • The InteractionMotion node relays information about how the character is situated within the world, and will eventually allow the character to change it by modifying terrain or picking up items.

  • The Brain node sends signals based on which keyboard keys or controller buttons are pressed.

All of these different nodes communicate with the root node, which contains a state machine that acts as the ground truth of what the character is currently doing and can do. This state machine reads when BodyAppearance sends signals marking ends of animations, or when InteractionMotion detects a surface that the unit can jump off of.

This method of loosely coupling children to their parents makes it very easy to work out interesting game mechanics and abilities.

For example, the Brain nodes of two units could be swapped, allowing a player to say, mind-control an enemy, or directly control a companion. The Brain node might be replaced by a variant which, instead of reacting to player inputs, follows AI logic. The unit only cares about what signals it receives and relays to its other nodes, and not about what its child nodes actually are.

In Pilum, this might mean an easy way to change units from AI controlled to player controlled, such as when a player gains the allegiance of new allies or defeated foes. Quickly swapping out stat containers and sprites might let us have a horseman dismount and become a foot soldier in the middle of a game, without having to destroy the entire unit and spawn a new one in its place.

The possibilities are endless!

Tags: Blog, Pilum

–Kiefer