• Suspension physics

  • Rolling and Sliding Friction for wheels

  • Ackermann Steering

  • Drifting

  • Controller for more arcade-style of driving

WHAT I WORKED ON

Role:

Systems Programmer

Language:

C#

Engine:

Unity 6

Team Size:

5

Project Duration:

2 weeks

Initial Sea Racing

  • As part of my education at Futuregames, I was tasked to participate in a group project where we had to make a racing game.


  • I was interested in how games simulated, or rather, approximated car physics, and how said physics was used to bring fun, arcade-like gameplay. And so I decided to program some car physics.


  • I started with implementing simple forces that would act on each wheel of the car. This would include suspension forces, rolling friction and sliding friction. I decided to not simulate torque since that would lean a lot more into realism, so I decided to use simple throttling forces instead.


  • I implemented a wheel class that would simulate the above-mentioned forces on each wheel. This way, wheels could be added and removed easily to a rigidbody.


  • I started with a simple controller that would do nothing but throttle, brake, reverse and steer the car. However, this proved to not be arcade-y and satisfying to control. I then proceeded to make another controller that derived from my base controller, which manipulated wheel properties at runtime to deliver a more arcade-y feel to driving a car.


OVERVIEW

  • I used a spring-damper equation to simulate spring forces on a rigidbody.


  • There is a rest length, a travel length dictating how far a spring could travel from rest length, spring constant and damping constant.


  • Sphere casts were used to detect where to place wheels.

CAR SUSPENSION PHYSICS

(C#) Suspension Physics

private void CalculateRestorationForce()
        {
            mspringVelocity = (mspringLengthCurrentFrame - mspringLengthPreviousFrame) / Time.fixedDeltaTime;
            mspringLengthPreviousFrame = mspringLengthCurrentFrame;

            //Calculate CHANGE in spring's length over time and take that as velocity
            //to multiply with the damping constant
            float displacement = mspringRestLength - mspringLengthCurrentFrame;
            float restorationForce = mspringConstant * displacement;
            float dampingForce = mspringVelocity * mspringDampingConstant;

            mspringForce = (restorationForce - dampingForce)
                           * mparentRigidbody.transform.up

  • Rolling Friction worked as a simple opposing force directly proportional to the rolling velocity.


  • Sliding friction was where it got interesting. Initially, I decided to implement a simple opposing force to directly oppose the sliding velocity just like I did for rolling friction. The reason I went for this approach was that applying an opposing force which depended on sliding acceleration was not enough when the car was stationary - it would still slip as the acceleration would be zero.


  • The approach with a simple opposing force, also resulted in a problem where the car would slide on it's own at high speeds, since the opposing force would be so high as the sliding velocity would also be high.


  • I solved the problem by opting for using acceleration-dependent sliding friction at high speeds, and velocity-dependent sliding friction at low speeds.

ROLLING AND SLIDING FRICTION

(C#) Rolling and Sliding Friction

private void CalculateSlidingFriction()
        {
            //-m*v works but when we get to high speeds, even when we don't need sideways friction, we'll
            //still have sideways forces getting applied even when we don't have to since the
            //velocity magnitude will be high.
            
            float slideVelocity = Vector3.Dot(mwheelVelocity, transform.right);
            float maxFriction = mgrip * mspringForce.magnitude;
            float desiredSidewaysFriction = 0.0f;
            
            //If we are stationary or moving very slowly, offset slide by adding a counter velocity,
            if (mparentRigidbody.linearVelocity.magnitude < 0.1f)
            {
                desiredSidewaysFriction = -mparentMass * slideVelocity;
            }
            //else just counter by using acceleration
            else
            {
                desiredSidewaysFriction = -mparentMass * slideVelocity / Time.fixedDeltaTime;
            }

            desiredSidewaysFriction = Mathf.Clamp(desiredSidewaysFriction, -maxFriction, maxFriction);
            mslidingFrictionForce = desiredSidewaysFriction * transform.right;
        }

        private void CalculateRollingFriction()
        {
            //Calculate rolling friction which is basically a linear function of velocity with a constant
            float rollingVelocity = Vector3.Dot(mwheelVelocity, transform.forward);
            float rollingFriction = mrollingFrictionConstant * mspringForce.magnitude;

            if (rollingVelocity < 0.1f && rollingVelocity > -0.1f)
            {
                mrollingFrictionForce = Vector3.zero;
                return;
            }
            
            mrollingFrictionForce = -Mathf.Sign(rollingVelocity) * rollingFriction * transform.forward

  • I decided to use Ackermann Steering as it seemed simple and intuitive.


  • I found a handy equation online that fulfilled the purpose, as well as researched for parameters from actual cars to use in that equation, and it turned out pretty good!

ACKERMANN STEERING

(C#) Ackermann Steering

//Steer the car left or right
        private void SteerCar()
        {
            if (msteerInput > 0.0f) //If we are steering right
            {
                mrightWheelSteerAngle = Mathf.Rad2Deg *
                                        Mathf.Atan2(mwheelBaseLength, mturnRadius - mrearTrackLength / 2) * msteerInput;
                mleftWheelSteerAngle = Mathf.Rad2Deg *
                                       Mathf.Atan2(mwheelBaseLength, mturnRadius + mrearTrackLength / 2) * msteerInput;
            }
            else if (msteerInput < 0.0f) //If we are steering left
            {
                mrightWheelSteerAngle = Mathf.Rad2Deg * 
                                        Mathf.Atan2(mwheelBaseLength, mturnRadius + mrearTrackLength / 2) * msteerInput;
                mleftWheelSteerAngle = Mathf.Rad2Deg *
                                       Mathf.Atan2(mwheelBaseLength, mturnRadius - mrearTrackLength / 2) * msteerInput;
            }
            else //Don't steer at all
            {
                mrightWheelSteerAngle = 0.0f;
                mleftWheelSteerAngle = 0.0f;
            }

            mfrontLeftWheel.SteerWheel(mleftWheelSteerAngle);
            mfrontRightWheel.SteerWheel(mrightWheelSteerAngle

  • I implemented an arcade-style controller that handled drifting for the car.


  • When the drift button is pressed, I decreased the grip(a.k.a. the sliding friction) on the hind wheels so that the car would slide when turning.


  • I added a counter force that would prevent the car from over-steering and spinning out.


DRIFTING

(C#) Drifting

private void DampenSlideOnWheel(ref IWheel someWheel)
        {
            currentSlidingVelocity = Vector3.Dot(mcarRigidBody.GetPointVelocity(someWheel.GetTransform().position),
                someWheel.GetTransform().right);

            //Find change in velocity and apply counter force to prevent over-steering
            float velocityChange = Mathf.Abs(currentSlidingVelocity);
            float restorationForce = mslideVelocityDampingConstant * velocityChange;
            restorationForce = Mathf.Clamp(restorationForce, -mdampingLimit, mdampingLimit);

            Vector3 forceToBeApplied =
                -Mathf.Sign(currentSlidingVelocity) * restorationForce * mcarRigidBody.transform.right;
            mcarRigidBody.AddForceAtPosition(forceToBeApplied, someWheel.GetTransform().position

gaurdian.kiran@gmail.com

+46 769666977

Contact: