OOP

OOP is a programming paradigm that we could consider almost the standard way to code. It is true there are non-OOP ways to code and there are even some non-OOP game coding languages and libraries. However, starting from scratch, as this book does, there is no reason to do things any other way. When the benefits of OOP become apparent you will never look back.

OOP will:

  • Make our code easier to manage, change, or update
  • Make our code quicker and more reliable to write
  • Make it possible to easily use other people's code (such as SFML)

We have already seen the third benefit in action. Let's look at the first two benefits by introducing a problem that needs solving. The problem we are faced with is the complexity of the current project. Let's consider just a single zombie and what we need to make it function in the game:

  • Horizontal and vertical position
  • Size
  • Direction it is facing
  • Different textures for each zombie type
  • Sprites
  • Different speeds for each zombie type
  • Different health for each zombie type
  • Keeping track of the type of each zombie
  • Collision-detection data
  • Intelligence (to chase the player)
  • Is the zombie alive or dead?

This list suggests perhaps a dozen variables for just one zombie! We would need, perhaps, whole arrays of each of these variables for managing a zombie horde. And what about all the bullets from the machine gun, the pick-ups, and the different level ups? The simple Timber!!! game was starting to get a bit unmanageable by the end, and it is easy to speculate that this more complicated shooter could be many times worse!

Fortunately, handling complexity is not a new problem, and C++ was designed from the start to be the solution for this complexity.

What is OOP?

OOP is a way of programming that involves breaking our requirements down into chunks that are more manageable than the whole.

Each chunk is self-contained, yet potentially reusable by other programs, while working together with the other chunks as a whole.

These chunks are what we have been referring to as objects. When we plan and code an object, we do so with a class.

Tip

A class can be thought of as the blueprint for an object.

We implement an object of a class. This is called an instance of a class. Think about a house blueprint. You can't live in it, but you can build a house from it. You build an instance of it. Often when we design classes for our games, we write them to represent real world things. In this project, we will write classes for the player, a zombie, a bullet, and more as well. However, OOP is more than this.

Tip

OOP is a way of doing things, a methodology that defines best practices.

The three core principles of OOP are encapsulation, polymorphism, and inheritance. This might sound complex, but actually, taken a step at a time, it is reasonably straightforward.

Encapsulation

Encapsulation means keeping the internal workings of your code safe from interference from the code that uses it. You can achieve this by allowing only the variables and functions, which you choose, to be accessed. This means your code can always be updated, extended, or improved without affecting the programs that use it, as long as the exposed parts are still accessed in the same way.

As an example, with proper encapsulation, it wouldn't matter if the SFML team needed to update the way their Sprite class works. As long as the function signatures remain the same, we don't have to worry about what goes on inside. Our code written before the update will still work after the update.

Polymorphism

Polymorphism allows us to write code that is less dependent on the types we are trying to manipulate. This will make our code clearer and more efficient. Polymorphism means different forms. If the objects that we code can be more than one type of thing, then we can take advantage of this. Polymorphism might sound a little bit like black magic at this point. We will use polymorphism in the final project starting in Chapter 12, Abstraction and Code Management - Making Better Use of OOP. Everything will then become clearer.

Inheritance

Just like it sounds, inheritance means we can harness all the features and benefits of other people's classes, including the encapsulation and polymorphism, while further refining their code specifically to our situation. We will use inheritance in the final project starting in Chapter 12, Abstraction and Code Management - Making Better Use of OOP.

Why do it like this?

When written properly, all this OOP allows you to add new features without worrying as much about how they interact with existing features. When you do have to change a class, its self-contained (encapsulated) nature means fewer or perhaps even zero consequences for other parts of the program.

You can use other people's code (such as the SFML classes) without knowing or perhaps even caring how it works inside.

OOP, and by extension SFML, allows you to write games that use complicated concepts, such as multiple cameras, multiplayer, OpenGL, directional sound, and more besides. All this without breaking a sweat.

Using inheritance you can create multiple similar, yet different, versions of a class without starting the class from scratch.

You can still use the functions intended for the original type of object with your new object because of polymorphism.

