Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Thrust, ship movement 'Asteroids' style

This thread is locked; no one can reply to it. rss feed Print
Thrust, ship movement 'Asteroids' style
krazak
Member #9,908
June 2008

As part of my efforts to learn C++ I decided to attempt an asteroids clone of sorts.
However, I'm stuck on movement! I can rotate the ship, draw the transperant sprite and move, however with all the different things I've tried, the ship never travels in the direction I'm pointing! It gets worse the more I rotate it, so I'm thinking it may be a problem when converting from degrees/radians (I'm not even sure which I'm using) to allegro fixed point. The current maths to calc thrust might be wrong, but it's just the last thing I tried out of 10+ attempts. I've been stuck on this for two days :(.

Anyways, here is the code(No doubt it is messy as hell, I figured once I got a working model, I would optimise/put things in structs/comment/etc):

1#include <png.h>
2#include <allegro.h> // includes allegro so I can use graphics
3#include <loadpng.h>
4#include <math.h>
5 
6int main(int argc, char *argv[]) // main function (duh.) ignore the integers they're allegro specific
7{
8 allegro_init(); // initializes allegro
9 install_keyboard(); // installs keyboard for use
10
11 set_alpha_blender();
12 set_color_depth(32); // sets the colour pallete
13 set_gfx_mode(GFX_AUTODETECT_WINDOWED, 1024,768,0,0); // makes the size of the window and some other options
14
15 BITMAP * bg = load_bitmap("untitled.bmp", NULL);
16 BITMAP * starbmp = load_bitmap("untitled.bmp", NULL);
17 BITMAP * ship = load_png("ship.png", NULL); //loads a bitmap named "ship" into my code
18 BITMAP * buffer = create_bitmap(SCREEN_W,SCREEN_H); // creates a blank bitmap called "buffer"
19 BITMAP * temp = create_bitmap(60,60);
20 float ship_x = 400;
21 float ship_y = 340;
22 float ship_accel = 0.01;
23 float ship_yspeed = 0;
24 float ship_xspeed = 0;
25 float ship_angle = 0;
26 // *** Star Structure ***
27 struct starStr {
28 int x;
29 int y;
30 int speed;
31 };
32struct starStr star[300];
33 int ctr;
34// == Fill the star struct with start positions
35 for(ctr=0;ctr<=299;ctr++)
36 {
37 star[ctr].x=rand()%SCREEN_W;
38 star[ctr].y=rand()%SCREEN_H;
39 star[ctr].speed = rand()%3+1; // Different speeds help with depth
40
41 }
42 
43 while (!key[KEY_ESC]) // escape closes the program
44 {
45
46 if (key[KEY_A]) // moves the ship guy left
47 {
48 ship_angle --;
49 }
50 if (key[KEY_D]) // moves the ship guy right
51 {
52 ship_angle ++;
53 }
54 if (key[KEY_W]) // moves the ship guy up
55 {
56 ship_xspeed = ship_xspeed + (sin(ship_angle) * ship_accel);
57 ship_yspeed = ship_yspeed + (-cos(ship_angle) * ship_accel);
58 }
59 if (key[KEY_S]) // moves the ship guy down
60 {
61 ship_yspeed = 0;
62 ship_xspeed = 0;
63 }
64
65 // == Check speed ==
66 if (ship_yspeed > 2)
67 ship_yspeed = 2;
68 if (ship_yspeed < -2)
69 ship_yspeed = -2;
70 if (ship_xspeed > 2)
71 ship_xspeed = 2;
72 if (ship_xspeed < -2)
73 ship_xspeed = -2;
74
75 // == Update each star in loop ==
76 for(ctr=0;ctr<=299;ctr++)
77 {
78
79 star[ctr].x = star[ctr].x + star[ctr].speed;
80 
81 if (star[ctr].x > SCREEN_W) {
82 star[ctr].x = 0;
83 }
84 draw_sprite(buffer, starbmp, star[ctr].x,star[ctr].y);
85 }
86 // == End of Stars update ==
87
88 //Update ship location
89
90
91 ship_y = ship_y + ship_yspeed;
92 ship_x = ship_x - ship_xspeed;
93 // == Draw objects ==
94 draw_sprite(buffer, bg, 0,0);
95 rotate_sprite(temp, ship, 0,0,(ship_angle * radtofix_r));
96 draw_trans_sprite(buffer, temp, ship_x,ship_y);
97 blit (buffer,screen,0,0,0,0,SCREEN_W,SCREEN_H); // puts out the buffer sprite
98 clear(buffer); // clears the buffer
99 clear(temp);
100}
101 destroy_bitmap(buffer); // gets rid of the bitmap after the program closes to reduce usage of memory
102 destroy_bitmap(ship); // SAA (Same As Above)
103 destroy_bitmap(starbmp);
104 destroy_bitmap(bg);
105 destroy_bitmap(temp);
106 return 0; // closes with no errors
107}
108 
109END_OF_MAIN(); // allegro specific

Any help would be most appreciated! This is stopping me from learning further!

StevenVI
Member #562
July 2000
avatar

The sin and cos functions take arguments in radians. Multiply your angles by 3.141592654/180.0 to convert from degrees to radians.

__________________________________________________
Skoobalon Software
[ Lander! v2.5 ] [ Zonic the Hog v1.1 ] [ Raid 2 v1.0 ]

krazak
Member #9,908
June 2008

I tried this:

if (key[KEY_W]) // moves the ship guy up
          {
          ship_angle = (ship_angle * (M_PI/180.0));
          ship_xspeed = ship_xspeed + (sin(ship_angle) * ship_accel);
          ship_yspeed = ship_yspeed + (-cos(ship_angle) * ship_accel);
          }

With this:

rotate_sprite(temp, ship, 0,0,(ship_angle * radtofix_r));

And now the ship simply flies upwards, no matter what the rotation, so something is making ship_angle = 0. :S

SiegeLord
Member #7,827
October 2006
avatar

Quote:

so something is making ship_angle = 0

That something is the code you just posted. :D Look very carefully at what it does to the ship_angle at every logic step.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

You should use a separate variable for the ship's angle in radians and another for the ships angle in allegro fixed degrees. This way you can change the ships angle in degrees the way you like it and convert it when you need to.
This line (quoted from your last post) :

ship_angle = (ship_angle * (M_PI/180.0));

Isn't going to do what you want. It will continually decrease your ships angle because you're altering the variable you're supposed to be storing your degree angle in.

Alter your angle in degrees as you like but then convert it when you need to , storing the values in different variables.

const float deg_to_rad = M_PI/180.0;
const float rad_to_afix_degrees = 128.0/M_PI;
const float deg_to_afix = 256.0/360.0;


float ship_angle_degrees = 0.0;
float ship_angle_rad = ship_angle_degrees*deg_to_rad;
float ship_angle_afix = ship_angle_degrees*deg_to_afix;

Since you want to limit the ships overall speed , it would be nicer to keep a variable just for that. To prevent changing direction of movement when limiting the speed , find the absolute speed first from your altered x and y velocities , reduce the absolute speed if necessary and then refind your x and y velocities.

You might also want to limit the degree angle your ships can travel to between [0.0,360.0) degrees by adding or subtracting 360.0 degrees as necessary.

krazak
Member #9,908
June 2008

Ok I tried to figure it out myself with the excellent advice from you guys, but I'm still at a pass! Now I can't seem to get the ship to accept modifications to its current angle value!
When set to 0 (Via initial definition when the variable is delcared) the ship flies straight up (shouldn't it go dead left?), and when set to anything between 1-249, it flies off what looks to be directly north-west and the ship graphic does not rotate to suit, and for anything between 250-360, it flies off south-west with no graphical rotation! Here's the current important code:

    float ship_x = 400;
    float ship_y = 340;
    float ship_accel = 0.01;
    float ship_yspeed = 0;
    float ship_xspeed = 0;
    const float deg_to_rad = M_PI/180.0;
    const float rad_to_afix_degrees = 128.0/M_PI;
    const float deg_to_afix = 256.0/360.0;
    float ship_angle_degrees = 360.0;
    float ship_angle_rad = ship_angle_degrees * deg_to_rad;
    float ship_angle_afix = ship_angle_degrees * deg_to_afix;

          if (key[KEY_D]) // moves the ship guy right
          {
          ship_angle_degrees ++;
          
          }
          if (key[KEY_W]) // moves the ship guy up
          {
          ship_xspeed = ship_xspeed + sin(ship_angle_afix * ship_accel);
          ship_yspeed = ship_yspeed + -cos(ship_angle_afix * ship_accel);
          }

      //Update ship location
      ship_y = ship_y + ship_yspeed;
      ship_x = ship_x - ship_xspeed;
      if (ship_angle_degrees > 360) ship_angle_degrees = 0;
    // == Draw objects ==
    draw_sprite(buffer, bg, 0,0);
    rotate_sprite(temp, ship, 0, 0, ship_angle_afix);
    draw_trans_sprite(buffer, temp, ship_x,ship_y);

Jonatan Hedborg
Member #4,886
July 2004
avatar

Without reading all posts, I thought I should point out that the last argument to rotate_sprite should be a "fixed", not a float. Do an ftofix, and obviously you need to update ship_angle_afix each frame.

You may want to take a look at the manual.

krazak
Member #9,908
June 2008

I did notice that it reqested a fixed and was getting a float, the compiler gave me warnings, but as they were just warnings, not errors, I thought it might be capable of taking float as well, I'll try ftofix, though I'm pretty sure I've tried it before, I'm at least certain I tried itofix when I had ship_angle as an integer.

EDIT: Just tried it, also put ftofix(ship_x) and same for ship_y in the draw_trans_sprite line, this caused the sprite to not be drawn at all. This is a mindbuggerer this little project is.

EDIT EDIT: float ship_angle_afix = ship_angle_degrees * deg_to_afix;
Since it is defined like that, shouldn't it update itself every time it's called and needs to return a value?

Jonatan Hedborg
Member #4,886
July 2004
avatar

No. That just initiates the variable to whatever that evaluates to at that point. A variable is just a number, nothing more. Maybe you should brush up on your C/C++ while you are at it :)

And once again, please read the manual.

krazak
Member #9,908
June 2008

I never claimed to be even close to proficient at C, thats why I'm doing this project, and why I'm asking for help! Also I've read the manual for every function used in the posted code. I feel like you just want to shoot me down instead of help me understand whats wrong :(.

Are you saying that instead of
float ship_angle_afix = ship_angle_degrees * deg_to_afix;
I should use

float ship_angle_afix = 0;
void update_anglefix()
{
ship_angle_afix = ship_angle_degrees * deg_to_afix;
}

and call it before using ship_angle_afix?

That it simply calculates ship_angle_degrees * deg_to_afix when it is declared, and returns that float, instead of returning ship_angle_degrees * deg_to_afix?

supermario
Member #4,862
July 2004

There was a pixelate article that was exactly about this subject (learning sin and cos), maybe you could search for it. Though its probably hard to find now.

Vanneto
Member #8,643
May 2007

In capitalist America bank robs you.

Schyfis
Member #9,752
May 2008
avatar

What you're trying to do can be accomplished with something like this:

1//we need math.h for sin, cos, and M_PI
2#include <math.h>
3//rotation of the ship in degrees
4int rotation;
5//current velocity and max velocity of the ship
6double vel;
7double maxvel=5;
8//x and y positions of the ship
9double x,y;
10//x and y velocities of the ship
11double xv,yv;
12//later in the code, during your game loop...
13if(key[KEY_UP]){
14 vel++;
15 if(vel>maxvel){
16 vel=maxvel;
17 }
18}
19if(key[KEY_LEFT]){
20 rotation--;
21}
22if(key[KEY_RIGHT]){
23 rotation++;
24}
25xv+=vel*cos((rotation+90)*M_PI/180);
26yv+=vel*sin((rotation+90)*M_PI/180);
27x+=xv;
28y+=yv;
29rotate_sprite(buffer,ship,(int)x,(int)y,ftofix(rotation*256/360));

Mess with the rotation+90 if the ship isn't facing the right way.
Good luck!

________________________________________________________________________________________________________
[freedwill.us]
[unTied Games]

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Quoted from your last post :

          if (key[KEY_W]) // moves the ship guy up
          {
          ship_xspeed = ship_xspeed + sin(ship_angle_afix * ship_accel);
          ship_yspeed = ship_yspeed + -cos(ship_angle_afix * ship_accel);
          }

These two lines are incorrect, you were closer before. As Harry Carey said , the sin and cos functions take a single argument of the angle in radians and return a ratio from -1 to 1. This ratio is what you want to multiply your acceleration by to find it's x and y components so you can add them to your velocity. After you add them , find the combined speed and if it is more than you wish to allow , reduce and re-figure your x and y velocities from it. Also your sin and cos calls were backwards , cos gives you the x value and sin gives you the y value :

1//
2 if (key[KEY_W]) { // accelerate the ship in the current facing direction
3 ship_xspeed += ship_accel*cos(ship_bearing_angle_rad);
4 ship_yspeed += ship_accel*sin(ship_bearing_angle_rad);
5 
6 if ((ship_yspeed == 0.0) && (ship_xspeed == 0.0)) {
7 ship_heading_angle_rad = ship_bearing_angle_rad;
8 } else {
9 ship_heading_angle_rad = atan2(ship_yspeed , ship_xspeed);
10 }
11 
12 ship_total_speed = sqrt(ship_xspeed*ship_xspeed + ship_yspeed*ship_yspeed);
13 
14 // reduce overall speed if necessary
15 if (ship_total_speed > ship_max_speed) {
16 ship_total_speed = ship_max_speed;
17 ship_xspeed = ship_total_speed*cos(ship_heading_angle_rad);
18 ship_yspeed = ship_total_speed*sin(ship_heading_angle_rad);
19 }
20 }
21//

I introduced some new variables , 'ship_total_speed' , 'ship_max_speed' , 'ship_bearing_angle_rad' , and 'ship_heading_angle_rad'. Since your ship isn't always moving the same direction that it is facing , it is better to track the two angles separately. Heading is the direction it is moving and bearing is the direction it is facing.

Your angle updates need a little work as well. When the user presses the key to adjust the angle , you should recalculate your angles in the other formats as well so that way they are never out of sync. Also , if the angle exceeds 360 degrees , you don't want to set it to 0 but rather subtract 360 degrees as necessary from it so the angle is equivalent. In this case your method may have the same effect since you only modify the degree angle by one at a time but if you ever plan to change the turn rate , you'll want to modify the angle by +- n*360 degrees instead of setting it to a constant value.

1 // earlier in initializations
2 float turn_rate_degrees = 1.0;// same as what you're using now but allows for adjustment
3 
4 /// Inside game loop
5 // rotate the ship counter-clockwise
6 if (key[KEY_A]) {
7 ship_bearing_angle_degrees -= turn_rate_degrees;
8 
9 // add n*360.0 if necessary so the angle is between [0.0,360.0)
10 if (ship_bearing_angle_degrees < 0.0) {
11 ship_bearing_angle_degrees += 360.0*( (float) ( (int)(ship_bearing_angle_degrees/360.0) + 1 );
12 }
13 // recalculate angle in other formats
14 ship_bearing_angle_rad = ship_bearing_angle_degrees*deg_to_rad;
15 ship_bearing_angle_afix = ship_bearing_angle_degrees*deg_to_afix;
16 }
17
18 // rotate the ship clockwise
19 if (key[KEY_D]) {
20 ship_bearing_angle_degrees += turn_rate_degrees;
21 
22 // subtract n*360.0 if necessary so the angle is between [0.0,360.0)
23 if (ship_bearing_angle_degrees >= 360.0) {
24 ship_bearing_angle_degrees -= 360.0*( (float)(int)(ship_bearing_angle_degrees/360.0) );
25 }
26 // recalculate angle in other formats
27 ship_bearing_angle_rad = ship_bearing_angle_degrees*deg_to_rad;
28 ship_bearing_angle_afix = ship_bearing_angle_degrees*deg_to_afix;
29 }
30//

One last thing , the only time you need to use ftofix is when a fixed data type is called for. In this case that means in the call to rotate_sprite.

// function parameters for rotate_sprite :
// void rotate_sprite(BITMAP *bmp, BITMAP *sprite, int x, int y, fixed angle);

rotate_sprite(temp , ship , 0 , 0 , ftofix(ship_bearing_angle_afix));

//

One last last thing , if any parts of your ship are outside a radius equal to the smaller value of (ship->w/2 , ship->h/2) then when you rotate it onto a bitmap of the same dimensions , part of it may be clipped. This can be worked around by using pivot_sprite onto a temporary bitmap that is square and has dimensions of :

//
float width = 2.0*(sqrt(((float)ship->w/2.0)*((float)ship->w/2.0) + ((float)ship->h/2.0)*((float)ship->h/2.0));
if ((width - (float)(int)width) != 0.0) {
  width = (float)( (int)width + 1 );
}
const int temp_bmp_width = (int)width;
BITMAP* temp = create_bitmap(temp_bmp_width , temp_bmp_width);
//

I know there's a lot here so if you have questions , feel free to ask.

James Stanley
Member #7,275
May 2006
avatar

Quote:

float ship_angle_afix = ship_angle_degrees * deg_to_afix;
Since it is defined like that, shouldn't it update itself every time it's called and needs to return a value?

#define ship_angle_afix (ship_angle_degrees * deg_to_afix)
;)
And, obviously, remove the declaration of ship_angle_afix.

Jonatan Hedborg
Member #4,886
July 2004
avatar

Stanley; Say after me! "I will not teach the newbies horrible programming practices" :)

James Stanley
Member #7,275
May 2006
avatar

Just to clarify, I wouldn't do that myself.
I was demonstrating how what he described could be done. :)

