Wiki

Clone wiki

agtools / Tutorial 5b - Entities to Ashes

Outline

ts5b.png

In this step we're going to learn how to kill entities - to individually remove them from the session. The ability to create and destroy entities in response to logic and interactions makes it possible to sustain a self contained world without a predetermined outcome.

Entity Types

We will add a few more entities to demonstrate this scenario:

    EntityType_SPINNER,
    EntityType_SMALL_EXP,
    EntityType_LARGE_EXP,

And we do all the same stuff as before, to define, load and attach the assets...

spritesheet spinner_asset;
spritesheet smlexp_asset;
    spinner_asset.load("spinner.emx");
    smlexp_asset.load("smlexp.emx");
    EntityDefAsset_Sprite(EntityType_SPINNER, &spinner_asset);
    EntityDefAsset_Sprite(EntityType_SMALL_EXP, &smlexp_asset);

Mainloop

However the mainloop needs modified again. We need to add the complementary service which cleans up destroyed entities. This function is EntityExecuteAllRemoves(). As is the case for most entity processing steps, all 'removed' entities go on a pending list and are destroyed together in this pass.

The mainloop section for entities now looks like:

    EntityTickAll();

    EntityExecuteAllLinks();

    EntityExecuteAllRemoves();

Tick Function

We make a slight change to craft_fntick( so we spawn 'spinner' objects instead of the armor object. The spinner will be animated to improve the look of the thing.

    EntitySpawnFrom(EntityType_SPINNER, _pself);

However we are also going to add tick functions for the new entities we have defined. This is where we demonstrate entity destruction - all of these new objects will perform this trick.

First, the spinner. This tick function animates the sprite but also counts up to some threshold using _pself->counter, at which point it does two things...

void spinner_fntick(entity_t *_pself)
{
    // animate continuously
    if ((_pself->counter++ & 3) == 0)
    {
        // ...every 4th frame. cycle through frames 0-7
        _pself->frame = (_pself->frame + 1) & 7;

        // if entity has been alive long enough...
        if (_pself->counter > 50)
        {
            // ...die in flames
            EntitySpawnFrom(EntityType_SMALL_EXP, _pself);
            EntityRemove(_pself);
        }
    }
}

The first thing it does is spawn yet another object - an explosion. EntityType_SMALL_EXP. This is spawned at the same world coordinates using EntitySpawnFrom(type,self).

The second thing it does is remove itself using EntityRemove(self). On the next tick pass, this spinner will not exist. It will have left the explosion behind in its wake. The explosion has a fresh state and its own behaviour.

The tick function for EntityType_SMALL_EXP looks similar, but with some differences. It animates in the same way, but the final animation frame (14, where 0-13 is valid) is the trigger for self deletion. On frame 14, this entity won't have a chance to be drawn - it will be deleted by EntitiyExecuteAllRemoves() before the draw pass.

void smallexp_fntick(entity_t *_pself)
{
    // animate until expiry
    if ((_pself->counter++ & 3) == 0)
    {
        // ...every 4th frame. cycle through frames 0-13
        _pself->frame++;

        // remove after last frame
        if (_pself->frame >= 14)
        {
            EntityRemove(_pself);
        }
    }
}

Note the common pattern of using the counter with a mask ((_pself->counter++ & 3) == 0) to act as a kind of 'clock divider' to limit how frequently a piece of logic executes. The tick functions occur every refresh, but often you want to limit the animation rate (or thinking speed) within that tick, for specific effect - or just to save CPU time. The counter mask is one efficient way to do this. A decrementing counter is another. The mask method has the advantage that the counter is not modified - so it can be used to count more than one thing at a time.

When writing performance AI for old machines like this, try to be clever with bits and operations to reduce the duty cycle of expensive logic. Quite often something only needs done every N'th refresh. The AND, ADD, SUB, XOR operations are your friend here.

We'll look at some other more subtle efficiency tricks later on.

The Entity Dictionary

Last, we add our entities to the dictionary so they may be spawned. As before, we are only specialising the fntick field to assign our tick functions. The other fields are left the same as for the other entities.

    // spinner
    { &spinner_fntick,0,0,

    // explosions
    { &smallexp_fntick,0,0,
    { &largeexp_fntick,0,0, 

Summary

What you should see is 3 craft drifting down the screen as before, but this time dropping spinning 'mines' which explode after a short time.

Again we have not changed much code. It's mostly the same types of changes as before - defining entities, assets, and making tick functions. The details are in the tick functions themselves.

The last step will put some finishing touches on the 'demo' and demonstrate entities switching behaviour within a tick function.

Updated