We will code a Pickup
class that has a Sprite
, member as well as other member data and functions. We will add pickups to our game in just a few steps:
Pickup.h
file. This will reveal all the details of the member data and the prototypes for the functions.Pickup.cpp
file which, of course, will contain the definitions for all the functions of the Pickup
class. As we step through it, I will explain exactly how an object of type Pickup
will work and be controlled.Pickup
class in the main
function to spawn them, update them and draw them.Let's get started with step 1.
To make the new header file, 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 Pickup.h
.
Add and study the following code in the Pickup.h
file and then we can go through it:
#pragma once #include <SFML/Graphics.hpp> using namespace sf; class Pickup { private: //Start value for health pickups const int HEALTH_START_VALUE = 50; const int AMMO_START_VALUE = 12; const int START_WAIT_TIME = 10; const int START_SECONDS_TO_LIVE = 5; // The sprite that represents this pickup Sprite m_Sprite; // The arena it exists in IntRect m_Arena; // How much is this pickup worth? int m_Value; // What type of pickup is this? // 1 = health, 2 = ammo int m_Type; // Handle spawning and disappearing bool m_Spawned; float m_SecondsSinceSpawn; float m_SecondsSinceDeSpawn; float m_SecondsToLive; float m_SecondsToWait; // Public prototypes go here };
The previous code declares all the private variables of the Pickup
class. Although the names should be quite intuitive, it might not be obvious why many of them are needed at all. Let's go through them, starting from the top:
const int HEALTH_START_VALUE = 50
: This constant variable is used to set the starting value of all health pickups. The value will be used to initialize the m_Value
variable, which will need to be manipulated throughout the course of a game.const int AMMO_START_VALUE = 12
: This constant variable is used to set the starting value of all ammo pickups. The value will be used to initialize the m_Value
variable, which will need to be manipulated throughout the course of a game.const int START_WAIT_TIME = 10
: This variable is how long a pickup will wait before it re-spawns after disappearing. It will be used to initialize the m_SecondsToWait
variable which can be manipulated throughout the game.const int START_SECONDS_TO_LIVE = 5
: This variable determines how long a pickup will last between spawning and being de-spawned. Like the previous three constants, it has a non-constant associated with it that can be manipulated throughout the course of the game. The non-constant it is used to initialize is m_SecondsToLive
.Sprite m_Sprite
: This is the sprite to visually represent the object.IntRect m_Arena
: This will hold the size of the current arena to help the pickup spawn in a sensible position.int m_Value
: How much health or ammo is this pickup worth? This value is used when the player levels-up the value of the health or ammo pickup.int m_Type
: This will be either zero or one for health or ammo. We could have used an enumeration class but that seemed like overkill for just two options.bool m_Spawned
: Is the pickup currently spawned?float m_SecondsSinceSpawn
: How long is it since the pickup was spawned?float m_SecondsSinceDeSpawn
: How long is it since the pickup was de-spawned (disappeared)?float m_SecondsToLive
: How long should this pickup stay spawned before de-spawning?float m_SecondsToWait
: How long should this pickup stay de-spawned before re-spawning?Note that most of the complexity of this class is due to the variable spawn time and its upgradeable nature. If the pickups just re-spawned when collected and had a fixed value this would be a very simple class. We need our pickups to be upgradeable, so the player is forced to develop a strategy to progress through the waves of zombies.
Next, add the following public function prototypes to the Pickup.h
file. Be sure to familiarize yourself with the new code so we can go through it:
// Public prototypes go here
public:
Pickup::Pickup(int type);
// Prepare a new pickup
void setArena(IntRect arena);
void spawn();
// Check the position of a pickup
FloatRect getPosition();
// Get the sprite for drawing
Sprite getSprite();
// Let the pickup update itself each frame
void update(float elapsedTime);
// Is this pickup currently spawned?
bool isSpawned();
// Get the goodness from the pickup
int gotIt();
// Upgrade the value of each pickup
void upgrade();
};
Let's talk briefly about each of the function definitions:
int
parameter. This will be used to initialize the type of pickup it will be (health or ammo).setArena
function receives an IntRect
. This function will be called for each Pickup
instance at the start of each wave. The Pickup
objects will then know the areas into which they can spawn.spawn
function will, of course, handle spawning the pickup.getPosition
function, just like in the Player
, Zombie
, and Bullet
classes will return a FloatRect
that represents the current location of the object in the game world.getSprite
function returns a Sprite
object that enables the pickup to be drawn, once each frame.update
function receives the time the previous frame took. It uses this value to update its private variables and make decisions about when to spawn and de-spawn.isSpawned
function returns a Boolean that will let the calling code know whether or nor the pickup is currently spawned.gotIt
function will be called when a collision is detected with the player. The Pickup
class code can then prepare itself for re-spawning at the appropriate time. Note that it returns an int
so that the calling code knows how much the pickup is worth in either health or ammo.upgrade
function will be called when the player chooses to levelup the properties of a pickup during the levelingup phase of the game.Now we have gone through the member variables and function prototypes, it should be quite easy to follow along as we code the function definitions.
Now we can create a new .cpp
file that will contain the function definitions. 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 Pickup.cpp
. Finally, click the Add button. We are now ready to code the class.
Add the code shown here to the Pickup.cpp
file. Be sure to review the code so we can discuss it:
#include "stdafx.h" #include "Pickup.h" #include "TextureHolder.h" Pickup::Pickup(int type) { // Store the type of this pickup m_Type = type; // Associate the texture with the sprite if (m_Type == 1) { m_Sprite = Sprite(TextureHolder::GetTexture( "graphics/health_pickup.png")); // How much is pickup worth m_Value = HEALTH_START_VALUE; } else { m_Sprite = Sprite(TextureHolder::GetTexture( "graphics/ammo_pickup.png")); // How much is pickup worth m_Value = AMMO_START_VALUE; } m_Sprite.setOrigin(25, 25); m_SecondsToLive = START_SECONDS_TO_LIVE; m_SecondsToWait = START_WAIT_TIME; }
In the previous code, we added the familiar include directives. Then we added the Pickup
constructor. We know it is the constructor because it has the same name as the class.
The constructor receives an int
called type
and the first thing the code does is assign the value received from type
to m_Type
. After this, there is an if…else
block that checks whether m_Type
is equal to 1
. If it is, m_Sprite
is associated with the health pickup texture and m_Value
is set to HEALTH_START_VALUE
.
If m_Type
is not equal to 1
, the else
block associates the ammo pickup texture with m_Sprite
and assigns the value of AMMO_START_VALUE
to m_Value
.
After the if…else
block, the code sets the origin of m_Sprite
to the center using the setOrigin
function and assigns START_SECONDS_TO_LIVE
and START_WAIT_TIME
to m_SecondsToLive
and m_SecondsToWait
, respectively.
The constructor has successfully prepared a Pickup
object that is ready for use.
Next we will add the setArena
function. Examine the code as you add it:
void Pickup::setArena(IntRect arena) { // Copy the details of the arena to the pickup's m_Arena m_Arena.left = arena.left + 50; m_Arena.width = arena.width - 50; m_Arena.top = arena.top + 50; m_Arena.height = arena.height - 50; spawn(); }
The setArena
function that we just coded simply copies the values from the passed in arena
, object but varies the values by plus fifty on the left and top and minus fifty on the right and bottom. The Pickup object is now aware of the area in which it can spawn. The setArena
function then calls its own spawn
function to make the final preparations for being drawn and updated each frame.
The spawn
function is next. Add the following code after the setArena
function:
void Pickup::spawn() { // Spawn at a random location srand((int)time(0) / m_Type); int x = (rand() % m_Arena.width); srand((int)time(0) * m_Type); int y = (rand() % m_Arena.height); m_SecondsSinceSpawn = 0; m_Spawned = true; m_Sprite.setPosition(x, y); }
The spawn
function does everything necessary to prepare the pickup. First it seeds the random number generator and gets a random number for both the horizontal and vertical position of the object. Notice that it uses m_Arena.width
and m_Arena.height
as the ranges for the possible horizontal and vertical positions.
The m_SecondsSinceSpawn
is set to zero so the length of time allowed before it is de-spawned is reset. The m_Spawned
variable is set to true
so that when we call isSpawned
, from main
, we will get a positive response. Finally, m_Sprite
is moved into position with setPosition
, ready for drawing to the screen.
In the following block of code, we have three simple getter functions. The getPosition
function returns a FloatRect
of the current position of m_Sprite
, getSprite
returns a copy of m_Sprite
itself, and isSpawned
returns true
or false
depending upon whether the object is currently spawned.
Add and examine the code we have just discussed:
FloatRect Pickup::getPosition() { return m_Sprite.getGlobalBounds(); } Sprite Pickup::getSprite() { return m_Sprite; } bool Pickup::isSpawned() { return m_Spawned; }
Next we will code the gotIt
function. This function will be called from main
when the player touches/collides with (gets) the pickup. Add the gotIt
function after the isSpawned
function:
int Pickup::gotIt() { m_Spawned = false; m_SecondsSinceDeSpawn = 0; return m_Value; }
The gotIt
function sets m_Spawned
to false
so we know not to draw and check for collisions at the moment. The m_SecondsSinceDespawn
is set to zero so the countdown to spawning begins again from the start and m_Value
is returned to the calling code so the calling code can handle adding extra ammunition or health, as appropriate.
Next we have the update
function, which ties together many of the variables and functions we have seen so far. Add and familiarize yourself with the update
function and then we can talk about it:
void Pickup::update(float elapsedTime) { if (m_Spawned) { m_SecondsSinceSpawn += elapsedTime; } else { m_SecondsSinceDeSpawn += elapsedTime; } // Do we need to hide a pickup? if (m_SecondsSinceSpawn > m_SecondsToLive && m_Spawned) { // Remove the pickup and put it somewhere else m_Spawned = false; m_SecondsSinceDeSpawn = 0; } // Do we need to spawn a pickup if (m_SecondsSinceDeSpawn > m_SecondsToWait && !m_Spawned) { // spawn the pickup and reset the timer spawn(); } }
The update function is divided into four blocks that are considered for execution each frame:
if
block that executes if m_Spawned
is true—if (m_Spawned)
. This block of code adds the time of this frame to m_SecondsSinceSpawned
, which keeps track of how long the pickup has been spawned.else
block that executes if m_Spawned
is false
. This block adds the time this frame took to m_SecondsSinceDeSpawn
, which keeps track of how long the pickup has waited since it was last de-spawned (hidden).if
block that executes when the pickup has been spawned for longer than it should have been—if (m_SecondsSinceSpawn > m_SecondsToLive && m_Spawned)
. This block sets m_Spawned
to false
and resets m_SecondsSinceDeSpawn
to zero. Now block 2 will execute until it is time to spawn again.if
block that executes when the time to wait since de-spawning has exceeded the necessary wait time, and the pickup is not currently spawned—if (m_SecondsSinceDeSpawn > m_SecondsToWait && !m_Spawned)
. When this block is executed, it is time to spawn again and the spawn function is called.These four tests and code are what controls the hiding and showing of a pickup.
Finally, add the definition for the upgrade
function:
void Pickup::upgrade() { if (m_Type == 1) { m_Value += (HEALTH_START_VALUE * .5); } else { m_Value += (AMMO_START_VALUE * .5); } // Make them more frequent and last longer m_SecondsToLive += (START_SECONDS_TO_LIVE / 10); m_SecondsToWait -= (START_WAIT_TIME / 10); }
The upgrade
function tests for the type of pickup, either health or ammo, and then adds 50 percent of the (appropriate) starting value on to m_Value
. The next two lines after the if…else
blocks increase the amount of time the pickup will remain spawned and decrease the amount of time the player has to wait between spawns.
This function is called when the player chooses to levelup the pickups during the LEVELING_UP
state. Our Pickup
class is ready for use.
18.188.64.66