All this makes sense, really. And, as we know, C++ was designed from the start with all of this OOP in mind.

Tip

The ultimate key to success with OOP and making games (or any other type of app), other than the determination to succeed, is planning and design. It is not so much just knowing all the C++, SFML, and OOP topics that will help you write great code, but rather applying all that knowledge to write code that is well structured and designed. The code in this book is presented in an order and manner appropriate to learning the various C++ topics in a gaming context. The art and science of structuring your code is called design patterns. As your code gets longer and more complex, effective use of design patterns will become more important. The good news is that we don't need to invent these design patterns ourselves. We will need to learn about them as our projects get more complex. More on design patterns in the final chapter.

In this project we will learn about and use basic classes and encapsulation, and in the final project we will get a bit more daring and use inheritance, polymorphism, and other OOP-related C++ features too.

What is a class?

A class is a bunch of code that can contain functions, variables, loops, and all the other C++ syntax we have already learned about. Each new class will be declared in its own .h code file with the same name as the class and its functions will be defined in their own .cpp file. This will become clearer when we actually look at writing some classes.

Once we have written a class, we can use it to make as many objects from it as we want. Remember, the class is the blueprint and we make objects based on the blueprint. The house isn't the blueprint just as the object isn't the class. It is an object made from the class.

Tip

You can think of an object as a variable and the class as a type.

Of course, with all this talk of OOP and classes we haven't actually seen any code. So let's fix that now.

The class variable and function declarations

Let's use a different game example to Zombie Arena. Consider the most basic game of all, Pong. A paddle/bat that bounces a ball. The paddle would be an excellent candidate for a class.

Tip

If you don't know what Pong is, then take a look at this link: https://en.wikipedia.org/wiki/Pong

Take a look at a hypothetical Paddle.h file:

class Paddle 
{ 
   private: 
 
      // Length of the pong paddle 
      int m_Length = 100;  
 
      // Height of the pong paddle 
      int m_Height = 10; 
 
      // Location on x axis 
      int m_XPosition;       
 
      // Location on y axis 
      int m_YPosition;       
 
   public: 
 
      void moveRight(); 
      void moveLeft(); 
}; 

At first glance the code might appear a little complex, but when it is explained we will see there are very few new concepts.

The first thing to notice is that a new class is declared using the class keyword followed by the name of the class, and that the entire declaration is enclosed in curly braces followed by a closing semicolon:

class Paddle 
{ 
 
}; 

Now look at the variable declarations and their names:

// Length of the pong paddle 
int m_Length = 100;  
 
// Length of the pong paddle 
int m_Height = 10; 
 
// Location on x axis 
int m_XPosition;       
 
// Location on x axis 
int m_YPosition;       

All the names are prefixed with m_. This is not necessary, but it is a good convention. Variables declared as part of the class are called member variables. Prefixing with an m_ makes it absolutely plain when we are dealing with a member variable. When we write functions for our classes, we will start to see local variables and parameters as well. The m_ convention will then prove itself useful.

Notice also that all the variables are in a section of the code headed with the private: keyword. Scan your eyes over the previous sample code and notice that the body of the class code is separated into two sections:

private: 
   // more code here 
 
public: 
   // More code here 

The public and private keywords control the encapsulation of our class. Anything that is private cannot be accessed directly by the user of an instance or object of the class. If you are designing a class for others to use, you don't want them being able to alter anything at will.

This means that our four member variables cannot be accessed directly by our game engine in main. They can be accessed indirectly by the code of the class. For the m_Length and m_Height variables this is fairly easy to accept, as long as we don't need to change the size of the paddle. The m_XPosition and m_YPosition member variables, however, do need to be accessed, or how on earth will we move the paddle?

This problem is solved in the public: section of the code as follows:

void moveRight(); 
void moveLeft(); 

The class provides two functions which are public and will be usable with an object of type Paddle. When we have seen the definition of these functions, we will see exactly how these functions manipulate the private variables.

In summary, we have a bunch of inaccessible (private) variables that cannot be used from the main function. This is good because encapsulation makes our code less error prone and more maintainable. We then solve the problem of moving the paddle by providing indirect access to the m_XPosition and m_YPosition variables by providing two public functions.

