Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


Game From Scratch C++ Edition Part 6

 

 

In this chapter we really get a move on, literally.  Bad puns aside, we are going to add keyboard controls to our PlayerPaddle, as well as create the game ball class.  In order to add input to our PlayerPaddle ( and other game objects ) we need to make some changes to our base class VisibleGameObject.  Lets open up VisibleGameObject.h and make a few changes:

 

#pragma once class VisibleGameObject { public: VisibleGameObject(); virtual ~VisibleGameObject(); virtual void Load(std::string filename); virtual void Draw(sf::RenderWindow & window); virtual void Update(float elapsedTime); virtual void SetPosition(float x, float y); virtual sf::Vector2f GetPosition() const; virtual bool IsLoaded() const; protected: sf::Sprite& GetSprite(); private: sf::Sprite _sprite; sf::Image _image; std::string _filename; bool _isLoaded; };

Click here to download VisibleGameObject.h

 

 

 

 

The biggest changes are the public: addition of Update(), GetPosition() and IsLoaded().  Update() is hands down the most important new addition, as it is the facility we will use to get the user’s input.  We will cover it in more detail shortly.  Next up we added the protected method GetSprite().  This is a mechanism for derived classes to get access to the inherited  private _sprite member.

 

 

Optional information

 

What is protected?


You have already seen the public and private keywords but this may be the first time you have encountered protected.  A protected member is only accessible to the class it is defined in and classes that derive from that class.  Therefore our GetSprite() method will be available to VisibleGameObjects and derived objects such as PlayerPaddle, however the outside world cannot access it.
 
Now, why did I make a method GetSprite() instead of simply making the _sprite object available?  That’s a good question and frankly it is simply a matter of preference.  By adding one layer of abstraction between the base classes sprite it allows us to potentially make alterations down the road in how we get the sprite without affecting tons of code, but reality is we most likely won’t.  So if you wished to keep things simpler and make the _sprite member protected and get rid of GetSprite() completely, that is perfectly valid.

 

 

 

Now lets take a look at VisibleGameObject.cpp.

#include "StdAfx.h" #include "VisibleGameObject.h" VisibleGameObject::VisibleGameObject() : _isLoaded(false) { } VisibleGameObject::~VisibleGameObject() { } void VisibleGameObject::Load(std::string filename) { if(_image.LoadFromFile(filename) == false) { _filename = ""; _isLoaded = false; } else { _filename = filename; _sprite.SetImage(_image); _isLoaded = true; } } void VisibleGameObject::Draw(sf::RenderWindow & renderWindow) { if(_isLoaded) { renderWindow.Draw(_sprite); } } void VisibleGameObject::Update(float elapsedTime) { } void VisibleGameObject::SetPosition(float x, float y) { if(_isLoaded) { _sprite.SetPosition(x,y); } } sf::Vector2f VisibleGameObject::GetPosition() const { if(_isLoaded) { return _sprite.GetPosition(); } return sf::Vector2f(); } sf::Sprite& VisibleGameObject::GetSprite() { return _sprite; } bool VisibleGameObject::IsLoaded() const { return _isLoaded; }

Click here to download VisibleGameObject.cpp

 

 

There is nothing really complicated going on here.  The most important change we made is to add the Update() method, but that is mostly for inherited classes as the base class implementation doesn’t do anything.  Otherwise we just added a couple accessor methods for getting and setting the position, as well as the protected method GetSprite() to allow derived classes access to the _sprite sf::Sprite object.

 

 

Next we look at the PlayerPaddle object, where the majority of changes occurred.  Let’s start with PlayerPaddle.h:

 

#pragma once #include "VisibleGameObject.h" class PlayerPaddle : public VisibleGameObject { public: PlayerPaddle(); ~PlayerPaddle(); void Update(float elapsedTime); void Draw(sf::RenderWindow& rw); float GetVelocity() const; private: float _velocity; // -- left ++ right float _maxVelocity; };

Click here to download PlayerPaddle.h

 

 

Not a ton of changes here.  First we declare that we are going to override Update() from our base class.  Second we add a pair of private member variables, _velocity and _maxVelocity.  These values are going to determine the speed and direction of our paddle, as well as the max speed the paddle can move.  We will see this in more detail now as we check out the changes to PlayerPaddle.cpp:

#include "StdAfx.h" #include "PlayerPaddle.h" #include "Game.h" PlayerPaddle::PlayerPaddle() : _velocity(0), _maxVelocity(600.0f) { Load("images/paddle.png"); assert(IsLoaded()); GetSprite().SetCenter(GetSprite().GetSize().x /2, GetSprite().GetSize().y / 2); } PlayerPaddle::~PlayerPaddle() { } void PlayerPaddle::Draw(sf::RenderWindow & rw) { VisibleGameObject::Draw(rw); } float PlayerPaddle::GetVelocity() const { return _velocity; } void PlayerPaddle::Update(float elapsedTime) { if(Game::GetInput().IsKeyDown(sf::Key::Left)) { _velocity-= 3.0f; } if(Game::GetInput().IsKeyDown(sf::Key::Right)) { _velocity+= 3.0f; } if(Game::GetInput().IsKeyDown(sf::Key::Down)) { _velocity= 0.0f; } if(_velocity > _maxVelocity) _velocity = _maxVelocity; if(_velocity < -_maxVelocity) _velocity = -_maxVelocity; sf::Vector2f pos = this->GetPosition(); if(pos.x < GetSprite().GetSize().x/2 || pos.x > (Game::SCREEN_WIDTH - GetSprite().GetSize().x/2)) { _velocity = -_velocity; // Bounce by current velocity in opposite direction } GetSprite().Move(_velocity * elapsedTime, 0); }

