Tuesday, February 5, 2013

Game Engine Part 2

As I said in the last post:

I'd been mulling the engine design over in my head while working on the graphics engine.  I wanted, above all, to make it easily extensible and cleanly separated from the rendering engine.  After all, Rogue Moon is to be an online client-server game.

Then I got off onto the tangent about game networking.  But it did have a direction.  If you followed that post, you may have realized that modern latency-compensating online games require two simultaneous (yes, I know I said simultaneity largely goes out the window) simulations, client and server.

Wouldn't it be really handy if you could use the same engine for both?  Now, I'm not a games industry insider (more of an aerospace/business back end sort of guy), so I'm not sure this is always done.  I'd expect smaller games would try to do this.  The AAA games and MMOs probably not so much; they have millions of dollars and hundreds of people to throw at these problems.  I would expect they would have a very reduced simulation subset on the client for efficiency and security reasons.

I'll certainly try to keep those issues in mind (or at least be very stingy with the data the servers send the client), but in practical terms using as close to the same engine as possible is a big win.

The problem with using the same engine in both places is avoiding dependencies.

By this I mean that the parts of the engine (whether on the client or server) should be loosely coupled.  To quote Wikipedia:

In computing and systems design a loosely coupled system is one in which each of its components has, or makes use of, little or no knowledge of the definitions of other separate components.

So on the client, doing something like GameEngine.SetPlayerPosition(x,y,z) is right out.  Nor in the code that handles updated the game simulation should you find DrawPlayer(model, renderDevice, x,y,z);.

The naive way to write this would be for the game shell (i.e. the executable that links parts of the engine together, accepts input, etc) to directly set values in the simulation engine based on input, to directly read from the simulation engine and draw things to the screen, etc.

Unfortunately then, when you want to reuse the simulation engine on a server that doesn't even have a video output, you discover that there are dozens of Draw calls, texture and 3D model loading functions, etc, riddled through it.  Worse, of course, is that your rendering engine won't be present, so you're missing lots of bits and will have to hack them out.

This is called 'tight coupling'.  Basically that's what you get when all the plumbing runs together.

Suddenly your client and server simulation engine have begun to diverge.

So: loose coupling.

On the client, rather than directly setting the PlayerPosition value in the simulation engine, you call GameEngine.EnqueueCommand(CommandTypes.ObjectPosition, object_id, x,y,z).  (Note: this is all pseudocode for clarity and bears little to no direct relation to how I'm doing things).

On the server side, the network layer receives packets from a client and extracts commands from them.  It then adds them to the simulation engine's processing queue by calling:

GameEngine.EnqueueCommand(receivedCommandType, object_id, x,y,z).

Suddenly your game shell, and for that matter the network engine, don't need to know much at all about the inner workings of the simulation engine.  They simply pass data off in a standard way.

On the client side, if you were clever, you might do something like:

//Send to internal client simulation.
GameEngine.EnqueueCommand(CommandTypes.ObjectPosition, object_id, x,y,z);

//Send to server for simulation.
NetworkEngine.EnqueueCommand(CommandTypes.ObjectPosition, object_id, x,y,z);

Thus the behavior of all the engines begins to become similar, making the whole thing easier.  This can easily be implemented as an interface, basically a contract of behavior between two object.  Basically it amounts to not knowing any real details about the other object, all you need to know are the few types of behavior that both objects mutually need.

Better, with this sort of behavior, as long as the interface does not change, parts of the game engine don't care if other parts change on the inside (yeah, this is pretty basic Object Oriented stuff).

Anyway, one of my goals with the basic design was to make all the components of the game as loosely coupled as possible.  Thus the simulation engine would have no dependency on the rendering engine.  The rendering engine no dependencies on the executable shell/input handler code, etc.

So at an extremely high level, this is what I have:
Ok, that was really bland.  Anyway, the real point here is that the code commonality is demonstrated.  The trick, of course, is keeping them from getting too deeply entertwined.  And keeping it all organized.

For that, and for driving the simulation engine overall behavior, I've decided to go with a entity-component system.  Thus far I am terribly pleased with my choice, it has been working extremely well and I find it very clean and elegant.

Basically, in a e-c system (enough with the long name!), your game, err, entities ( a tank, 'Garrus Vakarian', a unit of archers ala CIV, etc) are an aggregate of components.  Basically entities behave as 'have a' objects rather than the usual OOP 'is a'.  It is definitely a different way of thinking about organizing game data.

I'll talk about the system in detail in the next post; it is really the heart of the entire game system.  For a preview (and I believe the first place I read about entity-component systems), have a pass by the T-machine blog and read this starting post.

No comments:

Post a Comment