Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


Game From Scratch C++ Edition Part 4

 

In this section we are going to accomplish one very small task.  We are going to get this sprite:

 

paddle

 

to draw on the screen ( Click here to download paddle.png ).  In the process we are going to look a little deeper into object oriented programming, specifically inheritance, where you will see how a base class can be used to drive code in derived classes with no additional effort.  We will also write some exceptionally sloppy code in Game.cpp, but don’t worry, that’s only temporary.

 

 

Alright, lets get to it.  Time to create our new base class, VisibleGameObject.  A VisibleGameObject represents an item in the world that needs to be drawn, such as the player’s paddle or the game’s ball.  First we need to create a new header file, VisibleGameObject.h.  Once created, add the following code:

 

 

1 #pragma once 2 class VisibleGameObject 3 { 4 public: 5 VisibleGameObject(); 6 virtual ~VisibleGameObject(); 7 8 virtual void Load(std::string filename); 9 virtual void Draw(sf::RenderWindow & window); 10 11 virtual void SetPosition(float x, float y); 12 13 private: 14 sf::Sprite _sprite; 15 sf::Image _image; 16 std::string _filename; 17 bool _isLoaded; 18 19 };

Click here to download VisibleGameObject.h

 

This class is pretty straight forward.  We declare our new class of VisibleGameObject, a public constructor, destructor and three public methods, Load(), Draw() and SetPosition().  In C++ the constructor is called automatically ( and first ) when the object is created.  A destructor is called when an object is deleted, either using the delete keyword, or by going out of scope.  The three other methods are covered in detail later.

 

 

Finally we have our private member variables.  _sprite and _image hold the SFML Sprite and Image classes respectively.  _filename is simply used to store the name of the file that is loaded into the image, while _isLoaded is a simple boolean that we set to true when the Image data has been loaded, so we don’t attempt to draw a VisibleGameObject whose image data has not been loaded.

 

 

 

 

Optional information

 

What is virtual?


You may have noticed that all of the public methods as well as the destructor had the keyword virtual defined and may be wondering what it means. The virtual keyword, when applied to a member function ( and not the class itself ) tells the compiler to look for and use a derived version of the member function instead of using the type of the pointer to determine which to call.  I know that sounds confusing, so consider the following code:
 
struct BaseWithVirtual { virtual void DoSomething() { cout << "Doing something in the base class"; } }; struct DerivedFromVirtual : BaseWithVirtual { void DoSomething() { cout << "Doing something in the derived class"; } }; int main(int argc, char *argv[]) { BaseWithVirtual * bwv = new DerivedFromVirtual(); bwv->DoSomething(); }
 
 
In this case you see that we have a base class BaseWithVirtual with a virtual method DoSomething() then a derived class DerivedFromVirtual which implements DoSomething().  The key to understanding virtual is looking at how we declared it.  We create a pointer of the base class type “bwv”, but it actually points to a DerivedFromVirtual object.  However, since DoSomething was declared as virtual in the base class, when we call bwv->DoSomething() it prints “Doing something in the derived class”.
 
 
 
This is a very key feature if you want to make polymorphic code. This is code where there is a common interface between objects, but the implementation can change. Of note, notice how I didn’t use the virtual keyword on the derived classes definition of DoSomething()? This is because it is already virtual.  If a class is inherited from a class with virtual methods, those methods are automatically virtual in the derived class.
 
 
 
To best way to understand the use of virtual is to look at a non-virtual example:
struct BaseClass { void DoSomething() { cout << "Doing something in base class"; } }; struct DerivedClass : BaseClass { void DoSomething() { cout << "Doing something in derived class"; } }; int main(int argc, char* argv[]) { BaseClass * bcp = new DerivedClass(); bcp->DoSomething(); return 0; }

 

This time, when the inherited method isn’t virtual, when you run this code it prints “Doing something in base class”.  This is because without virtual, the type of the pointer ( in this case BaseClass* ) determines which method is called.

 

 

This is especially tricky in destructors, as if the base class does not contain a virtual destructor, it is quite possible your derived classes destructor will never be called, quite possibly causing a memory leak.

 

 

As a general rule of thumb, if you intend for your derived classes to override behavior from the base class, declare your member functions as virtual.

 

 

