Jump to content
  • Advertisement
venonumuis

C++ [SDL] shoot bullet projectile toward mouse position by calculating angle

Recommended Posts

Hello programmers.
 
I have a problem for a few months that I left out to focus on something else because I did not have the solution but I really need it now.
 
I had already looked at the solution and what came back most often is to calculate the angle of the trajection with atan2 and then to normalize with cos for x and sin for y
 
I do it and it works only if my speed multiplier is greater than 60 or something like that, otherwise with a lower speed the projectile is still moving towards my cursor but with a multiple pixel offset of the exact cursor position and it's worse if I drop more my speed.
 
so my problem is that the projectile goes to my cursor only with a high speed, this is a problem for me because I want to create sports games where the ball does not necessarily go very fast for example in a basketball game, then are there other techniques to achieve what I want to do?
 
here is my code, I do not know the problem comes from a small synthax fault, of course there are other lines about collision and other things so sorry if it's too long but it's not complicated at all
#include <exception>
#include <string>
#include <iostream>
#include <SDL.h>
#include <time.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <fstream>

using namespace std;

#define PI 3.14159265
#define sgn(X) (((X)==0)?(0):(((X)<0)?(-1):(1)))
#define WIDTH_SCREEN 640
#define HEIGHT_SCREEN 480
#define TILE_SIZE 32
#define LARGEUR_MONDE 4000
#define HAUTEUR_MONDE 480
#define NB_TILES_X LARGEUR_MONDE/TILE_SIZE
#define NB_TILES_Y HAUTEUR_MONDE/TILE_SIZE
#define CACHE_SIZE 256

bool BoxCollision(SDL_Rect box1,SDL_Rect box2);
int TileMapCollision(SDL_Rect test,int tile_map[CACHE_SIZE][CACHE_SIZE],
                    int largeur_tile,int hauteur_tile,float vx, float vy);
int BoxCollisionVelocity(SDL_Rect box1,float vx,float vy,SDL_Rect box2,float vx2,float vy2);

struct Obj
{
    SDL_Rect r;
    int vx, vy, type;
};