The code in main can call these functions, but the code inside the functions controls exactly how the variables are altered.

Let's take a look at the function definitions.

The class function definitions

The function definitions we will write in this book will all go in a separate file to the class and function declarations. We will use files with the same name as the class and the .cpp file extension. So, in our hypothetical example, this next code would go in a file called Paddle.cpp. Take a look at this really simple code that has just one new concept:

#include "stdafx.h" 
#include "Paddle.h" 
 
void Paddle::moveRight() 
{ 
   // Move the paddle a pixel to the right 
   m_XPosition ++; 
} 
 
void Paddle::moveLeft() 
{ 
   // Move the paddle a pixel to the left 
   m_XPosition --; 
} 

The first thing to note is that we must use an include directive to include the class and function declarations from the Paddle.h class.

The new concept we see here is the use of the scope resolution operator, ::. As the functions belong to a class, we must write the signature part by prefixing the function name with the class name and ::. void Paddle::moveLeft() and void Paddle::moveRight.

Note

Actually, we have briefly seen the scope resolution operator before. Whenever we declare an object of a class and we have not previously used using namespace...

Note also that we could have put the function definitions and declarations in one file like this:

class Paddle 
{ 
   private: 
 
      // Length of the pong paddle 
      int m_Length = 100;  
 
      // Height of the pong paddle 
      int m_Height = 10; 
 
      // Location on x axis 
      int m_XPosition;       
 
      // Location on x axis 
      int m_YPosition;       
 
   public: 
 
      void Paddle::moveRight() 
      { 
         // Move the paddle a pixel to the right 
         m_XPosition ++; 
      } 
 
      void Paddle::moveLeft() 
      { 
         // Move the paddle a pixel to the left 
         m_XPosition --; 
      } 
 
}; 

However, when our classes get longer (as they will with our first Zombie Arena class) it is more organized to separate the function definitions into their own file. Furthermore, header files are considered public, and are often used for documentation purposes if other people will be using the code that we write.

Using an instance of a class

Despite all the code we have seen related to classes, we haven't actually used the class. We already know how to do this as we have used the SFML classes many times already.

First, we would create an instance of Paddle like this:

Paddle paddle; 

The paddle object has all the variables we declared in Paddle.h. We just can't access them directly. We can, however, move our paddle using its public functions, like this:

paddle.moveLeft(); 

Or like this:

paddle.moveRight(); 

Remember that paddle is a Paddle, and as such it has all the member variables and all the functions available to it.

We could decide at a later date to make our Pong game multiplayer. In the main function, we could change the code to have two paddles. Perhaps like this:

Paddle paddle; 
Paddle paddle2;

It is vitally important to realize that each of these instances of Paddle are separate objects with their very own set of variables.

Constructors and getter functions

The simple Pong paddle example was a good way of introducing the basics of classes. Classes can be simple and short like Paddle, but they can also be longer, more complicated, and themselves contain other objects.

When it comes to making games, there is a vital thing missing from the hypothetical Paddle class. It might be fine for all these private member variables and public functions, but how will we draw anything? Our Pong paddles need a sprite and a texture too.

We can include other objects in our class in exactly the same way that we include them in main.

Tip

Here is an updated version of the private: section of Paddle.h code which includes a member Sprite and a member Texture too. Note that the file would also need the relevant SFML include directive for this code to compile.

private: 
 
   // Length of a pong paddle 
   int m_Length = 100;  
 
   // Height of a pong paddle 
   int m_Height = 10; 
 
   // Location on x axis 
   int m_XPosition;       
 
   // Location on x axis 
   int m_YPosition;      

   // Of course we will need a sprite   
   Sprite m_Sprite;  
 
   // And a texture   
   Texture m_Texture;

The new problem is immediately upon us. If m_Sprite and m_Texture are private, then how on earth will we draw them in the main function?

We will need to provide a function that allows access to m_Sprite so it can be drawn. Look carefully at the new function declaration in the public section of Paddle.h.

