XNA JigLibX Vehicle Class Modifications by Nikescar *Updated* & More

Adding a Proper Hand Brake, Tuning Capabilities & More
       The standard vehicle class has a hand brake that locks the rear wheels, thereby creating some drag and slowing the car down. That part is realistic. What isn't realistic is the rear wheels still have full traction. If you've ever pulled a hand brake in a car while moving you've notice that the rear end loses traction and wants to swing around causing a spin. It can be useful for making tight 180 degree turns, orienting your car coming into a turn and for good 'ol fashioned fun.

We'll have to start out by adding to Car.cs (changes/additions commented "new")

namespace JigLibX.Vehicles
{

    public class Car
    {
        enum WheelId
        {
            WheelBR = 0,
            WheelFR = 1,
            WheelBL = 2,
            WheelFL = 3,
            MaxWheels = 4
        }
        #region private fields
        private Chassis chassis;
        private List wheels; 

        private bool fWDrive;
        private bool bWDrive;
        private float maxSteerAngle;
        private float steerRate;
        private float wheelFSideFriction; // new
        private float wheelRSideFriction; // new
        private float wheelFwdFriction;
        private float wheelRwdFriction; // new
        private float wheelTravel;
        private float wheelRadius;
        private float wheelZOffset;
        private float wheelRestingFrac;
        private float wheelDampingFrac;
        private int wheelNumRays;
        private float topSpeed; // new
        private float driveTorque;
        private float gravity;

        // control stuff
        private float destSteering = 0.0f; // +1 for left, -1 for right
        private float destAccelerate = 0.0f; // +1 for acc, -1 for brake

        private float steering = 0.0f;
        private float accelerate = 0.0f;
        private float hBrake = 0.0f;

        #endregion

        /// On construction the physical/collision objects are created, but
        /// not registered
        // new
        public Car(bool FWDrive, bool RWDrive, float maxSteerAngle, float steerRate, float wheelFSideFriction, float wheelRSideFriction,
             float wheelFwdFriction, float wheelRwdFriction, float wheelTravel, float wheelRadius, float wheelZOffset, float wheelRestingFrac,
             float wheelDampingFrac, int wheelNumRays, float topSpeed, float driveTorque, float gravity)
        // new
        {
            this.fWDrive = FWDrive;
            this.bWDrive = RWDrive;
            this.maxSteerAngle = maxSteerAngle;
            this.steerRate = steerRate;
            this.wheelFSideFriction = wheelFSideFriction; // new
            this.wheelRSideFriction = wheelRSideFriction; // new
            this.wheelFwdFriction = wheelFwdFriction;
            this.wheelRwdFriction = wheelRwdFriction; // new
            this.wheelTravel = wheelTravel;
            this.wheelRadius = wheelRadius;
            this.wheelZOffset = wheelZOffset;
            this.wheelRestingFrac = wheelRestingFrac;
            this.wheelDampingFrac = wheelDampingFrac;
            this.wheelNumRays = wheelNumRays;
            this.topSpeed = topSpeed; // new
            this.driveTorque = driveTorque;
            this.gravity = gravity;

            chassis = new Chassis(this);

            SetupDefaultWheels();
        }


More changes...

wheels[(int)WheelId.WheelBR].Setup(this,
                          BRPos,
                          axis,
                          spring,
                          wheelTravel,
                          inertia,
                          wheelRadius,
                          wheelRSideFriction, // new
                          wheelRwdFriction, // new
                          damping,
                          wheelNumRays);

            wheels[(int)WheelId.WheelFR].Setup(this,
                          FRPos,
                          axis,
                          spring,
                          wheelTravel,
                          inertia,
                          wheelRadius,
                          wheelFSideFriction,
                          wheelFwdFriction,
                          damping,
                          wheelNumRays);

            wheels[(int)WheelId.WheelBL].Setup(this,
                          BLPos,
                          axis,
                          spring,
                          wheelTravel,
                          inertia,
                          wheelRadius,
                          wheelRSideFriction, // new
                          wheelRwdFriction, // new
                          damping,
                          wheelNumRays);

            wheels[(int)WheelId.WheelFL].Setup(this,
                          FLPos,
                          axis,
                          spring,
                          wheelTravel,
                          inertia,
                          wheelRadius,
                          wheelFSideFriction,
                          wheelFwdFriction,
                          damping,
                          wheelNumRays);
        }


and some more additions...

wheels[(int)WheelId.WheelBL].Lock = (hBrake > 0.5f);
            wheels[(int)WheelId.WheelBR].Lock = (hBrake > 0.5f);          

