How to Get Started Programing Your First Game - Part 2
In Part 1 (put html link here to part one), I talked about the very first step in programming your game once you got done designing your game, which was to design the code. I suggested doing this by going through and identifying all the different states your game will have throughout its' life. This article is about the very beginning of actually writing the code. It will talk about the beginning of the program and touch on three key things: the game loop, the game state, and, for lack of a better word, the game engine. It will then go on to talk about the game engine (otherwise it would be too short and be more of a snippet than a How To article).
The Start of Every Program in C++
main()
Every C++ program starts with the main() function. It seems only fitting to call the file that contains the main function Main.cpp. That's what I do. What it's called doesn't matter that much. All that matters is that you know where your main() function is.
The Anatomy Main.cpp
In my game programs, Main.cpp has only three jobs. It holds the instance of my 'game engine', it holds the major game loop, and it holds the switch for changing the major game states.
Main.cpp
#include "GameEngine.h"
#include "MajorGameStates.h"
int main()
{
GameEngine gameEngine;
GameStateBase* currentState = new IntroState( &gameEngine );
while(currentState != NULL)
{
currentState->HandleInput();
currentState->HandleLogic();
currentState->HandleOutput();
if(currentState->GetNextState() != MajorGameStates::NoChange)
{
switch( currentState->GetNextState() )
{
case MajorGameStates::IntroState:
delete currentState;
currentState = NULL;
currentState = new IntroState( &gameEngine );
break;
case MajorGameStates::TitleMenu:
delete currentState;
currentState = NULL;
currentState = new TitleMenuState( &gameEngine );
break;
case MajorGameStates::MainGameState:
delete currentState;
currentState = NULL;
currentState = new MainGameState( &gameEngine );
break;
case MajorGameState::Quit:
delete currentState;
currentState = NULL;
break;
}
}
}
return 0;
}
It includes two files: "GameEngine.h" and "MajorGameStates.h". Now we're going to delve into the very first line of code in Main.cpp, #include "GameEngine.h"
Just What is a Game Engine Exactly?
I think game engine is one of those buzz words that people use to sound fancy and unknowable. Game engine invokes all sorts of meaning in people that hear it, unfortuneately that meaning is drastically different. I'm not going to bother defining the term in general, I'm just going to define it for my use here in this series. That way I know you know what I'm talking about when I say "...the game engine ".
Game Engine Defined
The game engine is a collection of tools that are used through out the game. It contains the resource managers and key functions and resources that get used nearly everywhere in the game, but aren't state specific. I was so adverse to this game engine idea that I originally didn't even have it. It only came into existence because I got tired of passing a bunch of arguments through the constructor functions. I called it game engine simple because I couldn't think of anything else to call it. It's a consolidation to make passing the things I really care about easier. It also contains a tiny bit of abstraction. Other than that it's a container.
GameEngine.h
The first thing I want to talk about is the resource managers. But first, lets talk about resources. A resource something you get from outside of the game program. An image, a model, a sound effect, and a song are all examples of a resource. They are big files and take up a lot of memory. They are slow to pass around. You don't ever want to have more than one and you don't want it to go anywhere. A resource manager's job is thus to load a resource from file, make sure that it's never done again, and hold onto it so I know where it is, then drop it like a bad habit when I tell it to. Nothing more, nothing less. Here is an example of one of the managers, the image manager
Image Manager.h
Image Manager.cpp
As you can see, its very small and to the point. It has a pointer that is used to post it's error message. It uses an unordered_map to store the resource, in this case an sf::Image (which is pretty much a texture).
By it's very nature, a map really makes making sure I only have one instance of a resource loaded in memory really easy. It also makes it really easy to get at when I need to. A map is like a vector, but instead of using a number to access the data it stores, you can use ANY data type as your key. I use a string data, and the keys are the names of the files.
It is much easier to say and understand "Give me the sf::Image at "Tank Image.png" than to say give me sf::Image at 5. The image manager looks to see if there already exists what is being asked for, and if it does then just return the sprite and if not then load it and return the sprite (go to SFML to read about the specifics of sf::Image and sf::Sprite).
What the image manager does NOT do is worry about drawing images on the screen. It doesn't worry about animations. It doesn't care about anything other than putting things in its file cabinet, making sure it's unique, giving out light weight refrences, and then when told to it will dump the whole thing out and light the contents on fire so it can fill it up with new unique things. That is an important concept. Your class is not done when you can't add anymore (thats how you get God Objects), you are done writing your class when you cannot take anything else out of it without it being able to do it's job.
The managers are all made public. They protect their data all on their own, they don't need big brother game engine to do it for them. This makes it much easier to use them. However, you will notice that it has some private members. They also all have something in common, namely the screen. The sf::RenderWindow is the SFML class that allows you to draw to the screen (and other things).
The screenScaleX, and screenScaleY are the ratios between the virtual dimensions of the actual screen dimensions. This is to handle different resolutions (it's one way to go about it). The reason the sf::Renderwindow has been made private is because there are functions dealing with drawing to the screen that encapsulate all the transformations to things involving screen interactions.
It creates a seamless operation. There is also nothing regarding things like collision detection because only ONE state in the entire game cares about collision detection, so it is better contained in that state only.