3
\$\begingroup\$

How should a game entity add another game entity to the game world?

For example, an entity shooting a missile (which is another entity), means the new Missile will need to be added to the entities list in the GameWorld class (so that the next game-loop iteration it will be drawn, moved, etc).

I can think of two approaches:

Approach 1

The entity adds a new Missile to the GameWorld:

class SpaceShip extends Entity{
    // .. stuff omitted
    void shoot(){
        GameWorld.getInstance().add(new Missile(x,y));
    }
}

Approach 2

Have the GameWorld query it's entities every gameloop iteration for their 'stored entities'. For example (pseudocode):

class SpaceShip extends Entity{
    // .. stuff omitted
    List<Missile> missiles;
    void shoot(){
        missiles.add(new Missile(x,y));
    }
}

class MineCreatorThing extends Entity{
    // .. stuff omitted
    List<Mine> mines;
    void putMine(){
        mines.add(new Mine(x,y));
    }
}

class GameWorld{
    // .. stuff omitted
    void gameloop(){
        while(true){
            // .. more stuff omitted
            foreach entity in entities{
                if(entity instanceof MineCreatorThing)
                    foreach mine in entity.getMines() entities.add(mine);
                if(entity instanceof SpaceShip)
                    foreach missile in entity.getMissiles() entities.add(missile);
            }
        }
    }
}

Which approach should I pick?

\$\endgroup\$
2
  • \$\begingroup\$ I would go with the first option. \$\endgroup\$ – NauticalMile Jun 15 '14 at 22:35
  • \$\begingroup\$ Also, how much implementation have you done? I have found out the hard way the just dreaming up solutions to problems that don't yet exist is often a huge time-sink. Go and write code, and come back here when some specific game feature (e.g. pausing, game input, particle effects) is going against the grain of your architecture. Also, don't let yourself become an architecture astronaut :) \$\endgroup\$ – NauticalMile Jun 15 '14 at 22:41
1
\$\begingroup\$

First option is most likely going to be good enough if you are not developing a really big game. I would (personally) avoid using instanceof in that way.

If you really want to take your design to the next step, you can create an event queue for the GameWorld and just push any event (like addMissile, destroyShip etc.), and then on the beginning of every update handle all those events from queue in the GameWorld class. The good side of this approach is that all logic will be done from withing the GameWorld and before any logic for any particular frame, which means much less bugs and much more easier code to handle. If you got many object messing up with your lists directly, you're gonna have a bad time.

But, just to mention again, if you are not developing any big game that is going to have many objects doing many stuff and changing GameWorld constantly DO NOT do that. It will just be an overhead...

\$\endgroup\$
3
\$\begingroup\$

I almost voted that this is too opinion-based, but on thinking on it further, I think it's safe to say with some sureness that the second option presented is just bad. Don't do that.

Among other things, it means that any components which must create objects needs a dependency on another component, potentially a different component for each type of thing it creates.

More complex games do delay the entire creation of the object. This is because you can't just call new Foo in complex engines, especially component-based ones. You need a more intricate resource system that can clone objects from blueprint or the like, so you end up doing something like:

pGameWorld->SpawnObject(m_BlueprintHandle);

The actual creation and initialization of the object may happen immediately or be delayed until next frame (or even later, for engines that allow asynchronous on-demand loading of resources). It's also common to have initialization parameter structs for passing in position, velocity, orientation, and other common per-instance data, like:

SpawnData spawn;
spawn.position = Vector2d(x, y);
spawn.blueprint = m_BlueprintHandle;
spawn.baseName = "BlueMissile";
spawn.creator = this;
pGameWorld->SpawnObject(spawn);

Or it might take several different structs, some parameters and some structs, or so on. There's also engines that use serialization systems to set parameters, so you see things like:

MemoryParameterStream params;
params.write("position.x", x);
params.write("position.y", y);
pGameworld->SpawnObject(m_BlueprintHandle, params);

This can be combined with the previous approach to handle both common parameters (position, etc.) and any component-specific parameters that you need to set in less common circumstances.

How and when the game world actually creates the request object is abstracted away. It might return a handle to a temporary object, something like:

Handle GameWorld::SpawnObject(Handle blueprintHandle) {
  Handle newHandle = AllocateEmptyObject(blueprintHandle);
  m_PendingInitializations.push_back(newHandle);
  return newHandle;
}

// elsewhere
m_ChildHandle = pGameWorld->SpawnObject(m_BlueprintHandle);

Just so that the creator can hold onto references to the object if needed for later use. This is not uncommon for some attack systems that kill or despawn certain attack objects (bullets, laser beams, etc.) when the weapon or attacker that fired them dies, for instance.

In the end, your first suggestion is simple and Just Works(tm) for most simple games. Do that. Do not do your second option; it's more complicated with no benefits and just adds more coupling and dependencies.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.