Building the PlayableCharacter class

Now we know the basics about inheritance, polymorphism, and pure virtual functions, we will put them to use. We will build a PlayableCharacter class that has the vast majority of the functionality that any character from our game is going to need. It will have one pure virtual function, handleInput. The handleInput function will need to be quite different in the sub-classes, so this makes sense.

As PlayableCharacter will have a pure virtual function, it will be an abstract class and no objects of it will be possible. We will then build both Thomas and Bob classes, which will inherit from PlayableCharacter, implement the definition of the pure virtual function, and allow us to instantiate Bob and Thomas objects in our game.

Coding PlayableCharacter.h

As usual, when creating a class, we will start off with the header file that will contain the member variables and function declarations. What is new is that in this class, we will declare some protected member variables. Remember that protected variables can be used as if they are Public by classes, which inherit from the class with the protected variables.

Right-click Header Files in the Solution Explorer and select Add | New Item.... In the Add New Item window, highlight (by left-clicking) Header File ( .h ) and then in the Name field, type PlayableCharacter.h. Finally, click the Add button. We are now ready to code the header file for the PlayableCharacter class.

We will add and discuss the contents of the PlayableCharacter.h file in three sections. First, the protected section, followed by private, then public.

Add the code shown next to the PlayableCharacter.h file:

#pragma once 
#include <SFML/Graphics.hpp> 
 
using namespace sf; 
 
class PlayableCharacter 
{ 
protected: 
   // Of course we will need a sprite 
   Sprite m_Sprite; 
 
   // How long does a jump last 
   float m_JumpDuration; 
 
   // Is character currently jumping or falling 
   bool m_IsJumping; 
   bool m_IsFalling; 
 
   // Which directions is the character currently moving in 
   bool m_LeftPressed; 
   bool m_RightPressed; 
 
   // How long has this jump lasted so far 
   float m_TimeThisJump; 
 
   // Has the player just initialted a jump 
   bool m_JustJumped = false; 
 
   // Private variables and functions come next 

The first thing to notice in the code we just wrote is that all the variables are protected. This means that when we extend the class, all the variables we just wrote will be accessible to those classes that extend it. We will extend this class with Thomas and Bob classes.

Apart from the protected access specification, there is nothing new or complicated about the previous code. It is worth paying attention to some of the details, however. Then it will be easy to understand how the class works as we progress. So, let's run through those protected variables, one at a time.

We have our somewhat predictable Sprite, m_Sprite. We have a float called m_JumpDuration, which will hold a value representing the time that the character is able to jump for. The greater the value, the further/higher the character will be able to jump.

Next, we have a Boolean, m_IsJumping, which is true when the character is jumping and false otherwise. This will be useful for making sure that the character can't jump while in mid-air.

The m_IsFalling variable has a similar use to m_IsJumping. It will be useful to know when a character is falling.

Next, we have two Booleans that will be true if the character's left or right keyboard buttons are currently being pressed. These are relative depending upon the character (A and D for Thomas, Left and Right arrow keys for Bob). How we respond to these Booleans will be seen in the Thomas and Bob classes.

The m_TimeThisJump float variable is updated each and every frame that m_IsJumping is true. We can then know when m_JumpDuration has been reached.

The final protected variable is the Boolean m_JustJumped. This will be true if a jump was initiated in the current frame. It will be useful for knowing when to play a jump sound effect.

Next, add the following private variables to the PlayableCharacter.h file:

private: 
   // What is the gravity 
   float m_Gravity; 
 
   // How fast is the character 
   float m_Speed = 400; 
 
   // Where is the player 
   Vector2f m_Position; 
 
   // Where are the characters various body parts? 
   FloatRect m_Feet; 
   FloatRect m_Head; 
   FloatRect m_Right; 
   FloatRect m_Left; 
 
   // And a texture 
   Texture m_Texture; 
 
   // All our public functions will come next 

In the previous code, we have some interesting private variables. Remember that these variables will only be directly accessible to the code in the PlayableCharacter class. The Thomas and Bob classes will not be able to access them directly.

The m_Gravity variable will hold the number of pixels per second that the character will fall. The m_Speed variable will hold the number of pixels per second that the character can move left or right.

The Vector2f, m_Position variable is the position in the world (not the screen) where the center of the character is.

The next four FloatRect objects are important to discuss. When we did collision detection in the Zombie Arena game, we simply checked to see if two FloatRect objects intersected. Each FloatRect object represented an entire character, a pick-up, or a bullet. For the non-rectangular shaped objects (zombies and the player), this was a little bit inaccurate.

In this game, we will need to be more precise. The m_Feet, m_Head, m_Right, and m_Left FloatRect objects will hold the coordinates of the different parts of a character's body. These coordinates will be updated in each and every frame.

Through these coordinates, we will be able to tell exactly when a character lands on a platform, bumps his head during a jump, or rubs shoulders with a tile to his side.

Lastly, we have TextureTexture is private as it is not used directly by the Thomas or Bob classes but, as we saw, Sprite is protected because it is used directly.

Now add all the public functions to the PlayableCharacter.h file and then we will discuss them:

public: 
 
