Advertisement

Help with Missile Command

Started by July 18, 2017 04:28 AM
43 comments, last by Glydion 7 years, 4 months ago

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,&center,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.

 

screen1.png

screen2.png

I don't have SDL so can't try, but watching at the code I was wondering, where you write 


target = new Vector2(mX,mY);
    SetCannonAngle( target );

What is this angle set relative to? I mean, are you taking into consideration the origin of the cannon or you're taking the mouse input angle against the 0,0 coordinates of the screen?

Ihave no idea of what I'm talking about though probably :S

but I mean, 


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

I may be totally wrong but in that comment you mention the center axis of the cannon but I don't see it inside the function, unless I'm missing it

Advertisement

I also checked the function atan2, it says

 

Quote
Compute arc tangent with two parameters

Returns the principal value of the arc tangent of y/x, expressed in radians.

 

In your code though, the x comes before the y so the inputs are reversed

Hi MarcusAseth, thanks for your quick reply.

Hmm yes I noticed that too.  I use the bottom-center of the cannon when rendering the cannon itself, but not in the calculation of the angle.

So far I'm just passing the mouse X and Y and just making a Vector2 out of it.

Whoops, my bad.  Yes atan2 is supposed to have y before x, I'd better fix that, thanks for pointing that out.

I'm just thinking about how to take the cannon's center into account for the atan2 function.  Hmm, perhaps I subtract them?  I'll give that a shot and I'll post my update.

Oh one other question, do I need a second Vector2 to get the angle to work properly?

I suggest always sketch something that helps you reasoning :D  My sketch below

If your mouse has 350 then is effectively only 50 in the X, compared to the origin, so I suggest trying to pass atan2((mY-originY, mX - originX)) and see if it works :S

Edit: it won't work for the Y,  maybe ((originY) - (originY - mY)) or something like that for the Y

Untitled-1.thumb.png.c31417bca0141cd831bd31b74cba579b.png

Yeah I didn't think to sketch it out actually lol.

Pong and Snakes were pretty easy to just keep in my head (as they're simple in concept) but this Missile Command is the first game that's really challenged me.

I should know better.  Yes that makes so much more sense when it is drawn out in a sketch.

I'll give that a try in a bit, just got to do some laundry :) I appreciate your help Marcus.

Advertisement

Okay, just made those changes to the atan2 function and it appears that the cannon still behaves the same way.

I think we're getting closer, but I'll try some more tomorrow.  Thanks again for the assistance.

I'm going to sketch my thoughts out.

Well, could be also this


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

 

width is 32. You set the x position around 400, but then the center.x for the rotation pivot is just width/2, which means the center of the cannon is 16 while your cannon X position is near 400 ...doesn't looks legit :D

Hey Marcus,

Wow, I really should have thought these through.

Initially, when I set those values, I was just correlating them to where they would be on the screen.  Man I was way off.

I'm at work now but I'll redo that code when I get home later.  Great find, thanks for pointing that out.

Hello again,

I'm still working on getting it to do what I want, including grabbing graph paper and just plotting points.

I skipped over this part (when I really shouldn't have) and now I'm paying for it because of results that I can't seem to track.

I'm going to research arc tangents more because I feel like I'm missing something very important in this logic for the angle rotations.

So tonight, it's just me with pen and paper working through my logic some more (for the next game, I won't even touch a keyboard until I got this hammered down, better habit in the long run).

I appreciate your help from last night Marcus.  I'll keep at this and update later.

This topic is closed to new replies.

Advertisement