Hello everyone.
I'm fairly new to game programming and I've started the beginner's path to learning how to do game programming incrementally, as suggested by Alpha_ProgDes (hope I spelled his name right) in his fantastic article about Game Programming for Beginners and the 10 requisite games that you absolutely need to know and finish in order to get real game programming experience.
After about 4 or so months of trial and error and growing pains, I was able to complete a working (but not perfect with lots of bad practice, global variables, etc.) copy of Pong, Snakes and Breakout. Right now I'm just doing enough to get the games to work (passable) and then later, I will go and re-do them after getting better at this.
Currently, I'm on the fourth game of this list, which is Missile Command, and I'm only a little bit into the game and I'm now stuck on the first 'logic' aspect of just getting the cannon to rotate and change angle based on the position of the mouse.
No matter what I've tried, I just can't seem to get the cannon to angle properly as per the precise location of the mouse.
When I run the game, the cannon will move arbitrarily towards the right side, but never turn and angle to the left, even if my mouse is on the left-hand side of the game screen.
I've created some test output for the x and y coordinate of the mouse position as well as the cannon's angle but it hasn't helped me that much yet.
If it is allowed in these forums, I can record a small video that shows the output and paste it here as a link. But I'm not sure.
Here is my code for main as well as the Game, Vector2 and Cannon classes (I have other classes but I told myself I would only work on them once I've solved this particular logic problem):
main.h
#ifndef MAIN_H_
#define MAIN_H_
#include "game.h"
int main(int argc, char* argv[]);
#endif // MAIN_H_
main.cpp
//******************************************
// Missile Command - v1.0.0
// By: Pedro Miranda Jr. (Glydion)
//
// Start Date: Wednesday May 31, 2017
// Finish Date:
//******************************************
#include "main.h"
int main(int argc, char* argv[])
{
// Create the game object
Game* game = new Game();
// Initialize and run the game
if(game->Init())
{
game->Run();
}
// Clean up
delete game;
return 0;
}
Vector2.h
#ifndef VECTOR2_H_INCLUDED
#define VECTOR2_H_INCLUDED
#include <math.h>
class Vector2
{
public:
Vector2();
Vector2(float x, float y);
~Vector2();
float x, y;
};
#endif // VECTOR2_H_INCLUDED
Vector2.cpp
#include "Vector2.h"
Vector2::Vector2()
{
}
Vector2::Vector2(float x, float y)
{
this->x = x;
this->y = y;
}
Vector2::~Vector2()
{
}
Cannon.h
#ifndef CANNON_H_INCLUDED
#define CANNON_H_INCLUDED
#include "Entity.h"
#include <math.h>
// Define a missile speed in pixels per second
/*const float MISSILE_SPEED = 550;
class Missile : public Entity
{
public:
Missile(SDL_Renderer* renderer);
~Missile();
void Update(float delta);
void Render(float delta);
void SetDirection(float dirX, float dirY);
float dirX, dirY;
bool state; // Check if the missile is alive
private:
SDL_Texture* texture;
};
*/
class Cannon : public Entity
{
public:
Cannon(SDL_Renderer* renderer);
~Cannon();
// x, y, width and height for base piece
float baseX, baseY, baseWidth, baseHeight;
// cannon pipe angle
double angle;
// Missiles that the cannon fires
//Missile missile;
// Fire a missile
//void FireMissile();
void Update(float delta);
void Render(float delta);
float GetCenterX();
float GetCenterY();
private:
SDL_Texture* cannonTexture; // Texture for cannon pipe
SDL_Texture* baseTexture; // Texture for cannon base
SDL_Point center;
};
#endif // CANNON_H_INCLUDED
Cannon.cpp
#include "Cannon.h"
//******************************
// Missile function definitions
//******************************
//******************************
// Cannon function definitions
//******************************
Cannon::Cannon(SDL_Renderer* renderer) : Entity(renderer)
{
// Create cannon pipe texture
SDL_Surface* surface = IMG_Load("images/cannon-top.png");
cannonTexture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
// Create cannon base texture
surface = IMG_Load("images/cannon-base.png");
baseTexture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
// Set the cannon pipe's dimensions
width = 32;
height = 48;
// Set the cannon base's dimensions
baseWidth = 64;
baseHeight = 24;
// Set the cannon pipe's vertical position
x = 417 - width;
y = 505;
// Set the cannon base's vertical position
baseX = 432 - baseWidth;
baseY = 545;
// Set the cannon pipe's rotation pivot point
center.x = (width / 2);
center.y = height;
// Set the cannon pipe's angle
angle = 0.0f;
}
Cannon::~Cannon()
{
// Clean resources
SDL_DestroyTexture(cannonTexture);
SDL_DestroyTexture(baseTexture);
}
void Cannon::Update(float delta)
{
}
void Cannon::Render(float delta)
{
// Set source rectangle for cannon pipe
SDL_Rect rect;
rect.x = (int)(x + 0.5f);
rect.y = (int)(y + 0.5f);
rect.w = width;
rect.h = height;
// Render the cannon pipe
SDL_RenderCopyEx(renderer,cannonTexture,0,&rect,angle,¢er,SDL_FLIP_NONE);
// Set source rectangle for cannon base
rect.x = (int)(baseX + 0.5f);
rect.y = (int)(baseY + 0.5f);
rect.w = baseWidth;
rect.h = baseHeight;
// Render the cannon base
SDL_RenderCopy(renderer,baseTexture,0,&rect);
}
float Cannon::GetCenterX()
{
return center.x;
}
float Cannon::GetCenterY()
{
return center.y;
}
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>
// Inclusions for game objects
#include "Cannon.h"
#include "Vector2.h"
#include "Ground.h"
#include "Silo.h"
#define FPS_DELAY 500
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;
std::stringstream xValue;
std::stringstream yValue;
std::stringstream angleValue;
SDL_Texture* xTexture;
SDL_Texture* yTexture;
SDL_Texture* aTexture;
int xWidth, xHeight, yWidth, yHeight, aWidth, aHeight;
/*
END
*/
// Game objects
Cannon* cannon; // The cannon, both the pipe and the base
Silo* silo;
Ground* ground; // The ground
SDL_Cursor* cursor; // The crosshair cursor
Vector2* target; // The vector of the cursor
void Clean(); // Cleanup function
void Update(float delta); // Update game elements
void Render(float delta); // Render game elements
void NewGame(); // Start a new game
double ConvertDegrees(double radians); // Convert radians to degrees
void SetCannonAngle(Vector2* theVector); // Set the angle of the cannon pipe
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);
// 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);
}
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;
}
}
// 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;
delete ground;
delete silo;
Clean();
// Close the fonts
TTF_CloseFont(fontMouseX);
TTF_CloseFont(fontMouseY);
TTF_CloseFont(fontCannonAngle);
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);
// Create the target vector, normalize it
// and pass it to the cannon to set the
// current angle
target = new Vector2(mX,mY);
SetCannonAngle( target );
// If there are no more silos,
// start a new game
if(GetSiloCount() == 0)
{
NewGame();
}
// Update the cannon's angle
cannon->Update(delta);
/*
Test Data
*/
// Stream the X, Y and Angle 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;
// Set font color to WHITE
SDL_Color textColor = {255,255,255};
//***************************************
// DEBUG - Prepare the fonts for testing
//***************************************
// Render the X-coordinate
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
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
temp = TTF_RenderText_Solid( fontCannonAngle, angleValue.str().c_str(), textColor );
aTexture = SDL_CreateTextureFromSurface( renderer, temp );
aWidth = temp->w;
aHeight = temp->h;
SDL_FreeSurface(temp);
}
void Game::SetCannonAngle( Vector2* theVector )
{
// Calculate arc tangent between the
// the mouse's position and the center
// axis of the cannon and convert to degrees
double theAngle = atan2( theVector->x, theVector->y );
theAngle = ConvertDegrees(theAngle);
cannon->angle = theAngle;
}
double Game::ConvertDegrees(double radians)
{
// Convert the angle from radians to
// degrees and return the value
return radians * ( 180 / 3.141592653589793238 );
}
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);
/*
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);
// Present the renderer to display
SDL_RenderPresent(renderer);
}
That's the code.
Attached to this post is a set of stills that show that the cannon is not angling properly towards the crosshair cursor, either on the left side or the right side of the video screen.
Any help would be appreciated, folks.
Thank you for your time.