Wiki

Clone wiki

agtools / Spawning

Spawning Entities

Placing entities into the world is called instancing or spawning.

For example in a SHMUP or platformer you would typically spawn one object for the player plus some other objects for enemy craft or characters at various predetermined placements in the world.

Note: In AGT, the viewport itself is also an entity and must be spawned before anything can be drawn!

During the game you would spawn other objects as required - bullets, powerups, visual effects. These will often be short-lived objects. Some entity types may be spawned individually, others in great numbers. Many objects will act independently. Some may be connected with others.

However a wide array of other interesting things can be done with object spawns with a bit of imagination. More on this later.

What it does

Spawning an entity simply takes an entity type ID, looks up the entity dictionary and manufactures an object instance with those characteristics, linking it into the various AGT systems required. The instance is active and maintains its own state until it either removes itself, or something else removes it, or the entity manager is reset, at which point it gets unlinked from all systems.

In the majority of cases, an entity will remove itself once some condition has been satisfied. e.g. health depleted, a contact detected, a key collected, a counter times-out etc. This is the preferred pattern simply because it is the simplest and doesn't require any links between objects.

Note: Entries in the entity dictionary are not active - they just provide information used to create instances of each type. There is additional information on this in the Entity Management overview.

How to set it up

All entity related interfaces are accessed via the include file agtsys/entity.h

To spawn an entity requires an entity ID (also referred to as entity type) to be defined for it. This is straightforward. In the example below, we define only two. One for the viewport and one for the player (The viewport is a requirement when using entities at all). The list must be terminated with 'EntityType_MAX_'.

#!c++

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

    // player
    EntityType_PLAYER,

    // end!
    EntityType_MAX_
};

Somewhere in the project, there must be a dictionary defined with default fields for each entity type. Usually this is found at the bottom of the main sourcefile. Note: The dictionary can be empty if entities are not being used, but must still be defined.

#!c++

entitydef_t entity_dictionary[EntityType_MAX_] =
{
    // [functions]                              [coords]                        [control]       [tick state]        [rendering]             [classification properties]
    // tick,collide,asset                       px/y,vx/y,sx/y,ox/y             tickpri,drflag  h,d,frame,counter   drawtype,drawlayer      f_self,f_interactswith  

    // definition for EntityType_VIEWPORT
    { 0,0,0,                                    {0},{0},{0},{0},0,0,0,0,        0,0,            0,0,0,0,            EntityDraw_NONE,0,      EntityFlag_VIEWPORT|EntityFlag_SPATIAL, 0 },

    // definition for EntityType_PLAYER
    { 0,0,0,                                    {0},{0},{0},{0},0,0,0,0,        0,0,            30,0,0,0,           EntityDraw_EMXSPR,0,    EntityFlag_PLAYER|EntityFlag_TICKED|EntityFlag_SPATIAL, EntityFlag_POWERUP },

};

See documentation on assets and tick functions for configuring graphics data and behaviours for entities. For this section we'll deal only with spawning.

Before any entities can be spawned, the entity manager must be initialised once:

    EntitySetup(EntityType_MAX_);

This can also be used to reset the world and clean out entities at the end of a level. It does not affect drawing - it just removes active entities and prepares them for reuse.

Once an entity object has been spawned, it is in a 'pending' state where it cannot get an opportunity to draw or interact with other objects. This ensures objects don't begin interacting mid-way through the tick phase, as a result of having been spawned sometime during that phase. While this usually isn't a problem, it can sometimes lead to obscure ordering bugs in game code.

For example - if an object spawns a new object of its own type (a 'clone') while killing itself, then it could potentially never exit the tick phase, as the newly created object would be ticked after it in an endless chain. Deferring the spawns solves this, and other related problems in a simple way.

Spawns are therefore pending until the end of the tick phase (unless deliberately spawned using the 'immediate' method, documented below). Removals are handled in a similar way.

To convert entities from pending state into active objects it is necessary to call this function:

    EntityExecuteAllLinks();

This is typically done just before the mainloop (for entities spawned during level load) AND immediately after the entity tick phase within the mainloop, which would look like:

    ...
    EntityExecuteAllLinks();
mainloop:
    ...
    EntityTickAll();
    EntityExecuteAllLinks();
    ...

Some entities can also be removed from the world before the 'game level' is complete, and is subject to similar sorts of ordering problems as spawning, so a removal phase is also required. If it is not needed, it is benign so it makes sense to just include it and forget about it. So the mainloop part for entities looks like:

    ...
    EntityTickAll();
    EntityExecuteAllLinks();
    EntityExecuteAllRemoves();  
    ...

Note that entities require an opportunity to draw, which involves some other stuff added just after these phases. For documentation on those parts, see elsewhere on the wiki (the tutorials cover this also).

How to Spawn Entities

You're now ready to spawn the viewport & player objects into the world. This can be done with a single call.

    EntitySpawn(EntityType_PLAYER, 0, 0);

