Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » How To Turn A 2D Top Down Car

This thread is locked; no one can reply to it. rss feed Print
How To Turn A 2D Top Down Car
Nortski
Member #15,933
April 2015

Hi all. This is my first post so firstly I'll introduce myself. I'm Mark from the UK and I'm at the beginning of my coding adventure.

I've picked up bits and pieces down the years but never really got my head around things so this time around I've decided to learn by 'doing'.

So. I'm trying to create a simple 2D top down car racer thingy using Allegro 5. At the moment I'm only using structs to create my objects, not progressed to classes yet.

I've created a display, event queue, and a timer which all seem to work fine. I've created a simple car (as in a primitive rectangle) and I can move it forward and backwards using keyboard inputs with a very VERY simple acceleration expression.

Now, this is where my brain has went into melt down. I don't know how to turn the car to face new headings. I've read the article by Amarillion that explains the concepts, using sine and cosine, but I just don't 'get it'.

Could someone offer me little tidbits on how to begin so I can progress from there?

My code so far is (not including the objects.h):

#SelectExpand
1#include <allegro5\allegro.h> 2#include <allegro5\allegro_primitives.h> 3#include "objects.h" 4 5//GLOBALS============================== 6const int WIDTH = 800; 7const int HEIGHT = 700; 8enum KEYS{UP, DOWN, LEFT, RIGHT}; 9bool keys[4] = {false, false, false, false}; 10 11//prototypes 12void InitCar(Car &whiteCar); 13void DrawCar(Car &whiteCar); 14void MoveCarForward(Car &whiteCar); 15void MoveCarBackward(Car &whiteCar); 16void TurnCarLeft(Car &whiteCar); 17void TurnCarRight(Car &whiteCar); 18 19int main(void) 20{ 21 //primitive variable 22 bool done = false; 23 float steer = 0.1; 24 bool redraw = true; 25 int FPS = 60; 26 27 //object variables 28 Car whiteCar; 29 30 //Allegro variables 31 ALLEGRO_DISPLAY *display = NULL; 32 ALLEGRO_EVENT_QUEUE *event_queue = NULL; 33 ALLEGRO_TIMER *timer = NULL; 34 35 //Initialization Functions 36 if(!al_init()) //initialize Allegro 37 return -1; 38 39 display = al_create_display(WIDTH, HEIGHT); //create our display object 40 41 42 if(!display) //test display object 43 return -1; 44 45 al_init_primitives_addon(); 46 al_install_keyboard(); 47 48 event_queue = al_create_event_queue(); 49 timer = al_create_timer(1.0 / 60); 50 51 InitCar(whiteCar); 52 53 al_register_event_source(event_queue, al_get_keyboard_event_source()); 54 al_register_event_source(event_queue, al_get_timer_event_source(timer)); 55 al_register_event_source(event_queue, al_get_display_event_source(display)); 56 57 al_start_timer(timer); 58 59 while(!done) 60 { 61 ALLEGRO_EVENT ev; 62 al_wait_for_event(event_queue, &ev); 63 64 if(ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) 65 { 66 done = true; 67 } 68 else if(ev.type == ALLEGRO_EVENT_TIMER) 69 { 70 redraw = true; 71 if(keys[UP]) 72 { 73 MoveCarForward(whiteCar); 74 } 75 if(keys[DOWN]) 76 { 77 MoveCarBackward(whiteCar); 78 } 79 } 80 else if(ev.type == ALLEGRO_EVENT_KEY_DOWN) 81 { 82 switch(ev.keyboard.keycode) 83 { 84 case ALLEGRO_KEY_ESCAPE: 85 done = true; 86 break; 87 case ALLEGRO_KEY_UP: 88 keys[UP] = true; 89 break; 90 case ALLEGRO_KEY_DOWN: 91 keys[DOWN] = true; 92 break; 93 case ALLEGRO_KEY_LEFT: 94 keys[LEFT] = true; 95 break; 96 case ALLEGRO_KEY_RIGHT: 97 keys[RIGHT] = true; 98 break; 99 } 100 } 101 else if(ev.type == ALLEGRO_EVENT_KEY_UP) 102 { 103 switch(ev.keyboard.keycode) 104 { 105 case ALLEGRO_KEY_ESCAPE: 106 done = true; 107 break; 108 case ALLEGRO_KEY_UP: 109 keys[UP] = false; 110 whiteCar.speed = 0.1; 111 break; 112 case ALLEGRO_KEY_DOWN: 113 keys[DOWN] = false; 114 whiteCar.speed = 0.1; 115 break; 116 case ALLEGRO_KEY_LEFT: 117 keys[LEFT] = false; 118 break; 119 case ALLEGRO_KEY_RIGHT: 120 keys[RIGHT] = false; 121 break; 122 } 123 } 124 125 if(redraw && al_is_event_queue_empty(event_queue)) 126 { 127 redraw = false; 128 DrawCar(whiteCar); 129 130 al_flip_display(); 131 al_clear_to_color(al_map_rgb(0,0,0)); 132 } 133 } 134 135 al_destroy_timer(timer); 136 al_destroy_event_queue(event_queue); 137 al_destroy_display(display); //destroy our display object 138 139 return 0; 140} 141 142void InitCar(Car &whiteCar) 143{ 144 whiteCar.x = WIDTH / 2; 145 whiteCar.y = HEIGHT; 146 whiteCar.ID = PLAYER; 147 whiteCar.lives = 3; 148 whiteCar.vel_x = 0.1; 149 whiteCar.vel_y = 0.3; 150 whiteCar.speed = 0.1; 151} 152 153void DrawCar(Car &whiteCar) 154{ 155 al_draw_filled_rectangle(whiteCar.x - 25, whiteCar.y - 100, whiteCar.x + 25, whiteCar.y, al_map_rgb(255, 255, 255)); 156} 157void MoveCarForward(Car &whiteCar) 158{ 159 whiteCar.y -= (whiteCar.speed += whiteCar.vel_y); 160} 161void MoveCarBackward(Car &whiteCar) 162{ 163 whiteCar.y += (whiteCar.speed += whiteCar.vel_y); 164} 165//void TurnCarLeft(Car &whiteCar) 166//void TurnCarRight(Car &whiteCar)

