We’ve worked on our own game engine for the last 1 and a half year (instead of the game) and now we’d like to tell about it. The point was to make a game, but we’ve fallen into the classic trap of making the engine and thinking a game will pop out somehow. But the good part is that we have our own game engine at least :).
Original design of the engine was based on simple object-oriented structure. But then a need for analyzing of what was it we wanted to have in the game appeared. We didn’t want to plan the design in details then, but rather have an opportunity to add everything later.
Based on analysis we’ve chosen a message-driven component-based game engine design. The heart of this design is interaction of game objects and components where a component is a piece of game functionality that can be implemented independently of each other. The reason we’ve chosen this game design is to avoid tight coupling between different parts of the game. The graphics doesn’t care how the physics behind the models are, and physics doesn’t care how the graphics display it. By dividing the different domains into components, all there is to know is how to receive a message and to send a message.
Consider a player picking up some items. A player is an object, a weapon that player holds is an object, a nest with chicken and eggs are all independent objects. Now you want some eggs make pickable, some breakable, some glowing, some jumping, some small and some big and may be talking too. In this case a traditional object hierarchy maybe a choice. But what you do if you want a magic glowing half damaged egg that is talking to you and can be picked up if you have a basket? It’s not possible to fit this into a hierarchy.
Component-based design is when each object is kept as simple as possible and all the features are added through components. In this case we need only a player and an egg objects with some id, form and position and features/components: pickable, glowing, damaged, talking, jumpable, breakable.
Components and game objects communicate with each other via messages. A Message in this design is simply any subclass of the Message class. Each concrete Message have its own interface that is convenient for its task. The advantage of using messages is lower coupling between components. When invoking a method the caller needs to know the callee. But by using messages this is completely decoupled. If there is no receiver, then it doesn’t matter. Also how the receiver processes the message, if it does it at all, is not a concern of the caller.
For networking it’s a little more complex than that, but we’ve chosen to do it simple with dual inheritance. A message can be sent on the network if it inherits both Message and NetworkMessage, but the trick is to use the same message id for both kinds. If a message comes from the network or the game itself, it doesn’t matter. A message is a message, from wherever it’s sent.
The engine is written in C++, gluing the third party libraries together. A game engine is nothing more than an interface between the specialized libraries and the player(s). It manages user input and it controls the game loop.
The game loop is nothing more than keeping track of user input and doing the graphics, physics, networking and sound in the correct order. The game loop is the heart of a game. It feeds the other components with messages and tells them there is work to do.
An other aspect of this, is that the game loop is generic. It tells other components and engines there is work to do, but very little of it is specific to one game. What makes a game a game in a message-driven component-based system is how everything is set up during initializing. You want to fly, great, just add it. Don’t, leave it. It doesn’t matter to the game loop.
We’ve been using some third-party libraries in order to help us make progress and not reinventing the wheel. These are:
- Physics library (Bullet Physics),
- Graphics engine (Irrlicht),
- Networking (Boost Asio),
- Sound library (SFML),
- Math and noise library to make graphical patterns and calculations (Accidental noise),
- Shadows library (XEffects),
- CeGUI for user interactions.
- And finally Boost for smart pointers and necessary utility libraries.
Most libraries can be easily changed as they are used as independent components and engines.