As indicated above, calling this will create an instance of the PLAYER object type in a pending/dormant state, and it will become active after the next call to EntityExecuteAllLinks. Beyond that point, the object is subject only to its own behaviour (tick function), other objects and the rules governing the entity system. There is usually no need for the game mainloop or other 'global' program code to edit the entity but in a few cases this is still required (e.g. for the viewport).

EntitySpawn() is just one of several methods provided to spawn objects. It accepts the entity type ID and (x,y) world coordinates to place the object. Note that entity coordinates are always relative to the upper-left corner of the world, not the viewport/screen. However screen-relative coordinates can be implemented by secondary mechanism, covered in the tutorials.

Spawning an entity returns a pointer to that object, so it can be modified immediately. e.g.

#!c++

    entity_t *penew = EntitySpawn(EntityType_PLAYER, 0, 0);
    penew->vx = 1; // set initial velocity
    penew->vy = 0;

This is important since default fields cover a limited set of values - those most likely to remain in common across many instances. For fields such as velocity, the values are unknown in advance and must be filled as required. The (x,y) coordinate pair is such a common requirement that it is simply an argument to the EntitySpawn function itself.

The viewport object is spawned with a pointer kept, so it may be used by engine components.

    g_pe_viewport = EntitySpawn(EntityType_VIEWPORT, g_viewport_worldx, g_viewport_worldy);

Selecting a Spawn Method

There are however other variants on EntitySpawn() which provide either additional convenience or additional functionality, which becomes important for more sophisticated object behaviours. We'll cover those methods here.

The first we have covered briefly above:

entity_t * EntitySpawn(EntityType _etype, s16 px, s16 py);

This spawns entity of type _etype at world coordinates (px,py), returning a pointer to the new object. The returned pointer can be ignored if none of the entity's fields need changed at the time of spawn.

This function does not use any origin/offset information. It literally places the entity rectangle in the world using the upper-left corner, in terms of world coordinates. So (0,0) would place the object neatly in the top left of the map, just touching the map edges.

As with the other spawn methods accepting a parent argument, the newly spawned object will point to that parent during its lifetime (or until changed).

entity_t * EntitySpawnFrom(EntityType _etype, entity_t *_pparent);

This spawns a new object relative to another, existing object. Often this will be used in a tick function (e.g. by the PLAYER) to spawn bullets or similar. The (x,y) coordinates of the new (spawned) object are based on the coordinates of the parent (spawning) object.

The new object's rectangle world coordinates are calculated relative to the parent's rectangle (rx,ry), the parent's origin offset (ox,oy), and the origin offset of the object type being spawned. This formula used is:

new(rx,ry) = parent(rx,ry) + parent(ox,oy) - new(ox,oy)

However the effect is to centre the new object on the parent object, using the origins of both objects. Given that the entity system only uses the rectangle upper-left (x,y) for all subsystems, for efficiency, the se offsets are involved only during relative-spawning and related operations.

entity_t * EntitySpawnRel(EntityType _etype, entity_t *_pparent, s16 _ox, s16 _oy);

This is similar to EntitySpawnFrom, but including a user-defined offset from the parent. It is just a more flexible version of the same function, which assumed an offset of (0,0). The formula is therefore similar but includes the user offset.

new(rx,ry) = parent(rx,ry) + parent(ox,oy) - new(ox,oy) + user(ox,oy)

As with the other spawn methods accepting a parent argument, the newly spawned object will point to that parent during its lifetime (or until changed).

entity_t * EntitySpawnNear(EntityType _etype, entity_t *_pparent, s16 _px, s16 _py);

This is similar to EntitySpawn, in the sense that it places the new object using absolute world coordinates. However this version expects a pre-existing object to assist with the placement.

The entity system uses spatial search structures for efficiency, so providing an object which is already present in those structures helps accelerate the creation of a new object to be placed near it. This is not critical - but the closer the 'parent' is to the new spawn point, the more quickly the new object can be placed.

e.g. a useful pattern is to spawn visible objects using the VIEWPORT object as the parent. This at least makes sure those objects take a shortcut to insertion if the world is large and already full of half-awake entities. For other cases - where all surviving objects are in the viewport anyway - it doesn't provide much extra gain except in special cases e.g. generating enemy waves sequentially, where each new object is known to be very near the last.

As with the other spawn methods accepting a parent argument, the newly spawned object will point to that parent during its lifetime (or until changed).

Note that EntitySpawnFrom, EntitySpawnRel both implement this behaviour automatically, since the parent object has a known position relative to the new spawn.

entity_t * EntitySpawnImmediate(EntityType _etype, entity_t * _pparent, s16 _ox, s16 _oy);

This is a variation on EntitySpawnRel, which skips the pending phase and links the object immediately to all subsystems. This means it may or may not be ticked during the same phase - tick order is not guaranteed outwith the tick priority system and pending phases. However by properly using the object tick priorities the spawned object can be arranged to tick after its parent.

Updated