Adding force fields

Currently, in our game, our spaceships are destroyed with a single collision. This ends up creating a game that is over very quickly. It would be nice to have a force field to prevent the ship's destruction when a collision is about to occur. This will also give our AI something else it can do in its bag of tricks. When the shields are up, there will be a little force-field animation surrounding the spaceship that is using it. There is a time limit to shield use. That will prevent the player or the AI from keeping the shield up for the entire game. While the shield is active, the color of the shields will transition from green to red. The closer the color gets to red, the closer the shields are to running out of power. Every time the shields get hit, the player or AI's shields will have additional time taken off them. We have already created the class definition inside of the game.hpp file. Here is what it looks like:

class Shield : public Collider {
public:
bool m_Active;
int m_ttl;
int m_NextFrame;
Uint32 m_CurrentFrame;
Ship* m_Ship;
SDL_Texture *m_SpriteTexture;

SDL_Rect m_src = {.x = 0, .y = 0, .w = 32, .h = 32 };
SDL_Rect m_dest = {.x = 0, .y = 0, .w = 32, .h = 32 };

Shield( Ship* ship, const char* sprite_file );

void Move();
void Render();
bool Activate();
void Deactivate();
};

To accompany this class definition, we will need a shield.cpp file, where we can define all of the functions used by this class. The first function we will define inside our shield.cpp file is the Shield constructor function:

Shield::Shield( Ship* ship, const char* sprite_string ) : Collider(12.0) {
m_Active = false;
m_ttl = 25500;
m_Ship = ship;
m_CurrentFrame = 0;
m_NextFrame = ms_per_frame;
SDL_Surface *temp_surface = IMG_Load( sprite_string );

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 Shield constructor function will call the Collider constructor function, with a radius of 12.0. That is a larger radius than the ship's radius. We will want this Collider to be hit instead of the ship, if the shields are active. The first block of code in this constructor function sets the starting values for the attributes of this class:

m_Active = false;
m_ttl = 25500;
m_Ship = ship;
m_CurrentFrame = 0;
m_NextFrame = ms_per_frame;

Notice that we set m_ttl to 25500. That is the time you can use the shield in milliseconds. That amounts to 25.5 seconds. I wanted it to be a multiple of 255, so that the green color will transition from 255 to 0, based on the time left.

Conversely, the red color will transition from 0 to 255, also based on the time left. After that, we create the shield's sprite texture in the standard way:

SDL_Surface *temp_surface = IMG_Load( sprite_string );

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 );

After the constructor, we need to define our Move function:

void Shield::Move() {
if( m_Active ) {
m_NextFrame -= diff_time;
m_ttl -= diff_time;

if( m_NextFrame <= 0 ) {
m_NextFrame = ms_per_frame;
m_CurrentFrame++;

if( m_CurrentFrame >= 6 ) {
m_CurrentFrame = 0;
}
}
if( m_ttl <= 0 ) {
m_Active = false;
}
}
}

If the shield is not active, this function does not do anything. If it is active, the m_ttl  parameter is decremented based on the number of milliseconds passed since the last frame. Then, we increment the current frame if the proper number of milliseconds has elapsed. If the shield's time left drops below 0, the shields are deactivated.

After we have defined our Move function, we will define our Render function:

void Shield::Render() {
if( m_Active ) {
int color_green = m_ttl / 100 + 1;
int color_red = 255 - color_green;
m_src.x = m_CurrentFrame * m_dest.w;
m_dest.x = m_Ship->m_Position.x;
m_dest.y = m_Ship->m_Position.y;

SDL_SetTextureColorMod(m_SpriteTexture,
color_red,
color_green,
0 );

SDL_RenderCopyEx( renderer, m_SpriteTexture,
&m_src, &m_dest,
RAD_TO_DEG(m_Ship->m_Rotation),
NULL, SDL_FLIP_NONE );
}
}

Like the Move function, the Render function does not do anything if the active flag is false. We calculate the colors based on the time left using the following formulas:

int color_green = m_ttl / 100 + 1;
int color_red = 255 - color_green;

That will smoothly transition the color of our shields from green to red. We use a call to SDL_SetTextureColorMod to set the sprite texture's color:

SDL_SetTextureColorMod(m_SpriteTexture,
color_red,
color_green,
0 );

Everything else in the Shield::Render function is pretty standard and should look very familiar by now.