Object Management

Context


Up till now, our handling of game objects has been fairly straight-forward... almost "naive" even.

We've just created all our game objects at startup,
and referred to them via simple global variables.

This is very easy and, in small-scale projects, relatively convenient... but it's getting messy, isn't it?

All that "hard-coded" and repetitive ball1, paddle1,
paddle2 stuff is clumsy and error-prone.

Scale


...in particular, the approach we've been taking
so far doesn't scale, and a lack of scalability
should always worry you.

Imagine a game with hundreds, or thousands, of entities.
How could we hope to implement that
with our current techniques?

What we really need to do is generalise across the set of operations that we perform on our game entities, and provide more convenient mechanisms for accessing them in the code.

Another Brick in The Wall


We've actually seen this already, in Breakout...

Instead of treating each brick as an independent global object, you (hopefully!) wrapped them up in a container (the wall), and used that as a way of managing them collectively.

This approach allows us to write simulations of
more complex scenarios than we would otherwise
be capable of handling.

Beyond The Wall


The "wall" scenario is rather simple, and somewhat limited.

But the same principle can be extended to more general cases, where the objects inside the container are not quite so rigidly constrained...

An Entity Manager


What we need, in general, is an Entity Manager.

This would be a "service" object that allows us to create various game entities on demand, and automatically handle the "chores" of ensuring that we update and render them appropriately.

It may also provide a way of enumerating and accessing our entities at runtime, so that we can make them interact with each other (as required for, e.g., collision detection).

Consider...


We want to get away from crap like this:
function updateSimulation(du) {
    
    processDiagnostics();
    
    g_ship.update(du);
    
    g_extraShip1.update(du/2);
    g_extraShip1.update(du/2);
    
    g_extraShip2.update(du/4);
    g_extraShip2.update(du/4);
    g_extraShip2.update(du/4);
    g_extraShip2.update(du/4);
}

...And Move Towards...


Something nice like this:
function updateSimulation(du) {
    processDiagnostics();

    entityManager.update(du);
}

How Do We Do That?


Well, it's easy, isn't it?

The "entityManager" is looking after an array (or arrays):

entityManager.update = function(du) {

    for (var i = 0; i < this._aEntities.length; ++i) {

        this._aEntities[i].update(du);
        
    }
}

How Do We Get Stuff In?


That's easy too! :-)

Instead of having:
var g_ship = new Ship({
    cx : 140,
    cy : 200
});

We just do:
entityManager.generateShip({
    cx : 140,
    cy : 200
});

Implementation is Trivial


entityManager.generateShip = function(descr) {
    this._aEntities.push(new Ship(descr));
},

..and now we have the freedom to support arbitrarily many ships, with on-demand creation at runtime...
if (eatKey(KEY_GENSHIP)) entityManager.generateShip({
    cx : g_mouseX,
    cy : g_mouseY
});

We can have some fun with that! (DEMO)
This means we're winning.

Taking It To The Next Level!


In a realistic game, we have multiple types of object that we want to manage. We could, I suppose, have dedicated managers for each type...

...but they would have so much in common, that it seems better to have a single manager which handles them all.

However, it is still useful to retain the distinction between the different "types", and allow them to be managed somewhat separately when required...

Arrays of Arrays


We can create a separate array for each big "type" of entity
-- or, more broadly, for each "category" of types --
and extend the manager to handle all of them...
e.g.
entityManager.render = function(ctx) {

    for (var c = 0; c < this._categories.length; ++c) {

        var aCategory = this._categories[c];

        for (var i = 0; i < aCategory.length; ++i) {

            aCategory[i].render(ctx);

        }
    }
}

Many Types


This sort of approach allows us to easily add lots of new entity types to the system, while still being able to manage them in groups, in a fairly controlled way.

We could, for example, add a "bullet" type...

Or we could add some "rocks"...

...and ensure that each type was rendered in some suitably layered order (e.g. rocks, then bullets, then ships).

Homework!


Add bullets and rocks.

Bullets should have a life-span of 3 seconds,
after which they disappear.

Create 4 rocks by default, each with a random linear and angular velocity, in some sensible range.

Allow ships to be dragged around with the mouse.
(This involves "picking" the nearest ship).

Tips

Don't add gravity to the bullets:

To Be Or Not To Be?


In a realistic system we have to deal with the sad, but ultimately inevitable, fact that entities sometimes die...

There are a couple of ways of handling this but, for our present purposes, a good one is to let each entity's "update" routine return a value that indicates whether or not
it wants to keep on living...

Returning "entityManager.KILL_ME_NOW" means it doesn't.

In such cases, the manager can "splice" them out of the array.

Shuffle Off This Mortal Coil

...and let the garbage collector "take out the trash"


Multiple Sprites


This exercise also requires having multiple different sprites,
which originate from separate image files.

This complicates the image-preloading process,
which now has to wait for all of the images to load.

That's actually quite an annoying thing to implement,
so I've done it for you, but it's worth exploring...

Object Management

By Pat Kerr

Object Management

  • 2,405