Code a class called Engine that controls and binds together the different parts of the Thomas Was Late game. This video guides you in building the Engine class, which will hold all other functions.
- [Narrator] Hi. This video is about building the game engine. In the last video, we learned structuring the Thomas Was Late code. In this video, we're going to take a look at reusing the texture holder class coding engine.h and coding engine.cpp. As suggested in the previous discussion, we will code a class called engine that will control and bind together the different parts of the Thomas Was Late game. The first thing we will do is make the texture holder class from the previous project available in this one.
The texture holder class that we discussed and coded for the zombie arena game will also be useful in this project. While it is possible to add the files, texture holder.h and texture holder.cpp directly from a previous project without recoding them or re-creating the files. I don't want to make the assumption that you haven't jumped straight to this project. I'll guide you through few steps along with the complete code listing to create the texture holder class. To create the texture holder class from scratch, right-click header files in the solution Explorer and select add new item.
In the add new item window, click header file.h and then in the name field, type texture holder. So the name is texture holder.h. Finally, click the add button. Now add this code to texture holder.h. Notice that we have an include directive for map from the STL. Then we declare a map that holds string and SFML texture keyvalue pairs. This map is called m_textures.
Now, this line of code is quite interesting. Here we are declaring a static pointer to an object of type texture holder called m_s_instance. This means that the texture holder class has an object that is the same type as itself. Not only that, but because it is static it can be used with the class itself without an instance of the class. When we code the related.cpp file we will see how we use this. Now, in the public part of the class we have the prototype for the constructor function, texture holder.
The constructor takes no arguments and as usual, has no return type. This is the same as the default constructor. We're going to override the default constructor with a definition that makes our singleton work how we want it to. We have another function called get texture. Let's look at the signature again, and analyze exactly what is happening. First, notice that the function returns are referenced to a texture. This means that get texture will return a reference which is efficient because it avoids making a copy of what could be a fairly large graphic.
Also, notice the function is declared as static. This means the function can be used without an instance of the class. The function takes a string as a constant reference as a parameter. The effect of this is twofold. Firstly, the operation is efficient and secondly, because the reference is constant it can't be changed. Now, let's create texture holder.cpp file. Right-click source files in the solution Explorer and select add new item. In the add new item window, click C++ file.cpp and then in the name field, type texture holder.
Lastly, click the add button. Add this highlighted code to the file. See, this is the whole code. In the code, first we initialize our pointer to type texture holder to null PTR. This is a constructor function. Inside the constructor function, this line of code ensures that M_S_instance equals null PTR. If it doesn't, the game will exit execution. Then M_S_instance equals to this assigns the pointer to this instance.
Now, consider where this code is taking place. The code is in constructor. The constructor is the way we create instance of an object from classes. So effectively, we now have pointer to a texture holder that points the one and only instance of itself. In this line of code, we get a reference to M_textures. Then we attempt to get an iterator to keyvalue pair using the past in filename. If we find a matching key, we return the texture using this line of code.
Otherwise, first we create a new keyvalue pair using the filename and then we had texture to map and at last we return it to the calling code with the help of return texture. Texture holder class introduced lots of new concepts. Singleton, static functions, constant references, auto keyword and syntax. We can now get on with our new engine class. As usual we will start with the header file which holds the function declarations and member variables.
Note that we will revisit this file throughout the project to add more functions and member variables. For now, we will just add the code that is necessary at this stage. Right-click header files in the solution Explorer and select add new item. In the add new item window click header file.h and then in the name field, type engine. Then click the add button. Add this highlighted code to the file. This code consists of various member variables as well as the function declarations.
Take note of the function and variable names as well as whether they are private or public. Let's go through all of the private variables and functions. Texture holder TH, the one and only instance of the texture holder class. Tile size, a useful constant to remind us that each tile in the spreadsheet is 50 pixels wide and 50 pixels high. Verts in quad, a useful constant to make our manipulation of a vertex array less error-prone.
There are in fact for vertices in a quad. Now we can't forget it. Gravity, an instant INT value representing the number of pixels by which the game characters will be pushed outward each second. This is quite a fun value to play with once the game is done. We initialize it to 300 as this works well for our initial level designs. M_window, the usual render window object like we've had in all of our projects. Look at these SF ML view objects.
M_main view, M_left view, M_right view, M_BG main view, M_BG left view, M_BG right view and M_HUD view. The first three view objects that is M_main view is for the full-screen view. M_left view is for left split screen. And M_right view right split screen views of the game. Now these three SF ML view objects will draw the background behind.
The last view object M_HudView will be drawn on top of the appropriate combination of the other six views to display the score, the remaining time, and any messages to the players. Having seven different view objects might imply complexity but when you see how we deal with them as the section progresses, you will see they are quite straightforward. We will have the whole split screen full-screen conundrum sorted out by the end of the section. Sprite M_background Sprite and texture M_background texture this combination of SF ML Sprite and texture will be for showing and holding the background graphic from the graphics assets folder.
M_playing, this boolean will keep the game engine informed about whether the level has started yet by pressing the enter key. The player does not have the option to pause the game once they've started it. Now, we have set M_playing to false. M_character one, when the screen is full-screen should it center on Thomas, M_character one equal to true or Bob M_character one equal to false. Initially, it is initialized to true to center on Thomas.
M_split screen is the game currently being played in split screen mode are not. We will use this variable to decide how exactly to use all the view objects we declared a few steps ago. M_time remaining variable. This float variable holds how much time is remaining to get to the goal of the current level. We have set it to 10 for the purposes of testing until we actually get to set a specific time for each level. M_gametime total variable. This variable is an SF ML time object.
It keeps track of how long the game has been played for. M_new level required boolean variable. This variable keeps a check on whether the player has just completed or failed the level. We can then use it to trigger the loading of the next level or the restarting of the current level. We have set it to true. The input function. This function will handle all the players input which in this game is entirely from the keyboard. At first glance, it would appear that it handles all the keyboard input directly.
In this game however, we will be handling keyboard input that directly affects Thomas or Bob within the Thomas and Bob classes directly. We will call the input function and this function will directly handle keyboard inputs such as quitting, switching to split screen and any other keyboard input. The update function. This function will do all the work that we previously did in the update section of the main function. We will also call some other functions from the update function in order to keep the code organized.
If you look back at the code, you will see that it receives a float parameter which will hold the fraction of a second that has passed since the previous time. This of course is just what we need to update all our game objects. The draw function, this function will hold all the code that used to go in the drawing section of the main function in previous projects. We will however have some drawing code that is not kept in this function when we look at other ways to draw with SF ML. Let's run through all public constructor functions.
The engine constructor function. As we've come to expect, this function will be called when refers declare an instance of engine. It will do all the setup and initialization of the class. We will see exactly what when we code the engine.cpp file. The run function. This is the only public function that we need to call. It will trigger the execution of input, update and draw which will do all the work. Next we'll see the definition of all these functions and some of the variables in action.
Let's get started by coding engine and run it in engine.cpp. Right-click source files in the solution Explorer and select add new item. In the add new item window, click C++ file.cpp and then in the name field, type engine. Finally click the add button. In this file, add this entire code. We will add constructor engine and public run function. All other functions will go in their own .cpp file with a name that makes it clear which function goes where.
This will not be a problem for compiling as long as we had appropriate include directive hash include engine.h at the top of all files that contain function definition from engine class. We are now ready to code the .cpp file for the engine class. The code for this function will be in the engine.cpp file we just created. Here we are adding necessary include directives. Much of the code we have seen before. For example, there are the usual lines of code to get the screen resolution as well as to create a render window.
At the end of this code, we use the now familiar code to load a texture and assign it to a Sprite. In this case, first we are loading the background.PNG texture and assigning it to M_background Sprite. In this code in between, the four calls to the set viewport function that needs some explanation. The set viewport function assigns a portion of the screen to an SF ML view object. It doesn't work using pixel coordinates however. It works using a ratio where one is the entire screen, width or height.
The first two values in each call to set viewport are the starting position horizontally and vertically. And the last two are the ending position. Notice that the M_left view and M_BG left view are placed in exactly the same place starting at virtually the far left, 0.001 of the screen and ending two 1000s from the center, 0.498. The M_right view and M_BG right view are also in exactly the same position, starting just left of the previous two view objects 0.5 and extending to almost the far right-hand side, 0.998 of the screen.
Furthermore, all the views leave a tiny sliver of a gap at the top and bottom of the screen. When we draw these view objects on the screen on top of a white background, it will have the effect of splitting the screen with a thin white line between the two sides of the screen as well as a thin white border around the edges. I've tried to represent this effect in this diagram. The best way to understand it is to finish this section, run the code and see it in action. Let's take a look at coding the run function definition.
Add this highlighted code in engine.cpp just after the constructor code. The run function is the center of our engine. It initiates all the other parts. First, we declare a clock object. Next, we have the familiar while window is open loop which creates the game loop. Inside this while loop, we restart clock and save the time that the previous loop took in DT. Then, we keep track of the total time elapsed in M_gametime total.
Here declare and initialize a float to represent the fraction of a second that elapsed during the previous frame. Next, we call input function. Then we call update passing in the elapsed time, DT as seconds. And at last, we call draw function. All of this should look very familiar. What is new is that is wrapped in the run function. Now let's move on to coding the input function definition. Right-click source files in the solution Explorer and select add.
In that, click on new item. Now we have to select C++ file.cpp and then in the name field type input. So the name is now input.cpp. Finally, click the add button. We're now ready to code the input function. Now we have to add this highlighted code. The code for this function will go in its own file. This is the entire highlighted code that we have to add in the input.cpp file. In this highlighted code, first we will use hash include engine.h and prefix the function signature with engine to make sure the compiler is aware of our intentions.
As usual, when the escape key is pressed it closes the window and the game will quit. That is whenever we press escape key, the game will be closed. When enter key is pressed, it sets M_playing to true and eventually this will have the effect of starting a level. The cube keypress alternates the value of M_character one between true and false. This key only has an effect in full-screen mode. It will switch between Thomas and Bob being the center of the main view.
When E key is pressed, it switches M_split screen between true and false. This will have the effect of switching between fullscreen and split screen views. The majority of this keyboard functionality will be fully working by the end of the section. We're getting close to being able to run our game engine. Next, let's code update function. As usual, right-click source files in the solution Explorer and select add new item. In the add new item window, click C++ file.cpp and then in the name field type update.
Lastly, click the add button. We're now ready to write some code for the update function. Now add this highlighted code. The code for update function goes in update.cpp. Just as we did earlier, we will use hash include engine.h and prefix the function signature with engine to make sure the compiler is aware of our intentions. Notice that the update function receives the time the previous frame took as a parameter. This of course will be essential for the update function to fulfill its role.
Then it checks the if condition M is playing. If yes, then it subtracts the time the previous frame took from M_time remaining. It checks whether time has run out, and if it has, it sets M_new level required to true. All this code is wrapped in an if statement that only executes when M_playing is true. The reason for this is because as with the previous projects we don't want time advancing and objects updating when the game is not started.
We will build on this code as the project continues. Lastly, let's code the draw function definition right-click source files in the solution Explorer and select add new item. Then select C++ file.cpp and then in the name field, type draw. Now the name is draw.cpp. Then click the add button. We are now ready to add some code to the draw function. Now we'll add this code to the file to implement the draw function.
The code for draw function will go in its own file. You can see we will use hash include engine.h here also and prefix the function signature with engine. In this highlighted line of code we clear the screen. In this project, we clear the screen with white. What is new is the way the different drawing options are separated by a condition which checks whether the screen is currently split or full. If the screen is not split, we draw the background sprite in the background view that is M_BG main view, and then switch to the main full-screen, view M_main view.
Note that at the moment, we don't actually do any drawing in M_main view. On the other hand, if the screen is split the code in the else block is executed and we draw M_BG left view with the background sprite on the left of the screen followed by switching to M_left view. Then still in the else block, we draw M_BG right view with the background sprite on the right of the screen followed by switching to M_right view. Outside the if else structure, we run to M_HUD view.
At this stage, we're not actually drawing anything in M_HUD view. At last, we call the display function to show everything we have just drawn. As with the other two input update of the three most significant functions we will be back here at the draw function often. We will add new elements of our game that need to be drawn. You'll notice that each time we do, we'll add code into each of the main, left and right sections. Let's quickly recap the engine class and then we can fire it up. What we have achieved is the abstraction of all the code that used to be in the main function into the input, update and draw functions.
The continuous looping of these functions as well as the timing is handled by the run function. As you can see here, input.cpp, update.cpp and draw.cpp tabs open in Visual Studio. I've organized them in order. We will revisit each of these functions throughout the course of the project to add more code. Now that we have the basic structure and functionality the engine class, we can create an instance of it in the main function and see it in action. In this video, we've looked at building the game engine.
Great, in the next video, we'll learn coding the main function.
This course was created and produced by Packt Publishing. We are honored to host this training in our library.
- Abstract classes
- Level design
- Collision detection
- The HUD class
- Extending SFML classes
- Particle systems