Click here to download PlayerPaddle.cpp

 

 

 

One of the first changes is to PlayerPaddle’s constructor.  First we define default values for _velocity and _maxVelocity, but one change you may notice is we now Load the image file / sprite data within the constructor, instead of doing in in Game.cpp.  Since this value is never going to change, it makes sense for the PlayerPaddle to do it’s own file loading.

 

 

 

The next thing you may notice is the assert statement.  An assert simply evaluates that a given expression is true, in this case IsLoaded()’s return value.  If the value passed to an assert is not true BLAMMO!  your program dies a horrible death.  Why the hell would you want to do that? you may be wondering and the answer is pretty easy.  First, you cannot return a value from a constructor, so it wasn’t a simple matter of returning false.  Our other option would be to throw an exception and try to deal with the situation gracefully.  Reality is though, if the PlayerPaddle cannot find it’s image data, we simply cannot recover from that.  One of the simplest solutions is to throw an assert, so you the developer can notice and fix the problem.

 

 

 

Here is an example of the assert in action.  I have removed the Paddle.png image from the images directory, now when I run pang.exe the following happens:

 

 

 

image

 

 

 

 

If you look at the console window, you see:

 

 

image

 

 

 

As you can see from the output you receive, you should be able to quickly diagnose and fix the problem.  Asserts can be set up to be disabled at run time, but generally anything you are asserting on should be a complete show stopper.  However, you may want to present your user a slightly nicer user experience if something catastrophic happens.  I have seen my share of AAA games throw an assert message in my face, so it’s not unheard of to leave asserts in the final release build.  To enable assert() we added the <cassert> include to our stdafx.h.  You can of course choose a different method for handling catastrophic failures, but assert is a very valuable tool to have in your bag.

 

 

The very last thing to notice about PlayerPaddle’s constructor is the call to sf::Sprite’s SetCenter() method.  This makes the sprite position it’s center point instead of the default top left corner.  This makes certain activities like positioning the sprite in the middle of the screen, or rotating a sprite about it’s center point, a heck of a lot easier.

 

 

Now on to the meat of the changes, the Update() method.  Update is going to be called every frame, before Draw() and is the PlayerPaddle’s chance to, um… do stuff.  In our case, get the users input and move the paddle accordingly.  Update is passed a value which is the elapsed period of time since the last frame of graphics was drawn.  This tidbit of information is going to be very useful in a minute.

 

 

The first thing Update() does is polls to see if the Left arrow is pressed.  SFML offers two ways of handling input, event driven and polling.  We say an example of handling events in Game.cpp. In this case, we are going to use polling.  If the Left arrow is down, we decrement the _velocity by 5.0f.  In the event that the Right arrow is pressed, we increment the _velocity by 5.0f.  _velocity starts at 0.0f ( no movement ) and determines not only the direction we are going ( negative is left, positive is right ) but how fast by its size.  The value of _velocity represents the number of pixels per second to move the paddle.  So, if we have a _velocity of 0.0f there is no movement.  With a _velocity of –40.0f, we move left 40 pixels every second, while a _velocity of 200.0f would say to move right by 200 pixels per second.  Holding down or repeatedly pressing an arrow key will either cause the velocity to speed up, or slow down, depending which key is pressed and which direction we are currently moving. Make sense?

 

 

Finally, we check to see if the Down arrow was pressed.  Pressing the down arrow stops things dead by setting the velocity to 0.0f.  You can think of the Down arrow as the brakes.

 

 

Next we check against the _maxVelocity.  _maxVelocity is simply the maximum value we will allow the paddle to move.  If we didn’t cap the maximum velocity, your paddle will be going very very very fast, very very very quickly.  So we check to see if the current _velocity is greater than _maximum velocity and if it is, we assign _velocity equal to the _maxVelocity.   We then do exactly the same thing again, just instead this time checking to make sure _maxVelocity isn’t below the negative range.  Therefore the ball can never be moving more than 1000 pixels/second (to the left) or –1000 pixels/second (to the right) at any given time.

 

 

Next up we check to see that the paddle isn’t about to go off the screen in either direction.  Remember before when we SetCenter() on the _sprite?  Well now we need to take that into account.  Therefore we check to see if the paddle is about to hit the left edge, you don’t check against 0, instead 0 + half the paddle’s width.  The same thing for checking against the right edge of the screen.  Instead of checking to see if sprite position is greater than the screen resolution, we need to subtract half our sprites width when checking to see if the sprites position is within the screens width.  If we hit our screen boundary on either side, we simply flip the sign on _velocity, effectively causing our paddle to “bounce” off the side in the opposite direction.

 

 