int main( int argc, char * argv[] )
{
    unsigned int lastTime = 0, currentTime = 0;
    float dt=0.f,perso_vx=0.f,perso_vy=0.f,v=-1;
    int tile_type=0,cv=0;
    bool save_map=0,left_m=0,left=0,right=0,up=0,down=0;
    SDL_Rect r = {WIDTH_SCREEN/2-r.w/2,HEIGHT_SCREEN/2-r.h/2,32,32};
    SDL_Rect bullet = {-100,-100,12,12};
    SDL_Rect platform1 = {1000,200,128,32};
    SDL_Rect camera = {r.x-(WIDTH_SCREEN/2-r.w/2),r.y-(HEIGHT_SCREEN/2-r.h/2),WIDTH_SCREEN,HEIGHT_SCREEN};
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window *window;
    SDL_Renderer *renderer;
    SDL_CreateWindowAndRenderer(WIDTH_SCREEN,HEIGHT_SCREEN,SDL_WINDOW_RESIZABLE,&window,&renderer);
    SDL_Texture* bgd1 = SDL_CreateTexture(renderer,SDL_PIXELFORMAT_RGBA8888,
        SDL_TEXTUREACCESS_TARGET,LARGEUR_MONDE,HAUTEUR_MONDE);

    while(1) {
        SDL_Event e;
        lastTime = currentTime;
        currentTime = SDL_GetTicks();
        dt = (currentTime - lastTime)/1000.0f;
        while (SDL_PollEvent(&e)) {
            if (e.type==SDL_QUIT) return 0;
            if (e.type==SDL_KEYDOWN) {
                switch (e.key.keysym.sym){
                    case SDLK_LEFT: left = true; break;
                    case SDLK_RIGHT: right = true; break;
                    case SDLK_UP: up = true; break;
                    case SDLK_DOWN: down = true; break;
                    case SDLK_c: save_map=true; break;
                    case SDLK_KP_1: tile_type=1; break;
                    case SDLK_KP_2: tile_type=2; break;
                    case SDLK_KP_3: tile_type=3; break;
                    case SDLK_KP_4: tile_type=4; break;
                    case SDLK_KP_5: tile_type=5; break;
                }
            }
            if (e.type==SDL_KEYUP) {
                switch (e.key.keysym.sym){
                    case SDLK_LEFT: left = false; break;
                    case SDLK_RIGHT: right = false; break;
                    case SDLK_UP: up = false; break;
                    case SDLK_DOWN: down = false; break;
                    case SDLK_c: save_map=false; break;
                }
            }
            if (e.type==SDL_MOUSEBUTTONDOWN) {
                switch(e.button.button){
                    case SDL_BUTTON_LEFT: left_m=true; break;
                }
            }
            if (e.type==SDL_MOUSEBUTTONUP) {
                switch(e.button.button){
                    //case SDL_BUTTON_LEFT: left_m=false; break;
                }
            }
        }
        //r.x += perso_vx;
        //r.y += perso_vy;
        platform1.x+=v;
        cv+=v;
        if (cv<=-1000||cv>=1000) v=-v;
        int x, y;   
        SDL_GetMouseState( &x, &y );
        const Uint8 *state = SDL_GetKeyboardState(NULL);

        if (left_m)
        {
            bullet.x += 10*(cos(atan2(y-200, x-300)));
            bullet.y += 10*(sin(atan2(y-200, x-300)));
        }
        if (bullet.x<=camera.x||bullet.x>=camera.x+WIDTH_SCREEN||
            bullet.y<=camera.y||bullet.y>=camera.y+HEIGHT_SCREEN)
        {
            left_m=0;
            bullet.x=300;
            bullet.y=200;
        }
        cout<<10*cos(atan2(y-200, x-300))<<endl;

        if (state[SDL_SCANCODE_LEFT]) perso_vx = -4;
        if (state[SDL_SCANCODE_RIGHT]) perso_vx = 4;
        if (!state[SDL_SCANCODE_RIGHT]&&!state[SDL_SCANCODE_LEFT]) perso_vx = 0;
        if (state[SDL_SCANCODE_UP]) perso_vy = -4;
        else if (state[SDL_SCANCODE_DOWN]) perso_vy = 4;
        else perso_vy = 0;
        
        if (!BoxCollisionVelocity(r,perso_vx,0,platform1,0,0)) r.x+=perso_vx;
        else// if (perso_vx>=0&&v<0) r.x+=v;
            for (int i = 0; i < abs(perso_vx); ++i)
                if (!BoxCollisionVelocity(r,sgn(perso_vx),0,platform1,0,0)) r.x+=sgn(perso_vx);

        if (!BoxCollisionVelocity(r,0,perso_vy,platform1,0,0)) r.y+=perso_vy;
        else
            for (int i = 0; i < abs(perso_vy); ++i)
                if (!BoxCollisionVelocity(r,0,sgn(perso_vy),platform1,0,0)) r.y+=sgn(perso_vy);

        camera.x = r.x-(WIDTH_SCREEN/2-r.w/2);
        camera.y = r.y-(HEIGHT_SCREEN/2-r.h/2);
        if (camera.x <= 0) camera.x = 0;
        if (camera.x + WIDTH_SCREEN >= LARGEUR_MONDE) camera.x = LARGEUR_MONDE - WIDTH_SCREEN;
        if (camera.y <= 0) camera.y = 0;
        if (camera.y + HEIGHT_SCREEN >= HAUTEUR_MONDE) camera.y = HAUTEUR_MONDE - HEIGHT_SCREEN;

        SDL_SetRenderTarget(renderer, bgd1);
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);
        SDL_SetRenderDrawColor(renderer, 200, 100, 50, 255);
        SDL_RenderFillRect(renderer, &platform1);
        SDL_RenderFillRect(renderer, &bullet);
        SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
        SDL_RenderFillRect(renderer, &r);
        SDL_SetRenderTarget(renderer, NULL);
        SDL_RenderCopy(renderer, bgd1, &camera, NULL);
        SDL_RenderPresent(renderer);
        SDL_Delay(10);
    }
    SDL_DestroyTexture(bgd1);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

