Wiki

Clone wiki

agtools / Entities

Entities & Entity Management

The entity management system provides a very thin layer of abstraction on top of some related systems including (but not limited to) assets, drawing, behaviour and collision detection / interactions.

Using entities helps coordinate these things in a simple way, and allows efficient batch processing of related types of work. Fewer interfaces & component transitions are involved, fewer machine cycles are wasted, less programming time is required.

Consistency & coherence between objects and systems is also improved since all operations of a given type are executed in a single pass at a known point in time within the 'main loop' or 'foreground thread'.

Ok fine, so how does it work?

Entity core type:

Every object in the game world is represented by one core type:

entity_t

This is a simple C struct with an equivalent struct representation in the 68k modules (It may be edited/extended, but the 68k representation will need updated if this happens). It's big enough to hold the most common state variables for a game object, but light enough to create quickly in reasonable numbers.

entity_t also provides the minimal interface to AGT systems. It can be further wrapped to add greater complexity for games which require it (converting it into a proxy representation subject to control by another more comprehensive kind of game object). Note: None of the current examples apply this kind of wrapping but it is a powerful way to extend the entity system without rewriting it for each scenario.

Defining entities:

To differentiate between different kinds of entity, it's necessary to set up an entity enumeration list and dictionary to match (the dictionary contains any default values for the static fields of the object):

enum EntityType : int
{
    // special viewport entity, tracks visible world
    EntityType_VIEWPORT,

    // player
    EntityType_PLAYER,

    // enemies...
    EntityType_BADGUY,


    // end!
    EntityType_MAX_
};

The dictionary is an array of entitydef_t objects, each defining the default fields for one entity type:

entitydef_t entity_dictionary[EntityType_MAX_] =
{
    { entity 0 defaults... },
    { entity 1 defaults... },
    { entity 2 defaults... },
    { entity 3 defaults... },
};

The default fields are:

  • fntick: the tick or AI function to be executed on each game tick (0 = none)
  • fncollide: the collision response function to call when an interaction occurs (0 = none)
  • passet: the asset (usually a sprite graphic) to use for drawing (0 = none)
  • rx/frx: integer/fixedpoint object/hitbox x position (top left)
  • ry/fry: integer/fixedpoint object/hitbox y position (top left)
  • vx/fvx: integer/fixedpoint x velocity
  • vy/fvy: integer/fixedpoint y velocity
  • sx: integer object/hitbox width
  • sy: integer object/hitbox height
  • ox: integer object origin x-offset (from rx)
  • oy: integer object origin y-offset (from ry)
  • tick_priority: lower values are ticked first
  • draw_hitflash: flags affecting drawing
  • health: health points
  • damage: damage caused to other objects on contact (if any)
  • frame: animation frame to use (from graphic asset)
  • counter: general purpose AI counter
  • drawtype: one of several builtin drawing types (EntityDraw_NONE = invisible)
  • drawlayer: if layer support is enabled, EntityLayer_??? sets the draw priority order
  • f_self: entity flags defining 'self'
  • f_interactswith: entity flags defining possible interactions with others

Notes:

The entity list must always include the VIEWPORT object type, which is needed by some AGT systems. The other objects are up to the programmer - even the player object is optional.

The dictionary only contains defaults used to initialise new entities of each type. All fields may still be modified after an entity has been spawned. The ability to easily swap tick functions for example to be important for AI programming.

Any fields not used for their intended purpose can be repurposed. e.g. 'damage' points are not always relevant for an entity, so it can be used as a counter or general purpose variable.

Avoid modifying the dictionary itself - while it is possible, it is probably a fast track to weird bugs since it is shared across all entity spawn methods.

See detail elsewhere on draw types, interaction flags etc.

Initialisation:

Assuming the entity types and dictionary are defined, it is necessary to initialise the entity manager:

EntitySetup(EntityType_MAX_);

This can also be used to reset the entity system (e.g. between game levels, or after player death).

Spawning Entities:

All entities are implemented in terms of the two types described above: entitydef_t and entity_t. The former describes defaults for all entities of a given type. The latter describes an instance of that type which is spawned or activated into the game world. There can be multiple instances of any type of entity active at any time. It is the instances of entity_t which have position, animation, other state and get created and destroyed at runtime. The entitydef_t types in the dictionary are just templates from which the real instances get created.

This is a common pattern in games and most other other kinds of software. It's important to understand the concepts here before making use of the entity system.

The entity_t type is internally made up of two 'sections' of data/variables. A static section - default properties which are defined in an entity dictionary - and a dynamic section, which is maintained only by the entity instance and engine systems dealing with it.

When an entity is spawned into the world, the static fields from the entity dictionary entitydef_t are copied into each entity_t instance created. The dynamic fields are not. This arrangement accelerates the spawning of small entities like bullets without too much overhead (either manually setting fields, or copying too many dictionary fields on each spawn).

entity_t *g_pe_player = NULL; // global pointer to entity 'player'
...
g_pe_player = EntitySpawn(EntityType_PLAYER, 0, 0); // spawn 'player' at world position 0,0

Note: Some hidden tricks are used to accelerate the static init for each entity spawned. This requires the entity_t to be defined in a unusual way in the header file help make the copying step efficient for the static fields.

Preparing entities for mainloop:

Spawning an entity will (normally) schedule that entity to be 'linked' into related systems which get updated during the mainloop - the AI-tick and 2D-coherence systems being the main ones. This linking operation is scheduled for all new entities, once on each tick (and optionally before the first tick, if some entities are made in advance - e.g. the player).

Since the spawning of entities only schedules them for linking into the world at some later point - the links must then be be executed. This is done by one function:

EntityExecuteAllLinks();

Notes:

Executing all links at once is faster than doing it ad-hoc on every spawn and helps avoid spawn/tick ordering bugs.

There are several variations on the EntitySpawn() function, with different features. At least one will cause an immediate link upon spawn - see docs on EntitySpawn() for cases where this is useful.

Processing entities in the mainloop:

During each pass of the mainloop, each entity gets a chance to execute its own local AI behaviour or 'tick function' (entity_t::fntick).

Only linked entities which are also tagged with EntityFlag_TICKED will take part in this step (Entities which have been spawned but not yet linked will need to wait for the next tick).

EntityTickAll();

Note: Since newly spawned entities are normally not linked immediately, their first tick will usually be on the next frame - not later in the current frame. This is usually important for game state coherence, and to avoid certain kinds of evil game AI bugs.

Once ticked, the entities have a chance to interact with each other. Only linked entities which are at least partially visible and also tagged with EntityFlag_SPATIAL will take part in this step.

EntityInteractVisible();

Each entity is represented internally by a rectangle (or hitbox). This is independent of whatever drawing method and/or offsets are used to represent it onscreen. Interactions are (at the top level at least) in terms of this rectangle, not the graphic (finer, more specific levels of interaction test can be implemented in the hit functions themselves if required).

Note: Entity rectangle position & size defined by:

entity_t::rx, entity_t::ry
entity_t::sx, entity_t::sy

Next we must execute any links for entities created during the tick pass (on this frame).

EntityExecuteAllLinks();

...and then to remove any entities killed during the last tick pass.

EntityExecuteAllRemoves();

Finally each linked (& surviving) entity gets a chance to represent itself onscreen:

EntityDrawVisible(...);

Summary:

This was a simplified walkthrough, omitting many of the finer details. It covers only the basics of entities and how the entity functions relate to the game mainloop.

Updated