Finally we actually Move() the sprite.  Move() simply moves the sprite relative to its previous position.  This is where elapsedTime becomes very important.  Here is where one very fundamental problem in all video games comes into play… we all have different computers and they all run at different speeds.  So if your computer calls Update() a thousand times a second, while mine calls it five hundred times a second, your Paddle is going to move twice as fast!  We obviously can’t have this.

 

 

This is where _elapsedTime comes in.  It represents the amount of time has passed since the last frame was drawn, so say your computer is running at 1000 updates per second _elapsedTime would be 0.001, meaning 0.001 seconds have elapsed since the last frame was drawn.  Thus if we want to draw our paddle moving at say… 40 pixels / per second, that means in this given frame we actually want to move your paddle by ( 0.001 * 40 ) or 0.04 pixels.  As your computer is going to run this command a thousand times per second, 1000 times 0.04 pixels of movement = 40 pixels every second.  Now consider I was running my computer at a much slower 500 updates per second, that would make _elapsedTime 0.002.  This means that the paddle would move at 40 * ( 0.002) or 0.08 pixels per call.  This means my computer running this at 500 updates per second, moving at 0.08 pixels per update would move the paddle at… 40 pixels per second!  This keeps the rate of movement the same regardless to the speed of the computer the game is running on.

 

 

Optional information

 

How do you calculate FPS?

You may be wondering at this point how you calculate a games FPS ( frames per second ). It is actually remarkably simple, simply divide 1 by the frame delta, which is to say the amount of time that elapsed between frames.
 
So for example, say the elapsed time between frames was 0.002, you would calculate the framerate as:
 
1 / 0.002 == 500 FPS

 

 

 

As this post is already remarkably long, I will not post the entire code changes to GameObjectManager.h and GameObjectManager.cpp ( click either link to see source code ) as we made only one simple change, we added the public method UpdateAll():

 

 

void GameObjectManager::UpdateAll() { std::map<std::string,VisibleGameObject*>::const_iterator itr = _gameObjects.begin(); float timeDelta = Game::GetWindow().GetFrameTime(); while(itr != _gameObjects.end()) { itr->second->Update(timeDelta); itr++; } }

 

 

If you think that code looks familiar, you are correct!  It’s basically just a copy and paste job of DrawAll().  The only real difference is that we call GetFrameTime() which as we already covered, returns the time elapsed since the last frame was drawn by sfml.  Otherwise, it works exactly the same as DrawAll(), we run through all of our VisibleGameObjects and call their Update() method.

 

 

We also made a few small changes to Game.h and Game.cpp.  First off, we added a pair of const int to represent the game’s width and height, named appropriately SCREEN_WIDTH and SCREEN_HEIGHT.  This is much cleaner than hard coding these values everywhere, especially if we ever want to change them.  We also added the accessor method GetInput() which just makes accessing _mainWindows sf::Input a bit cleaner and makes it just a tiny bit easier if we want to change input libraries down the road.  Next we removed the call to player1->Load() as it is now being handled in PlayerPaddle’s constructor.  Finally we added another VisibleGameObject derived object, the ball.

 

 

And on that topic, lets introduce the GameBall.  First, it’s sprite:

 

 

ball

 (Click here to download the image)

 

 

Yeah, I really put a lot of effort into that one, didn’t I? Smile  Alright, at some point in the future I will come back and make a less ugly ball sprite, or you obviously can do that for yourself, but for now this one will do.  Now we look at the classes that represent our ball.  First we will create GameBall.h:

 

#pragma once #include "visiblegameobject.h" class GameBall : public VisibleGameObject { public: GameBall(); virtual ~GameBall(); };

Click here to download GameBall.h

 

 

 

Yeah, pretty trivial over all. Next, we created GameBall.cpp:

 

#include "StdAfx.h" #include "GameBall.h" GameBall::GameBall() { Load("images/ball.png"); assert(IsLoaded()); GetSprite().SetCenter(15,15); } GameBall::~GameBall() { }

Click here to download GameBall.cpp

 

 

 

Frankly, nothing new or even interesting in either new class, but we have to start somewhere!  Finally in Game.cpp we create our new game ball right smack in the center of the screen and add it to the _gameObjectManager, like this:

 

GameBall *ball = new GameBall(); ball->SetPosition((SCREEN_WIDTH/2),(SCREEN_HEIGHT/2)-15); _gameObjectManager.Add("Ball",ball);

 

 

 

And here is Pang! in action at this point. 

 

PANG6v2

 

 

 

You can download the entire project right here.

 

EDIT(5/14/2012): Download SFML 2.0RC project here. This is the above tutorial ported to SFML 2.0. Note, this was ported by a reader. The code will vary slightly from the tutorial above.

 

 

In the next part we will look into the issue of collision detection as we move much closer to actually being a real game.

 

 



Back to Part Five Forward to Part Seven




blog comments powered by Disqus


Month List

Popular Comments