Monday, 12 April 2010

Simple 2D car steering physics in games

How to do a realistic moving car in only 6 lines of code!

I find nothing more frustrating in car games than when the developer has implemented some ridiculously unrealistic model for how the car moves. For most games, especially flash-based mini-games, simulation grade 3D physics are not required, but still the car should move and turn roughly like you'd expect a real car to.

Have a quick go at this car parking game, or this one, try to turn into a parking space, something doesn't feel right. More specifically the rear wheels are sliding sideways whenever you turn - this does not (normally) happen in a real car, and it makes this game very hard for all of us used to controlling a real car. I'll show you here how to get better physics than this in just half a dozen lines of code.


The very simple assumption that we will start with is that each wheel can only move in the direction it is pointing. This is actually a very good approximation for normal driving, and exactly appropriate for these kinds of parking games.  The other simplification we will make is to use what is called a bicycle model, we imagine that the car has just two wheels; one at the front in the middle to steer, and one at the back in the middle that cannot steer.  Of course we can draw a real car with four wheels, but the physics will only be considering two wheels at the centre of each axle.

To keep track of the state of our car in the game we probably have something like:

Vector2 carLocation;
float carHeading;
float carSpeed;
float steerAngle;
float wheelBase; // the distance between the two axles

Our physics algorithm should update the carLocation and carHeading each frame according to the above assumptions, and the input part of our game can update carSpeed and steerAngle based on the user input.  So how to actually calculate the next position and heading of the car?  It can be broken down into three steps:
  1. Find the world position of the front and back imaginary wheels
  2. Move each wheel forward in the direction it is pointing
  3. Calculate the new car location and heading from the new wheel positions
Step 1
We can use geometry calculations to find the actual positions of the wheels (which are at the centre of each axle). The gap between the axles is given by the wheelBase, so each wheel is half that distance from the car centre, in the direction that the car is facing. The following diagram shows the geometry:

And the code:
Vector2 frontWheel = carLocation + wheelBase/2 * new Vector2( cos(carHeading) , sin(carHeading) );
Vector2 backWheel = carLocation - wheelBase/2 * new Vector2( cos(carHeading) , sin(carHeading) );