Alright, lets move on to the cpp file.  Create a new file called VisibleGameObject.cpp and add the following code:

 

#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::SetPosition(float x, float y) { if(_isLoaded) { _sprite.SetPosition(x,y); } }

Click here to download VisibleGameObject.cpp

 

This code is fairly self explanatory.  In the constructor we set the _isLoaded flag to false.  Then in VisibleGameObject::Load() we attempt to load an sf::Image from file , which if successful we create the sf::Sprite to use the loaded sf::Image and set _isLoaded to true, otherwise we set it to false.

 

 

VisibleGameObject::Draw() simple checks to see if the image has been loaded, and if it has, draws VisibleGameObject’s sf::Sprite to the RenderWindow.  VisibleGameObject::SetPosition() works very similar, be checking if the image has been loaded, and if so sets the _sprite’s position to the values provided.  Nothing overly complex going on behind the curtain.

 

 

Optional information

 

Constructor member initialization lists

I was/am very hesitant to mention this, as this is very much an example of a premature optimization, but I figured I should point it out as I was just technically incorrect in something I just did. In the earlier code:
VisibleGameObject::VisibleGameObject() { _isLoaded = false; }

 

It would have been better to have used:

VisibleGameObject::VisibleGameObject() : _isLoaded(false) { }

 

As constructors should generally be used for initialization, not assignment.  The difference between these two pieces of code is very minor, but important.  In the first, _isLoaded’s default constructor would be called before VisibleGameObject’s constructor, then an assignment operation would occur.  In the second code fragment, the assignment is removed from the equation.

 

It is a fairly miniscule optimization, especially in this scenario using built-in types, however in a more complex scenario, with a type called millions of times in a tight loop, this can have a big effect on performance.

 

Now that I have told you this, I ask that you forget it completely! Smile  I only mention this out of a sake of being thorough and to ease my conscience for steering you wrongly, as frankly this is an implementation detail that is of little importance to a new developer and is something you can easily implement down the road.

 

For more details on constructor initialization lists read this.

 

 

Alright, now that we have our VisibleGameObject base class, its time to do something with it.  Lets create the player’s paddle.  Create a new file called PlayerPaddle.h and add the following code:

 

1 #pragma once 2 #include "VisibleGameObject.h" 3 4 class PlayerPaddle : 5 public VisibleGameObject 6 { 7 public: 8 PlayerPaddle(); 9 ~PlayerPaddle(); 10 11 }; 12

Click here to download PlayerPaddle.h

 

Yeah, not much to it.    We are deriving a new class PlayerPaddle from the base class VisibleGameObject.  We declare a public constructor and desctructor and that’s about it.

 

 

This in another key part of object oriented programming, and as you will see shortly, PlayerPaddle gets a lot of ability from VisibleGameObject with no additional code required.  The key thing to note here is the use of public.  This is saying when we derive PlayerPaddle, we are only deriving the public members of VisibleGameObject.  This is a key concept for hiding complexity.

 

 

You see, VisibleGameObject had a number of private data members, including the sprite and image data, and the derived PlayerPaddle has all of these as well, but it has no direct access to them.  This is a good way of hiding complexity from derived classes, as well as keeping control where it should properly be.

 

 

When you are declaring a class you can declare members as public, protected and private, you use the same three keywords when deriving a class from a base class. 

 

 

 

Now we create PlayerPaddle.cpp which will be some of the most laughably simple code you ever write:

 

1 #include "StdAfx.h" 2 #include "PlayerPaddle.h" 3 4 5 PlayerPaddle::PlayerPaddle() 6 { 7 } 8 9 10 PlayerPaddle::~PlayerPaddle() 11 { 12 }

Click here to download PlayerPaddle.cpp

 

Yeah, that’s all there is to it.  Don’t worry, we will be adding more to it later.  But for now, this is all we need.  You now have a fully functional player object.  Now lets use it, we now need to open up Game.h and add some ugly code.

 

1 #pragma once 2 #include "SFML/Window.hpp" 3 #include "SFML/Graphics.hpp" 4 #include "PlayerPaddle.h" 5 6 class Game 7 { 8 public: 9 static void Start(); 10 11 private: 12 static bool IsExiting(); 13 static void GameLoop(); 14 15 static void ShowSplashScreen(); 16 static void ShowMenu(); 17 18 enum GameState { Uninitialized, ShowingSplash, Paused, 19 ShowingMenu, Playing, Exiting }; 20 21 static GameState _gameState; 22 static sf::RenderWindow _mainWindow; 23 static PlayerPaddle _player1; 24 };

