Patterns - adammitchelldev/util8 GitHub Wiki
ECS has the advantage of being fully decoupled from components to systems and systems to systems; it is a very effective long-term choice of architecture, but can end up requiring a lot of disconnected but inter-related code when used to control game behavior. There is also additional overhead required in managing which entities are processed by which systems, which in most implementations requires parsing component requirement declarations and querying or modifying the entity lists.
Layer-Entity-Event-Behavior
An Entity is a Lua table, it may contain data relevant to various Behaviors
Events are keys where an Entity may have some invokable Behavior (a function in the table).
Behaviors are functions that operate on some Entity or Entities (in response to an Event being invoked).
Layers are lists of Entities. This is used to allow Events to be invoked separately on Entities, most commonly when doing collision detection (performed on two sets of layers).
Example:
The main events in most games are update
and draw
. These events are typically invoked on all Entities of all Layers. If the target entity has an update
function, it is invoked. The invoked update
may now choose to delegate behavior by invoking other events on the entity (such as physics
or animate
) or it may choose to directly invoke other behavior to create more complex inherited behaviors (such as an update_player
behavior invoking update_object
to inherit normal object behavior). Polymorphic behavior delegation can occur by invoking other events from each behavior, such as update_enemy
invoking the think
event.
Naming conventions:
Layers are named as plurals (players, enemies, items, etc.)
Entities do not have names themselves, but the entity constructor should be in the form new_%entity%
.
Events are simply the keys of behaviors, they should be simple, descriptive verbs where possible, nouns otherwise.
Behaviors should be named so they can be re-used. Typically, they should be named %event%_%behavior%
to indicate that they are meant to be used in a particular event (they may expect parameters). If a particular behavior is default, it may simply be called %event%
and in the case that it is only for one entity type, it should be %event%_%entity%
. Anonymous behaviors are strongly discouraged since they result in Lua instantiations.
The main update loop typically consists of:
for layer in all(layers) do
update(layer) --invoke update event on all entities in the layer
end
--invoke behavior for the collisions of entities in layers
for_collisions(bullets,enemies,shot_enemy)
for_collisions(player,enemies,player_hit)
--etc.
And the main draw loop:
for layer in all(layers) do
draw(layer) --invoke draw event on all entities in the layer
end
Events that occur in an update can be thought of as "systems", they should typically only ever be invoked once on each entity per update. This can be ensured by only allowing the event to be invoked from one behavior (a behavior specific to another system), or only behaviors of a specific system.
For example, only allowing animate
to ever be invoked in an update
behavior.
When sets of systems will be invoked by multiple behaviors of a system, a base behavior for that system should be made that invokes the systems (DRY), and then that behavior is invoked directly (like how one would invoke super()
in OOP).