In order to be able to run the game and see our new characters, we have to declare instances of them, call their spawn
functions, update them each frame, and draw them each frame. Let's do that now.
Open up the Engine.h
file and add the highlighted lines of code, as shown in the following:
#pragma once #include <SFML/Graphics.hpp> #include "TextureHolder.h" #include "Thomas.h" #include "Bob.h" using namespace sf; class Engine { private: // The texture holder TextureHolder th; // Thomas and his friend, Bob Thomas m_Thomas; Bob m_Bob; const int TILE_SIZE = 50; const int VERTS_IN_QUAD = 4; ... ...
Now we have an instance of both Thomas
and Bob
, which are derived from PlayableCharacter
.
Now we will add the ability to control the two characters. This code will go in the input part of the code. Of course, for this project, we have a dedicated input
function. Open up Input.cpp
and add this highlighted code:
void Engine::input()
{
Event event;
while (m_Window.pollEvent(event))
{
if (event.type == Event::KeyPressed)
{
// Handle the player quitting
if (Keyboard::isKeyPressed(Keyboard::Escape))
{
m_Window.close();
}
// Handle the player starting the game
if (Keyboard::isKeyPressed(Keyboard::Return))
{
m_Playing = true;
}
// Switch between Thomas and Bob
if (Keyboard::isKeyPressed(Keyboard::Q))
{
m_Character1 = !m_Character1;
}
// Switch between full and split-screen
if (Keyboard::isKeyPressed(Keyboard::E))
{
m_SplitScreen = !m_SplitScreen;
}
}
}
// Handle input specific to Thomas
if(m_Thomas.handleInput())
{
// Play a jump sound
}
// Handle input specific to Bob
if(m_Bob.handleInput())
{
// Play a jump sound
}
}
Note how simple the previous code is, as all the functionality is contained within the Thomas
and Bob
classes. All the code has to do is add an include directive for each of the Thomas
and Bob
classes. Then, within the input
function, the code just calls the pure virtual handleInput
functions on m_Thomas
and m_Bob
. The reason we wrap each of the calls in an if
statement is because they return true
or false
based upon whether a new jump has just been successfully initiated. We will handle playing the jump sound effects in Chapter 15, Sound Spacialization and HUD.
This is broken down into two parts. First, we need to spawn Bob and Thomas at the start of a new level, and second, we need to update (by calling their update
functions) each frame.
We need to call the spawn functions of our Thomas
and Bob
objects in a few different places as the project progresses. Most obviously, we need to spawn the two characters when a new level begins. In the following chapter, as the number of tasks we need to perform at the beginning of a level increases, we will write a loadLevel
function. For now, lets just call spawn
on m_Thomas
and m_Bob
in the update
function, as shown in the following highlighted code. Add the code, but keep in mind that this code will eventually be deleted and replaced:
void Engine::update(float dtAsSeconds)
{
if (m_NewLevelRequired)
{
// These calls to spawn will be moved to a new
// loadLevel() function soon
// Spawn Thomas and Bob
m_Thomas.spawn(Vector2f(0,0), GRAVITY);
m_Bob.spawn(Vector2f(100, 0), GRAVITY);
// Make sure spawn is called only once
m_TimeRemaining = 10;
m_NewLevelRequired = false;
}
if (m_Playing)
{
// Count down the time the player has left
m_TimeRemaining -= dtAsSeconds;
// Have Thomas and Bob run out of time?
if (m_TimeRemaining <= 0)
{
m_NewLevelRequired = true;
}
}// End if playing
}
The previous code simply calls spawn
and passes in a location in the game world, along with the gravity. The code is wrapped in an if
statement that checks whether a new level is required. The actual spawning code will be moved to a dedicated loadLevel
function, but the if
condition will be part of the finished project. Also, m_TimeRemaining
is set to a somewhat arbitrary 10 seconds.
Next, we will update Thomas and Bob. All we need to do is call their update
functions and pass in the time this frame has taken.
Add the following highlighted code:
void Engine::update(float dtAsSeconds)
{
if (m_NewLevelRequired)
{
// These calls to spawn will be moved to a new
// LoadLevel function soon
// Spawn Thomas and Bob
m_Thomas.spawn(Vector2f(0,0), GRAVITY);
m_Bob.spawn(Vector2f(100, 0), GRAVITY);
// Make sure spawn is called only once
m_NewLevelRequired = false;
}
if (m_Playing)
{
// Update Thomas
m_Thomas.update(dtAsSeconds);
// Update Bob
m_Bob.update(dtAsSeconds);
// Count down the time the player has left
m_TimeRemaining -= dtAsSeconds;
// Have Thomas and Bob run out of time?
if (m_TimeRemaining <= 0)
{
m_NewLevelRequired = true;
}
}// End if playing
}
Now that the characters can move, we need to update the appropriate View
objects to center around the characters and make them the center of attention. Of course, until we have some objects in our game world, the sensation of actual movement will not be achieved.
Add the highlighted code, as shown in the following snippet:
void Engine::update(float dtAsSeconds)
{
if (m_NewLevelRequired)
{
// These calls to spawn will be moved to a new
// LoadLevel function soon
// Spawn Thomas and Bob
m_Thomas.spawn(Vector2f(0,0), GRAVITY);
m_Bob.spawn(Vector2f(100, 0), GRAVITY);
// Make sure spawn is called only once
m_NewLevelRequired = false;
}
if (m_Playing)
{
// Update Thomas
m_Thomas.update(dtAsSeconds);
// Update Bob
m_Bob.update(dtAsSeconds);
// Count down the time the player has left
m_TimeRemaining -= dtAsSeconds;
// Have Thomas and Bob run out of time?
if (m_TimeRemaining <= 0)
{
m_NewLevelRequired = true;
}
}// End if playing
// Set the appropriate view around the appropriate character
if (m_SplitScreen)
{
m_LeftView.setCenter(m_Thomas.getCenter());
m_RightView.setCenter(m_Bob.getCenter());
}
else
{
// Centre full screen around appropriate character
if (m_Character1)
{
m_MainView.setCenter(m_Thomas.getCenter());
}
else
{
m_MainView.setCenter(m_Bob.getCenter());
}
}
}
The previous code handles the two possible situations. First, the if(mSplitScreen)
condition positions the left-hand view around m_Thomas
and the right-hand view around m_Bob
. The else
clause that executes when the game is in fullscreen mode tests to see if m_Character1
is true
. If it is, then the fullscreen view (m_MainView
) is centered around Thomas, otherwise it is centered around Bob. You probably remember that the player can use the E key to toggle split screen mode and the Q key to toggle between Bob and Thomas, in fullscreen mode. We coded this in the input
function of the Engine
class, back in Chapter 12, Abstraction and Code Management - Making Better Use of OOP.
Make sure the Draw.cpp
file is open and add the highlighted code, as shown in the following snippet:
void Engine::draw() { // Rub out the last frame m_Window.clear(Color::White); if (!m_SplitScreen) { // Switch to background view m_Window.setView(m_BGMainView); // Draw the background m_Window.draw(m_BackgroundSprite); // Switch to m_MainView m_Window.setView(m_MainView); // Draw thomas m_Window.draw(m_Thomas.getSprite()); // Draw bob m_Window.draw(m_Bob.getSprite()); } else { // Split-screen view is active // First draw Thomas' side of the screen // Switch to background view m_Window.setView(m_BGLeftView); // Draw the background m_Window.draw(m_BackgroundSprite); // Switch to m_LeftView m_Window.setView(m_LeftView); // Draw bob m_Window.draw(m_Bob.getSprite()); // Draw thomas m_Window.draw(m_Thomas.getSprite()); // Now draw Bob's side of the screen // Switch to background view m_Window.setView(m_BGRightView); // Draw the background m_Window.draw(m_BackgroundSprite); // Switch to m_RightView m_Window.setView(m_RightView); // Draw thomas m_Window.draw(m_Thomas.getSprite()); // Draw bob m_Window.draw(m_Bob.getSprite()); } // Draw the HUD // Switch to m_HudView m_Window.setView(m_HudView); // Show everything we have just drawn m_Window.display(); }
Notice that we draw both Thomas and Bob for the full screen, the left, and the right. Also note the very subtle difference in the way that we draw the characters in split screen mode. When drawing the left side of the screen, we switch the order the characters are drawn and draw Thomas after Bob. So, Thomas will always be on top on the left, and Bob on the right. This is because the player controlling Thomas is catered for on the left and Bob the right.
You can run the game and see Thomas and Bob in the center of the screen:
If you press the Q key to switch focus from Thomas to Bob, you will see the View
make the slight adjustment. If you move either of the characters left or right (Thomas with A and D, Bob with the arrow keys) you will see them move relative to each other.
Try pressing the E key to toggle between fullscreen and split-screen. Then try moving both characters again to see the effect. In the following screenshot, you can see that Thomas is always centered in the left-hand window and Bob is always centered in the right-hand window:
If you leave the game running long enough, the characters will re-spawn in their original positions every ten seconds. This is the beginnings of the functionality we will need for the finished game. This behavior is caused by m_TimeRemaining
going below zero and then setting the m_NewLevelRequired
variable to true
.
Also note that we can't see the full effect of movement until we draw the details of the level. In fact, although it can't be seen, both characters are continuously falling at 300 pixels per second. As the camera is centering around them every frame and there are no other objects in the game-world, we cannot see this downward movement.
If you want to demonstrate this to yourself, just change the call to m_Bob.spawn
, as shown in the following code:
m_Bob.spawn(Vector2f(0,0), 0);
Now that Bob has no gravitational effect, Thomas will visibly fall away from him. This is shown in the following screenshot:
We will add some playable levels to interact with in the following chapter.
3.145.177.115