            // new
            wheels[(int)WheelId.WheelFL].MaxAngVel = topSpeed;
            wheels[(int)WheelId.WheelFR].MaxAngVel = topSpeed;
            wheels[(int)WheelId.WheelBL].MaxAngVel = topSpeed;
            wheels[(int)WheelId.WheelBR].MaxAngVel = topSpeed;

            // new
            if (hBrake > 0.5f)
            {
                wheels[(int)WheelId.WheelBL].Side = 2.0f;
                wheels[(int)WheelId.WheelBR].Side = 2.0f;
            }
            else
            {
                wheels[(int)WheelId.WheelBL].Side = this.wheelRSideFriction;
                wheels[(int)WheelId.WheelBR].Side = this.wheelRSideFriction;
            }
            // new

I'll talk about the 2.0f later.


Now for some similar changes in CarObject.cs

namespace JiggleGame.PhysicObjects
{
    class CarObject : PhysicObject
    {

        private Car car;
        private Model wheel;

        public CarObject(Game game, Model model,Model wheels, bool FWDrive,
                       bool RWDrive,
                       float maxSteerAngle,
                       float steerRate,
                       float wheelFSideFriction, // new
                       float wheelRSideFriction, // new
                       float wheelFwdFriction,
                       float wheelRwdFriction, // new
                       float wheelTravel,
                       float wheelRadius,
                       float wheelZOffset,
                       float wheelRestingFrac,
                       float wheelDampingFrac,
                       int wheelNumRays,
                       float topSpeed,
                       float driveTorque,
                       float gravity)
            : base(game, model)
        {
            // new
            car = new Car(FWDrive, RWDrive, maxSteerAngle, steerRate,
                wheelFSideFriction, wheelRSideFriction, wheelFwdFriction,  
                wheelRwdFriction, wheelTravel, wheelRadius,
                wheelZOffset, wheelRestingFrac, wheelDampingFrac,
                wheelNumRays, topSpeed, driveTorque, gravity);
            // new

            this.body = car.Chassis.Body;
            this.collision = car.Chassis.Skin;
            this.wheel = wheels;

            SetCarMass(100.0f);
        }


And at the top of Wheel.cs under 'private fields' 
       // things that change   
        private float angVel;
        private float steerAngle;
        private float torque;
        private float driveTorque;
        private float axisAngle;
        private float displacement; // = mTravel when fully compressed  
        private float upSpeed; // speed relative to the car  
        private bool locked;
        private float maxAngVel;


And still in the Wheel.cs

        public bool Lock
        {
            get { return locked; }
            set { locked = value; }
        }

        // new
        public float Side
        {
            get { return sideFriction; }
            set { sideFriction = value; }
        }