Thanks in advance for any pointers ;D

Chris Katko
Member #1,881
January 2002
avatar

For starters, speed up/down? You're setting it to the same value for up and down. You need to be adding/subtracting it (for modifying velocity while you hold the key), OR, at least set them to .1 and 0, or .1 and -1 (two specific velocities).

So you want (in old A4 code):

if(key[KEY_UP])object.speed += 0.1; // some acceleration value
if(key[KEY_DOWN])object.speed -= 0.1;

Your object should have the following values:

struct object{
float x;
float y;
float angle; //the angle of the vector (where the car is pointing)
float speed; //where speed is actually the LENGTH of the vector (how fast it moves in a given time)
}; //NOT x_vel and y_vel!

(GET RID OF X_VEL and Y_VEL, more on that at the bottom.)*

Every logic frame (cycle of your main loop) represents a point in time. Those frames represent equally long periods of time. So if your time was 1 second, and your car moved 10 feet, that means your car is moving at 10 feet/sec.

So every logic frame you do:

 x = x + cos(angle) * speed;
 y = y + sin(angle) * speed;

We take the original x and y, add a vector that represents that movement. If it was ten feet per second, and your logic is a second (normally much smaller than that), then speed = 10. If your car is moving to the right, the angle is 0 degrees. So angle = 0. That's it. Keep adding the x/y every frame and the car keeps moving 10 feet per frame.

Now let's say you turn your car to the left. All you have to do is turn the angle value to point to the left [TYPO: not RIGHT]! Positive angles are COUNTER-clockwise, so we add a positive value to the angle, and boom, we're now moving to the left of where we were (but still probably right on the screen). To turn right, decrease the angle by adding a negative number to it. You don't even have to loop the angle at 0 and 360 degrees, sin and cos work just fine with 300, 600, 52000 degrees (or Radians).

if(key[KEY_LEFT])angle -= 0.1; //turn left
if(key[KEY_RIGHT])angle += 0.1; //turn right

