Hey everybody,
A little over a month ago, I made a post asking for help with my Missile Command game that I was making with regards to getting the Cannon to angle properly, and I was very pleased with all of the support. It really lit a fire under me (even moreso before I worked up the courage to ask the first time).
Well, the game is not finished yet, but I have made a lot of progress since then ( it's very motivating ) and I've been able to work through the code and logic for shooting missiles from the cannon.
The missile logic is almost ready, but there is one small bug that I discovered last week that has been wracking my brain non-stop, so I'm posting a new topic here.
Here is an animated GIF of the missile behavior so far, in this shot, it's working as expected when you click the Left mouse button to fire one missile at a time:
https://i.imgur.com/WyM2oxi.gifv
But what I didn't expect to happen, was here, when you click the Left button to fire a new missile when the other missile is still in transit:
https://i.imgur.com/PFJTDV1.gifv
When the next missile is fired, the original missile changes course, re-angles itself and flies towards the next clicked spot, abandoning it's first clicked target, which is not what I wanted lol.
It's like the missiles turn into smart missiles all of a sudden. The good news is that they're being destroyed when they hit the spot anyways, so that's something.
So, for anyone reading this post, how can I make the previous missiles stay on their original target, even if I click the mouse to summon a new missile?
Each missile's path should be independent of each other.
This will also play into the logic for the upcoming falling Asteroid class I'll be adding, so solving this one for me will also allow me to make independent asteroids and I'm almost done the game! lol
Here's the code:
Game.h
#ifndef GAME_H_
#define GAME_H_
// Preprocessors
#include "SDL.h"
#include "SDL_image.h"
#include "SDL_ttf.h"
#include "SDL_mixer.h"
#include <iostream>
#include <sstream>
#include <stdio.h>
#include <math.h>
#include <cmath>
#include <vector>
// Inclusions for game objects
#include "Cannon.h"
#include "Missile.h"
#include "Ground.h"
#include "Silo.h"
#define FPS_DELAY 500
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
#define MAX_ROTATION 0
#define MIN_ROTATION 180
#define DEG_TO_RAD(deg) degrees * ( 3.141592653589793238 / 180)
#define RAD_TO_DEG(rad) radians * ( 180 / 3.141592653589793238 )
#define OVER_PI ( 180 / 3.141592653589793238 )
class Game
{
public:
Game();
~Game();
bool Init();
void Run();
private:
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Texture* texture;
// Timing
unsigned int lastTick, fpsTick, fps, frameCount;
// Test
float testX, testY;
/*
Fonts for testing
*/
TTF_Font* fontMouseX;
TTF_Font* fontMouseY;
TTF_Font* fontCannonAngle;
TTF_Font* fontFire;
TTF_Font* fontAmmo;
std::stringstream xValue;
std::stringstream yValue;
std::stringstream angleValue;
std::stringstream fireValue;
std::stringstream ammoValue;
SDL_Texture* xTexture;
SDL_Texture* yTexture;
SDL_Texture* aTexture;
SDL_Texture* fTexture;
SDL_Texture* ammoTexture;
int xWidth, xHeight, yWidth, yHeight, aWidth, aHeight;
int fWidth, fHeight, ammoWidth, ammoHeight;
/*
END
*/
// Game objects
Cannon* cannon; // The cannon, both the pipe and the base
std::vector<Missile*> missileVec; // A vector of missiles
Silo* silo;
Ground* ground; // The ground
SDL_Cursor* cursor; // The crosshair cursor
// Flag to check if the cannon
// is currently firing a missile
bool isFiring;
int clickedX;
int clickedY;
void Clean(); // Cleanup function
void Update(float delta); // Update game elements
void Render(float delta); // Render game elements
void NewGame(); // Start a new game
void SetCannonAngle( float vecX, float vecY); // Set the angle of cannon pipe
double SetMissileAngle( float vecX, float vecY); // Set the angle of missile
double ConvertDegrees(double radians); // Convert radians to degrees
double ConvertRadians(double degrees); // Convert degrees to radians
void ExplodeMissile(); // Explode the missile once it
// has reached the target
void CheckAsteroidCollisions(); // See if missile hits asteroid
void CheckSiloCollisions(); // See if asteroid hits silo
int GetSiloCount(); // Check if there are silos remaining
};
#endif // GAME_H_
Game.cpp
#include "Game.h"
Game::Game()
{
window = 0;
renderer = 0;
}
Game::~Game()
{
}
bool Game::Init()
{
// Initialize the SDL video and audio subsystems
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
// Create window
window = SDL_CreateWindow("Missile Command v1.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
800, 600, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
if(!window)
{
std::cout << "Error creating window: " << SDL_GetError() << std::endl;
return false;
}
// Create renderer
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if(!renderer)
{
std::cout << "Error creating renderer: " << SDL_GetError() << std::endl;
return false;
}
// Enable TTF loading
TTF_Init();
// Load the fonts
fontMouseX = TTF_OpenFont("lato.ttf", 14);
fontMouseY = TTF_OpenFont("lato.ttf", 14);
fontCannonAngle = TTF_OpenFont("lato.ttf", 14);
fontFire = TTF_OpenFont("lato.ttf", 14);
fontAmmo = TTF_OpenFont("lato.ttf", 14);
// Initialize resources
SDL_Surface* surface = IMG_Load("test.png");
texture = SDL_CreateTextureFromSurface(renderer,surface);
SDL_FreeSurface(surface);
// Set mouse cursor to crosshair
cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
SDL_SetCursor(cursor);
// Initialize timing
lastTick = SDL_GetTicks();
fpsTick = lastTick;
fps = 0; // Set starting FPS value
frameCount = 0; // Set starting frame count
testX = 0;
testY = 0;
return true;
}
void Game::Clean()
{
// Clean resources
SDL_DestroyTexture(texture);
SDL_FreeCursor(cursor);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
// Clean test data
SDL_DestroyTexture(xTexture);
SDL_DestroyTexture(yTexture);
SDL_DestroyTexture(aTexture);
SDL_DestroyTexture(fTexture);
SDL_DestroyTexture(ammoTexture);
}
void Game::Run()
{
// Create game objects
cannon = new Cannon(renderer);
ground = new Ground(renderer);
silo = new Silo(renderer);
// Start a new game
NewGame();
// Main loop
while(1)
{
// Event handler
SDL_Event e;
// If event is a QUIT event, stop the program
if(SDL_PollEvent(&e))
{
if(e.type == SDL_QUIT)
{
break;
}
// If a mouse button was pressed...
else if(e.type == SDL_MOUSEBUTTONDOWN)
{
// If the left mouse button was pressed,
// set the "fire" flag to TRUE and
// record x,y position of click
if(e.button.button == SDL_BUTTON_LEFT)
{
clickedX = e.button.x;
clickedY = e.button.y;
isFiring = true;
}
}
}
// Calculate delta and fps
unsigned int curTick = SDL_GetTicks();
float delta = (curTick - lastTick) / 1000.0f;
// Cap FPS delay to specific amount
if(curTick - fpsTick >= FPS_DELAY)
{
fps = frameCount * (1000.0f / (curTick - fpsTick));
fpsTick = curTick;
frameCount = 0;
//std::cout << "FPS: " << fps << std::endl;
char buf[100];
snprintf(buf,100,"Missile Command v1.0 (fps: %u)", fps);
SDL_SetWindowTitle(window,buf);
}
else
{
frameCount++;
}
lastTick = curTick;
// Update and render the game
Update(delta);
Render(delta);
}
delete cannon;
//missileVec.clear();
delete ground;
delete silo;
Clean();
// Close the fonts
TTF_CloseFont(fontMouseX);
TTF_CloseFont(fontMouseY);
TTF_CloseFont(fontCannonAngle);
TTF_CloseFont(fontFire);
TTF_CloseFont(fontAmmo);
TTF_Quit();
SDL_Quit();
}
void Game::NewGame()
{
}
void Game::Update(float delta)
{
// Game logic
// Input, get mouse position to determine
// the cannon pipe's angle for rendering
int mX, mY;
SDL_GetMouseState(&mX,&mY);
// Assign the vector to variables
float vectorX = mX - cannon->x;
float vectorY = mY - cannon->y;
// "Snap" the cannon angle to the angle between
// cannon and mouse
SetCannonAngle( vectorX, vectorY );
// If cannon is firing, create a missile and add
// it to the missile vector container, and set
// "isFiring" to false again
if( isFiring == true )
{
Missile* newMissile = new Missile(renderer, cannon->x, cannon->y);
missileVec.push_back(newMissile);
isFiring = false;
}
// If there are missiles in the vector container, set their direction
// angle, fire them and update the missile positions
for( int i = 0; i < missileVec.size(); i++ )
{
// Update the missile's trajectory upon travel
missileVec[i]->endX = clickedX;
missileVec[i]->endY = clickedY;
// Set a vector between the clicked location
// and the missile
float targetX = clickedX - missileVec[i]->x;
float targetY = clickedY - missileVec[i]->y;
// Set facing angle for missile
missileVec[i]->angle = SetMissileAngle(targetX,targetY);
// Update the missile's position
missileVec[i]->Update(delta);
// If the missile is triggered to be destroyed,
// destroy it
if( missileVec[i]->destroyNow == true )
{
// Remove the destroyed missile
// from the vector
missileVec.pop_back();
}
}
/*// If there are no more silos,
// start a new game
if(GetSiloCount() == 0)
{
NewGame();
}*/
// Check if the cannon has fired a missile
//cannon->missile->Update(delta, mX - cannon->x, mY - cannon->y);
/*
Test Data
*/
// Stream the X, Y, Angle and isFired values
xValue.str(""); // Clear the stream before piping the xValue
xValue << "X: " << mX;
yValue.str(""); // Clear the stream before piping the yValue
yValue << "Y: " << mY;
angleValue.str(""); // Clear the stream before piping the angleValue
angleValue << "Angle: " << cannon->angle;
fireValue.str(""); // Clear the stream before piping the fireValue
fireValue << "isFiring: " << std::boolalpha << isFiring;
ammoValue.str(""); // Clear the stream before piping the ammoValue
ammoValue << "Missiles in Vector: " << missileVec.size();
// Set font color to WHITE
SDL_Color textColor = {255,255,255};
//***************************************
// DEBUG - Prepare the fonts for testing
//***************************************
// Render the X-coordinate text
SDL_Surface* temp = TTF_RenderText_Solid( fontMouseX, xValue.str().c_str(), textColor );
xTexture = SDL_CreateTextureFromSurface( renderer, temp );
xWidth = temp->w;
xHeight = temp->h;
SDL_FreeSurface(temp);
// Render the Y-coordinate text
temp = TTF_RenderText_Solid( fontMouseY, yValue.str().c_str(), textColor );
yTexture = SDL_CreateTextureFromSurface( renderer, temp );
yWidth = temp->w;
yHeight = temp->h;
SDL_FreeSurface(temp);
// Render the angle text
temp = TTF_RenderText_Solid( fontCannonAngle, angleValue.str().c_str(), textColor );
aTexture = SDL_CreateTextureFromSurface( renderer, temp );
aWidth = temp->w;
aHeight = temp->h;
SDL_FreeSurface(temp);
// Render the isFiring text
temp = TTF_RenderText_Solid( fontFire, fireValue.str().c_str(), textColor );
fTexture = SDL_CreateTextureFromSurface( renderer, temp );
fWidth = temp->w;
fHeight = temp->h;
SDL_FreeSurface(temp);
// Render the missileVec.size() text
temp = TTF_RenderText_Solid( fontAmmo, ammoValue.str().c_str(), textColor );
ammoTexture = SDL_CreateTextureFromSurface( renderer, temp );
ammoWidth = temp->w;
ammoHeight = temp->h;
SDL_FreeSurface(temp);
}
void Game::SetCannonAngle(float vecX, float vecY)
{
// "Snap" cannon angle to angle where mouse
// cursor is located in order to track it
double theAngle = atan2(vecY, vecX);
// Pass radian angle to convert to degrees
theAngle = ConvertDegrees(theAngle);
// Convert degrees format from -180 to 180
// to 0 to 360
if( theAngle < 0 )
{
theAngle = 360 - (-theAngle);
}
if(theAngle > MAX_ROTATION && theAngle < 90)
{
cannon->angle = MAX_ROTATION;
}
else if(theAngle < MIN_ROTATION && theAngle > 90)
{
cannon->angle = MIN_ROTATION;
}
else
{
cannon->angle = theAngle;
}
}
double Game::SetMissileAngle(float vecX, float vecY)
{
// "Snap" missile angle to angle where mouse
// cursor is located in order to track it
double theAngle = atan2(vecY,vecX);
// Pass radian angle to convert to degrees
theAngle = ConvertDegrees(theAngle);
// Convert degrees format from -180 to 180
// to 0 to 360
if( theAngle < 0 )
{
theAngle = 360 - (-theAngle);
}
return theAngle;
}
double Game::ConvertDegrees(double radians)
{
// Convert the angle from radians to
// degrees and return the value
return RAD_TO_DEG(rad);
}
double Game::ConvertRadians(double degrees)
{
// Convert the angle from degrees to
// radians and return the value
return DEG_TO_RAD(deg);
}
int Game::GetSiloCount()
{
int siloCount = 0;
// If the current silo is still alive,
// the game is not over
if(silo->state)
{
siloCount = 1;
}
return siloCount;
}
void Game::Render(float delta)
{
// Clear the renderer
SDL_RenderClear(renderer);
// Render the game objects
cannon->Render(delta);
ground->Render(delta);
silo->Render(delta);
// If there is a missile in the vector,
// render it
for( int i = 0; i < missileVec.size(); i++ )
{
missileVec[i]->Render(delta);
}
/*
Setting source rectangles and then
rendering the x, y and angle fonts
*/
// For x
SDL_Rect rect;
rect.x = 20;
rect.y = 20;
rect.w = xWidth;
rect.h = xHeight;
SDL_RenderCopy(renderer, xTexture, 0, &rect);
// For y
rect.x = 20;
rect.y = 40;
rect.w = yWidth;
rect.h = yHeight;
SDL_RenderCopy(renderer, yTexture, 0, &rect);
// For angle
rect.x = 20;
rect.y = 60;
rect.w = aWidth;
rect.h = aHeight;
SDL_RenderCopy(renderer, aTexture, 0, &rect);
// For isFired
rect.x = 20;
rect.y = 80;
rect.w = fWidth;
rect.h = fHeight;
SDL_RenderCopy(renderer, fTexture, 0, &rect);
// For isFired
rect.x = 20;
rect.y = 100;
rect.w = ammoWidth;
rect.h = ammoHeight;
SDL_RenderCopy(renderer, ammoTexture, 0, &rect);
// Present the renderer to display
SDL_RenderPresent(renderer);
}
Missile.h
#ifndef MISSILE_H_INCLUDED
#define MISSILE_H_INCLUDED
#include "Entity.h"
#include <math.h>
class Missile : public Entity
{
public:
Missile(SDL_Renderer* renderer, float cX, float cY);
~Missile();
void Update(float delta);
void Render(float delta);
void Fire();
void Move();
// Starting and ending positions for missile
float startX, startY, endX, endY;
float directionX, directionY;
float distance, speed;
// The missile's angle
double angle;
// Flag to have missile destroyed
// when certain conditions are met
bool destroyNow;
private:
SDL_Texture* texture; // Texture for missile
SDL_Point center;
};
#endif // MISSILE_H_INCLUDED
Missile.cpp
#include "Missile.h"
Missile::Missile(SDL_Renderer* renderer, float cX, float cY) : Entity(renderer)
{
// Create missile texture
SDL_Surface* surface = IMG_Load("images/missile.png");
texture = SDL_CreateTextureFromSurface(renderer,surface);
SDL_FreeSurface(surface);
// Set angle variable default,
// width, height
angle = 0.0f;
width = 34;
height = 13;
// Set missile offset for positioning relative to cannon
float xOffset = 8;
// Set starting x,y position
x = cX + xOffset;
y = cY;
// Set the missile's starting x and y position
// and speed
startX = x;
startY = y;
speed = 150;
// Set the missile's center for rotation
center.x = ( width / 2 );
center.y = ( height / 2 );
// Set destruction flag to false
destroyNow = false;
}
Missile::~Missile()
{
// Clean resources
SDL_DestroyTexture(texture);
}
void Missile::Move()
{
// Determine the distance from the starting
// position to the ending position
distance = sqrt( pow(endX - startX, 2) + pow(endY - startY, 2));
// Determine direction of movement
directionX = (endX - startX) / distance;
directionY = (endY - startY) / distance;
}
void Missile::Update(float delta)
{
// Call move function
Move();
// If the missile is moving,
// update its position
this->x += directionX * speed * delta;
this->y += directionY * speed * delta;
// If the missile goes BEYOND the
// destination x and y (endX and endY),
// set the missile's position to endX
// and endY and stop movement
if( sqrt( pow( this->x - startX, 2) + pow( this->y - startY, 2) ) >= distance )
{
this->x = endX;
this->y = endY;
this->destroyNow = true;
}
}
void Missile::Render(float delta)
{
// Render the missile
SDL_Rect rect;
rect.x = (int)(x + 0.5f);
rect.y = (int)(y + 0.5f);
rect.w = width;
rect.h = height;
// Render the missile at given angle
SDL_RenderCopyEx(renderer,texture,0,&rect,angle,¢er,SDL_FLIP_NONE);
}
I appreciate any feedback, guys. Thanks in advance.
Also, let me know if you need more information about this post and I can provide it when I can.