Tobias Dammers
Member #2,604
August 2002
avatar

...while what you should have done is tell him not to do things like this.

Anyway, my piece of advice: In your game logic, use radians as the ONLY format for your angles. Ever. The only situation where you ever touch fixed-point numbers, atofix, and allegro's 256-degrees format is when you absolutely have to. Which is at the very moment when you draw your rotated sprite. A simple conversion function is all you need:

int radians_to_allegro_angle(double radians)
{
  return ftofix(radians * 128.0 / M_PI);
}

// and in your drawing code:
rotate_sprite(screen, sprite, (int)ship_x, (int)ship_y, radians_to_allegro_angle(ship_angle));

Everything else you do with angles is done using libm's trig functions, and they all consistently use radians (which, from a programming point of view, is the only choice that really makes sense).

On a side note: Chunks of code like this one:

    float ship_x = 400;
    float ship_y = 340;
    float ship_accel = 0.01;
    float ship_yspeed = 0;
    float ship_xspeed = 0;

...just cry for a class (or at least a struct if you're in plain C):

typedef struct {
  float x;
  float y;
  float angle;
  float accel;
  float xspeed;
  float yspeed;
} SHIP;
/*                          x       y       angle accel  xspeed yspeed
struct SHIP player_ship = { 400.0f, 340.0f, 0.0f, 0.01f, 0.0f,  0.0f };

Then later on, you access the members like this:

player_ship.x += player_ship.xspeed * delta_t;

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

Go to: