2015年12月13日 星期日

Unity + logitech g27

使用 Logitech g27  跟Unity 製作搭配之前,所先要先下載 DirectX 安裝取得xinput1_3.dll ,這裡所使用x64位元,x86 目前沒進行測試。

如果使用 System.Windows.Forms.dll,請更改 .NET2.0  (PlayerSetting 位置)

DirectX  下載

Logitech G27 SDK 下載

Logitech G27 Driver 下載

System.Windows.Forms.dll 下載

Logitech G27  3個踏板 是分開數值,以往的Unity Input.GetAxis("Vertical") 有些不同

Logitech G27 圖片:




-------------------------------------------------

之前賽車程式來當範例:

標註: 紅色文字跟Logitech G27  SDK 相關

PlayerCar_Script (C#) :

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(AudioSource))]
public class PlayerCar_Script : MonoBehaviour {

    // These variables allow the script to power the wheels of the car.
    [SerializeField]
    WheelCollider frontLeftWheel, frontRightWheel, backLeftWheel, backRightWheel;

    /**
      * These variables are for the gears, the array is the list of ratios. The script
        uses the defined gear ratios to determine how much torque to apply to the wheels.
   */
    [SerializeField]
    float[] gearRatio;
    [SerializeField]
    float differentialRatio = 3.21f;
    [SerializeField]
    int currentGear = 0;

    /**
      * These variables are just for applying torque to the wheels and shifting gears.
        using the defined Max and Min Engine RPM, the script can determine what gear the
        car needs to be in.
     */
    [SerializeField]
    float engineTorque = 600f, maxEngineRPM = 700f, minEngineRPM = 1000f, engineRPM = 0f;
    [SerializeField]
    int frontWheelDrive = 1, rearWheelDrive = 1;

    // Center Of Mass
    [SerializeField]
    float comX = 0f, comY = 0f, comZ = 0f;

    private Rigidbody rigidbody;
    private AudioSource audioSource;

    // Use this for initialization
    void Start () {
        // I usually alter the center of mass to make the car more stable. I'ts less likely to flip this way.
        rigidbody = GetComponent<Rigidbody>();
        rigidbody.centerOfMass = new Vector3(comX, comY, comZ);

        audioSource = GetComponent<AudioSource>();
    }

    int vertical;

    // Update is called once per frame
    void Update () {

        if (LogitechGSDK.LogiUpdate() && LogitechGSDK.LogiIsConnected(0))
        {

            LogitechGSDK.DIJOYSTATE2ENGINES rec;
            rec = LogitechGSDK.LogiGetStateUnity(0);
            //update center of mass
            rigidbody.centerOfMass = new Vector3(comX, comY, comZ);

            /**
              * This is to limith the maximum speed of the car, adjusting the drag probably isn't the best way of doing it,
                but it's easy, and it doesn't interfere with the physics processing.
            */
            rigidbody.drag = rigidbody.velocity.magnitude / 250;

            // Compute the engine RPM based on the average RPM of the two wheels, then call the shift gear function
            engineRPM = Mathf.Abs(backLeftWheel.rpm + backRightWheel.rpm) / 2 * gearRatio[currentGear] * differentialRatio;
            if (engineRPM > 10000)
                engineRPM = 10000;
            if (engineRPM < 0)
                engineRPM = 0;
            shiftGears();

            /**
              * set the audio pitch to the percentage of RPM to the maximum RPM plus one, this makes the sound play
                up to twice it's pitch, where it will suddenly drop when it switches gears.
            */
            audioSource.pitch = Mathf.Abs(engineRPM / maxEngineRPM) + 0.5f;
            if (audioSource.pitch > 1.5f)
                audioSource.pitch = 1.5f;

            /**
              * finally, apply the values to the wheels.
                The torque applied is divided by the current gear, and
                multiplied by the user input variable.
            */

            vertical = (-Mathf.Abs(32767 - rec.lY)/2) + (Mathf.Abs(32767 - rec.lRz)/2);
           
            if (frontWheelDrive.Equals(1)) {

                frontLeftWheel.motorTorque = -engineTorque * gearRatio[currentGear] * differentialRatio * vertical;
                frontRightWheel.motorTorque = -engineTorque * gearRatio[currentGear] * differentialRatio * vertical;
            }

            if (rearWheelDrive.Equals(1))
            {
                backLeftWheel.motorTorque = -engineTorque * gearRatio[currentGear] * differentialRatio * vertical;
                backLeftWheel.motorTorque = -engineTorque * gearRatio[currentGear] * differentialRatio * vertical;
            }
           
            // the steer angle is an arbitrary value multiplied by the user input.
            frontLeftWheel.steerAngle = rec.lX / 1050;
            frontRightWheel.steerAngle = rec.lX / 1050;

        }else if (!LogitechGSDK.LogiIsConnected(0)) {
            System.Windows.Forms.MessageBox.Show("PLEASE PLUG IN A STEERING WHEEL OR A FORCE FEEDBACK CONTROLLER");
        }else {
            System.Windows.Forms.MessageBox.Show("THIS WINDOW NEEDS TO BE IN FOREGROUND IN ORDER FOR THE SDK TO WORK PROPERLY");
        }
    }
    void shiftGears(){

        /**
          * this funciton shifts the gears of the vehcile, it loops through all the gears, checking which will make
            the engine RPM fall within the desired range. The gear is then set to this "appropriate" value.
        */

        if (engineRPM >= maxEngineRPM)
        {
            int appropriateGear = currentGear;
            for (int i = 0; i < gearRatio.Length; i++)
            {
                if (Mathf.Abs(backLeftWheel.rpm + backRightWheel.rpm) / 2 * gearRatio[i] * differentialRatio < maxEngineRPM)
                {
                    appropriateGear = i;
                    break;
                }
            }
            currentGear = appropriateGear;
        }
    }
}



