Space Racers Breakdown Part 3: Vehicle Handling

Part 2: Node Navigation

I've actually wanted to post about the vehicle physics for several months now, but it's such a vast topic I've never really known how to approach it, even when I have had the time to write something. Now, though, I've decided to break it into smaller parts, starting with the handling of the vehicles. There are numerous resources out there on the interwebs which cover how to do this sort of thing, and I'll link as many as I can remember, although not all are directly pertinent to Space Racers - which this post is about. The first option I looked at was the ever-present box2D, and there are many useful articles based around it, but in the end it just didn't feel right to me. This left me with one other option, which was to attempt to try to understand and create my own vehicle physics system. No small task. Credit to the authors of the afore-linked articles; although I chose not to use box2D the articles themselves did help explain what I needed to do to get my own system up and running.

    First off, then, was to get some basic movement going. The movement of a vehicle (or any object for that matter) in 2D can be broken down as:

directionVector * speed * dt;

directionVector is a two component unit vector which represents the direction the vehicle is facing. Note that it is important to use a unit vector else multiplying it with any number will lead to an incorrect magnitude. speed is a floating point scalar which represents the number of units per second at which the vehicle is currently traveling. dt is the delta time - the time elapsed since the last frame update. Multiplying the movement by dt will make sure the final movement represents the distance the vehicle would have traveled between two frames. Calculating the directionVector is pretty easy in SFML. Assuming you have a Sprite or Shape representing your vehicle you can use its on-screen rotation to rotate the normal vector taken from the front of the vehicle. This can be broken down as:

const sf::Vector2f frontNormal(0.f, 1.f); //pointing down
sf::Transform t;
t.rotate(vehicle.getRotation());
directionVector = t.transformPoint(frontNormal);

Rather than use cos() and sin() to rotate the vector I use the SFML Transform class which uses a 3x3 matrix that can perform translation, rotation and scale calculations, and is computationally lighter than a call to cos() and sin(). I don't use the vehicle's getTransform(), however, as this will also include translation and scale which I don't want applied to the unit vector.

    Speed is generally not a constant, we want to start and stop our vehicle, and, rather than come to a dead start or stop on key presses, it would be nice to employ some acceleration and deceleration. This is pretty straight forward too, set up a few constants - maxSpeed and minSpeed (although minSpeed could just be 0)  and acceleration and deceleration (which are, again, in units per second). Then the current speed can be calculated:

if(sf::Keyboard::isKeyPressed(accelerate))
{
    currentSpeed += acceleration * dt;
    if(currentSpeed > maxSpeed) currentSpeed = maxSpeed;
}

else
{
    currentSpeed -= deceleration *dt;
    if(currentSpeed < minSpeed) currentSpeed = minSpeed;
}

acceleration and deceleration are also multiplied by dt, so that the increase / decrease in speed is smooth and consistent between frames. Assuming you also handle key presses for rotating the vehicle this is all that's needed to get a vehicle moving around on screen. If you try it you'll notice you get movement similar to that of Asteroids - where the vehicle can rotate while still traveling in the same direction. If you want to try it out there's a gist here which will work with SFML 2.x.

This isn't the most natural handling in the world, and so this is where it gets a bit more complicated. The first and easiest thing I found to add was to add a 'wheelspin' effect to the vehicle when turning sharply and accelerating in the opposite direction. By taking the dot product of the vehicle's direction vector before and after a sharp rotation we get a value, handily between 0 - 1 thanks to the use of direction as a unit vector, which can be used to multiply the move speed. This is most notable in the sample code when turning ~180 degrees before accelerating in the opposite direction - the speed appears to slow right down before speeding up, rather than letting the vehicle shoot off in the new direction at its current speed. When rotating smaller amounts, however, this effect does not work so well. In fact this was a real head scratcher for me, and it took a lot of reading before I found this post which provided a convenient way of controlling how much lateral (sideways) motion the vehicle should have. The lateral motion is what gives the feeling of skidding when turning - and is not controlled at all in the demo - which is why the vehicle feels so 'floaty'. A sideways vector can be created by using a constant value and rotating it about the vehicle, the same way as the front facing vector. The only difference is the the constant value will be (-1.f, 0.f) rather than (0.f, 1.f). Taking the dot product of this rotated vector and the current movement vector we get a value based on how far the vehicle has rotated compared to its current velocity. It's then fairly trivial to reduce the sideways velocity by multiplying it by the dot product - a value which can also be controlled by, say, a handbrake which allows more skidding (lessens the lateral force reduction) when it is activated.

    That pretty much sums up the basic handling in Space Racers. It's probably not the most ideal method - it's certainly not the most advanced, but it is configurable enough that I can create different vehicles with different styles of handling. It's just one of those things that can be tweaked almost indefinitely. It certainly gave me a healthy respect for the programmers who worked on games such as the original Micro Machines. In the next post I plan to write about how the vehicles interact with each other, and collisions between the map/race track.

Popular Posts