That's it!

[edit]

*x_vel and y_vel are something similiar. (x_vel, y_vel) and (angle, distance) are actually the exact same thing. They're just in a different format. If you wanted say, gravity to be down, you could either do:

y += y_gravity_vel; 
//OR 
y += sin(gravity_angle)*gravity_velocity;

As long as the angle points down, it's the same thing. You just use whatever format is more convenient. But a car has a direction, and a velocity, so it's much easier to keep the object in that angle, distance format. It's not wrong, or broken, but more convenient and natural to think of a car that way. Whereas for a ball falling, I tend to use y_vel.

-----sig:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

Nortski
Member #15,933
April 2015

Thank you so much Chris for this explanation, I really didn't expect someone to take the time to answer my question as detailed as you did!

I will have to read through it a few times to digest it and lodge it into my brain.

Much appreciated!
:)

Chris Katko
Member #1,881
January 2002
avatar

Glad to hear it! If there's any areas that need clarification or elaboration, please let me know.

-----sig:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

Nortski
Member #15,933
April 2015

Haha right! So I can turn my car now, still need to rotate the primitive to face the heading though (I'll do that later).

I've had to set my object.angle = 4.713 to start on a heading for north. Can you explain why that is please?

BTW it's 2.20am here so I'm calling it a night!

Can't wait to continue this tomorrow :)

Chris Katko
Member #1,881
January 2002
avatar

Nortski said:

I've had to set my object.angle = 4.713 to start on a heading for north. Can you explain why that is please?

2 * PI radians = a full circle

4.713 / 2*pi = 0.75 = 75% rotated all the way around the circle.

Now, in normal math +x is right, +y is up. BUT, for a computer screen, 0,0 is the top-left. So that means that +y is DOWN. The y-axis is flipped.

So where as 0 degrees (0 radians) is to the right (+x), when you rotate positive 90 degrees (1.57... radians) it looks like you're rotating clockwise! But we know from that normal math says positive 90 should be counter-clockwise. That's because you've flipped the y-axis 180 degrees (because we're using positive Y values DOWNWARD from the screen).

So you can do one of two things:

1 - Use opposite angles.
2 - Flip the y coordinate back!
3 - Flip the angles.

1) Angles are angles, whether you assume 0 is up, right, left, or down, or somewhere inbetween is entirely up to you. But you will have to convert your coordinates accordingly. So if you leave things exactly as they are, you can just use negative or (360 - angle) to be your angle.

2) Flip the y-coordinate back:

Instead of using draw_thing(x, y), you do draw_thing(x, y - SCREEN_HEIGHT). Boom, that's it. 0 on the y axis is now at the bottom, and positive y values move upward.

3) Flip the angles.

I don't really recommend it because it can get confusing, but you can use variations of sin/cos to do a coordinate transformation to fix the angle issue.

 x = sin(angle)*distance;// normally cos!
 y = cos(angle)*distance;// normally sin!

or some combination of:

 x = -cos(angle)*distance;
 y = -sin(angle)*distance;

or

 x = cos(-angle)*distance;
 y = sin(-angle)*distance;

or

 x = cos(2*PI - angle)*distance;
 y = sin(2*PI - angle)*distance;

You don't necessarily change both lines of sin/cos. I don't recall the trig off the top of my head, so tread carefully. I don't recommend this route, I just want you to see that it's possible to move coordinates many different ways, whether it's internally in the objects x/y, or converting at the very end by changing the rendering direction.

-----sig:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

SiegeLord
Member #7,827
October 2006
avatar

I actually suggest option #3 (or what I think is option #3, I may be confused). Everything just works that way, as long as you don't need to display angles to the user. atan2 works, cos is still associated with x and so on.

EDIT: To be clear, I advocate treating PI/2 as pointing down on the screen.

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

Nortski
Member #15,933
April 2015

This is all great stuff to be learning and exactly what I wanted to be doing when I started this project, to learn while 'doing' :)

I think for now I'll leave the angles how it is, now that I understand that +y is downwards and where the figure 4.713 comes from.

Thanks guys!

I will have more questions later ;)

Go to: