- [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.