Step 2
Each wheel should move forward by a certain amount in the direction it is pointing. The distance it needs to move depends on the car speed, and the time between frames (I'll call it dt here). The rear wheel is easy, it moves in the same direction the car is heading. For the front wheel, we have to add the steer angle to the car heading as the diagram shows:


And the code:
backWheel += carSpeed * dt * new Vector2(cos(carHeading) , sin(carHeading));
frontWheel += carSpeed * dt * new Vector2(cos(carHeading+steerAngle) , sin(carHeading+steerAngle));

Step 3
The new car position can be calculated by averaging the two new wheel positions. The new car heading can be found by calculating the angle of the line between the two new wheel positions:


In code:
carLocation = (frontWheel + backWheel) / 2;
carHeading = atan2( frontWheel.Y - backWheel.Y , frontWheel.X - backWheel.X );

And that's it - a total of 6 lines of code to get a simple car to move realistically.

48 comments:

  1. Hello,

    Thanks for your code. It was helpful, but i'm still having problems to make the car move right.

    My car looks like in a soap road...sliding or spinning like a fan.

    Can you give some example using the structure of a XNA Game? Like...putting the code into the right methods?

    Best regards.

    ReplyDelete
    Replies
    1. you have to add friction you dumb dumb

      Delete
  2. Assuming you have a Car class, you would need to put the 5 fields I mention at the top in this Car class. The Car's Update method would then include the 6 lines I mentioned. You probably also want to set the carSpeed and steerAngle variables based on some keyboard input.

    In the Car's Draw method you would then use carLocation and carHeading to actually draw the car.

    ReplyDelete
    Replies
    1. Hello engineer, I am doing a project on opengl to drive a tank, the tank was given to us the issue is to move the tank, could you explain your code a bit more. I am not also aable to add carLocation with wheelBase for some reason. any pointers would help

      Delete
    2. Vector2 is the XNA class for a 2D vector. You cannot add a scalar (like wheelBase) to a 2D vector. The first line of code multiplies wheelBase by a 2D direction vector (which results in a new 2D vector scaled by the value of wheelBase) and then adds that to carLocation.

      Delete
  3. Sweet.. Breaking down into two steps is something i had never considered.. thnx :)

    ReplyDelete
  4. Christopher Night1 September 2011 at 18:34

    Hey, I think there's a problem with this algorithm. It doesn't conserve the baseline, that is, the distance between the front and rear tire. Over time, if you move forward while turning, the baseline will shrink down to carSpeed * dt, and the motion will become completely unrealistic. I tested it out with an initial baseline of 50, carSpeed = 50, dt = 1/60, and steerAngle = 20 degrees. By frame 500 the baseline had shrunk down to 26.2.

    Maybe the most realistic way to rectify this is to have the front tire move extra in order to maintain the baseline. I get that the front tire must move a distance of sqrt(B^2+C)-B, where:

    B = (b-r) cos(steerAngle)
    C = r(2b-r)
    b is the size of the baseline
    r = carSpeed * dt is the distance the rear wheel moves this frame

    If you use this quantity in place of r in your update of frontWheel, the baseline should be conserved.

    ReplyDelete
    Replies
    1. I noticed this too. The change you added seemed to work.

      Delete
  5. Thanks for the reply Christopher, however you need to re-calculate all 6 lines every frame. This will recalculate the front and rear wheel positions from scratch based on the car center point and heading every frame. You'll then find the distance between the two wheels is always equal to wheelBase.

    ReplyDelete
  6. Christopher Night2 September 2011 at 16:50

    Ah, I understand. Yes, that definitely means the problem I was seeing was my mistake. It looks good!

    I think that my modification makes it slightly more realistic, but it's probably not worth the effort. Thanks for the response!

    ReplyDelete
  7. Yes your solution is definitely more accurate, and at very sharp steering angles (nearer to 90 degrees rather than straight ahead) it should make a visible difference. I think we also have to modify the rear wheel distance, as at a 90 degree steer angle the rear wheel should stay stationary - also how to define what carSpeed means in this situation?

    But whichever way you use, it's not a good idea to keep track of each wheel totally separately, they will drift apart due to numerical precision errors no matter how good the maths is.

    ReplyDelete
  8. how you work with floats and int conversion?

    cos(angle)* speed; 2.40232;
    posx and posy are natural numbers 1,2,3,4 ...
    i have troubles with that.. the "car" is moving to the left and up the screen.

    (i use SDL to simulation)

    ReplyDelete
  9. Here I'm just using floats, but I understand the reasons for wanting to avoid them for posx and posy (accuracy loss with large values).

    If cos(angle)*speed*dt is coming out around 2.4, then you shouldn't use ints alone for the position, there isn't enough resolution to make the car move the direction it is facing.

    If you need to avoid doubles/floats then try fixed point, it will work if you use enough bits in the fractional part, but the math become a little more complex. You might even need to use a separate variable to store the fractional part of the result (and then posx and posy can remain as integers). This should solve your problem.

    ReplyDelete
  10. hello. i hope this isn't too much to ask, but could you do the same example, but without the vectors. this is like the only example ive found on the net i can understand, all the others are waay to complicated. the problem im having is that i write in blitz basic. and it doesn't use vectors. ive tried converting your code but it doesnt seem to work for me. so could you try to write the code again, except without all the vector operations?

    ReplyDelete
  11. Hi Anonymous, it will work if you just do the x and y component separately for each line.

    In your case you will need to keep track of carLocationX and carLocationY as separate variables from frame to frame. Within each frame you then need to do the steps I mentioned, but split into X and Y components. Something like this:

    frontWheelX = carLocationX + wheelBase/2 * cos(carHeading);
    frontWheelY = carLocationY + wheelBase/2 * sin(carHeading);

    backWheelX = carLocationX - wheelBase/2 * cos(carHeading);
    backWheelY = carLocationY - wheelBase/2 * sin(carHeading);

    backWheelX += carSpeed * dt * cos(carHeading);
    backWheelY += carSpeed * dt * sin(carHeading);

    frontWheelX += carSpeed * dt * cos(carHeading+steerAngle);
    frontWheelY += carSpeed * dt * sin(carHeading+steerAngle);

    carLocationX = (frontWheelX + backWheelX) / 2;
    carLocationY = (frontWheelY + backWheelY) / 2;

    carHeading = atan2( frontWheelY - backWheelY , frontWheelX - backWheelX );

    Let me know if you're still having problems.

    ReplyDelete
  12. thank you so much it works perfectly now. i see what i did wrong, i had the variables aligned in the wrong order. lol now have to learn how to make the car drift. *prepares self for difficult journey into car physics*

    ReplyDelete
  13. If you want physically correct drifting with realistic control, then you want to totally ignore this tutorial and start again considering forces and accelerations on the tyres. However there is a quick hack I found that you can add to this tutorial that gives a rough impression of drifting for very little effort.

    The trick is to create a delay on the carHeading value, so that the value used in the sin and cos terms when moving the wheels forwards, is the value from a few frames earlier. But still use the instantaneous value for drawing the car, so this gives the impression that the car is sliding sideways a bit (the length of the delay controls how much the car will slide). It might take some tweaking to get it to work, because after all this is just a pure hack to give a very rough approximation of drifting.

    ReplyDelete
  14. Hi

    Firstly thanks for the great tutorial. I'm having a bit of a problem though (using C#). My car heading value keeps getting stuck between two values and my car just rotates a little to the left and then back to the right, even when I am holding just one key (eg. 'a' key).

    I'm using this atm

    carHeading = (float)Math.Atan2(Convert.ToDouble(frontWheel.Y - backWheel.Y), Convert.ToDouble(frontWheel.X - backWheel.X));

    I have to convert to double because the functions take doubles, but then i have to change back to float because Vectors use float.

    Thanks if you can help me any.

    ReplyDelete
  15. Hi James, that line looks fine to me, the mistake must be in the rest of your code. Are you running all 6 lines of code every frame? What if you fix steerAngle and carSpeed to be constant - does the car drive around in a circle?

    ReplyDelete
  16. Hey TheEngineer

    I think i may have fixed it! And it is all thanks to you suggesting fixing the steerAngle and carSpeed to a constant. That really helped me find out what was really going on. Good call :)
    It seems to be working as expected right now.

    What i'd like to try now is to get the steer angle to sort of square up with the car heading while not holding down the left or right (a or d) keys. If you know what i mean? At the moment. Sort of how when you let go of the steering wheel in a real car the wheels will go back straight. I think this might just be as easy as adding or subtracting to the steering angle until it is the same as the car heading?

    Once again thanks!

    - James

    ReplyDelete
  17. Hi James, glad you got it working. To return the steering angle back to zero, I usually just subtract a fixed amount back towards zero when neither left nor right are being pressed. Watch out not to overshoot zero though. Something like:

    const float returnRate = 0.01 * dt;
    if( !A_Pressed && !D_Pressed)
    {
    float delta = steerAngle;
    if( delta > returnRate ) delta = returnRate;
    if( delta <-returnRate ) delta =-returnRate;
    steerAngle -= delta;
    }

    You can adjust returnRate to set how quickly the wheels return back to centre. I prefer it to be a bit faster than the rate at which the wheels turn when holding down the key - you then let the player get some quite smooth turning by just tapping the a or d keys.

    You could even make returnRate proportional to the car speed, that might be more realistic (when you are stopped the wheels then wouldn't return back, which is correct).

    ReplyDelete
  18. Thanks for the simple and easy to follow tut on steering.

    For those interested in drifting, another option/hack is to create a 'carDriftHeading' variable that is calculated something like this;
    carDriftHeading = carHeading + (steerAngle * (carSpeed/carSpeedMax) * driftDampening)

    This doesn't actually produce any real drift, rather the illusion of drift by over rotating the car at high speeds so may not suit some situations but it will give almost no drift at low speeds and a lot of drift at high speeds so it is somewhat realistic and when tunned right, provides a fun driving experience.

    I suspect combining a little of this approach and a little of the approach
    'The Engineer' mentions above might be enough to get a lo-fi game up and running.

    ReplyDelete
  19. Hey TheEngineer,

    Thanks for the tutorial. I have been looking for days for a simple car driving algorithm for days!

    That said, I'm having some problem with my code. For some reason my car simply jitters in place, maybe moving like 2 pixels back and forth not sure why. I think the problem might be converting to and from Radians to Degrees. The SteerAngle is set to 45 degrees and the CarSpeed is 1.5f.

    float controlAngle = 45f;
    float carFacingAngle = MathUtils.radiansToDegrees * mBody.getAngle();
    steerAngle = controlAngle - carFacingAngle; // angle from Car to Control

    carSpeed = 1.5f;

    frontWheel.x = carLocation.x + wheelBase/2 * MathUtils.sin(carHeading);
    frontWheel.y = carLocation.y + wheelBase/2 * MathUtils.cos(carHeading);

    backWheel.x = carLocation.x - wheelBase/2 * MathUtils.sin(carHeading);
    backWheel.y = carLocation.y - wheelBase/2 * MathUtils.cos(carHeading);
    backWheel.x += carSpeed * deltaTime * MathUtils.sin(carHeading);
    backWheel.y += carSpeed * deltaTime * MathUtils.cos(carHeading);

    frontWheel.x += carSpeed * deltaTime * MathUtils.sin(carHeading+steerAngle);
    frontWheel.y += carSpeed * deltaTime * MathUtils.cos(carHeading+steerAngle);

    carLocation.x = (frontWheel.x + backWheel.x) / 2;
    carLocation.y = (frontWheel.y + backWheel.y) / 2;

    carHeading = MathUtils.atan2( frontWheel.y - backWheel.y , frontWheel.x - backWheel.x );

    I swapped the Sin and Cos because in LibGDX angles are backwards I think, so 90 degrees faces left instead of up.

    ReplyDelete
    Replies
    1. Hi Abdullah,

      I'm not sure how you are calculating steerAngle, but this is meant to be the angle of the front wheels relative to straight ahead. Try setting this to 0 and check the car goes straight. If not I would simplify the whole thing down to just one line like:

      carLocation.x += carSpeed * deltaTime;

      Then at least the car should move horizontally at a constant speed (although it might not face the right way). If even that doesn't work then you've got an error somewhere else in your code.

      Let me know how it goes.

      Delete
    2. So I found out what the problem is. Apparently in LibGDX Sprites and Box2d Bodies have their positions at the bottom left corner of the object. So the carLocation would be (0,0) instead of say (3,3) for an object that has all sides the length of 6.

      I guess what I need to do is figure out a way to either move the origin of the position to the middle of the sprite or reset the calculations to take that into account.

      I think I'll try to fix the positioning so that the return position is the center not the bottom left.

      Delete
  20. Thanks for this article, i was loocking for a simple steering physics and it helps me a lot.
    As the rear wheel should follow the front wheel, may i propose some modifications to make it slightly more realistic :

    // Old front wheel position
    Vector2 frontWheel = carLocation + wheelBase/2 * new Vector2( cos(carHeading) , sin(carHeading) );

    // New front wheel position
    frontWheel += carSpeed * dt * new Vector2(cos(carHeading+steerAngle) , sin(carHeading+steerAngle));

    // Old back wheel position
    Vector2 backWheel = carLocation - wheelBase/2 * new Vector2( cos(carHeading) , sin(carHeading) );

    // new car heading
    carHeading = atan2( frontWheel.Y - backWheel.Y , frontWheel.X - backWheel.X );

    // new car location
    carLocation = frontWheel - wheelBase/2 * new Vector2( cos(carHeading) , sin(carHeading) );

    ReplyDelete
    Replies
    1. Thanks for your alternative algorithm idea - it got me thinking! It's easy to test the accuracy of your idea by using a specific input to the model with a very small timestep and then seeing how inaccurate it gets with larger timesteps.

      So you can hopefully replicate the results I used the following settings: Car starting with centre point at (0,0) and heading 0 degrees, wheel base of 2 units, a speed of 1 unit per second and a steer angle of 5 degrees. I ran this setup for 1 second simulation time and then output the final car heading.

      First off I used a dt of 0.001 and got 2.497 degrees with my algorithm described above and 2.496 degrees with your algorithm - so it seems pretty close (I assume using a smaller dt will get even closer results). Then I adjusted the timestep to 0.01 and got 2.497 again with my algorithm, but yours gave 2.484. With a dt of 0.1 mine still gives 2.497 and yours is down to 2.378.

      I think the source of the error in your algorithm is that you are trying to calculate the new angle of the car based on the old rear wheel position and the new front wheel position, so the result will obviously depend heavily on the value of the time-step (which it shouldn't). Mine uses the new updated position of both wheels so does not depend as much on the time-step value.

      The thing that initially rang alarm bells with your idea was that there is no physical reason why the front wheel should be treated any differently to the rear wheel - both should be moved forwards the direction they are pointing each time step, and then it doesn't matter if the car is going forwards or backwards, or if the front or rear wheels (or both) are being steered.

      Delete
  21. Thank you so much for this! :o)

    Could I just ask what the draw code would be, as I am getting it all wrong, with regards to position and rotation of the sprite itself.

    ReplyDelete
    Replies
    1. Hi AWP, I'm using XNA in 3D for this, so my draw code simply builds a world matrix from the position and rotation of the car:

      Matrix mWorld = Matrix.CreateRotationY( heading ) * Matrix.CreateTranslation( location.X , -axleHeight + axles[0].wheelRadius, location.Z );

      This is then set in the normal way before drawing the mesh.

      In 2D you need to set the rotation of the sprite to "heading" and then translate it according to "location".

      Delete
  22. Thanks for the reply! Will try what you suggested.

    ReplyDelete
  23. Hello man!
    First of all, good tutorial! You have solved many questions for me with this.
    Now, my problem: i'm trying to adapt these code to Python, using Pygame.
    But, this isn't working good: the parts of my car are going in strange directions when I try to change the angle. But works perfectly using direction = PI, angle = 0.
    Can you help me with this?

    The code:
    distance = v*dt

    frontX = centerX + (math.cos(angle)*base/2) + (math.cos(angle+direction)*distance)
    frontY = centerY + (math.sin(angle)*base/2) + (math.sin(angle+direction)*distance)

    backX = centerX - (math.cos(angle)*base/2) + (math.cos(angle)*distance)
    backY = centerY - (math.sin(angle)*base/2) + (math.sin(angle)*distance)

    meioX = (frontX + backX)/2
    meioY = (frontY + backY)/2

    direction = math.atan((frontY- backY)/(frontX - backX))

    ReplyDelete
    Replies
    1. I forgot to wish you happy holidays new year! Happy new year!

      Delete
    2. Carlos, your last line of code "direction=..." should be "angle=...". Using more descriptive variable names helps to avoid mistakes like this.
      Merry Christmas!

      Delete
  24. Hi, nice code up in there, but I think there should be some correction in rear wheel movement, don't you think? I agree that each wheel can only move in its pointing direction and that makes the front wheels to move correclty. But when you are making a turn, the very next car position has always a new carHeading. That makes the rear wheels to have a new direction as well.

    With that in mind, your algorithm can only work with small dt in time increment. Or rather, your accuracy will be better the smaller your dt is.

    Think about a very tight turn with a large time step. The front wheels will move only a few to the front direction and a lot to the side direction.With this large time step, the next position of the rear wheels will not only be in the old carHeading direction but also will drift a little bit to the side of the turn.

    I'm not very good in expressing myselft, but can you understand what I am saying? If so, can you think of a good solution for this?

    ReplyDelete
    Replies
    1. Take a look at this image I've just made:

      http://i155.photobucket.com/albums/s312/Felipe_Rbias/car_physics_zps47f1e89a.png

      it represents what I'm trying to say. This still may not be the perfect solution but may be a good approximation making both green angles to be equal.

      Delete
    2. Hi Felipe, yes I understand your concern about the errors with large values of dt. I wrote the code with a frame rate of 30 or 60 fps in mind, and with this value of dt and "normal" car turning speeds the error is very small (less than 1%). The idea was to show that with only a few lines of code you can get a much better handling car than many web-games out there.

      If you did want to do it 100% accurately (eg you have a much bigger value of dt) it would be quite straightforward (but more than 6 lines of code!). First you need to find the instantaneous centre of rotation of the car, do this by drawing two lines out perpendicular from each wheel and find where they cross. Each wheel will then follow an arc around this centre point. Then for your value of dt you can calculate how far along the arc the wheel will have moved, and use this as the new positions. You value of dt can be as large as you want then and it will still be accurate, so long as the steering angle is constant throughout the time period.

      Delete
    3. Yes, that is it! And I agree that for small dt your code is very good indeed. This arc method is also very interesting and quite simple. I did not think about that. Thanks anyway and good luck with your blog!

      Delete
  25. This comment has been removed by a blog administrator.

    ReplyDelete
  26. Steering goes bananas when the car reverses, has anyone else found this, or have I got something wrong?

    ReplyDelete
  27. I fixed the steering problem I had, but I can't remember exactly what I did, it wasn't anything to do with your code.

    ReplyDelete
  28. The blog is unique that’s providing the nice material. Please post more interesting articles here.

    click Here

    ReplyDelete
  29. عندما يتعلق الأمر بالحفاظ على منزل أنيق ومرتب، لا تجعل تجتاح ScrewYOU عليك التخلي طويلة قبل ان الوقت قد حان لفصل الربيع التنظيف. بدلا من ذلك، كسر الوادي اللعين وظيفة (غسل الملابس، تنقية الحوض) بانخفاض في الاشياء الصغيرة يمكنك المسيل للدموع هنا وهناك. وهذه التغييرات الصغيرة إلى روتينك تساعد على الحفاظ على نظافة المنزل ومنظم مع الحد الأدنى من الوقت والجهد.
    شركة نقل اثاث بخميس مشيط
    شركة تنظيف فلل بخميس مشيط
    شركة مكافحة حشرات بخميس مشيط
    شركة كشف تسربات المياه بخميس مشيط
    شركة تنظيف بخميس مشيط
    شركة تنظيف بالرياض
    شركة نظافة بالرياض
    شركة تنظيف منازل بالرياض
    شركة تنظيف فلل بالرياض
    شركة كشف تسربات المياه بالرياض
    شركة نقل اثاث بالرياض
    شركة تخزين اثاث بالرياض
    شركة مكافحة حشرات بالرياض
    شركة مكافحة النمل الابيض بالرياض

    ReplyDelete
  30. Thanks for sharing this excellent data. Thank you!

    ReplyDelete
  31. Can you please help with a force diagram for a basic car steering? Anyone ? Please?

    ReplyDelete
  32. This is exactly what I was looking for. Thanks a lot!

    ReplyDelete
  33. Great tutorial - I used Carlos Jr's implementation as the engine I'm using doesn't have vectors. :)

    Has anyonѥ tried implementing a full Ackermann in 2d?


    https://en.wikipedia.org/wiki/Ackermann_steering_geometry

    ReplyDelete