int BoxCollisionVelocity(SDL_Rect box1,float vx,float vy,SDL_Rect box2,float vx2,float vy2)
{
    SDL_Rect r1 = {box1.x+vx,box1.y+vy,box1.w,box1.h};
    SDL_Rect r2 = {box2.x+vx2,box2.y+vy2,box2.w,box2.h};
    if (BoxCollision(r1,r2))
        return true;
    else
        return false;
}

int TileMapCollision(SDL_Rect test,int tile_map[CACHE_SIZE][CACHE_SIZE],
    int largeur_tile,int hauteur_tile,float vx,float vy)
{
    int xmin = (test.x+vx)/largeur_tile;
    int xmax = (test.x+test.w-1+vx)/largeur_tile;
    int ymin = (test.y+vy)/hauteur_tile;
    int ymax = (test.y+test.h-1+vy)/hauteur_tile;

    for (int i = ymin; i <= ymax; i++)
    {
        for (int j = xmin; j <= xmax; j++)
        {
            if(tile_map[i][j]>1) return 1;
        }
    }
    return 0;
}

bool BoxCollision(SDL_Rect box1, SDL_Rect box2)
{
    if((box2.x >= box1.x + box1.w)      // trop à droite
    || (box2.x + box2.w <= box1.x)      // trop à gauche
    || (box2.y >= box1.y + box1.h)      // trop en bas
    || (box2.y + box2.h <= box1.y))     // trop en haut
        return false;
    else
        return true;
}

 

Edited by venonumuis

Share this post


Link to post
Share on other sites
Advertisement

