Creating an enemy
So, now that we have a player ship that is shooting, we can work on adding an enemy ship. It will be similar to the PlayerShip class. Later, we will get into class inheritance so that we will not end up with a copied and pasted version of the same code, but for right now we will add a new class definition to our game.hpp file that is almost identical to our PlayerShip class:
enum FSM_STUB {
SHOOT = 0,
TURN_LEFT = 1,
TURN_RIGHT = 2,
ACCELERATE = 3,
DECELERATE = 4
};
class EnemyShip {
public:
const char* c_SpriteFile = "sprites/BirdOfAnger.png";
const Uint32 c_MinLaunchTime = 300;
const int c_Width = 16;
const int c_Height = 16;
const int c_AIStateTime = 2000;
Uint32 m_LastLaunchTime;
SDL_Texture *m_SpriteTexture;
FSM_STUB m_AIState;
int m_AIStateTTL;
float m_X;
float m_Y;
float m_Rotation;
float m_DX;
float m_DY;
float m_VX;
float m_VY;
EnemyShip();
void RotateLeft();
void RotateRight();
void Accelerate();
void Decelerate();
void CapVelocity();
void Move();
void Render();
void AIStub();
};
You will notice that before the EnemyShip class we defined an FSM_STUB enumeration. An enumeration is like a new data type that you can define inside your C or C++ code. We will be discussing artificial intelligence and finite state machines in another chapter, but right now we still want our enemy ship to do something, even if that something is not very intelligent. We created an FSM_STUB enumeration to define the things that our enemy ship can currently do. We have also created an AIStub inside our EnemyShip class that will act as a stand-in for future AI logic. The m_AIStateTTL integer attribute is a countdown timer to an AI state change. There is also a new constant called c_AIStateTime that has a value of 2000. That is the number of milliseconds our AI state will persist before it changes randomly.
We will create an enemy_ship.cpp file and add nine functions to it. The first function is our constructor, which is preceded by the #include of our game.hpp file:
#include "game.hpp"
EnemyShip::EnemyShip() {
m_X = 60.0;
m_Y = 50.0;
m_Rotation = PI;
m_DX = 0.0;
m_DY = 1.0;
m_VX = 0.0;
m_VY = 0.0;
m_LastLaunchTime = current_time;
SDL_Surface *temp_surface = IMG_Load( c_SpriteFile );
if( !temp_surface ) {
printf("failed to load image: %s\n", IMG_GetError() );
return;
}
else {
printf("success creating enemy ship surface\n");
}
m_SpriteTexture = SDL_CreateTextureFromSurface( renderer,
temp_surface );
if( !m_SpriteTexture ) {
printf("failed to create texture: %s\n", IMG_GetError() );
return;
}
else {
printf("success creating enemy ship texture\n");
}
SDL_FreeSurface( temp_surface );
}
After that, we have the functions RotateLeft and RotateRight which are used to turn the space ship:
void EnemyShip::RotateLeft() {
m_Rotation -= delta_time;
if( m_Rotation < 0.0 ) {
m_Rotation += TWO_PI;
}
m_DX = sin(m_Rotation);
m_DY = -cos(m_Rotation);
}
void EnemyShip::RotateRight() {
m_Rotation += delta_time;
if( m_Rotation >= TWO_PI ) {
m_Rotation -= TWO_PI;
}
m_DX = sin(m_Rotation);
m_DY = -cos(m_Rotation);
}
The functions Accelerate, Decelerate and CapVelocity are all used to modify the Enemy Ship's velocity.:
void EnemyShip::Accelerate() {
m_VX += m_DX * delta_time;
m_VY += m_DY * delta_time;
}
void EnemyShip::Decelerate() {
m_VX -= (m_DX * delta_time) / 2.0;
m_VY -= (m_DY * delta_time) / 2.0;
}
void EnemyShip::CapVelocity() {
float vel = sqrt( m_VX * m_VX + m_VY * m_VY );
if( vel > MAX_VELOCITY ) {
m_VX /= vel;
m_VY /= vel;
m_VX *= MAX_VELOCITY;
m_VY *= MAX_VELOCITY;
}
}
The next thing we add to the file is the Render function:
void EnemyShip::Render() {
dest.x = (int)m_X;
dest.y = (int)m_Y;
dest.w = c_Width;
dest.h = c_Height;
float degrees = (m_Rotation / PI) * 180.0;
int return_code = SDL_RenderCopyEx( renderer, m_SpriteTexture,
NULL, &dest,
degrees, NULL, SDL_FLIP_NONE );
if( return_code != 0 ) {
printf("failed to render image: %s\n", IMG_GetError() );
}
}
Finally, we add the Move and AIStub functions:
void EnemyShip::Move() {
AIStub();
if( m_AIState == TURN_LEFT ) {
RotateLeft();
}
if( m_AIState == TURN_RIGHT ) {
RotateRight();
}
if( m_AIState == ACCELERATE ) {
Accelerate();
}
if( m_AIState == DECELERATE ) {
Decelerate();
}
CapVelocity();
m_X += m_VX;
if( m_X > 320 ) {
m_X = -16;
}
else if( m_X < -16 ) {
m_X = 320;
}
m_Y += m_VY;
if( m_Y > 200 ) {
m_Y = -16;
}
else if( m_Y < -16 ) {
m_Y = 200;
}
if( m_AIState == SHOOT ) {
Projectile* projectile;
if( current_time - m_LastLaunchTime >= c_MinLaunchTime ) {
m_LastLaunchTime = current_time;
projectile = projectile_pool->GetFreeProjectile();
if( projectile != NULL ) {
projectile->Launch( m_X, m_Y, m_DX, m_DY );
}
}
}
}
void EnemyShip::AIStub() {
m_AIStateTTL -= diff_time;
if( m_AIStateTTL <= 0 ) {
// for now get a random AI state.
m_AIState = (FSM_STUB)(rand() % 5);
m_AIStateTTL = c_AIStateTime;
}
}
These functions are all the same as the functions defined in our player_ship.cpp file, except for the Move function. We have added a new function, AIStub. Here is the code in the AIStub function:
void EnemyShip::AIStub() {
m_AIStateTTL -= diff_time;
if( m_AIStateTTL <= 0 ) {
// for now get a random AI state.
m_AIState = (FSM_STUB)(rand() % 5);
m_AIStateTTL = c_AIStateTime;
}
}
This function is meant to be temporary. We will eventually define a real AI for our enemy spaceship. Right now, this function uses m_AIStateTTL to count down a fixed number of milliseconds until it reaches or goes below 0. At this point, it randomly sets a new AI state based on one of the values in the enumeration we defined earlier called FSM_STUB. We have also made some modifications to the Move() function that we created for the player ship:
void EnemyShip::Move() {
AIStub();
if( m_AIState == TURN_LEFT ) {
RotateLeft();
}
if( m_AIState == TURN_RIGHT ) {
RotateRight();
}
if( m_AIState == ACCELERATE ) {
Accelerate();
}
if( m_AIState == DECELERATE ) {
Decelerate();
}
CapVelocity();
m_X += m_VX;
if( m_X > 320 ) {
m_X = -16;
}
else if( m_X < -16 ) {
m_X = 320;
}
m_Y += m_VY;
if( m_Y > 200 ) {
m_Y = -16;
}
else if( m_Y < -16 ) {
m_Y = 200;
}
if( m_AIState == SHOOT ) {
Projectile* projectile;
if( current_time - m_LastLaunchTime >= c_MinLaunchTime ) {
m_LastLaunchTime = current_time;
projectile = projectile_pool->GetFreeProjectile();
if( projectile != NULL ) {
projectile->Launch( m_X, m_Y, m_DX, m_DY );
}
}
}
}
I have taken the code from our PlayerShip::Move function and made some modifications to it. At the beginning of this new function, we have added a call to the AIStub function. This function is a stand-in for our future AI. Instead of looking at our keyboard input as we did for the player ship, the enemy ship will look at the AI state and choose to rotate left, rotate right, accelerate, decelerate, or shoot. That is not real AI, it is just the ship doing random things, but it allows us to get an idea of what the ship will look like when it has real AI, and it will allow us to add more functionality later, such as collision detection.