|
Acceleration & Deceleration |
Nortski
Member #15,933
April 2015
|
After receiving some great advice and pointers from Chris on another thread I've managed to code the very beginnings of my 2D top down car racer game. At the moment I can load my sprite to the screen, move forward, backward, turn left and right and have the sprite rotate to face the direction it's heading. I now want to add better acceleration and deceleration. Presently; object.speed just increases by 0.2 while the UP key is pressed until it reaches object.maxSpeed What would be a better way to implement acceleration? When the UP key is released the car just stops dead in it's tracks, which is no good of course. I thought by adding object.speed *= 0.8 to the key up event would solve this abrupt stopping of the car, it never So; can someone give advise on how to achieve this? Thanks in advance guys |
Chris Katko
Member #1,881
January 2002
|
You'd be good to do some reading up on derivatives/integrals and how they map to position, velocity, and acceleration. Position is what you think. Velocity, is change in position for a given time. So if you're moving 10 feet, and your logic loop is 1 second, velocity is 10 feet per second. So velocity is delta V / T. (Change in V per time) And to use it in your game, you just do x += velocity every frame right? Acceleration is just the change in velocity over a given time. So acceleration is delta A / T. And to use it in your game, you just do velocity += acceleration every frame! So instead of setting velocity, you can do: Lastly, FORCE is an acceleration. From newtons equation, force = mass * acceleration. So if you factor in that a car has a mass, you can simulate a heavy or lighter car, and a beefier, or smaller engine easily. Your engine is making a force. (Technically a force that changes per speed, which is why you have gears in a car, but you don't need that just yet!) Your car has a mass. (mass is weight divided by earth's gravity) So instead of directly applying acceleration, you apply FORCE. So if F=ma, then we can rearrange that to be a=f/m, acceleration is force divided by mass. Which passes the sanity test of "car accelerates faster if we reduce the mass, or increase the engine force." float engine_force = 10; //or whatever float car_mass = 1; //... if(key[KEY_UP]) { acceleration += engine_force / car_mass; //a = f/m } That's it! Now what about naturally slowing down? There's rolling resistance, and wind resistance. Rolling resistance is a function of the friction of everything rolling. Rolling resistance scales linearly with speed. Wind resistance is the same thing, but is the square of speed. //rolling resistance float rolling_resistance_factor = .1; //Some number. Best to just make one up. float rolling_resistance_force = velocity * rolling_resistance_factor; car_acceleration -= rolling_resistance_force / car_mass; //that's it! //wind float coefficient_of_drag = .01; //can look these up on wikipedia! float drag_force = (velocity * velocity) * coefficient_of_drag; car_acceleration -= drag_force / car_mass; //that's it!
-----sig: |
Nortski
Member #15,933
April 2015
|
You know when you get that feeling of biting off more than you can chew...... Thanks for the great reply Chris, once again! I think once I can grasp exactly what velocity is, this will make things a whole lot easier. My current velocity is stored in 2 variables: In your explanation you have velocity as a single variable, or do you? |
Thomas Fjellstrom
Member #476
June 2000
|
I think it just looks more complex than it is. Velocity is just your speed (or change in position over time) The rest of the stuff is just additional "factors" to determine the changes to apply to acceleration and velocity over time when you want to speed up or slow down. It makes it feel more "natural". In a racing game, you may actually make the car mass, or engine mass variable based on each car. More powerful cars may have larger engines, with greater mass, making their enertia greater, causing their acceleration to change slower. You could even apply "resistance" to steering (to object.angle, so you'd have an object.angle_accelleration or resistance something like that). Compare old american muscle cars or dragsters that have a very hard time turning, to rice rockets or super cars that can turn on a dime. -- |
Nortski
Member #15,933
April 2015
|
Hi Thomas, yes it will certainly be good to add steering resistance etc. Firstly though, I need to get my head around this acceleration/deceleration concept. I'm having trouble implementing it into my code. I hate to ask but I think I need someone to point out exactly where in my code I would insert Chris's explanations. I think after that I should be able to work out the how's and why's of it all. Here is my code (just using structs at the moment as I'm a newbie): 1#include <allegro5\allegro.h>
2#include <allegro5\allegro_primitives.h>
3#include <allegro5\allegro_image.h>
4#include "objects.h"
5#include "math.h"
6
7//GLOBALS==============================
8const int WIDTH = 1200;
9const int HEIGHT = 700;
10enum KEYS{UP, DOWN, LEFT, RIGHT};
11bool keys[4] = {false, false, false, false};
12
13//prototypes
14void InitCar(Car &whiteCar, ALLEGRO_BITMAP *car);
15void DrawCar(Car &whiteCar);
16void MoveCarForward(Car &whiteCar);
17void MoveCarBackward(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 float acceleration = 0.0;
27
28
29 //object variables
30 Car whiteCar;
31
32 //Allegro variables
33 ALLEGRO_DISPLAY *display = NULL;
34 ALLEGRO_EVENT_QUEUE *event_queue = NULL;
35 ALLEGRO_TIMER *timer = NULL;
36 ALLEGRO_BITMAP *car = NULL;
37
38
39 //Initialization Functions
40 if(!al_init()) //initialize Allegro
41 return -1;
42 display = al_create_display(WIDTH, HEIGHT);
43 if(!display) //test display object
44 return -1;
45
46 al_init_primitives_addon();
47 al_install_keyboard();
48 al_init_image_addon();
49
50
51 event_queue = al_create_event_queue();
52 timer = al_create_timer(1.0 / 60);
53 car = al_load_bitmap("objects/YellowCar.png");
54
55 InitCar(whiteCar, car);
56
57 al_register_event_source(event_queue, al_get_keyboard_event_source());
58 al_register_event_source(event_queue, al_get_timer_event_source(timer));
59 al_register_event_source(event_queue, al_get_display_event_source(display));
60
61 al_start_timer(timer);
62
63 while(!done)
64 {
65 ALLEGRO_EVENT ev;
66 al_wait_for_event(event_queue, &ev);
67
68 if(ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
69 {
70 done = true;
71 }
72 else if(ev.type == ALLEGRO_EVENT_TIMER)
73 {
74 redraw = true;
75 if(keys[UP])
76 {
77 whiteCar.speed += 0.2;
78 MoveCarForward(whiteCar);
79 }
80 if(keys[DOWN])
81 {
82 whiteCar.reverseSpeed -= 0.1;
83 MoveCarBackward(whiteCar);
84 }
85 if(keys[LEFT])
86 {
87 whiteCar.angle -= 0.07;
88 }
89 if(keys[RIGHT])
90 {
91 whiteCar.angle += 0.07;
92 }
93 }
94 else if(ev.type == ALLEGRO_EVENT_KEY_DOWN)
95 {
96 switch(ev.keyboard.keycode)
97 {
98 case ALLEGRO_KEY_ESCAPE:
99 done = true;
100 break;
101 case ALLEGRO_KEY_UP:
102 keys[UP] = true;
103 break;
104 case ALLEGRO_KEY_DOWN:
105 keys[DOWN] = true;
106 break;
107 case ALLEGRO_KEY_LEFT:
108 keys[LEFT] = true;
109 break;
110 case ALLEGRO_KEY_RIGHT:
111 keys[RIGHT] = true;
112 break;
113 }
114 }
115 else if(ev.type == ALLEGRO_EVENT_KEY_UP)
116 {
117 switch(ev.keyboard.keycode)
118 {
119 case ALLEGRO_KEY_ESCAPE:
120 done = true;
121 break;
122 case ALLEGRO_KEY_UP:
123 keys[UP] = false;
124 whiteCar.speed = 0.0;
125 break;
126 case ALLEGRO_KEY_DOWN:
127 keys[DOWN] = false;
128 whiteCar.reverseSpeed = 0.0;
129 break;
130 case ALLEGRO_KEY_LEFT:
131 keys[LEFT] = false;
132 break;
133 case ALLEGRO_KEY_RIGHT:
134 keys[RIGHT] = false;
135 break;
136 }
137 }
138
139 if(redraw && al_is_event_queue_empty(event_queue))
140 {
141 redraw = false;
142 DrawCar(whiteCar);
143
144 al_flip_display();
145 al_clear_to_color(al_map_rgb(0,0,0));
146 }
147 if (whiteCar.speed >= whiteCar.maxForwardSpeed)
148 whiteCar.speed = whiteCar.maxForwardSpeed;
149 if (whiteCar.reverseSpeed <= whiteCar.maxReverseSpeed)
150 whiteCar.reverseSpeed = whiteCar.maxReverseSpeed;
151 }
152
153 al_destroy_timer(timer);
154 al_destroy_event_queue(event_queue);
155 al_destroy_display(display);
156 al_destroy_bitmap(car);
157
158 return 0;
159}
160
161void InitCar(Car &whiteCar, ALLEGRO_BITMAP *car)
162{
163 whiteCar.x = WIDTH / 2;
164 whiteCar.y = HEIGHT;
165 whiteCar.ID = PLAYER;
166 whiteCar.angle = 4.713;
167 whiteCar.speed = 0;
168 whiteCar.reverseSpeed = 0;
169 whiteCar.maxForwardSpeed = 10;
170 whiteCar.maxReverseSpeed = -5;
171 whiteCar.image = car;
172}
173
174void DrawCar(Car &whiteCar)
175{
176 al_draw_rotated_bitmap(whiteCar.image, 75, 25, whiteCar.x, whiteCar.y - 100, whiteCar.angle, 0);
177}
178void MoveCarForward(Car &whiteCar)
179{
180 whiteCar.x = whiteCar.x + cos(whiteCar.angle) * whiteCar.speed;
181 whiteCar.y = whiteCar.y + sin(whiteCar.angle) * whiteCar.speed;
182}
183void MoveCarBackward(Car &whiteCar)
184{
185 whiteCar.x = whiteCar.x + cos(whiteCar.angle) * whiteCar.reverseSpeed;
186 whiteCar.y = whiteCar.y + sin(whiteCar.angle) * whiteCar.reverseSpeed;
187}
|
Chris Katko
Member #1,881
January 2002
|
Nortski said: In your explanation you have velocity as a single variable, or do you? Velocity is a vector. A vector has a magnitude, and an angle. The magnitude determines how much is added each frame (of velocity, or position), and the angle determines what direction. [edit] You want the velocity, and acceleration updates to occur every logic event, typically after you've processed input. So with no mass or anything like that, you get something like this: if(ev.type == ALLEGRO_EVENT_TIMER) { if(keys[UP]) acceleration += .1; if(keys[DOWN]) acceleration -= .1; if(keys[LEFT]) whiteCar.angle += .1; if(keys[RIGHT]) whiteCar.angle -= .1; whiteCar.velocity += acceleration; whiteCar.x += cos(whiteCar.angle)*whiteCar.velocity; whiteCar.y += sin(whiteCar.angle)*whiteCar.velocity; } You shouldn't need a backward and forward functions because negative velocity goes backwards! -----sig: |
Edgar Reynaldo
Major Reynaldo
May 2007
|
Chris Katko said: So velocity is delta V / T. (Change in V per time) And to use it in your game, you just do x += velocity every frame right? Acceleration is just the change in velocity over a given time. So acceleration is delta A / T. Don't have much to add to this thread, except that this is not accurate. Velocity is the change in position per unit of time, not dV/t, and acceleration is change in velocity per unit of time, not dA/t. I think you just misspoke. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Chris Katko
Member #1,881
January 2002
|
Whoops, I did write that reversed. Thanks for noticing it. Velocity is velocity over delta-T, or V/dt. Delta means change. So the amount that time changes between two points. For some reason, I can't edit that post anymore. -----sig: |
Nortski
Member #15,933
April 2015
|
Ah right OK, I was confusing myself (happens often). I thought my object.speed and your object.velocity were separate when in fact they both just mean 'length' right? I just changed mine to object.velocity. OK that seems to work. Except, my maxVelocity no longer works since we added the ACCELERATION variable. I had it set as: 1if (object.velocity >= object.maxVelocity)
2 object.velocity = object.maxVelocity;
I've got rid of the BACKWARD function. I had it because I wanted to set a separate maxVelocity for reverse. |
Chris Katko
Member #1,881
January 2002
|
Maximum velocity should still work fine, just make sure it's checked after the acceleration is applied: velocity += acceleration; if(velocity > max_velocity)velocity = max_velocity; if(velocity < -(max_reverse_velocity))velocity = -max_reverse_velocity;
Nortski said: Ah right OK, I was confusing myself (happens often). I thought my object.speed and your object.velocity were separate when in fact they both just mean 'length' right? Yes. They all mean the same thing in this context, but can occasionally mean other things in other contexts. -----sig: |
Nortski
Member #15,933
April 2015
|
OK things are looking much better now. The car will switch direction, forward and backwards, much more realistically now as long as there is no interval between the UP and DOWN keys. I still can't get it to slow down when a key is depressed, it's still stops dead. I'm trying to implement the rolling resistance method and have tried it in various positions in my game loop. On a side note. I have an ALLEGRO_EVENT_TIMER in my game loop that executes 60 times a second. Does this execute independently from the main game loop? It's easy to put these things into your code but unless you understand what's happening it just becomes data entry rather than learning. |
Edgar Reynaldo
Major Reynaldo
May 2007
|
You need to apply drag by multiplying your velocity by a set factor. Use something close to 1.00 or so and only adjust the velocity by the drag when a timer is fired and after applying acceleration and not when decelerating (as that has its own coefficient). Also, acceleration is usually constant but you can increase it with time if a key is held down, like pushing harder on a gas or brake pedal. Example : My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Bruce Perry
Member #270
April 2000
|
Nortski said: I still can't get it to slow down when a key is depressed, it's still stops dead. This actually sounds like a logic error to me. Either you accidentally set 'speed' instantly back to 0 somewhere, or you stopped applying the 'position += speed' when nothing was pressed. From your description in your first post, it sounds as if you had a pretty good idea (speed increases linearly up to a maximum) all along. By the way, if you want to base things on real physics: friction with a surface applies a linear deceleration (for example, speed -= 0.2 each frame; it's more complicated if you have your velocity defined in 2D), and air resistance applies an opposing force proportional to the square of the speed (i.e. speed -= someConstant * speed * speed every frame; again, more complicated in 2D). What Edgar suggested is however very useful if you don't care about realism and just want something that feels nice, and it's also very easy to generalise to 2D, unlike the other two schemes. -- |
Nortski
Member #15,933
April 2015
|
Thanks for the info guys, very informative. At first, I just wanted something that 'did the job' but now that I've started I want to delve deeper into the physics. Unfortunately for me, maths has never been my strong point so I'm battling to take this all in. Bruce, you were right. I hadn't included the velocity heading in my IF KEY UP statement! Now, my car will roll to a stop when moving in a forward direction, but stops dead in a reverse direction. It will be a logical error but I have tired eyes and cannot identify it. My MoveCar() function is: 1void MoveCar(Car &object)
2{
3 if (keys[UP] == true || keys[DOWN] == true)
4 {
5 object.velocity += acceleration;
6 if(object.velocity > object.maxVelocity)
7 {
8 object.velocity = object.maxVelocity;
9 acceleration = 0;
10 }
11
12 if(object.velocity < -(object.maxReverseVelocity))
13 {
14 object.velocity = -object.maxReverseVelocity;
15 acceleration = 0;
16 }
17 object.x += cos(object.angle) * object.velocity;
18 object.y += sin(object.angle) * object.velocity;
19 }
20
21 else if(keys[UP] == false)
22 {
23 {
24 object.x += cos(object.angle) * object.velocity;
25 object.y += sin(object.angle) * object.velocity;
26 object.velocity -= 0.2;
27 if(object.velocity < 0)
28 {
29 object.velocity = 0;
30 acceleration = 0;
31 }
32 }
33 }
34 else if(keys[DOWN] == false)
35 {
36 {
37 object.x += cos(object.angle) * object.velocity;
38 object.y += sin(object.angle) * object.velocity;
39 object.velocity -= 0.2;
40 if(object.velocity > 0)
41 {
42 object.velocity = 0;
43 acceleration = 0;
44 }
45 }
46 }
47}
Also, I would like to apply something a little more realistic than object.velocity -=0.2 to roll my car to a stop. Rolling resistance or wind resistance? OR something else? |
Bruce Perry
Member #270
April 2000
|
Think about those 'if' conditions. To get you on the right track, think about the four possibilities at any given time: UP is held You'll have to shuffle stuff around to fix it. You should also think about whether any of the lines you're repeating can be moved around so that you only have them once. The problems/solutions we've got here will appear in everything you write - it's worth spending time thinking through things like this so it gets easier next time And actually, "velocity -= 0.2" is pretty much the exact correct operation for a car rolling to a stop. Wind resistance will only become significant at high speeds, and it's pretty subtle, but it's the equation I gave you above (but be careful because if the speed gets even higher, speed * speed can become too big and your step will take you to a very high speed in the opposite direction...) Generally it's not worth putting that kind of thing in, unless you find a gameplay reason for it. Definitely it's a lower priority than sorting out the logic. -- |
Nortski
Member #15,933
April 2015
|
OK very close now! The car rolls to a stop in both directions now, yey. But there's a little flaw that's annoying me. If I press a direction key, say UP, release before I reach maxSpeed and then quick press the opposite direction key, the car gains a sudden boost in acceleration in the original direction. 1void MoveCar(Car &object)
2{
3 if (keys[UP] == true && keys[DOWN] == false)
4 {
5 object.x += cos(object.angle) * object.velocity;
6 object.y += sin(object.angle) * object.velocity;
7 object.velocity += acceleration;
8 if(object.velocity > object.maxVelocity)
9 {
10 object.velocity = object.maxVelocity;
11 acceleration = 0;
12 }
13 }
14
15 else if (keys[DOWN] == true && keys[UP] == false)
16 {
17 object.x += cos(object.angle) * object.velocity;
18 object.y += sin(object.angle) * object.velocity;
19 object.velocity += acceleration;
20 if(object.velocity < -(object.maxReverseVelocity))
21 {
22 object.velocity = -object.maxReverseVelocity;
23 acceleration = 0;
24 }
25 }
26
27 else if (keys[UP] == false && object.velocity > 0)
28 {
29 object.x += cos(object.angle) * object.velocity;
30 object.y += sin(object.angle) * object.velocity;
31 object.velocity -= 0.2;
32 if(object.velocity < 0)
33 {
34 object.velocity = 0;
35 acceleration = 0;
36 }
37 }
38 else if (keys[UP] == true && object.velocity && keys[DOWN] == true)
39 {
40 object.x += cos(object.angle) * object.velocity;
41 object.y += sin(object.angle) * object.velocity;
42 object.velocity += acceleration;
43 if(object.velocity > object.maxVelocity)
44 {
45 object.velocity = object.maxVelocity;
46 acceleration = 0;
47 }
48
49 if(object.velocity < -(object.maxReverseVelocity))
50 {
51 object.velocity = -object.maxReverseVelocity;
52 acceleration = 0;
53 }
54 }
55 else if (keys[DOWN] == false && object.velocity < 0)
56 {
57 object.x += cos(object.angle) * object.velocity;
58 object.y += sin(object.angle) * object.velocity;
59 object.velocity += 0.2;
60 if(object.velocity > 0)
61 {
62 object.velocity = 0;
63 acceleration = 0;
64 }
65 }
66}
|
Chris Katko
Member #1,881
January 2002
|
This code hurts my brain a little. There's lots of nested ifs/elseifs, and duplicate code. Whenever that happens, you need to take a step back and make sure you're not over-complicating things. void MoveCar(Car &object) { if (keys[UP] == true && keys[DOWN] == false) { object.x += cos(object.angle) * object.velocity; object.y += sin(object.angle) * object.velocity; object.velocity += acceleration; if(object.velocity > object.maxVelocity) { object.velocity = object.maxVelocity; acceleration = 0; } } //... etc Why not do something more like this: 1void MoveCar(Car &object)
2 {
3
4 if(keys[UP])
5 {
6 acceleration += 0.1;
7 if(acceleration > max_acceleration)acceleration = max_acceleration;
8 }
9 if(keys[DOWN])
10 {
11 acceleration -= 0.1;
12 if(acceleration < max_reverse_acceleration)acceleration = max_reverse_acceleration;
13 }
14 if(object.velocity > object.maxVelocity)
15 {
16 object.velocity = object.maxVelocity;
17 acceleration = 0;
18 }//likewise for reverse
19 object.velocity += acceleration; //ADD acceleration AFTER you check/set it, not before.
20
21//ADD velocity to position AFTER you've set velocity.
22 object.x += cos(object.angle) * object.velocity;
23 object.y += sin(object.angle) * object.velocity;
24
25
26//rolling resistance
27 if(object.velocity > 0)
28 {
29 object.velocity -= 0.2;
30 if(object.velocity < 0)object.velocity = 0; //Make sure we don't go from +.1 to -.1!
31 //the car shouldn't be going backwards after it slows down.
32 }
33//likewise for moving reverse
34}
Note how the velocity and acceleration code gets called EVERY FRAME regardless of whether or not you're pressing a key. Only use the code in the key if statements to change the acceleration/angle, not to do logic that needs to be done regardless of whether the key is down or not. -----sig: |
Bruce Perry
Member #270
April 2000
|
Sorry, I think I confused you I wanted you to think about what your existing code would do in those four states, to help you see that your second 'if' would always be entered if reached, and your third 'if' would never even be reached. It wasn't my intention to get you to write separate 'if' blocks for all those four states. Chris has largely done the work for you, but you will have to read through it and add some missing blocks where commented. When I talked about lines you might be repeating unnecessarily, I meant the ones with sin and cos in them, which you can see Chris only has once. Hope we've helped -- |
Nortski
Member #15,933
April 2015
|
Both posts were a lot of help. I only have the cos and sin lines in once now. But, I now have ten IF statements! haha Once my understanding of logic develops I'm sure scenarios like this will be easily streamlined. As it is, the car now moves pretty much as I want it to, although there are still tweaks to be made. |
|