Advanced Patterns - GoldhawkInteractive/X2-Modding GitHub Wiki
Archetype
s are implicitly convertible to and from Matcher
s. This is particularly handy when creating Families based on them. It gives you safety when you alter the Archetype
and makes the definition of the Family
easier to read.
public class GameArchetypes {
public static Archetype Player = Matcher.All<PlayerComponent>();
}
var players = World.RegisterFamily(GameArchetypes.Player);
Typically Events can easily be distinguished into two separate groups: Commands and Reports. A Command event typically instructs the game to execute some logic, while the Report signals its outcome. Internally we've found it handy to use this naming in the class name and even use it in the class hierarchy, so that we can filter on each one respectively. Per example:
// An Action is defined as any move or attack done by a entity in this game
public abstract class ActionCommand : Event {}
public abstract class ActionReport : Event {}
// Orders a unit to move to a certain location
public class MoveToCommand : ActionCommand {}
// Thrown to indicate that the Unit has started or finished movement.
public class MovementStartedReport : ActionReport {}
public class MovementFinishedReport : ActionReport {}
Using the above structure, one could implement an OverwatchSystem
that listen to all ActionReport
events to decide whether it needs to trigger.
All Event
s have a Consumed
variable, which any System
can set to true during its handling to ensure the Event
is not forwarded to any Systems
coming after it. This allows us to implement a pattern in which a System
throws an Event
in the World
and subscribes to it itself, executing some logic that can then be overridden by other Systems
.
So if the event does enter the System
, it executes the default logic. However, any System
added to the World
that subscribes to the event, can consume it to block the default logic from executing. This way you can implement priority-based execution of some logic. (Think default clean-up behavior, gates of logic on whether the default logic may execute, etc)
A more concrete example would be using the above ActionReport
again. Let's say we define an AISystem
that in response to the ActionReport
by any AI unit will issue the next command to the AI. We could implement an OverwatchSystem
that listens to ActionReport
s and consumes them if it triggers an Overwatch, ensuring the AI still remains in a halted state.
After execution it would then give a signal to the AI that it could start again.
This pattern can be implemented in various ways:
Create an event and hold a reference to it, throw it in the World
and see if any System
has responded to it.
Use this pattern if you don't want to wait until the World executes the queue till you receive your results.
var movementReport = new MovementEndedReport(entity, location);
World.HandleEvent(movementReport) // Immediately delegates this event to all subscribing Systems
if(movementReport.Consumed) // Consumed is defined by default, to be more expressive, one can define a specific field in the Event, e.g.: MovementShouldStop
StopMovement();
Subscribe to the event, and ensure that the System it receives events last. This can be done by adding the System last to the World. This pattern is useful if you don't immediately need to execute the task and can wait on the World till the event is executed.
public void Execute(){
...
World.HandleEvent(new OverwatchEvent());
...
}
[Subscriber]
public void Cleanup(OverwatchEvent overwatch){
// The event was not picked up/consumed by anyone, so clean it up.
overwatch.entity.Delete();
}
Given the above pattern, one could implement a design that allows any Systems
to subscribe to an Event
and alter it. The altered event is then used to execute some logic. A more concrete example of this would be in an RPG game where an attack is made on another entity. Per example:
- Define a
DamageSystem
, it creates aDamageEvent
which does some base damage on an Entity, and throws the event in theWorld
usingHandleEvent()
. - Subscribe other systems to the
DamageEvent
, that add damage to thisDamageEvent
based on logic. E.g.:ResistanceSystem
which reduces the damage done by a DamageEvent based on the resistance of an entity, orEnvironmentDamageSystem
which adds damage done by the environment. - The
DamageSystem
sees how much damage was done by the event, and applies it to the entity.
var damage = new DamageEvent(entity, value);
World.HandleEvent(damage) // Both ResistanceSystem and EnvironmentDamageSystem add/subtract their respective values from the event
entity.DeltaHealth(-damage.value);