Hands-On Game Development with WebAssembly
上QQ阅读APP看书,第一时间看更新

Pooling the player's projectiles

Now that we have looked at the class definitions for our Projectile and ProjectilePool classes, we need to create a projectile.cpp file and a projectile_pool.cpp file to store the function code for those classes. Because this is in Chapter 6Game Objects and the Game Loop, I would recommend creating a new folder named Chapter06 to hold these files.  This code will do the work of pooling our projectiles, requesting an inactive projectile when we need one, and moving and rendering our active projectiles. First, let's look at the code we have in projectile.cpp:

#include "game.hpp"

Projectile::Projectile() {
m_Active = false;
m_X = 0.0;
m_Y = 0.0;
m_VX = 0.0;
m_VY = 0.0;

SDL_Surface *temp_surface = IMG_Load( c_SpriteFile );

if( !temp_surface ) {
printf("failed to load image: %s\n", IMG_GetError() );
return;
}

m_SpriteTexture = SDL_CreateTextureFromSurface( renderer,
temp_surface );

if( !m_SpriteTexture ) {
printf("failed to create texture: %s\n", IMG_GetError() );
return;
}

SDL_FreeSurface( temp_surface );
}

void Projectile::Move() {
m_X += m_VX;
m_Y += m_VY;
m_TTL -= diff_time;

if( m_TTL <= 0 ) {
m_Active = false;
m_TTL = 0;
}
}

void Projectile::Render() {
dest.x = m_X;
dest.y = m_Y;
dest.w = c_Width;
dest.h = c_Height;

int return_val = SDL_RenderCopy( renderer, m_SpriteTexture,
NULL, &dest );
if( return_val != 0 ) {
printf("SDL_Init failed: %s\n", SDL_GetError());
}
}

void Projectile::Launch(float x, float y, float dx, float dy) {
m_X = x;
m_Y = y;
m_VX = c_Velocity * dx;
m_VY = c_Velocity * dy;
m_TTL = c_AliveTime;
m_Active = true;
}

That is the code that deals with moving, rendering, and launching a single projectile. The first function declared here is the constructor:

Projectile::Projectile() {
m_Active = false;
m_X = 0.0;
m_Y = 0.0;
m_VX = 0.0;
m_VY = 0.0;

SDL_Surface *temp_surface = IMG_Load( c_SpriteFile );

if( !temp_surface ) {
printf("failed to load image: %s\n", IMG_GetError() );
return;
}

m_SpriteTexture = SDL_CreateTextureFromSurface( renderer,
temp_surface );

if( !m_SpriteTexture ) {
printf("failed to create texture: %s\n", IMG_GetError() );
return;
}
SDL_FreeSurface( temp_surface );
}

The primary concern of this constructor is to set the projectile to inactive and create an SDL texture that we will later use to render our sprite to the canvas element. After defining our constructor, we define our Move function:

void Projectile::Move() {
m_X += m_VX;
m_Y += m_VY;
m_TTL -= diff_time;
if( m_TTL <= 0 ) {
m_Active = false;
m_TTL = 0;
}
}

This function changes the x and y position of our projectile based on the velocity, and reduces the time to live of our projectile, setting it to inactive and recycling it into the projectile pool if it's time to live is less than or equal to zero. The next function we define is our Render function:

void Projectile::Render() {
dest.x = m_X;
dest.y = m_Y;
dest.w = c_Width;
dest.h = c_Height;

int return_val = SDL_RenderCopy( renderer, m_SpriteTexture,
NULL, &dest );

if( return_val != 0 ) {
printf("SDL_Init failed: %s\n", SDL_GetError());
}
}

This code is similar to the code we used to render our spaceship, so it should look pretty familiar to you. Our final projectile function is the Launch function:

void Projectile::Launch(float x, float y, float dx, float dy) {
m_X = x;
m_Y = y;
m_VX = c_Velocity * dx;
m_VY = c_Velocity * dy;
m_TTL = c_AliveTime;
m_Active = true;
}

This function is called from the PlayerShip class whenever the player presses the spacebar on the keyboard. The PlayerShip object will pass in the x and y coordinates of the player's ship, as well as the direction the ship is facing in the dx and dy parameters. These parameters are used to set the x and y coordinates for the projectile as well as the x and y velocity of the projectile. The game sets the time to live to the default alive time and then sets the object to active.

Now that we have fully defined our Projectile class, let's set the ProjectilePool class that will manage those projectiles. The following code will be in our projectile_pool.cpp file:

#include "game.hpp"

ProjectilePool::ProjectilePool() {
for( int i = 0; i < 10; i++ ) {
m_ProjectileList.push_back( new Projectile() );
}
}

ProjectilePool::~ProjectilePool() {
m_ProjectileList.clear();
}

void ProjectilePool::MoveProjectiles() {
Projectile* projectile;
std::vector<Projectile*>::iterator it;

for( it = m_ProjectileList.begin(); it != m_ProjectileList.end(); it++ ) {
projectile = *it;
if( projectile->m_Active ) {
projectile->Move();
}
}
}

void ProjectilePool::RenderProjectiles() {
Projectile* projectile;
std::vector<Projectile*>::iterator it;

for( it = m_ProjectileList.begin(); it != m_ProjectileList.end(); it++ ) {
projectile = *it;
if( projectile->m_Active ) {
projectile->Render();
}
}
}

Projectile* ProjectilePool::GetFreeProjectile() {
Projectile* projectile;
std::vector<Projectile*>::iterator it;

for( it = m_ProjectileList.begin(); it != m_ProjectileList.end(); it++ ) {
projectile = *it;
if( projectile->m_Active == false ) {
return projectile;
}
}
return NULL;
}

The first two functions are the constructor and destructor functions. These functions create and destroy the projectiles inside our list. The next function is the MoveProjectiles function, which loops through our m_ProjectileList looking for active projectiles and moving them. After that, we have a RenderProjectiles function, which is quite similar to our MoveProjectiles function. This function loops through our list calling the Render function on all active projectiles. The final function is the GetFreeProjectile function, which steps through m_ProjectileList looking for the first projectile that is not active in order to return it. Whenever we want to launch a projectile, we will need to call this function to find one that is not active.