2015年12月1日 星期二

Unity editor basic apply (Unity 編輯視窗基本應用)

Unity Editor 應用注意:

Unity Edit need MonoBehaviour apply gameobjcet, so create script extends MonoBehaviour can use.
建立新的腳本,並且要繼承 MonoBehaviour,否則 Unity Editor  腳本不能套用在物件身上。

TestEdit (C#) : 

using UnityEngine;
using System.Collections;
using UnityEditor;

/**
  * Unity Edit need MonoBehaviour apply gameobjcet,
    so create script extends MonoBehaviour can use.
  */
[CustomEditor(typeof(PlayerTest))] 
public class TestEdit : Editor {

    Color color;

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
  
        // Color setting windows
        color = EditorGUILayout.ColorField("color",color);
        
        if (GUILayout.Button("color Setting")) {
            PlayerTest obj = target as PlayerTest; 
            obj.GetComponent<Renderer>().material.color = color;
        }   
    }
}

結果圖:



2015年11月30日 星期一

Unity GUI collocation Json and Create text write Date (Unity GUI 搭配 Json和建立文字檔並寫入資訊)

TestOnGUI (C#) : 

using UnityEngine;
using System.Collections;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;

// _Book class wait write data
public class _Book {
    public int ID { get; set; }
    public string Name { get; set;}
}

// _BookDate afterwards increase _Book class data
public class _BOOKData {
    public List<_Book> book = new List<_Book>();
}

public class TestOnGUI : MonoBehaviour {

    _Book bookTest;

    void Start() {
        bookTest = new _Book();
    }

    // Input Data value
    string id = "", name = ""; 

    void OnGUI() {

        /**
         * GUI can't dirct designation _Book class parameter, so wording. 
         * Name variable only write in global.
        */
        id = GUI.TextArea(new Rect(Screen.width/Screen.width,Screen.height/Screen.height, 100, 30), id, 200);

        // int to String
        int intId;
        int.TryParse(id, out intId);
        bookTest.ID = intId;

        name = GUI.TextArea(new Rect(Screen.width / Screen.width, Screen.height / Screen.height+50f, 100, 30), name, 200);
        bookTest.Name = name;

        if (GUI.Button(new Rect(Screen.width/Screen.width, Screen.height/Screen.height+100, 100, 50),"確定")) {

            // RootLibrary add book class Date
            _BOOKData bookDate = new _BOOKData();
            bookDate.book.Add(bookTest);

            // Json Encoding
            string outPut = JsonConvert.SerializeObject(bookDate);
            print(outPut);

            // Json Decoding
            _BOOKData _lib = JsonConvert.DeserializeObject<_BOOKData>(outPut);

            foreach (_Book book in _lib.book) {
                print("ID" + book.ID);
                print("Name" + book.Name);

                for (int i = 0; i < _lib.book.Count; i++)
                {
                    print("ListDate[" + i + "]:" +_lib.book[i]);
                }
            }

            //Create text file
            string[] createTxtValue = {bookTest.ID.ToString(), bookTest.Name};
            File.WriteAllLines("C:/Users/user/Desktop/AAA.txt", createTxtValue);
        }
    }
}

結果圖:






Unity C# Create Text (文字檔)

TestCreateTxt (C#): 

using UnityEngine;
using System.Collections;
using System.IO;

public class TestCreateTxt : MonoBehaviour {
    void OnGUI() {
        if (GUI.Button(new Rect(Screen.width / Screen.width, Screen.height / Screen.height, 500, 20), "建立文件檔")) {
            string[] lines = { "First line", "Second line", "Time line" };

            File.WriteAllLines("C:/Users/user/Desktop/AAA.txt", lines);

            string text = "A class is the most powerful data type in C#. Like a structure, " +
                       "a class defines the data and behavior of the data type. ";

            File.WriteAllText("C:/Users/user/Desktop/TestTxt.txt", text);

            StreamWriter file = new StreamWriter("C:/Users/user/Desktop/test2.txt");

            foreach (string line in lines) {
                if (!line.Contains("Second")) {
                    file.WriteLine(line);
                }
            }
        }   
    }

}

結果圖:




2015年11月29日 星期日

Unity for Json


Download Newtonsoft.Json



Testjson (C#):  寫入資料

using UnityEngine;
using System.Collections;
using System.IO;
using Newtonsoft.Json;
using System.Collections.Generic;

/**
  * Book Class set Date ID, Name, Author.
    using System.Collections.Generic class, user liset class 
  */
public class Book
{
    public int ID;       
    public string Name;
    public string Author;

    public List<string> listDate = new List<string>(); 
}

/**
  * RootLibrary is add Date
*/
public class RootLibrary{
    public List<Book> Library = new List<Book>();
}

public class Testjson : MonoBehaviour {

void Start () {
        
        // set Date Book class value
        Book book = new Book();
        book.ID = 1;
        book.Name = "Bee";
        book.Author = "bee";
        
        // list add Date value
        book.listDate.Add("Box");
        book.listDate.Add("Bananana");
        book.listDate.Add("Ball");

        // set Date Book2 calss value is string
        Book book2 = new Book();
        book2.ID = 2;
        book2.Name = "Cat";
        book2.Author = "cat";

        // list add Date value is string
        book2.listDate.Add("Box2");
        book2.listDate.Add("Test222") ;
        book2.listDate.Add("Back");

        // RootLibrary add book class Date
        RootLibrary lib = new RootLibrary();
        lib.Library.Add(book);
        lib.Library.Add(book2);

        // Json Encoding
        string outPut = JsonConvert.SerializeObject(lib);
        print(outPut);

        // Json Decoding
        RootLibrary _lib = JsonConvert.DeserializeObject<RootLibrary>(outPut);

        foreach (Book _book in _lib.Library) {
            print("--------------");
            print("ID"+ _book.ID);
            print("Name" + _book.Name);
            print("Author:" +_book.Author);

            for (int i =0; i<_book.listDate.Count; i++) {
                print("ListDate["+i+"]:" + _book.listDate[i]);
            }
        }
    }
}

結果圖: 

























----------------------------------------------

TestjsonInquire (c#) : 收尋資料

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

// Root_Library calss set value
public class Root_Library{
    public string Type;
}

public class TestjsonInquire : MonoBehaviour {

    void Start() {

        Root_Library lib = new Root_Library();
        lib.Type = "BookLibaray";

        /**
          * Json Encoding
          *  Json set vlaue Type
        */
        string output = JsonConvert.SerializeObject(lib) ;
        print(output);

        /**
          * Json Decoding 
          * Json Inquire Type value
         */
        JObject obj = JsonConvert.DeserializeObject<JObject>(output);
        print(obj.GetValue("Type"));
        print(obj["Type"]);
    }
}

結果圖:




















----------------------------------------------

Json、Bson 綜合應用:

using UnityEngine;
using System;
using System.IO;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json.Linq;

public class Test : MonoBehaviour {

void Start () {

        var info = new List<object> {
            new {
                Date = DateTime.Now,
                name = "Moe Loli A",
                Age = 5,
                NumberA = new int[]{000,111,222},
                NumberB = new List<int> {2, 1, 0}
            },
            new {
                Date = DateTime.Now.AddDays(1),
                name = "Moe Loli B",
                Age = 7,
                NumberA = new int[]{333,444,555},
                NumberB = new List<int> {5, 4, 3}
            },
        };

        var data = new {info};

        string json = JsonConvert.SerializeObject(data, Formatting.Indented);
        JObject Jobj = JsonConvert.DeserializeObject<JObject>(json);
        print("Json: "+json);

        JArray ja = Jobj["info"] as JArray;
        foreach (JObject obj in ja) {
            DateTime date = (DateTime)obj["Date"];
            print("Json: "+data);
        }

        byte[] bson = new byte[] { };
        using (MemoryStream steam = new MemoryStream()) {
            using (BsonWriter writer = new BsonWriter(steam)) {
                new JsonSerializer().Serialize(writer, data);
            }
            bson = steam.ToArray();
        }

        JObject Bobj;

        using (var steam = new MemoryStream(bson)) {
            using (BsonReader reader = new BsonReader(steam)) {
                JsonSerializer serializer = new JsonSerializer();
                Bobj = serializer.Deserialize<JObject>(reader);
            }
        }
        print("Bson: "+ Bobj);

        JArray ja2 = Bobj["info"] as JArray;
        foreach (JObject obj in ja2) {
            DateTime date = (DateTime)obj["Date"];
            print("Bson: "+date);
        }
    }
}

結果圖: 


























2015年11月27日 星期五

Unity Car controller / AI car (C# 版本)

Wheel Collider 詳細資料位置
Unity Car controller / AI car (JS 版本)

PlayerCar_Script  (C#) :

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(AudioSource))]
public class PlayerCar_Script : MonoBehaviour {

    // These variables allow the script to power the wheels of the car.
    [SerializeField]
    WheelCollider frontLeftWheel, frontRightWheel, backLeftWheel, backRightWheel;

    /**
      * These variables are for the gears, the array is the list of ratios. The script
        uses the defined gear ratios to determine how much torque to apply to the wheels.
   */
    [SerializeField]
    float[] gearRatio;
    [SerializeField]
    float differentialRatio = 3.21f;
    [SerializeField]
    int currentGear = 0;

    /**
      * These variables are just for applying torque to the wheels and shifting gears.
        using the defined Max and Min Engine RPM, the script can determine what gear the
        car needs to be in.
     */
    [SerializeField]
    float engineTorque = 600f, maxEngineRPM = 700f, minEngineRPM = 1000f, engineRPM = 0f;
    [SerializeField]
    int frontWheelDrive = 1, rearWheelDrive = 1;

    // Center Of Mass
    [SerializeField]
    float comX = 0f, comY = 0f, comZ = 0f;

    private Rigidbody rigidbody;
    private AudioSource audioSource;

    // Use this for initialization
    void Start () {
        // I usually alter the center of mass to make the car more stable. I'ts less likely to flip this way.
        rigidbody = GetComponent<Rigidbody>();
        rigidbody.centerOfMass = new Vector3(comX, comY, comZ);

        audioSource = GetComponent<AudioSource>();
    }

    // Update is called once per frame
    void Update () {
        //update center of mass
        rigidbody.centerOfMass = new Vector3(comX, comY, comZ);

        /**
          * This is to limith the maximum speed of the car, adjusting the drag probably isn't the best way of doing it,
            but it's easy, and it doesn't interfere with the physics processing.
        */
        rigidbody.drag = rigidbody.velocity.magnitude / 250;

        // Compute the engine RPM based on the average RPM of the two wheels, then call the shift gear function
        engineRPM = Mathf.Abs(backLeftWheel.rpm + backRightWheel.rpm) / 2 * gearRatio[currentGear] * differentialRatio;
        if (engineRPM > 10000)
            engineRPM = 10000;
        if (engineRPM < 0)
            engineRPM = 0;
        shiftGears();

        /**
          * set the audio pitch to the percentage of RPM to the maximum RPM plus one, this makes the sound play
            up to twice it's pitch, where it will suddenly drop when it switches gears.
        */
        audioSource.pitch = Mathf.Abs(engineRPM / maxEngineRPM) + 0.5f;
        if (audioSource.pitch > 1.5f) 
            audioSource.pitch = 1.5f;

        /**
          * finally, apply the values to the wheels. The torque applied is divided by the current gear, and
            multiplied by the user input variable.
        */
        if (frontWheelDrive.Equals(1)) {
            frontLeftWheel.motorTorque = -engineTorque * gearRatio[currentGear] * differentialRatio * Input.GetAxis("Vertical");
            frontRightWheel.motorTorque = -engineTorque * gearRatio[currentGear] * differentialRatio * Input.GetAxis("Vertical");
        }
        if (rearWheelDrive.Equals(1)) {
            backLeftWheel.motorTorque = -engineTorque * gearRatio[currentGear] * differentialRatio * Input.GetAxis("Vertical");
            backLeftWheel.motorTorque = -engineTorque * gearRatio[currentGear] * differentialRatio * Input.GetAxis("Vertical");
        }

        // the steer angle is an arbitrary value multiplied by the user input.
        frontLeftWheel.steerAngle = 35 * Input.GetAxis("Horizontal");
        frontRightWheel.steerAngle = 35 * Input.GetAxis("Horizontal");

    }

    void shiftGears()
    {

        /**
          * this funciton shifts the gears of the vehcile, it loops through all the gears, checking which will make
            the engine RPM fall within the desired range. The gear is then set to this "appropriate" value.
        */

        if (engineRPM >= maxEngineRPM)
        {
            int appropriateGear = currentGear;
            for (int i = 0; i < gearRatio.Length; i++)
            {
                if (Mathf.Abs(backLeftWheel.rpm + backRightWheel.rpm) / 2 * gearRatio[i] * differentialRatio < maxEngineRPM)
                {
                    appropriateGear = i;
                    break;
                }
            }
            currentGear = appropriateGear;
        }
    }
}

------------------------------------------------

WheelAntiRoll_Script(C#):

using UnityEngine;
using System.Collections;

public class WheelAntiRoll_Script : MonoBehaviour {

    // Car two Wheel
    [SerializeField]
    WheelCollider wheelL, wheelR;  

    [SerializeField]
    float AntiRoll = 5000.0f;

    private Rigidbody rigidbody;

    void Start() {
        rigidbody = GetComponent<Rigidbody>();
    }

    void FixedUpdate() {

        WheelHit hits;

        bool groundedL = wheelL.GetGroundHit(out hits);

        float travelL = 1.0f;
        float travelR = 1.0f;
  
        if (groundedL) 
            travelL = (-wheelL.transform.InverseTransformPoint(hits.point).y - wheelL.radius) / wheelL.suspensionDistance;

        bool groundedR = wheelR.GetGroundHit(out hits);
        if (groundedR)
            travelR = (-wheelR.transform.InverseTransformPoint(hits.point).y - wheelR.radius) / wheelR.suspensionDistance;

        float antiRollForce = (travelL - travelR) * AntiRoll;

        if (groundedL)
            rigidbody.AddForceAtPosition(wheelL.transform.up * - antiRollForce, wheelR.transform.position);

        if (groundedR)
            rigidbody.AddForceAtPosition(wheelR.transform.up * antiRollForce, wheelR.transform.position);
    }
}
------------------------------------------------

WheelAlignment_Script (C#) :

using UnityEngine;
using System.Collections;

public class WheelAlignment_Script : MonoBehaviour {

    [SerializeField]
    WheelCollider correspondingCollider;
    [SerializeField]
    GameObject slipSmoke;

    float rotationValue = 0f;
    void FixedUpdate()
    {

        // define a hit point for the raycast collision
        RaycastHit hits;

        /**
          * Find the collider's center point, you need to do this because the center of the collider might not actually be
            the real position if the transform's off.
        */
        Vector3 colliderCenterPoint = correspondingCollider.transform.TransformPoint(correspondingCollider.center);

        /**
          * now cast a ray out from the wheel collider's center the distance of the suspension, if it hit something, then use the "hit"
            variable's data to find where the wheel hit, if it didn't, then se tthe wheel to be fully extended along the suspension.
        */
        Vector3 hitsporit = (Physics.Raycast(colliderCenterPoint, -correspondingCollider.transform.up, out hits, correspondingCollider.suspensionDistance + correspondingCollider.radius)) ? hits.point + (correspondingCollider.transform.up * correspondingCollider.radius) : colliderCenterPoint - (correspondingCollider.transform.up * correspondingCollider.suspensionDistance);
        transform.position = hitsporit;

        /**
          * now set the wheel rotation to the rotation of the collider combined with a new rotation value. This new value
            is the rotation around the axle, and the rotation from steering input.
        */
        transform.rotation = correspondingCollider.transform.rotation * Quaternion.Euler(rotationValue, correspondingCollider.steerAngle, 0);

        // increase the rotation value by the rotation speed (in degrees per second)
        rotationValue += correspondingCollider.rpm * (360 / 60) * Time.deltaTime;

        /**
          * define a wheelhit object, this stores all of the data from the wheel collider and will allow us to determine
            the slip of the tire.
        */
        WheelHit wheelhits;
        correspondingCollider.GetGroundHit(out wheelhits);


        /**
         * if the slip of the tire is greater than 2.0, and the slip prefab exists, create an instance of it on the ground at
            a zero rotation.
        */
        if (Mathf.Abs(wheelhits.sidewaysSlip) > 2)
        {
            if (slipSmoke)
                Instantiate(slipSmoke, wheelhits.point, Quaternion.identity);
            }
        }
    }

------------------------------------------------

WheelFriction_Script (C#) :

using UnityEngine;
using System.Collections;

public class WheelFriction_Script : MonoBehaviour {

    [SerializeField]
    WheelCollider frontLeftWheel, frontRightWheel, backLeftWheel, backRightWheel;

    // Wheel Friction curves
    [SerializeField]
    float forwardExtremumSlip = 10f,   //1.0f
          forwardExtremumValue = 100f, //20000.0f
          forwardAsymptoteSlip = 100f, //2.0f
          forwardAsymptoteValue = 1f,  //10000.0f
          forwardStiffness = 1.0f,
          sidewaysExtremumSlip = 6f,    //1.0f
          sidewaysExtremumValue = 100f, //20000.0f
          sidewaysAsymptoteSlip = 30f,  //2.0f
          sidewaysAsymptoteValue = 1,   //1000f
          sidewaysStiffness = 2.0f;

    WheelFrictionCurve frontLeftWheelFrictionCurve, frontRightWheelFrictionCurv, backLeftWheelFrictionCurv, backRightWheelFrictionCurv;
    Rigidbody rigidbody;


    void Start() {
        frontLeftWheelFrictionCurve = frontLeftWheel.forwardFriction;
        frontRightWheelFrictionCurv = frontRightWheel.forwardFriction;
        backLeftWheelFrictionCurv = backLeftWheel.forwardFriction;
        backRightWheelFrictionCurv = backRightWheel.forwardFriction;
        rigidbody = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update () {

        // Forward Friction
        frontLeftWheelFrictionCurve.extremumSlip = forwardAsymptoteSlip;
        frontLeftWheelFrictionCurve.extremumValue = forwardExtremumValue;
        frontLeftWheelFrictionCurve.asymptoteSlip = forwardAsymptoteSlip;
        frontLeftWheelFrictionCurve.asymptoteValue = forwardAsymptoteValue;
        frontLeftWheelFrictionCurve.stiffness = forwardStiffness;

        frontRightWheelFrictionCurv.extremumSlip = forwardAsymptoteSlip;
        frontRightWheelFrictionCurv.extremumValue = forwardExtremumValue;
        frontRightWheelFrictionCurv.asymptoteSlip = forwardAsymptoteSlip;
        frontRightWheelFrictionCurv.asymptoteValue = forwardAsymptoteValue;
        frontRightWheelFrictionCurv.stiffness = forwardStiffness;

        backLeftWheelFrictionCurv.extremumSlip = forwardExtremumSlip;
        backLeftWheelFrictionCurv.extremumValue = forwardExtremumValue;
        backLeftWheelFrictionCurv.asymptoteSlip = forwardAsymptoteSlip;
        backLeftWheelFrictionCurv.asymptoteValue = forwardAsymptoteValue;
        backLeftWheelFrictionCurv.stiffness = forwardStiffness;

        backRightWheelFrictionCurv.extremumSlip = forwardExtremumSlip;
        backRightWheelFrictionCurv.extremumValue = forwardExtremumValue;
        backRightWheelFrictionCurv.asymptoteSlip = forwardAsymptoteSlip;
        backRightWheelFrictionCurv.asymptoteValue = forwardAsymptoteValue;
        backRightWheelFrictionCurv.stiffness = forwardStiffness;

        //Sideways Friction
        frontLeftWheelFrictionCurve.extremumSlip = sidewaysExtremumSlip;
        frontLeftWheelFrictionCurve.extremumValue = sidewaysExtremumValue;
        frontLeftWheelFrictionCurve.asymptoteSlip = sidewaysAsymptoteSlip;
        frontLeftWheelFrictionCurve.asymptoteValue = sidewaysAsymptoteValue;
        frontLeftWheelFrictionCurve.stiffness = sidewaysStiffness;

        frontRightWheelFrictionCurv.extremumSlip = sidewaysExtremumSlip;
        frontRightWheelFrictionCurv.extremumValue = sidewaysExtremumValue;
        frontRightWheelFrictionCurv.asymptoteSlip = sidewaysAsymptoteSlip;
        frontRightWheelFrictionCurv.asymptoteValue = sidewaysAsymptoteValue;
        frontRightWheelFrictionCurv.stiffness = sidewaysStiffness;

        backLeftWheelFrictionCurv.extremumSlip = sidewaysExtremumSlip;
        backLeftWheelFrictionCurv.extremumValue = sidewaysExtremumValue;
        backLeftWheelFrictionCurv.asymptoteSlip = sidewaysAsymptoteSlip;
        backLeftWheelFrictionCurv.asymptoteValue = sidewaysAsymptoteValue;
        backLeftWheelFrictionCurv.stiffness = sidewaysStiffness;

        backRightWheelFrictionCurv.extremumSlip = sidewaysExtremumSlip;
        backRightWheelFrictionCurv.extremumValue = sidewaysExtremumValue;
        backRightWheelFrictionCurv.asymptoteSlip = sidewaysAsymptoteSlip;
        backRightWheelFrictionCurv.asymptoteValue = sidewaysAsymptoteValue;
        backRightWheelFrictionCurv.stiffness = sidewaysStiffness;
        
        // The rigidbody velocity is always given in world space, but in order to work in local space of the car model we need to transform it first.
        Vector3 relativeVelocity = transform.InverseTransformDirection(rigidbody.velocity);
        UpdateFriction(relativeVelocity);
    }

    void UpdateFriction(Vector3 relativeVelocity) {

        float sqrVel = relativeVelocity.x * relativeVelocity.x;
        
        // Forward
        int maxFStif = 100, minFStif = 70;

        frontLeftWheelFrictionCurve.extremumValue = Mathf.Clamp(maxFStif - sqrVel,0,maxFStif);
        frontLeftWheelFrictionCurve.asymptoteValue = Mathf.Clamp(minFStif - (sqrVel / 2), 0, minFStif);
        frontRightWheelFrictionCurv.extremumValue = Mathf.Clamp(maxFStif - sqrVel, 0, maxFStif);
        frontRightWheelFrictionCurv.asymptoteValue = Mathf.Clamp(minFStif - (sqrVel / 2), 0, minFStif);

        backLeftWheelFrictionCurv.extremumValue = Mathf.Clamp(maxFStif - sqrVel, 10, maxFStif);
        backLeftWheelFrictionCurv.asymptoteValue = Mathf.Clamp(minFStif - (sqrVel / 2), 10, minFStif);
        backRightWheelFrictionCurv.extremumValue = Mathf.Clamp(maxFStif - sqrVel, 10, maxFStif);
        backRightWheelFrictionCurv.asymptoteValue = Mathf.Clamp(minFStif - (sqrVel / 2), 10, minFStif);

        // Add extra sideways friction based on the car's turning velocity to avoid slipping
        int maxSStif = 100, minSStif = 50;
        frontLeftWheelFrictionCurve.extremumValue = Mathf.Clamp(maxSStif - sqrVel, 10, maxSStif);
        frontLeftWheelFrictionCurve.asymptoteValue = Mathf.Clamp(minSStif - (sqrVel / 2), 10, minSStif);
        frontRightWheelFrictionCurv.extremumValue = Mathf.Clamp(maxSStif - sqrVel, 10, maxSStif);
        frontRightWheelFrictionCurv.asymptoteValue = Mathf.Clamp(minSStif - (sqrVel / 2), 10, minSStif);

        backLeftWheelFrictionCurv.extremumValue = Mathf.Clamp(maxSStif - sqrVel, 10, maxSStif);
        backLeftWheelFrictionCurv.asymptoteValue = Mathf.Clamp(minSStif - (sqrVel / 2), 10, minSStif);
        backRightWheelFrictionCurv.extremumValue = Mathf.Clamp(maxSStif - sqrVel, 10, maxSStif);
        backRightWheelFrictionCurv.asymptoteValue = Mathf.Clamp(minSStif - (sqrVel / 2), 10, minSStif);
    }
}

------------------------------------------------

WheelSuspension_Script (C#) :

using UnityEngine;
using System.Collections;

public class WheelSuspension_Script : MonoBehaviour {

    [SerializeField]
    WheelCollider frontLeftWheel, frontRightWheel, backLeftWheel, backRightWheel;

    [SerializeField]
    float wheelRadius = 0.33f,
          springLength = 0.1f,
          damperForce = 50f,           // 50
          springForceFront = 8500f,    // 18500
          springForceRear = 5000f;     // 9000;


    JointSpring frontLeftWheelJointSpring, frontRightWheelJointSpring, backLeftWheelJointSpring, backRightWheelJointSpring;
    

    void Start() {
        frontLeftWheelJointSpring = frontLeftWheel.suspensionSpring;
        frontRightWheelJointSpring = frontRightWheel.suspensionSpring;
        backLeftWheelJointSpring = backLeftWheel.suspensionSpring;
        backRightWheelJointSpring = backRightWheel.suspensionSpring;

    }

    // Update is called once per frame
    void Update () {

        frontLeftWheel.radius = wheelRadius;
        frontLeftWheel.suspensionDistance = springLength;
        frontLeftWheelJointSpring.spring = springForceFront;
        frontLeftWheelJointSpring.damper = damperForce;

        frontRightWheel.radius = wheelRadius;
        frontRightWheel.suspensionDistance = springLength;
        frontRightWheelJointSpring.spring = springForceFront;
        frontRightWheelJointSpring.damper = damperForce;

        // Rear Suspension
        backLeftWheel.radius = wheelRadius;
        backLeftWheel.suspensionDistance = springLength;
        backLeftWheelJointSpring.spring = springLength;
        backLeftWheelJointSpring.damper = damperForce;

        backRightWheel.radius = wheelRadius;
        backRightWheel.suspensionDistance = springLength;
        backRightWheelJointSpring.spring = springLength;
        backRightWheelJointSpring.damper = damperForce;
    }
}

------------------------------------------------

AICar_Script (C#) :

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(AudioSource))]
public class AICar_Script : MonoBehaviour {

    // These variables allow the script to power the wheels of the car.
    [SerializeField]
    WheelCollider frontLeftWheel, frontRightWheel, backLeftWheel, backRightWheel;

    /**
      * These variables are for the gears, the array is the list of ratios. The script
        uses the defined gear ratios to determine how much torque to apply to the wheels.
    */
    [SerializeField]
    float[] gearRatio;
    [SerializeField]
    float differentialRatio = 3.1f;
    [SerializeField]
    int currentGear = 0;

    /**
      * These variables are just for applying torque to the wheels and shifting gears.
        using the defined Max and Min Engine RPM, the script can determine what gear the
        car needs to be in.
    */
    [SerializeField]
    float engineTorque = 600f,
          maxEngineRPM = 3000f,
          minEngineRPM = 1000f;
    private float engineRPM = 0f;

    /**
      * Here's all the variables for the AI, the waypoints are determined in the "GetWaypoints" function.
        the waypoint container is used to search for all the waypoints in the scene, and the current
        waypoint is used to determine which waypoint in the array the car is aiming for.
    */
    [SerializeField]
    GameObject waypointContainer;
    private Transform[] waypoints;
    private int currentWaypoint = 0;

    /**
      * input steer and input torque are the values substituted out for the player input. The 
        "NavigateTowardsWaypoint" function determines values to use for these variables to move the car
         in the desired direction.
    */
    private float inputSteer, inputTorque;

    Rigidbody rigidbody;
    AudioSource audioSource;

    void Start () {
        // I usually alter the center of mass to make the car more stable. I'ts less likely to flip this way.
        rigidbody = GetComponent<Rigidbody>();
        rigidbody.centerOfMass = new Vector3(rigidbody.centerOfMass.x, 0.45f, 0.15f);

        audioSource = GetComponent<AudioSource>();
        /**
          * Call the function to determine the array of waypoints. This sets up the array of points by finding
            transform components inside of a source container.
        */
        getWaypoints();
    }

    // Update is called once per frame
    void Update () {
        
        /**
          * This is to limith the maximum speed of the car, adjusting the drag probably isn't the best way of doing it,
       but it's easy, and it doesn't interfere with the physics processing.
        */
        rigidbody.drag = rigidbody.velocity.magnitude / 250;

        /**
          * Call the funtion to determine the desired input values for the car. This essentially steers and
            applies gas to the engine.
        */
        navigateTowardsWaypoint();

        // Compute the engine RPM based on the average RPM of the two wheels, then call the shift gear function
        engineRPM = (backLeftWheel.rpm + backRightWheel.rpm) / 2 * gearRatio[currentGear] * differentialRatio;
        shiftGears();

        /**
          * set the audio pitch to the percentage of RPM to the maximum RPM plus one, this makes the sound play
            up to twice it's pitch, where it will suddenly drop when it switches gears.
        */
        audioSource.pitch = Mathf.Abs(engineRPM/maxEngineRPM)+0.5f;
        // this line is just to ensure that the pitch does not reach a value higher than is desired.
        if (audioSource.pitch > 1.5f)
            audioSource.pitch = 1.5f;

        /**
          * finally, apply the values to the wheels. The torque applied is divided by the current gear, and
            multiplied by the calculated AI input variable.
            FrontLeftWheel.motorTorque = EngineTorque / GearRatio[CurrentGear]*DifferentialRatio * inputTorque;
            FrontRightWheel.motorTorque = EngineTorque / GearRatio[CurrentGear]*DifferentialRatio * inputTorque;
        */
        backLeftWheel.motorTorque = -engineTorque / gearRatio[currentGear] * differentialRatio * inputTorque;
        backRightWheel.motorTorque = -engineTorque / gearRatio[currentGear] * differentialRatio * inputTorque;

        // the steer angle is an arbitrary value multiplied by the calculated AI input.
        frontLeftWheel.steerAngle =  35 * inputSteer;
        frontRightWheel.steerAngle = 35 * inputSteer;
    }

    void shiftGears() {

        /**
          * this funciton shifts the gears of the vehcile, it loops through all the gears, checking which will make
            the engine RPM fall within the desired range. The gear is then set to this "appropriate" value.
        */
        int appropriateGear;
        if (engineRPM >= maxEngineRPM){
            appropriateGear = currentGear;
            for (int i =0; i < gearRatio.Length; i++) {
                if (backLeftWheel.rpm * gearRatio[i] * differentialRatio < maxEngineRPM) {
                    appropriateGear = i;
                    break;
                }
            }
            currentGear = appropriateGear;
        }
        if (engineRPM <= minEngineRPM) {
            appropriateGear = currentGear;
            for (int j = gearRatio.Length-1; j >= 0; j--){
                if (engineRPM <= minEngineRPM){
                    appropriateGear = j;
                    break;
                }
            }
            currentGear = appropriateGear;
        }
    }

    void getWaypoints(){

        /**
          * Now, this function basically takes the container object for the waypoints, then finds all of the transforms in it,
            once it has the transforms, it checks to make sure it's not the container, and adds them to the array of waypoints.
        */
        Transform[] potentialWaypoints = waypointContainer.GetComponentsInChildren<Transform>();
        //waypoints = new Transform[];
        foreach (Transform potentialWaypoint in potentialWaypoints) {
            if (potentialWaypoint != waypointContainer.transform) 
                waypoints[waypoints.Length] = potentialWaypoint;
        }
    }

    void navigateTowardsWaypoint() {

        /**
         * now we just find the relative position of the waypoint from the car transform,
            that way we can determine how far to the left and right the waypoint is.
        */
        Vector3 relativeWaypointPosition = transform.InverseTransformPoint(new Vector3(waypoints[currentWaypoint].position.x, transform.position.y, waypoints[currentWaypoint].position.z));

        // by dividing the horizontal position by the magnitude, we get a decimal percentage of the turn angle that we can use to drive the wheels
        inputSteer = relativeWaypointPosition.x / relativeWaypointPosition.magnitude;

        // now we do the same for torque, but make sure that it doesn't apply any engine torque when going around a sharp turn...
        float value = (Mathf.Abs(inputSteer) < 1.0) ? relativeWaypointPosition.z / relativeWaypointPosition.magnitude - Mathf.Abs(inputSteer) : 0f;
        inputTorque = value;

        /**
          * this just checks if the car's position is near enough to a waypoint to count as passing it, if it is, then change the target waypoint to the
            next in the list.
        */
        if (relativeWaypointPosition.magnitude < 30){
            currentWaypoint++;
            if (currentWaypoint >= waypoints.Length) 
                currentWaypoint = 0;
        }
    }
}

------------------------------------------------

DrawWaypointGizmos_Script (C#) :

using UnityEngine;
using System.Collections;

public class DrawWaypointGizmos_Script : MonoBehaviour {

    void OnDrawGizmos() {
        
        // make a new array of waypoints, then set it to all of the transforms in the current object
        Transform waypoints = gameObject.GetComponentInChildren<Transform>();
       
        // now loop through all of them and draw gizmos for each of them
        foreach (Transform waypoint in waypoints) {
            Gizmos.DrawSphere(waypoint.position, 1.0f);
        }
    }
}