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

Object pooling

We have defined our first game object, which represents our player's spaceship, but all we can do is fly around the game screen. We need to allow our player to shoot a projectile. If we created a new projectile object every time a player shot a projectile, we would quickly fill up the WASM module's memory. What we need to do is create what is known as an object pool. Object pools are used to create objects with a fixed lifespan. Our projectiles only need to be alive long enough to either hit a target or travel a fixed distance before disappearing. If we create a set number of projectiles that is a little more than we need on the screen at one time, we can keep those objects in a pool in either an active or inactive state. When we need to launch a new projectile, we scan our object pool for an inactive one, then activate it and place it at the launch point. This way, we are not continually allocating and de-allocating memory to create our projectiles.

Let's go back to our game.hpp file and add a few class definitions right before the #endif macro:

class Projectile {
public:
const char* c_SpriteFile = "sprites/Projectile.png";
const int c_Width = 8;
const int c_Height = 8;
SDL_Texture *m_SpriteTexture;
bool m_Active;
const float c_Velocity = 6.0;
const float c_AliveTime = 2000;
float m_TTL;
float m_X;
float m_Y;
float m_VX;
float m_VY;

Projectile();
void Move();
void Render();
void Launch(float x, float y, float dx, float dy);
};

class ProjectilePool {
public:
std::vector<Projectile*> m_ProjectileList;
ProjectilePool();
~ProjectilePool();
void MoveProjectiles();
void RenderProjectiles();
Projectile* GetFreeProjectile();
};

extern ProjectilePool* projectile_pool;

So, we have defined all of our classes inside the game.hpp file. Right now, we have three classes: PlayerShip, Projectile, and ProjectilePool.

The PlayerShip class existed before, but we are adding some additional functionality to that class to allow us to fire projectiles. To allow for this new functionality, we are adding some new public attributes to our class definition:

public:
const char* c_SpriteFile = "sprites/Franchise.png";
const Uint32 c_MinLaunchTime = 300;
const int c_Width = 16;
const int c_Height = 16;
Uint32 m_LastLaunchTime;
SDL_Texture *m_SpriteTexture;

We moved a few of the values we had in #define macros directly into the class. The  c_SpriteFile constant is the name of the PNG file we will load to render our player's spaceship sprite. The c_MinLaunchTime constant is the minimum amount of time in milliseconds between two launches of projectiles. We have also defined the width and height of our sprite with the  c_Width and c_Height constants. This way, we can have different values for different object types. The m_LastLaunchTime attribute tracks the most recent projectile launch time in milliseconds. The sprite texture, which had previously been a global variable, will move into the attributes of the player's ship class.

After making our modifications to the PlayerShip class definition, we must add a class definition for two new classes. The first of these two classes is the Projectile class:

class Projectile {
public:
const char* c_SpriteFile = "sprites/Projectile.png";
const int c_Width = 8;
const int c_Height = 8;
const float c_Velocity = 6.0;
const float c_AliveTime = 2000;

SDL_Texture *m_SpriteTexture;
bool m_Active;
float m_TTL;
float m_X;
float m_Y;
float m_VX;
float m_VY;

Projectile();
void Move();
void Render();
void Launch(float x, float y, float dx, float dy);
};

This class represents the projectile game objects that will be shot by the player, and later the enemy spaceship. We start with several constants that define where we place our sprite in the virtual filesystem, as well as the width and height:

class Projectile {
public:
const char* c_SpriteFile = "sprites/Projectile.png";
const int c_Width = 8;
const int c_Height = 8;

The next attribute is m_SpriteTexture, which is a pointer to the SDL texture used to render our projectiles. We need a variable to tell our object pool that this game object is active. We have called that attribute m_Active. Next, we have a constant that defines how fast our projectile will move in pixels per second, called c_Velocity, and a constant that indicates how long the projectile will stay alive in milliseconds before self-destructing, called c_AliveTime.

The m_TTL variable is a time to live variable that tracks how many milliseconds remain until this projectile will change its m_Active variable to false and recycle itself back into the projectile pool. The m_X, m_Y, m_VX, and m_VY variables are used to track the x and y position and the x and y velocity of our projectile.

We then declare four functions for our projectile class:

Projectile();
void Move();
void Render();
void Launch(float x, float y, float dx, float dy);

The Projectile function is our class constructor. If our projectile is currently active, Move and Render will be called once per frame. The Move function will manage the movement of an active projectile and Render will manage drawing the projectile sprite to our HTML canvas element. The Launch function will be called from our PlayerShip class to make our ship launch a projectile in the direction the ship is facing.

The final class definition we must add to our game.hpp file is the ProjectilePool class:

class ProjectilePool {
public:
std::vector<Projectile*> m_ProjectileList;
ProjectilePool();
~ProjectilePool();
void MoveProjectiles();
void RenderProjectiles();
Projectile* GetFreeProjectile();
};

This class manages a pool of 10 projectiles stored inside a vector attribute, m_ProjectileList. The functions for this class include a constructor and destructor, MoveProjectiles, RenderProjectils, and GetFreeProjectile.

The MoveProjectiles() function loops over our projectile list calling the move function on any active projectile. The RenderProjectiles() function loops over our projectile list and renders to canvas any active projectile, and GetFreeProjectile returns the first projectile in our pool that is not active.