   void spawn(Vector2f startPosition, float gravity); 
 
   // This is a pure virtual function 
   bool virtual handleInput() = 0; 
   // This class is now abstract and cannot be instanciated 
 
   // Where is the player 
   FloatRect getPosition(); 
 
   // A rectangle representing the position  
   // of different parts of the sprite 
   FloatRect getFeet(); 
   FloatRect getHead(); 
   FloatRect getRight(); 
   FloatRect getLeft(); 
 
   // Send a copy of the sprite to main 
   Sprite getSprite(); 
 
   // Make the character stand firm 
   void stopFalling(float position); 
   void stopRight(float position); 
   void stopLeft(float position); 
   void stopJump(); 
 
   // Where is the center of the character 
   Vector2f getCenter(); 
 
   // We will call this function once every frame 
   void update(float elapsedTime); 
    
};// End of the class 

Let's talk about each of the function declarations that we just added. This will make coding their definitions easier to follow.

  • The spawn function receives a Vector2f called startPosition and a float called gravity. As the names suggest, startPosition will be the coordinates in the level at which the character will start and gravity will be the number of pixels per second at which the character will fall.
  • The bool virtual handleInput() = 0 is, of course, our pure virtual function. As PlayableCharacter has this function, any class that extends it, if we want to instantiate it, must provide a definition for this function. Therefore, when we write all the function definitions for PlayableCharacter in a minute, we will not provide a definition for handleInput. There will of course need to be definitions in both the Thomas and Bob classes.
  • The getPosition function returns a FloatRect that represents the position of the whole character.
  • The getFeet() function, as well as getHead, getRight, and getLeft, each return a FloatRect that represents the location of a specific part of the character's body. This is just what we need for detailed collision detection.
  • The getSprite function, as usual, returns a copy of m_Sprite to the calling code.
  • The stopFalling, stopRight, stopLeft, and stopJump function receive a single float value, which the function will use to reposition the character and stop it walking or jumping through a solid tile.
  • The getCenter function returns a Vector2f to the calling code to let it know exactly where the center of the character is. This value is, of course, held in m_Position. We will see later that it is used by the Engine class to center the appropriate View around the appropriate character.
  • The update function we have seen many times before and as usual, it takes a float parameter, which is the fraction of a second that the current frame has taken. This update function will need to do more work than previous update functions (from other projects), however. It will need to handle jumping, as well as updating the FloatRect objects that represent the head, feet, left, and right.

Now we can write the definitions for all the functions, except, of course, handleInput.

Coding PlayableCharacter.cpp

Right-click Source Files in the Solution Explorer and select Add | New Item.... In the Add New Item window, highlight (by left-clicking) C++ File ( .cpp ) and then in the Name field, type PlayableCharacter.cpp. Finally, click the Add button. We are now ready to code the .cpp file for the PlayableCharacter class.

We will break up the code and discussion into a number of chunks. First, add the include directives and the definition of the spawn function:

#include "stdafx.h" 
#include "PlayableCharacter.h" 
 
void PlayableCharacter::spawn(Vector2f startPosition, float gravity) 
{ 
   // Place the player at the starting point 
   m_Position.x = startPosition.x; 
   m_Position.y = startPosition.y; 
 
   // Initialize the gravity 
   m_Gravity = gravity; 
 
   // Move the sprite in to position 
   m_Sprite.setPosition(m_Position); 
 
} 

The spawn function initializes m_Position with the passed-in position, as well as initializing m_Gravity. The final line of code moves m_Sprite to its starting position.

Next, add the definition for the update function, immediately after the preceding code:

void PlayableCharacter::update(float elapsedTime) 
{ 
 
   if (m_RightPressed) 
   { 
      m_Position.x += m_Speed * elapsedTime; 
   } 
 
   if (m_LeftPressed) 
   { 
      m_Position.x -= m_Speed * elapsedTime; 
   } 
 
 
   // Handle Jumping 
   if (m_IsJumping) 
   { 
      // Update how long the jump has been going 
      m_TimeThisJump += elapsedTime; 
 
      // Is the jump going upwards 
      if (m_TimeThisJump < m_JumpDuration) 
      { 
         // Move up at twice gravity 
         m_Position.y -= m_Gravity * 2 * elapsedTime; 
      } 
      else 
      { 
         m_IsJumping = false; 
         m_IsFalling = true; 
      } 
 
   } 
 
   // Apply gravity 
   if (m_IsFalling) 
   { 
      m_Position.y += m_Gravity * elapsedTime; 
   } 
 
   // Update the rect for all body parts 
   FloatRect r = getPosition(); 
 
    
   // Feet 
   m_Feet.left = r.left + 3; 
   m_Feet.top = r.top + r.height - 1; 
   m_Feet.width = r.width - 6; 
   m_Feet.height = 1; 
 
   // Head 
   m_Head.left = r.left; 
   m_Head.top = r.top + (r.height * .3); 
   m_Head.width = r.width; 
   m_Head.height = 1; 
 
   // Right 
   m_Right.left = r.left + r.width - 2; 
   m_Right.top = r.top + r.height * .35; 
   m_Right.width = 1; 
   m_Right.height = r.height * .3; 
 
   // Left 
   m_Left.left = r.left; 
   m_Left.top = r.top + r.height * .5; 
   m_Left.width = 1; 
   m_Left.height = r.height * .3; 
 
   // Move the sprite into position 
   m_Sprite.setPosition(m_Position); 
 
} 

