Menu

Game Engine Design

Game engine design is a big topic and has more than one valid approach. I will explain the one that worked for me. Keep in mind that every game has his own needs and the engine must cover them all. Don't be afraid to modify or experiment with the engine, but remember to make backups.

I must make a little pause here to explain about State Machines.

The idea is to divide the game in different states. For example, a Main Menu, Game, Credits. This three states (or scenes) can be switched any given time. So when we execute the program the Main Menu state will run, there we can navigate trough the menu, change options, etc. And when we click on Start Game, then exit the Main Menu state and run the Game state were all the game logic is.

This is what an state looks like:

class State
{
public :

	enum SCREEN { MAIN_MENU, GAME, CREDITS, EXIT };

    virtual int Run () = 0;

    // some vars and extra functions
    ...
};

-- Main Menu State
class Main_Menu: public State
{
	int Run()
	{
		// vars
		bool running = true;
		int state = 0;

		while(running)
		{
			// do actions (updates, draw, etc)

			// if the user has clicked on Start or Credits button
			if (goStartGame) 
			{
				running = false;
				state = STATE::GAME;
			}
			if (goToCredits)
			{
				running = false;
				state = STATE::CREDITS;
			}

			update();
			draw();
		}

		return state;
	}
}

And then we put them in the main file:

-- main.cpp
#include <State.h>

int state = MAIN_MENU;	// Start the program in Main Menu
std::vector<State> vState;
vState.pushback(Main_Menu);
vState.pushback(Game);
vState.pushback(Credits);

// While the game is running
while (state != EXIT)
{
	// run the current state and recover the next state when it finishes
	state = vState[state].Run();
}

return 0;

The game engine is just a "box" or machine that contains all the data, classes, functions, variables, etc. And then applies the logic to perform actions or calculations to finally draw everything on the screen.

The engine must be modular. That means we separate the different game logic in different engine parts. One controls the input, other controls the enemies, objetcs, bullets, map, interface... This way, if we modify something in enemies, it doesn't affect the interface or player code.

It's important to separate the game logic from the rendering. Drawing something before or between movement calculations can lead to future problems or unexpected behaviour like enemies going trough walls or bad collisions. Your game loop can be something like this:

Game_Engine Engine;
sf::RenderWindow Window;
sf::Clock Clock;
...

while (!gameover)
{
	// Get window events (close, minimize...) and inputs (keyboard, mouse...)
	sf::Event Event;
    while (Window.GetEvent(Event))
    {
 		if (Event.Type == sf::Event::Closed)
        {
			return STATE::EXIT;
        }

        Engine.processEvent();
    }

    // Get the time between frames
    float time = Clock.getElapsedTime().asSeconds();
    Clock.restart();

    // Update the game logic withr the current frame time
	Engine.update(time);

	// Clear the window with black color
	Window.clear(sf::color::Black);

	// Tell the engine to draw everything on the window
	Engine.draw(&Window);

	// Display all the changes
	Window.display();
}

return STATE::MAIN_MENU;

Designing the engine is like making a grocery list. What do we need to play the game? One level (minimum), the player, a bunch of enemies, many objects... Add any variable and function you need.

-- Engine.h
#include <Level.h>
#include <Player.h>
#include <Enemy.h>
#include <Object.h>
#include <Bullet.h>

class Engine
{
	// functions
	void update(float time);
	void draw(sf::RenderWindow * w);
	...

	// vars
	Player m_player;
	Level  m_level;
	std::vector<Object> vObject;
	std::vector<std::unique_ptr<Enemy>>  vEnemy;
	std::vector<std::unique_ptr<Bullet>> vBullet;
	...
}

Why I use std::unique_ptr for Enemies and Bullets? Because these two clases are created and destroyed multiple times (specially bullets). So we don't need a permanent storage like Objects. You can use std::shared_ptr instead, it depends on your own design.

Inside the update() function is where all the game logic must be. Updating movement, collisions, bullet damage, level events (traps, spawn enemies, lights...). After we are done with all this calculations, the we can draw everything. Is important to draw only what's visible on screen. If your game is a sidecroller, you don't need to draw ALL the objects and the whole level. The less draw calls you make, the better performance. Try to divide your maps in "chunks" if it's too large.

If your level is made of tiles, you can use a sf::VertexArray to build it and then draw it with a single call.

Take a look at this tutorial SFML VertexArray

The key to design a good engine is to externalize the most data possible in text files. Like level layouts, enemy data (life, damage...), objects positions, etc. This can be done with XML. Create a structure that holds all the data of a level and then read it in runtime. This way we can load different maps without hardcoding all the data in the executable and need to recompile every time.

The XML would look like this:

<?xml version="1.0" encoding="UTF-8"?>
<map name="dungeon" >
	<object type="491" px="2727" py="223" zvalue="0" />
	<object type="144" px="1152" py="240" zvalue="0" />
	<object type="530" px="1296" py="240" zvalue="0" />
	<object type="212" px="1440" py="240" zvalue="0" />
	<object type="210" px="1872" py="240" zvalue="0" />
	<object type="144" px="2112" py="240" zvalue="0" />
	<object type="529" px="2256" py="240" zvalue="0" />
	...
</map>

<enemy type="soldier" life="80" armor="10" damage="15" speed="200" />
<enemy type="archer"  life="50" armor="0"  damage="10" speed="150" />
...

I use rapidXML to read the data and store it in the engine like this:

xml_document<> doc;
xml_node<> * root_node;

// Parse the buffer using the xml file parsing library into doc 
ifstream inFile(filename, ios::in);

if (!inFile)
{
	std::cout << "ERROR: Reading Data File" << std::endl;
	return;
}

// read the whole file into the buffer
std::vector<char> buffer = vector<char>((istreambuf_iterator<char>(inFile)), istreambuf_iterator<char>());
// add '\0' for rapidxml compute
buffer.push_back('\0');

inFile.close();

doc.parse<0>(&buffer[0]);
	
root_node = doc.first_node("enemy");

// enemies
for (xml_node<> * enemy_node = root_node->first_node("enemy"); enemy_node; enemy_node = enemy_node->next_sibling("enemy"))
{
	type    = enemy_node->first_attribute("type")->value();
	life	= atoi(enemy_node->first_attribute("life")->value());
	armor	= atoi(enemy_node->first_attribute("armor")->value());
	damage	= atoi(enemy_node->first_attribute("damage")->value());		
	speed   = atoi(enemy_node->first_attribute("speed")->value());
	...
}

You can use XML or JSON or even plain text. Just be sure to write a parser to load the data stored in this files.

And finally for holding resources like textures, sounds, fonts, etc. It's very useful to make a Resource Manager that can hold all of our game assets. I use my own custom made manager, but you can take a look at Thor, an useful extension to SFML that adds many features, like an implementation of Resource Holder.

This will cover most of the basics for making a game engine. Be sure to check out the links above and read more info, and try it for yourself. Experience is key in programming. Dont't want to make all at once. Start with something simple and continue adding more things and features.


Read more:






AVAILABLE ON STEAM





Icons made by Lorc. Available on Game-Icons.net