Just a quick guess (I haven't properly read your code) but it sounds like it might be an integer rounding issue. If your destination is e.g. at 190, 100 and you move 1/100th of the way each time, converted to integers you might either move 1, 1 pixel at a time or 2, 1 (depending on how you round) so you would end up at 200, 100 or 100, 100. If you move faster it will be more accurate (e.g. 19, 10 per move) and slower, less accurate.

Share this post


Link to post
Share on other sites

I want to point out that using atan2 followed by cos and sin is needlessly complicated, as it’s often the case when you use angles. You don’t need angles.

Here’s how you normalize a vector:

float inv_length = 1.0f / sqrt(x*x + y*y);

x *= inv_length;
y *= inv_length;

 

Share this post


Link to post
Share on other sites
1 hour ago, lawnjelly said:

Just a quick guess (I haven't properly read your code) but it sounds like it might be an integer rounding issue. If your destination is e.g. at 190, 100 and you move 1/100th of the way each time, converted to integers you might either move 1, 1 pixel at a time or 2, 1 (depending on how you round) so you would end up at 200, 100 or 100, 100. If you move faster it will be more accurate (e.g. 19, 10 per move) and slower, less accurate.

A quick scan of your code looks like this is the case:

    SDL_Rect bullet = {-100,-100,12,12}

and:

            bullet.x += 10*(cos(atan2(y-200, x-300)));
            bullet.y += 10*(sin(atan2(y-200, x-300)))

You should instead use something like:

    float bullet_x;
    float bullet_y;

To track your bullet position. Update the bullet position using float (as per alvaro). Then when you want to actually compare the bullet to other SDL stuff:

    SDL_Rect temp_bullet = {(int) bullet_x, (int) bullet_y, 12, 12};

Hope that helps!

Share this post


Link to post
Share on other sites
1 hour ago, lawnjelly said:

Just a quick guess (I haven't properly read your code) but it sounds like it might be an integer rounding issue. If your destination is e.g. at 190, 100 and you move 1/100th of the way each time, converted to integers you might either move 1, 1 pixel at a time or 2, 1 (depending on how you round) so you would end up at 200, 100 or 100, 100. If you move faster it will be more accurate (e.g. 19, 10 per move) and slower, less accurate.

sorry for my code but i use this basically

bullet.x += 10*(cos(atan2(y-200, x-300)));
bullet.y += 10*(sin(atan2(y-200, x-300)));

what you said is actually the only explanation of the offset when i use atan2 and cos / sin, i thought that for my projectile to go to my cursor with a low speed without declination it must this projectile follow another projectile with a high speed to record the trajectory and follow it, I do not know if it is the right solution

44 minutes ago, gdunbar said:

A quick scan of your code looks like this is the case:


    SDL_Rect bullet = {-100,-100,12,12}

and:


            bullet.x += 10*(cos(atan2(y-200, x-300)));
            bullet.y += 10*(sin(atan2(y-200, x-300)))

You should instead use something like:


    float bullet_x;
    float bullet_y;

To track your bullet position. Update the bullet position using float (as per alvaro). Then when you want to actually compare the bullet to other SDL stuff:


    SDL_Rect temp_bullet = {(int) bullet_x, (int) bullet_y, 12, 12};

Hope that helps!

thanks for the advice, i write this quicky after seen your reply, but i have already this "offset" with a low speed, and sdl_rect use always integers cause it move by pixels so i dont know if i  still use casts ?

bullet.x += (int)((float)10*(cos(atan2(y-200, x-300))));
bullet.y += (int)((float)10*(sin(atan2(y-200, x-300))));

 

1 hour ago, alvaro said:

I want to point out that using atan2 followed by cos and sin is needlessly complicated, as it’s often the case when you use angles. You don’t need angles.

Here’s how you normalize a vector:


float inv_length = 1.0f / sqrt(x*x + y*y);

x *= inv_length;
y *= inv_length;

 

thanks for this, what is 1.0f and this division ?

my projectile dont move

bullet.x *= 1.0f / sqrt(pow((x-300),2) + pow((y-200),2));
bullet.y *= 1.0f / sqrt(pow((x-300),2) + pow((y-200),2));

i know already this too

double rx = (x-325);
double ry = (y-245);
double n = sqrt(pow((rx),2)+pow((ry),2));
double sx = rx/n;
double sy = ry/n;

but i had this same offset with it at low speed

Edited by venonumuis

Share this post


Link to post
Share on other sites

Part of the problem for us is that your code shown includes too much irrelevant stuff to easily work out what is happening, or intended (for me, at least! :) ), and your explanation of what is intended is not clear. If you are reading the mouse x, y each loop and using the bullet start position each loop to determine the angle, you will get problems.

Are you trying to have a bullet that has a constant direction? Or are you trying to create a homing missile?

For a constant direction you could for example RECORD the target position when fired, then calculate the move towards this target each loop:

e.g.

// calculate offset from bullet to target
// Where vector 2 is using floats rather than ints
Vector2 ptOffset = ptTarget - ptBullet;

// make offset length 1
ptOffset.Normalize();

// make length of offset equal to desired move length
ptOffset *= fMoveLength;

// probably have some logic for reaching target
// ....

// move the bullet towards target
ptBullet += ptOffset;

A similar approach to this might also work for a moving target (but usually you'd use something a bit more complex to limit bullet direction changes etc).

You could also probably more appropriately use this kind of thing for a constant direction bullet:

class CBullet
{
public:
  Vector2 m_ptPos;
  Vector2 m_ptVelocity;
  
  void Fire(const Vector2 &ptStart, const Vector2 &ptTarget)
  {
    m_ptPos = ptStart;
    m_ptVelocity = ptTarget - ptStart;
    m_ptVelocity.Normalize();
    m_ptVelocity *= SPEED;
  }
  
  void Update()
  {
    m_ptPos += m_ptVelocity;
  }
  
};

It seems like you are using a different x, y target position each time: (more like a homing missile)

SDL_GetMouseState( &x, &y );

In which case any direction for a bullet move should be based on the current bullet position, not the bullet start position.

Share this post


Link to post
Share on other sites

there is a most basic version

#include <iostream>
#include <SDL.h>
#include <math.h>

using namespace std;

#define PI 3.14159265
#define WIDTH_SCREEN 640
#define HEIGHT_SCREEN 480

int main( int argc, char * argv[] )
{
    int speed = 60;
    bool left_m=0;
    SDL_Rect bullet = {300,200,12,12};
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window *window;
    SDL_Renderer *renderer;
    SDL_CreateWindowAndRenderer(WIDTH_SCREEN,HEIGHT_SCREEN,SDL_WINDOW_RESIZABLE,&window,&renderer);
    SDL_Texture* bgd1 = SDL_CreateTexture(renderer,SDL_PIXELFORMAT_RGBA8888,
        SDL_TEXTUREACCESS_TARGET,WIDTH_SCREEN,HEIGHT_SCREEN);

    while(1) {
        SDL_Event e;
        while (SDL_PollEvent(&e)) {
            if (e.type==SDL_QUIT) return 0;
            if (e.type==SDL_MOUSEBUTTONDOWN) {
                switch(e.button.button){
                    case SDL_BUTTON_LEFT: left_m=true; break;}}}
        int x, y;
        SDL_GetMouseState( &x, &y );
        const Uint8 *state = SDL_GetKeyboardState(NULL);

        if (left_m)
        {
            bullet.x += speed*(cos(atan2(y-200, x-300)));
            bullet.y += speed*(sin(atan2(y-200, x-300)));
        }
        if (bullet.x<=0||bullet.x>=WIDTH_SCREEN||
            bullet.y<=0||bullet.y>=HEIGHT_SCREEN)
        {
            left_m=0;
            bullet.x=300;
            bullet.y=200;
        }

        SDL_SetRenderTarget(renderer, bgd1);
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);
        SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
        SDL_RenderFillRect(renderer, &bullet);
        SDL_SetRenderTarget(renderer, NULL);
        SDL_RenderCopy(renderer, bgd1, NULL, NULL);
        SDL_RenderPresent(renderer);
        SDL_Delay(10);
    }
    SDL_DestroyTexture(bgd1);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

like i said i copied what i founded in several forums who uses atan2 etc for shoot projectile toward mouse and it follow exact mouse cursor only if i increase with high speed, if not i have offset of several pixel with a low speed multiplier and this is a problem for me because i want to create sports games, exemple : if speed=10 there will be an offset but if speed=60 no.

i understand your code but i dont have .Normalize(); in sdl or c++ so i cant try it, there is a equivalent ?

with your code the projectile follow cursor with a low speed ?

thanks for the reply

Edited by venonumuis

Share this post


Link to post
Share on other sites

Copying and pasting from examples can be okay but afterwards you should usually go through it to make sure you fully understand what it is doing (of course, we all copy and paste code sometimes! :) ). Yes, when correct, the code should work whatever the speed of the projectile.

Here are some tutorials on 2d vector math for games, they will explain it far better than I can, e.g.

http://blog.wolfire.com/2009/07/linear-algebra-for-game-developers-part-1/

https://www.gamefromscratch.com/page/Game-Development-Math-Recipes.aspx

https://www.gamefromscratch.com/post/2012/12/17/Gamedev-Math-Handling-sprite-based-shooting.aspx

https://www.google.com/search?q=2d+vector+math+games&oq=2d+vector+math+games

The code that alvaro posted is basically how to normalize a vector, although you should usually do a check for zero length vector to prevent divide by zero, which will cause an error. Normalizing (aka Unitizing) a vector takes a vector in any direction (except zero length vector, because the direction is undefined) and resizes it so that the length in that direction is 1.0. It is a very common operation in games.

Share this post


Link to post
Share on other sites
15 hours ago, lawnjelly said:

Copying and pasting from examples can be okay but afterwards you should usually go through it to make sure you fully understand what it is doing (of course, we all copy and paste code sometimes! :) ). Yes, when correct, the code should work whatever the speed of the projectile.

 Here are some tutorials on 2d vector math for games, they will explain it far better than I can, e.g.

 http://blog.wolfire.com/2009/07/linear-algebra-for-game-developers-part-1/

https://www.gamefromscratch.com/page/Game-Development-Math-Recipes.aspx

https://www.gamefromscratch.com/post/2012/12/17/Gamedev-Math-Handling-sprite-based-shooting.aspx

https://www.google.com/search?q=2d+vector+math+games&oq=2d+vector+math+games

The code that alvaro posted is basically how to normalize a vector, although you should usually do a check for zero length vector to prevent divide by zero, which will cause an error. Normalizing (aka Unitizing) a vector takes a vector in any direction (except zero length vector, because the direction is undefined) and resizes it so that the length in that direction is 1.0. It is a very common operation in games.

ok thanks for the links, this is big so i'll come back when i understand better vector knowledge and get some help

Edited by venonumuis

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!