The first two parts of the code check whether m_RightPressed or m_LeftPressed is true. If either of them is, m_Position is changed using the same formula as the previous project (elapsed time multiplied by speed).

Next, we see whether or not the character is currently executing a jump. We know this from if(m_IsJumping). If this if statement is true, these are the steps the code takes:

  1. Update m_TimeThisJump with elapsedTime.
  2. Check if m_TimeThisJump is still less than m_JumpDuration. If it is, change the y coordinate of m_Position by twice gravity multiplied by the elapsed time.
  3. In the else clause that executes when m_TimeThisJump is not lower than m_JumpDuration, then m_Falling is set to true. The effect of doing this will be seen next. Also, m_Jumping is set to false. This prevents the code we have just been discussing from executing, because if(m_IsJumping) is now false.

The if(m_IsFalling) block moves m_Position down each frame. It is moved using the current value of m_Gravity and the elapsed time.

The following code (almost all of the remaining code) updates the body parts of the character, relative to the current position of the sprite as a whole. Take a look at the following diagram to see how the code calculates the position of the virtual head, feet, left, and right sides of the character:

Coding PlayableCharacter.cpp

The final line of code uses the setPosition function to move the sprite to its correct location after all of the possibilities of the update function.

Now add the definition for the getPosition, getCenter, getFeet, getHead, getLeft, getRight, and getSprite functions, immediately after the previous code:

FloatRect PlayableCharacter::getPosition() 
{ 
   return m_Sprite.getGlobalBounds(); 
} 
 
Vector2f PlayableCharacter::getCenter() 
{ 
   return Vector2f( 
      m_Position.x + m_Sprite.getGlobalBounds().width / 2, 
      m_Position.y + m_Sprite.getGlobalBounds().height / 2 
      ); 
} 
 
FloatRect PlayableCharacter::getFeet() 
{ 
   return m_Feet; 
} 
 
FloatRect PlayableCharacter::getHead() 
{ 
   return m_Head; 
} 
 
FloatRect PlayableCharacter::getLeft() 
{ 
   return m_Left; 
} 
 
FloatRect PlayableCharacter::getRight() 
{ 
   return m_Right; 
} 
 
Sprite PlayableCharacter::getSprite() 
{ 
   return m_Sprite; 
} 

The getPosition function returns a FloatRect that wraps the entire sprite, and getCenter returns a Vector2f, which contains the center of the sprite. Notice that we divide the height and width of the sprite by two in order to dynamically arrive at this result. This is because Thomas and Bob will be of different heights.

The getFeet, getHead, getLeft, and getRight functions return the FloatRect objects that represent the body parts of the character that we update each frame in the update function. We will write the collision detection code that uses these functions in the following chapter.

The getSprite function, as usual, returns a copy of m_Sprite.

Finally, for the PlayableCharacter class, add the definitions for the stopFalling, stopRight, stopLeft, and stopJump functions. Do so immediately after the previous code:

void PlayableCharacter::stopFalling(float position) 
{ 
   m_Position.y = position - getPosition().height; 
   m_Sprite.setPosition(m_Position); 
   m_IsFalling = false; 
} 
 
void PlayableCharacter::stopRight(float position) 
{ 
    
   m_Position.x = position - m_Sprite.getGlobalBounds().width; 
   m_Sprite.setPosition(m_Position); 
} 
 
void PlayableCharacter::stopLeft(float position) 
{ 
   m_Position.x = position + m_Sprite.getGlobalBounds().width; 
   m_Sprite.setPosition(m_Position); 
} 
 
void PlayableCharacter::stopJump() 
{ 
   // Stop a jump early  
   m_IsJumping = false; 
   m_IsFalling = true; 
} 

Each of the previous functions receives a value as a parameter that is used to reposition either the top, bottom, left, or right of the sprite. Exactly what these values are and how they are obtained will be seen in the following chapter. Each of the previous functions also repositions the sprite.

The final function is the stopJump function that will also be used in collision detection. It sets the necessary values for m_IsJumping and m_IsFalling to end a jump.

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

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