Click here to download Game.h

 

First we add out new include on line 4 so Game has some concept what a Player Paddle is.  Second we declare a PlayerPaddle named _player1 on line 23.   Now we need to add a bit of code to Game.cpp.

1 #include "stdafx.h" 2 #include "Game.h" 3 #include "MainMenu.h" 4 #include "SplashScreen.h" 5 6 void Game::Start(void) 7 { 8 if(_gameState != Uninitialized) 9 return; 10 11 _mainWindow.Create(sf::VideoMode(1024,768,32),"Pang!"); 12 13 _player1.Load("images/paddle.png"); 14 _player1.SetPosition((1024/2)-45,700); 15 16 _gameState= Game::ShowingSplash; 17 18 while(!IsExiting()) 19 { 20 GameLoop(); 21 } 22 23 _mainWindow.Close(); 24 } 25 26 bool Game::IsExiting() 27 { 28 if(_gameState == Game::Exiting) 29 return true; 30 else 31 return false; 32 } 33 34 void Game::GameLoop() 35 { 36 switch(_gameState) 37 { 38 case Game::ShowingMenu: 39 { 40 ShowMenu(); 41 break; 42 } 43 case Game::ShowingSplash: 44 { 45 ShowSplashScreen(); 46 break; 47 } 48 case Game::Playing: 49 { 50 sf::Event currentEvent; 51 while(_mainWindow.GetEvent(currentEvent)) 52 { 53 _mainWindow.Clear(sf::Color(sf::Color(0,0,0))); 54 _player1.Draw(_mainWindow); 55 _mainWindow.Display(); 56 57 if(currentEvent.Type == sf::Event::Closed) _gameState = Game::Exiting; 58 59 if(currentEvent.Type == sf::Event::KeyPressed) 60 { 61 if(currentEvent.Key.Code == sf::Key::Escape) ShowMenu(); 62 } 63 } 64 65 break; 66 } 67 } 68 } 69 70 void Game::ShowSplashScreen() 71 { 72 SplashScreen splashScreen; 73 splashScreen.Show(_mainWindow); 74 _gameState = Game::ShowingMenu; 75 } 76 77 void Game::ShowMenu() 78 { 79 MainMenu mainMenu; 80 MainMenu::MenuResult result = mainMenu.Show(_mainWindow); 81 switch(result) 82 { 83 case MainMenu::Exit: 84 _gameState = Game::Exiting; 85 break; 86 case MainMenu::Play: 87 _gameState = Game::Playing; 88 break; 89 } 90 } 91 92 // A quirk of C++, static member variables need to be instantiated outside of the class 93 Game::GameState Game::_gameState = Game::Uninitialized; 94 sf::RenderWindow Game::_mainWindow; 95 PlayerPaddle Game::_player1;

Click here to download Game.cpp

 

Although that looks like an absolute wall of code, not that much has changed here.  First on line 13 and 14 we Load() the image our paddle will use, then we set its position to the middle/bottom of the screen.

 

 

Next in our GameLoop we added line 54, which simply tells _player1 to Draw() itself to _mainWindow.  As you noticed when we declared PlayerPaddle, there is no drawing code at all, so it is inheriting this functionality from it’s base class VisibleGameObject!

 

 

Lastly because _player1 is a static member variable, it needs to be instantiated in the global namespace.  ( This is a C++ism, no other languages have this requirement, at least, not that I am aware of ).

 

 

Finally, here is our running application, now with a paddle!

 

image

 

 

Exciting stuff, eh?  No… alright, hang in there, it will get more exciting soon.

 

 

If that whole section felt kinda hackish, it was.  Even though our game world only has a few visible objects, Game.cpp could start getting very messy and fast as we add more and more VisibleGameObject’s to it.  Therefore in the next section we are going to create a slightly more elegant storage solution.

 

 

You can download the updated project here.

 

 

 

Back to Part Three Forward to Part Five




blog comments powered by Disqus


Month List

Popular Comments