public: 
 
   void moveRight(); 
   void moveLeft(); 
    
   // Send a copy of the sprite to main   
   Sprite getSprite();

The previous code declares a function called getSprite. The significant thing to notice is that getSprite returns a Sprite object. We will see the definition of getSprite very soon.

If you are sharp minded, you will also have noticed that at no point have we loaded the texture or called m_Sprite.setTexture(m_Texture) to associate the texture with the sprite.

When a class is coded, a special function is created by the compiler. We don't see this function in our code but it is there. It is called a constructor. When we need to write some code to prepare an object for use, often a good place to do this is the constructor. When we want the constructor to do anything other than simply create an instance, we must replace the default (unseen) constructor provided by the compiler.

Note

First, we provide a constructor function declaration. Note that constructors have no return type, not even void. Also note that we can immediately see that it is the constructor function because the function name is the same as the class, Paddle.

public: 
 
   // The constructor   
   Paddle(); 

   void moveRight(); 
   void moveLeft(); 

   // Send a copy of the sprite to main 
   Sprite getSprite(); 

The next code shows the new function definitions in Paddle.cpp (getSprite and the constructor, Paddle):

// The constructor 
Paddle::Paddle() 
{ 
   // Code assumes paddle.png is a real image 
   m_Texture.loadFromFile("graphics/paddle.png"); 
    
   // Associate a texture with the sprite 
   m_Sprite.setTexture(m_Texture); 
} 
 
// Return a copy of the sprite to main 
Sprite Paddle::getSprite() 
{ 
   return m_Sprite; 
} 

In the previous code, we use the constructor function, Paddle, to load the texture and associate it with the sprite. Remember that this function is called at the time that an object of type Paddle is declared. More specifically, when the code Paddle paddle is executed, the constructor is called.

In the getSprite function there is just one line of code that returns a copy of m_Sprite to the calling code.

We could do other setup work for our objects in the constructor as well and will do so when we build our first real class.

If you want to see exactly how the getSprite function could be used, the code in main would look like this:

window.draw(paddle.getSprite()); 

The previous line of code assumes we have an SFML RenderWindow object called window. As getSprite returns an object of type Sprite, the previous line of code works exactly as if the sprite had been declared in main. Now we have a neatly encapsulated class that provides controlled access via its public functions.

Jumping around in the code

I find that when I read books that jump around in the code files, I often find it hard to follow exactly what is going on. What follows are the complete listings for the hypothetical Paddle.h and Paddle.cpp, to get everything in context. Be sure to study them before moving on:

Paddle.h

#pragma once 
#include <SFML/Graphics.hpp> 
 
using namespace sf; 
 
class Paddle 
{ 
   private: 
    
      // Length of a pong paddle 
      int m_Length = 100;  
      // Height of a pong paddle 
      int m_Height = 10; 
      // Location on x axis 
      int m_XPosition;       
      // Location on y axis 
      int m_YPosition;       
       
      // Of course we will need a sprite 
      Sprite m_Sprite; 
 
      // And a texture 
      Texture m_Texture; 
 
   public:   
 
      // The constructor 
      Paddle(); 
       
      void moveRight(); 
      void moveLeft(); 
       
      // Send a copy of the sprite to main 
      Sprite getSprite(); 
}; 

Paddle.cpp

#include "stdafx.h" 
#include "Paddle.h" 
 
// The constructor 
Paddle::Paddle() 
{ 
   // Code assumes paddle.png is a real image 
   m_Texture.loadFromFile("graphics/paddle.png"); 
    
   // Associate a texture with the sprite 
   m_Sprite.setTexture(m_Texture); 
} 
 
// Return a copy of the sprite to main 
Sprite Paddle::getSprite() 
{ 
   return m_Sprite; 
} 
 
void Paddle::moveRight() 
{ 
   // Move the paddle a pixel to the right 
   m_XPosition ++; 
} 
 
void Paddle::moveLeft() 
{ 
   // Move the paddle a pixel to the left 
   m_XPosition --; 
} 

We will constantly be revisiting classes and OOP throughout the rest of the book. For now, however, we know enough to get started on our first real class for the Zombie Arena game.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.3.255