Entity Control Architecture

Since RTS games require a different approach to entity control, we've developed a system for player input/interaction based on several controllers and abstractions:

Architecture can be displayed in action when a player gives an order to units.

architecture image

Let's start at the bottom.

1. Entity (units and buildings)

ARTSEntity is the base class for both units and buildings, and inherits from AActor. All the entities in the game that have health and have presence on the map will derive from this base classes, including building rubbles, neutral creatures, etc. As an ongoing effort during the development, we're trying to unify as much the common logic and rely on configuration and composition over inheritance as much as possible. This means that most likely, we will try to remove derived classes (such as RTSResourceBuilding) and replace with components (RTSEntityResourceComponent).

The main reason is that we want to be flexible. For example, we used to have RTSProductionBuilding (any building that can build units), RTSResourceBuilding (any building that produces resources), and RTSFortress. But if you know the BFME games at all, you'll realize that Fortresses can both produce units (Heroes and Builders), generate resources, and even purchase upgrades. Double inheritance would be a bad idea, and chaining inheritance wouldn't make sense either. Furthermore, units can also purchase upgrades! And maybe in the future we want some special unit that generates resources, or that can produce other units as well. The solution?

Move the functionality to components that can be attached to any ARTSEntity. In this way, we can select, mix and match any of those functionalities into a particular entity without worrying about their specific class. This is an ongoing process that is not complete, and we might still keep a few separated classes for specific entities as long as it makes sense, or if the benefits of the refactor are not high since we need to prioritise work efficiently.

1.1. Unit State Tree

Units have complex behaviors, determined by the orders received and other gameplay circumstances (for example, if they are knocked back or garrisoned inside another entity). In order to manage those situations effectively, we implemented Unreal's new State Trees (a lightweight version of Behavior Trees in a way), which follow a Finite State Machine pattern to manage states. For example, units will be in "move" state when running towards a target, or "attack" state when in range of an enemy.

1.2 Boid

Boids are the representation of units in the movement simulation. They drive the movement of units constantly based on the data input by the state trees and the unit itself. See Movement docs for a more complete explanation on the system.

2. Entity Handle

The class ARTSEntityHandle is an abstraction that represents a controllable item by the player. In BFME, units can be grouped into battalions. But the control over either units, buildings and battalions all behaves in the same way. For this reason, we decided to have an abstraction layer in the Entity Handle, that allows us to centralize all logic related to selection and order input. If a single entity (for example, a Hero or a building) is controlled, we still have an Entity Handle, that just happens to contain a single entity.

Entity Handles will perform actions based on their OrderQueue. This allows us to queue orders to be performed in sequence (such as when shift clicking), and it's the main data source for the entity handle state tree. The struct FRTSOrder is the data container for any order, and provides all necessary data for all orders to be performed.

Some complex orders will be able to issue more than one "sub-order" to the handle. For example, attack-move orders work as follows (simplified):

  • Attack-move order is queued.
  • The handle will first perform a "move" sub-order, towards the location clicked by the player. During the movement it will scan the surroundings to find enemies.
  • When an enemy is found, an "attack" sub-order will be queued BEFORE the previous order (inserted in the array before the current attack-move order).
  • After destroying the enemy target, the "attack" order is revoked and removed from the queue.
  • Then, the entity will proceed to perform the next order in the queue, which happens to be the original attack move order.

2.1. Entity Handle State Tree

This only exists (for now) for handles that control units (but not buildings). It controls mostly the high level behaviour of battalions according to the order queue. There will be a rough correspondence of a order in the queue, and the state that the handle is in. For example the state tree will be in "move" state when a handle receives a "move" order, and when the move task is completed, the state tree will remove this order from the queue. It will also propagate the state to its entities. So when a "move" order is given, the handle state tree will notify units that they should switch to "move" state.

3. Order replicator

This class performs almost no gameplay logic in itself. It is introduced mostly to optimise the networking of orders in multiplayer.

When a player has selected several entities and issues a order for them, most of the data for their orders is common. For example if it's a move order, the destination (clicked map location) is going to be the same for all of them. Or if it's an attack order, the target entity is also going to be the same. If we issue a single reliable RPC for each entity, we are duplicating most of the order data, and also sending potentially dozens or hundreds of repliable RPCs in a single frame. This will fill the reliable RPC buffer with bad consequences for anything else that needs to be replicated over the net.

The solution is simple: We just pack the data into an order packet that has a list of affected handles, and the rest of the data (location, target etc) is only included once. Then on the receiving end, the order replicator will unpack the packet and issue individual orders to each entity.

It also has the additional benefit to provide a single entry point for all orders. Sometimes we need to order entities programmatically (for example, units can be ordered to exit a building after being purchased) and this ensures that the orders are consistent over the servers and client.

4. Player Handle

The ARTSPlayerHandle was introduced as a similar class to PlayerState, which is replicated to all clients (whereas player controller is only replicated to owning client). It is also common to both human and bot controlled players, so we can use a single interface for both.

Player Handle has logic to select entities, and to issue orders to the selected handles.

5. Player Controller

The RTSPlayerController handles human input. As such, it will link the enhanced input actions to gameplay logic. For example, it will detect when a player clicks on the map, and determine if it clicked a unit/building, or if it clicked the landscape while having units selected. Then it will forward the potential orders or actions to the PlayerHandle if conditions are met.

It will also perform actions that are local to the player - such as playing a unit "move" sound when ordering them to move, or centering the camera on a unit when double-tapping their control group.