        // new
        public float MaxAngVel
        {
            get { return maxAngVel; }
            set { maxAngVel = value; }
        }

Also, delete the line float maxAngVel = 300; in the Update() method.

That should almost do it. Get some speed then turn hard and press 'b' (or whatever you have it mapped to) to see the improved hand brake.


Adding Tuning Capabilities 
       A bonus to all this work for the hand brake is you now have a lot more freedom to fine tune your car. Instead of having to change friction of all four tires you can change the front and rear seperately to get a car that drifts or handles however you want.

Open JiggleGame.cs and paste the following in place of the old variables

carObject = new CarObject(this, carModel, wheelModel, true, true, 30.0f, 5.0f, 3.6f, 2.6f,
                .5f, 1f, 0.3f, 0.4f, -0.10f, 0.5f,
                0.9f, 1, 250.0f, 400.0f, physicSystem.Gravity.Length());

250.0f is the new top speed variable. These settings should be a fun place to start. You'll also notice that some of these settings give the car body roll which not only looks better but is more realistic.

This setup is a little more drift car like:

carObject = new CarObject(this, carModel, wheelModel, true, true, 30.0f, 5.0f, 3.6f, 3.0f,
                .5f, 1f, 0.20f, 0.4f, 0.05f, 0.5f,
                0.9f, 1, 250.0f, 400.0f, physicSystem.Gravity.Length());

Keep in mind that you likely won't be able to keep a drift if you're using the keyboard.

3.6f is the front lateral friction and 2.6f is the rear. These settings will make the rear slide out when taking a corner to fast. If they are even the car will never spin out.

.5f is the front longitudinal (I think that's what it's called or acceleration, should've gone to college) friction and 1f is the rear. Having a bigger number in the rear let's you keep a drift around corners.

Both these settings are proportional between the front and rear so 4f in front and 3f in rear will handle pretty similar to 5f in front and 3.75f in rear. Play with these till you get them where you want.

Now back to the 2.0f. This adjusts the lateral friction everytime the hand brake is activated letting the rear end slide around. Since it doesn't adjust the longitudinal friction the locked tires still create the same amount of drag as before.

An updated version of the car settings is below and thoroughly explained:

carObject = new CarObject(this, carModel, wheelModel,
                true,    // front wheel drive 
                true,    // rear wheel drive 
                35.0f,  // max steering angle 
                5f,       // steering rate 
                3.6f,    // front lateral traction 
                3.1f,    // rear lateral traction 
                1f,       // front longitudinal traction 
                2f,       // rear longitudinal traction 
                2.5f,    // handbrake rear lateral traction 
                .75f,    // handbrake rear longitudinal traction 
                6f,       // starting slide factor 
                15f,     // threshold 1 slide factor 
                30f,     // threshold 2 slide factor 
                .7f,      // slip threshold 1 
                10.0f,  // slip threshold 2 
                2.0f,    // slide speed factor
                0.7f,    // traction loss factor on slip 
                0.3f,    // suspension travel 
                0.45f,  // wheel radius 
                -0.10f, // wheel mounting point  
                0.6f,    // spring rate 
                0.6f,    // shock dampening 
                2,        // wheel rays 
                2.5f,    // roll resistance 
                300.0f, // top speed 
                500.0f, // torque 
                physicSystem.Gravity.Length() // gravity 
                );


       This is a good place to start and should be very usable for a wide variety of games. There are still many features I want to add later such a nitro boost. A little more in depth explanation of these settings is in the CarObject.cs (car object class). Next time I'll go into what all the CarObject settings do as well as a few new tweaks to the car class.


I would also suggest you change the gravity in PhysicsSystem.cs from -10 to -30
        public PhysicsSystem()
        {
            CurrentPhysicsSystem = this;
            Gravity = -30.0f * Vector3.Up;
        }

This will make the car and physics in general less like the moon and more like Earth.


Adding a Wheel Skid Event

Physics may or may not be my strong suit.  I'm still trying to figure that out.


For adding smoke when the car skid you have to know when the tires are skidding (I know, obvious).  You do that in the Wheel.cs.

Add to the fields:

private bool wheelSkid;

Change this:

    if ((fwdVel > slipVel) || (fwdVel < -slipVel))
                friction *= slipFactor;
            else
                if ((fwdVel > noslipVel) || (fwdVel < -noslipVel))
                    friction *= 1.0f - (1.0f - slipFactor) * (System.Math.Abs(fwdVel) - noslipVel) / (slipVel - noslipVel);

To this:

            if ((sideVel > slipVel) || (sideVel < -slipVel))
            {
                friction *= slipFactor;
                wheelSkid = true;
            }
            else
            {
                wheelSkid = false;
                if ((sideVel > noslipVel) || (sideVel < -noslipVel))
                    friction *= 1.0f - (1.0f - slipFactor) * (System.Math.Abs(sideVel) - noslipVel) / (slipVel - noslipVel);
            } 

Add near the other returns at the end of the class:

        public bool WheelSkid 
        { 
            get { return wheelSkid; } 
        } 

Now in the Car.cs add:

private bool skid; 

and add this in the PostPhysics method:

            if (wheels[(int)WheelId.WheelFL].WheelSkid | wheels[(int)WheelId.WheelFR].WheelSkid | 
                wheels[(int)WheelId.WheelBL].WheelSkid | wheels[(int)WheelId.WheelBR].WheelSkid) 
            { 
                skid = true; 
            } 
            else skid = false; 

You can pick whichever wheels you want to create smoke when a skid occurs, like the rear only for instance.

then add a return with the rest of the returns:

        public bool Skid 
        { 
            get { return skid; } 
        } 

You would then check for skid=true by calling carObject.Car.Skid and telling the emitter to initialize.


I hope this helps someone.