Component Based Game Engine From Scratch Part 1

Introduction

This is going to be the first part in a continuing series where I try to explain how and why I’m creating my own game engine using C++ and the SDL library. My engine isn’t going to do anything amazing, but will borrow ideas from other engines I’ve used before such as Unity.

Booting an Engine

Rather than sitting down and attempting to plan out the ultimate game engine, I’m taking an organic and iterative approach. My real aim is to learn how to make games, and how to make games quickly. To stop me from wandering off in the inner details of the engine, I’m using games to help design the features needed.

I started with a very simple copy of the Snake game, and just made it work, not trying to create an engine at all. Once the game was finished, I wanted to create a simple Asteroids game. Obviously there was quite a lot of code that could be re-used, but everything was tangled up amongst the Snake game logic.

Instead of just hacking the code to fit into Asteroids, I took Snake and refactored everything so it still did Snake, but was more modular and better designed. All the non-Snake code was put into its own project that I could include into a new game. All the standard boilerplate code that every game would have was copied into a project template so creating a new game would be quick – lowering the barrier to just trying something out is important.

After a few days of refactoring I had the exact same Snake game as before, but with about 10x the code and complexity. However what I’ve learnt is that sometimes having complex code means you can express complex ideas in a more simple manner. Think of it like building a tool to make a manual task easier – sure, you can dig holes with your hands, but build a spade and the task is easier, even though you first have to build the tool.

Now I’m working on Asteroids, using my newly created proto-engine. When Asteroids is complete, I will mix in any new parts that aren’t Asteroids specific, and create a new game. This way I get the benefit of not trying to make an engine that does everything, and have a growing selection of test cases to ensure the engine runs properly. No matter what I do with the engine, the old games should still work with nothing more than a recompile (or minor edits if I do major refactoring to fix design problems).

Think of it like iterative design and testing on multiple levels.

Overall engine design

The engine, which is called “Teapot” (I’m British, we have a thing about tea) is – at its core – a game engine that uses object composition and components, rather than a hierarchy of objects. I have thoroughly “borrowed” ideas from the following places:

I am trying hard to avoid coupling parts of the system together, there shouldn’t be any unnecessary links between classes that have nothing to do with each other. So no pointers to user input routines that get passed through classes, and no (well, one) global variables containing pointers to “manager” classes.

The game loop

The loop currently runs synched to VSYNC and I want it to be deterministic (I like how the original Doom could record demos and play them back). Writing a decent game loop that doesn’t stutter or lag is an ongoing process.

The game engine loops around these core processes

  • Collecting user input
  • Running an update function
  • Running a draw function

The update and draw functions are the two places where user code goes.

The different parts

GameObject

GameObjects are classes that represent a unique object within the game. This could be a high level object like the player, or it could be a part of a larger object. GameObjects have children, they also have GameComponents. They are mostly containers, and do not have to contain any user code beyond initialisation that creates or adds Game Components. They can defer all their logic to their associated components.

GameObjects contain user functions for drawing, and updating which can be overridden if needed.

There is a root GameObject that contains all others.

GameComponent

A class that represents an item of functionality or data that can be associated with a GameObject. A GameComponent might represent a particular way of drawing the parent GameObject, or it might be a particle system, etc.

GameComponents also contain functions for drawing and updating. They are virtual functions, but only need to be overridden if being used – a component used solely for drawing on the screen doesn’t need to override and create an empty Update function just to keep the compiler happy.

Game Service

The engine has the concept of “services” which are global classes that allow communication with the input system, sound, display and anything else that there can only be one of in a game. Some of the current game services are

  • Audio
  • Input

A “service” is a pure virtual abstract class in C++ which implements nothing. It has a pure virtual destructor, and is merely used as a way to tagother classes as being services.

Its code looks like this:

namespace Teapot {
	class IService {
	public:
		virtual ~IService() = 0 {};
	};
}Code language: JavaScript (javascript)

Something wanting to be a service just needs to inherit from that IService class, there are no specific functions it must implement.

class Input : public IService
	{
	public:Code language: CSS (css)

Service Locator

This is the only singleton in the entire engine and works as a database/lookup system to find Game Services. It allows me to get access to the game input from within a GameComponent, inside a GameObject. For example I might create a spaceship GameObject, and then create a GameComponent that implements an Asteroids style control system. In there would be a call to the Service Locator to get the user input service.

The service locator uses some very hairy template code to register services, or get access to them. The result is an implementation that can be easily expanded and used just by creating new classes that implement the Game Service functionality.

This is its code:

class ServiceLocator
	{
	public:
		// It's a singleton. I tried without one, but the template voodoo wouldn't compile
		static ServiceLocator &Instance() { static ServiceLocator sl; return sl; };
		template <class T>
		T* AddService()
		{
			// Is it even a component type?
			if (std::is_base_of<Teapot::IService, T>()) {
				IService *tmp = new T();
				services.push_back(tmp);
				return static_cast<T*>(tmp);
			}
			printf("That's not a component!\n");
			return NULL;
		}

		// Here be dragons
		template <class T>
		T* GetServiceByType()
		{
			for (auto i : services) {
				// Is it the same type?
				std::cout << "Type is" << typeid(*i).name() << "," << typeid(T).name() << "\n";
				if (typeid(*i) == typeid(T)) return static_cast<T*>(i);
			}

			return NULL;
		}
	private:
		ServiceLocator() {};
		std::vector<Teapot::IService*> services;
	};Code language: JavaScript (javascript)

State Machine

There is also a basic state machine which is used to switch between different game modes or screens. It is hoped the state machine can be further abstracted to work inside game logic.

State machine states also contain update and draw functions. They’re the topic of another post later.