Search Unity

How to rotate car without using transform

Discussion in 'Scripting' started by rakifsul, Aug 30, 2014.

  1. rakifsul

    rakifsul

    Joined:
    Mar 30, 2014
    Posts:
    47
    Hi.

    I'm making a clone of vigilante 8 second offense. Now i need my AI car to aim its weapon to enemy's car. This car doesn't have turret that can rotate freely at any given angle. So, to aim its weapon, the whole car body must be rotated to the enemy. My car is simulated using wheel collider and moved by assigning WheelCollider.motorTorque and WheelCollider.steerAngle. I encapsulated those functionalities into a class called VLCarMotor (c#). To rotate, the AI car maxTorque must be > 0 (the car must be accelerated).

    The AI car is simulated using WheelCollider and its movements looks like a real car. It means that i cannot using a simple transform.Rotate to rotate this AI car to the target. I want the WheelCollider.steerAngle do this. How to measure the WheelCollider.steerAngle and the WheelCollider.motorTorque to make the AI car rotate its heading to the target?

    I know about Steering Behavior and the AI car can seek, arrive, avoid obstacle and followpath. But i have no idea how to rotate the AI car to the target while the target is close to the AI car. Could you tell me your ideas and solution to solve this problem? Thanks.
     
  2. rakifsul

    rakifsul

    Joined:
    Mar 30, 2014
    Posts:
    47
    no answer anyone? if you give me a correct answer, i will post my game prototype scripts in this thread ;). With those scripts you can learn to make a game like vigilante 8 second offense (but it's not finished yet):
    • Pathfinding for car (Pro Only) with custom navmeshagent script
    • Obstacle avoidance
    • Turret
    • How to convert mouse point to world (accurate)
    • FSM
    • N-wheeled car
    • Anti roll
    • Down force
     
  3. DreamintheInsane

    DreamintheInsane

    Joined:
    Aug 30, 2014
    Posts:
    1
    Could you add torque to the turn your vehicle, and also set a raycast on the front of the vehicle to determine when your car is in front of the car you are aiming at? Therefore, it would be set up to stop the addition of torque when in view.
     
  4. rakifsul

    rakifsul

    Joined:
    Mar 30, 2014
    Posts:
    47
  5. rakifsul

    rakifsul

    Joined:
    Mar 30, 2014
    Posts:
    47
    Ok, at least there is an answer to my question. I will add one more answer of my question. To make the AI car rotate to target, follow these steps:
    1. Get Target position
    2. Subtract it with player position
    3. Do Pathfinding at a point on the line between Target and Player.
    4. If AI car close enough to player, then do pathfinding in reverse mode. I mean, the AI car should follow the path found while moving backward.
    5. The steering force will rotate the steering wheels and move the drive wheels to make the AI car forward axis parallel with line between Target and Player.
    As i promised, i will post my game prototype codes. I only post the scripts, but don't worry, i planned to release a commercial unitypackage for battle car project. I will post the link here when i'm ready.

    First of all, you will need Tag Frenzy (search it on asset store, it's free).

    Part 1: Tag Frenzy Modification.
    Tag Frenzy will show an error in web player build setting. To fix it, copy System.Xml.Linq.dll from ..\unity\editor\data\mono\lib\mono\unity\ folder to your projet' Asset\Plugins.

    At Tag Frenzy file: TagFrenzy\Scripts\Core\MultiTagManager.cs. line 216 paste this code:

    Code (csharp):
    1.  
    2. //File.WriteAllLines(tagClassName, contents.ToArray());
    3.                
    4.                 FileStream fs = new FileStream(tagClassName, FileMode.Create);
    5.                 //BinaryWriter w = new BinaryWriter(fs);
    6.                 StreamWriter w = new StreamWriter(fs);
    7.                 for(int i = 0; i < contents.ToArray().Count(); ++i)
    8.                 {
    9.                     w.Write(contents.ToArray()[i]);//for loop through these
    10.                     //w.WriteLine();
    11.                     //w.Write(,System.Text.Encoding.UTF8.GetBytes(contents.ToArray()[i]));
    12.                     //w.WriteLine(contents.ToArray()[i]);
    13.                 }
    14.                 w.Close();
    15.                 fs.Close();
    16.  
    Part 2: N-Wheeled Car: VLCarMotor.cs:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class VLCarMotor : MonoBehaviour
    6. {
    7.     public enum DriveMode
    8.     {
    9.         NonSteeringWheelDrive,
    10.         SteeringWheelDrive,
    11.         AllWheelDrive
    12.     }
    13.  
    14.     public Transform centerOfMass;
    15.     public WheelCollider[] steeringWheels;
    16.     public WheelCollider[] driveWheels;
    17.     public DriveMode driveMode;
    18.  
    19.     public float maxTorque = 50f;
    20.     public float maxSpeed = 80f;
    21.     public float maxReverseSpeed = 50;
    22.     public float decceleration = 30f;
    23.     public float maxBrakeTorque = 70f;
    24.     public float maxSteerAngle = 20f;
    25.     public int gear;
    26.     public int[] gearRatio;
    27.     public bool processSlip = false;
    28.     private float myForwardFriction;
    29.     private float mySidewayFriction;
    30.     public float slipForwardFriction = 0.04f;
    31.     public float slipSidewayFriction = 0.02f;
    32.  
    33.     public float currentSpeed;
    34.     public float motorTorque;
    35.  
    36.     public bool braked = false;
    37.     public Vector3 moveVector = new Vector3(0,0,0);
    38.     // Use this for initialization
    39.     void Start ()
    40.     {
    41.         rigidbody.centerOfMass = centerOfMass.localPosition;
    42.         InitializeFrictionValues ();
    43.     }
    44.  
    45.     void InitializeFrictionValues()
    46.     {
    47.         if(driveWheels.Length == 0)
    48.             return;
    49.         myForwardFriction = driveWheels[0].forwardFriction.stiffness;
    50.         mySidewayFriction = driveWheels[0].sidewaysFriction.stiffness;
    51.     }
    52.  
    53.     public void Process()
    54.     {
    55.         ProcessMotion ();
    56.         ProcessGear ();
    57.     }
    58.  
    59.     protected void ProcessMotion()
    60.     {
    61.         if(steeringWheels.Length == 0 || driveWheels.Length == 0)
    62.             return;
    63.         //
    64.         moveVector.x = Mathf.Clamp (moveVector.x, -1, 1);
    65.         moveVector.y = Mathf.Clamp (moveVector.y, -1, 1);
    66.         moveVector.z = Mathf.Clamp (moveVector.z, -1, 1);
    67.         //
    68.         currentSpeed = 2 * 22 / 7 * driveWheels[0].radius * driveWheels[0].rpm * 60 / 1000;
    69.         //currentSpeed = 2 * 22 / 7 * steeringWheels[0].radius * steeringWheels[0].rpm * 60 / 1000;
    70.         //currentSpeed = Mathf.Round(currentSpeed);
    71.         //
    72.         foreach(WheelCollider wc in steeringWheels)
    73.         {
    74.             wc.brakeTorque = 0;
    75.             wc.motorTorque = 0;
    76.         }
    77.  
    78.         foreach(WheelCollider wc in driveWheels)
    79.         {
    80.             wc.brakeTorque = 0;
    81.             wc.motorTorque = 0;
    82.         }
    83.         //
    84.         if(driveMode == DriveMode.NonSteeringWheelDrive)
    85.             ProcessNonSteeringWheelDrive();
    86.         else if(driveMode == DriveMode.SteeringWheelDrive)
    87.             ProcessSteeringWheelDrive();
    88.         else if(driveMode == DriveMode.AllWheelDrive)
    89.             ProcessAllWheelDrive();
    90.         //
    91.         if(processSlip)
    92.             ProcessSlip ();
    93.         //
    94.         ProcessSteer ();
    95.         //
    96.         motorTorque = driveWheels[0].motorTorque;
    97.         //
    98.         moveVector = Vector3.zero;
    99.         braked = false;
    100.     }
    101.  
    102.     protected void ProcessNonSteeringWheelDrive()
    103.     {
    104.         if(currentSpeed < maxSpeed && currentSpeed > -Mathf.Abs(maxReverseSpeed))
    105.         {
    106.             foreach(WheelCollider wc in driveWheels)
    107.             {
    108.                 wc.motorTorque = moveVector.z * maxTorque;
    109.             }
    110.         }
    111.         else
    112.         {
    113.             foreach(WheelCollider wc in driveWheels)
    114.             {
    115.                 wc.motorTorque = 0;
    116.             }
    117.         }
    118.         //bug fix: exceed max speed when turning
    119.         if(currentSpeed > maxSpeed)
    120.         {
    121.             foreach(WheelCollider wc in driveWheels)
    122.             {
    123.                 wc.motorTorque = 0;
    124.                 wc.brakeTorque = 10 * (currentSpeed-maxSpeed);
    125.             }
    126.         }
    127.         //
    128.         if(moveVector.z == 0 && !braked)
    129.         {
    130.             foreach(WheelCollider wc in driveWheels)
    131.             {
    132.                 wc.brakeTorque = decceleration;
    133.             }
    134.         }
    135.         else if(braked)
    136.         {
    137.             foreach(WheelCollider wc in driveWheels)
    138.             {
    139.                 wc.brakeTorque = maxBrakeTorque;
    140.                 wc.motorTorque = 0;
    141.             }
    142.             foreach(WheelCollider wc in steeringWheels)
    143.             {
    144.                 wc.brakeTorque = maxBrakeTorque;
    145.             }
    146.         }
    147.     }
    148.  
    149.     protected void ProcessSteeringWheelDrive()
    150.     {
    151.         if(currentSpeed < maxSpeed && currentSpeed > -Mathf.Abs(maxReverseSpeed))
    152.         {
    153.             foreach(WheelCollider wc in steeringWheels)
    154.             {
    155.                 wc.motorTorque = moveVector.z * maxTorque;
    156.             }
    157.         }
    158.         else
    159.         {
    160.             foreach(WheelCollider wc in steeringWheels)
    161.             {
    162.                 wc.motorTorque = 0;
    163.             }
    164.         }
    165.         //bug fix: exceed max speed when turning
    166.         if((2 * 22 / 7 * steeringWheels[0].radius * steeringWheels[0].rpm * 60 / 1000) > maxSpeed)
    167.         {
    168.             foreach(WheelCollider wc in steeringWheels)
    169.             {
    170.                 wc.motorTorque = 0;
    171.                 wc.brakeTorque = 10 * ((2 * 22 / 7 * steeringWheels[0].radius * steeringWheels[0].rpm * 60 / 1000)-maxSpeed);
    172.             }
    173.         }
    174.         //
    175.         if(moveVector.z == 0 && !braked)
    176.         {
    177.             foreach(WheelCollider wc in steeringWheels)
    178.             {
    179.                 wc.brakeTorque = decceleration;
    180.             }
    181.         }
    182.         else if(braked)
    183.         {
    184.             foreach(WheelCollider wc in driveWheels)
    185.             {
    186.                 wc.brakeTorque = maxBrakeTorque;
    187.                 wc.motorTorque = 0;
    188.             }
    189.             foreach(WheelCollider wc in steeringWheels)
    190.             {
    191.                 wc.brakeTorque = maxBrakeTorque;
    192.                 wc.motorTorque = 0;
    193.             }
    194.         }
    195.     }
    196.  
    197.     protected void ProcessAllWheelDrive()
    198.     {
    199.         if(currentSpeed < maxSpeed && currentSpeed > -Mathf.Abs(maxReverseSpeed))
    200.         {
    201.             foreach(WheelCollider wc in steeringWheels)
    202.             {
    203.                 wc.motorTorque = moveVector.z * maxTorque;
    204.             }
    205.             foreach(WheelCollider wc in driveWheels)
    206.             {
    207.                 wc.motorTorque = moveVector.z * maxTorque;
    208.             }
    209.         }
    210.         else
    211.         {
    212.             foreach(WheelCollider wc in steeringWheels)
    213.             {
    214.                 wc.motorTorque = 0;
    215.             }
    216.             foreach(WheelCollider wc in driveWheels)
    217.             {
    218.                 wc.motorTorque = 0;
    219.             }
    220.         }
    221.         //bug fix: exceed max speed when turning
    222.         if((2 * 22 / 7 * steeringWheels[0].radius * steeringWheels[0].rpm * 60 / 1000) > maxSpeed)
    223.         {
    224.             foreach(WheelCollider wc in steeringWheels)
    225.             {
    226.                 wc.motorTorque = 0;
    227.                 wc.brakeTorque = 10 * ((2 * 22 / 7 * steeringWheels[0].radius * steeringWheels[0].rpm * 60 / 1000)-maxSpeed);
    228.             }
    229.         }
    230.         if((2 * 22 / 7 * driveWheels[0].radius * driveWheels[0].rpm * 60 / 1000) > maxSpeed)
    231.         {
    232.             foreach(WheelCollider wc in driveWheels)
    233.             {
    234.                 wc.motorTorque = 0;
    235.                 wc.brakeTorque = 10 * ((2 * 22 / 7 * driveWheels[0].radius * driveWheels[0].rpm * 60 / 1000)-maxSpeed);
    236.             }
    237.         }
    238.         //
    239.         if(moveVector.z == 0 && !braked)
    240.         {
    241.             foreach(WheelCollider wc in steeringWheels)
    242.             {
    243.                 wc.brakeTorque = decceleration;
    244.             }
    245.             foreach(WheelCollider wc in driveWheels)
    246.             {
    247.                 wc.brakeTorque = decceleration;
    248.             }
    249.         }
    250.         else if(braked)
    251.         {
    252.             foreach(WheelCollider wc in driveWheels)
    253.             {
    254.                 wc.brakeTorque = maxBrakeTorque;
    255.                 wc.motorTorque = 0;
    256.             }
    257.             foreach(WheelCollider wc in steeringWheels)
    258.             {
    259.                 wc.brakeTorque = maxBrakeTorque;
    260.                 wc.motorTorque = 0;
    261.             }
    262.         }
    263.     }
    264.  
    265.     protected void ProcessSlip()
    266.     {
    267.         if(braked)
    268.         {
    269.             if(rigidbody.velocity.magnitude > 1)
    270.             {
    271.                 SetSlip(slipForwardFriction, slipSidewayFriction);
    272.             }
    273.             else
    274.             {
    275.                 SetSlip(1, 1);
    276.             }
    277.         }
    278.         else
    279.         {
    280.             SetSlip(myForwardFriction, mySidewayFriction);
    281.         }
    282.     }
    283.  
    284.     protected void ProcessSteer()
    285.     {
    286.         float newSteer = moveVector.x * maxSteerAngle;
    287.         foreach(WheelCollider wc in steeringWheels)
    288.         {
    289.             wc.steerAngle = newSteer;
    290.         }
    291.     }
    292.  
    293.     protected void ProcessGear()
    294.     {
    295.         if(gearRatio.Length <= 0)
    296.             return;
    297.         if(maxSpeed > gearRatio [gearRatio.Length - 1])
    298.             gearRatio [gearRatio.Length - 1] = (int)maxSpeed + 1;
    299.    
    300.         int c = 0;
    301.         for(c = 0; c < gearRatio.Length; c++)
    302.         {
    303.             if(gearRatio[c] > currentSpeed)
    304.             {
    305.                 gear = c;
    306.                 break;
    307.             }
    308.         }
    309.         if(c >= gearRatio.Length)
    310.             c = gearRatio.Length-1;
    311.    
    312.         float gearMinValue = 0.00f;
    313.         float gearMaxValue = 0.00f;
    314.         if(c == 0)
    315.         {
    316.             gearMinValue = 0;
    317.             gearMaxValue = gearRatio[c];
    318.         }
    319.         else
    320.         {
    321.             //if(c > 0)
    322.             {
    323.                 gearMinValue = gearRatio[c - 1];
    324.             }
    325.             //if(c >= 0 && c < gearRatio.length)
    326.             if(c < gearRatio.Length)
    327.             {
    328.                 gearMaxValue = gearRatio[c];
    329.             }
    330.             else
    331.             {
    332.                 Debug.Log(c + "---" + gearRatio.Length );
    333.             }
    334.             //if(i > gearRatio.length - 1)
    335.             //{
    336.             //    Debug.Log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    337.             //}
    338.         }
    339.         float enginePitch = ((currentSpeed - gearMinValue) / (gearMaxValue - gearMinValue)) + 1;
    340.         audio.pitch = enginePitch;
    341.     }
    342.  
    343.     void SetSlip(float currentForwardFriction, float currentSidewayFriction)
    344.     {
    345.         foreach(WheelCollider wc in driveWheels)
    346.         {
    347.             WheelFrictionCurve wfc = wc.forwardFriction;
    348.             wfc.stiffness = currentForwardFriction;
    349.             wc.forwardFriction = wfc;
    350.  
    351.             WheelFrictionCurve wfcs = wc.sidewaysFriction;
    352.             wfcs.stiffness = currentSidewayFriction;
    353.             wc.sidewaysFriction = wfcs;
    354.         }
    355.         foreach(WheelCollider wc in steeringWheels)
    356.         {
    357.             WheelFrictionCurve wfc = wc.forwardFriction;
    358.             wfc.stiffness = currentForwardFriction;
    359.             wc.forwardFriction = wfc;
    360.  
    361.             WheelFrictionCurve wfcs = wc.sidewaysFriction;
    362.             wfcs.stiffness = currentSidewayFriction;
    363.             wc.sidewaysFriction = wfcs;
    364.         }
    365.     }
    366. }
    367.  
    Part 3: Anti Roll:VLAntiRoll.cs:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class VLAntiRoll : MonoBehaviour
    6. {
    7.     public WheelCollider wheelL;
    8.     public WheelCollider wheelR;
    9.     public float antiRoll = 10000f;
    10.  
    11.     void FixedUpdate()
    12.     {
    13.         WheelHit hit;
    14.         float travelL = 1.0f;
    15.         float travelR = 1.0f;
    16.  
    17.         bool groundedL = wheelL.GetGroundHit (out hit);
    18.         if (groundedL)
    19.             travelL = (-wheelL.transform.InverseTransformPoint(hit.point).y - wheelL.radius) / wheelL.suspensionDistance;
    20.    
    21.         bool groundedR = wheelR.GetGroundHit(out hit);
    22.         if (groundedR)
    23.             travelR = (-wheelR.transform.InverseTransformPoint(hit.point).y - wheelR.radius) / wheelR.suspensionDistance;
    24.    
    25.         var antiRollForce = (travelL - travelR) * antiRoll;
    26.    
    27.         if (groundedL)
    28.             rigidbody.AddForceAtPosition(wheelL.transform.up * -antiRollForce,
    29.                                         wheelL.transform.position);
    30.         if (groundedR)
    31.             rigidbody.AddForceAtPosition(wheelR.transform.up * antiRollForce,
    32.                                         wheelR.transform.position);
    33.     }
    34. }
    35.  
    Part 4: Down Force: VLDownForce:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class VLDownForce : MonoBehaviour
    6. {
    7.     public float forceFactor = 1000;
    8.     void FixedUpdate ()
    9.     {
    10.         rigidbody.AddForce(-transform.up * rigidbody.velocity.magnitude * forceFactor);
    11.     }
    12. }
    13.  
    Part 5: Turret: VLTurret.cs:
    This turret can pitch and yaw to a direction wherever your car roll without rolling itself.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class VLTurret : MonoBehaviour
    6. {
    7.     public Transform turretHead;
    8.     public float turretRotationDamping = 20f;
    9.     public Vector3 targetDirection = Vector3.zero;
    10.  
    11.     private Quaternion tmpRotation;
    12.     private Quaternion targetRotationYaw;
    13.     private Quaternion targetRotationPitch;
    14.     private Vector3 tgtPos;
    15.  
    16.     public Vector3 targetPosition
    17.     {
    18.         get
    19.         {
    20.             return tgtPos;
    21.         }
    22.         set
    23.         {
    24.             tgtPos = value;
    25.             targetDirection = tgtPos - turretHead.position;
    26.         }
    27.     }
    28.  
    29.     void Start()
    30.     {
    31.    
    32.     }
    33.     void FixedUpdate()
    34.     {
    35.         if(turretHead.parent.parent == null)
    36.         {
    37.             Debug.LogError("turretHead.parent.parent == null");
    38.             return;
    39.         }
    40.         tmpRotation = turretHead.parent.localRotation;
    41.         targetRotationYaw = Quaternion.LookRotation (targetDirection, turretHead.parent.parent.up);
    42.         turretHead.parent.rotation = Quaternion.Slerp(turretHead.parent.rotation, targetRotationYaw, Time.deltaTime * turretRotationDamping);
    43.         turretHead.parent.localRotation = Quaternion.Euler( tmpRotation.eulerAngles.x, turretHead.parent.localRotation.eulerAngles.y, tmpRotation.eulerAngles.z);
    44.    
    45.         tmpRotation = turretHead.localRotation;
    46.         targetRotationPitch = Quaternion.LookRotation (targetDirection, turretHead.parent.transform.up);
    47.         turretHead.rotation = Quaternion.Slerp(turretHead.rotation, targetRotationPitch, Time.deltaTime * turretRotationDamping);
    48.         turretHead.localRotation = Quaternion.Euler( turretHead.localRotation.eulerAngles.x, tmpRotation.eulerAngles.y, tmpRotation.eulerAngles.z);
    49.     }
    50. }
    51.  
    Part 6: How to convert mouse point to world (accurate) : Code Snippet:

    Code (csharp):
    1.  
    2. VLTurret turretScript = GetComponent<VLTurret>();
    3. RaycastHit hit = new RaycastHit();
    4.             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    5.             if(Physics.Raycast(ray, out hit, 9999f))
    6.             {
    7.                 turretScript.targetPosition = hit.point;
    8.             }
    9.             else
    10.             {
    11.                 float t = ray.origin.y / ray.direction.y;
    12.                 turretScript.targetPosition = ray.GetPoint(t);
    13.             }
    14.  
    Part 7: Pathfinding for car (Pro Only) with custom navmeshagent script including Obstacle Avoidance: VLCarAIController.cs

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using TagFrenzy;
    6.  
    7. public class VLCarAIController : MonoBehaviour
    8. {
    9.     public enum BehaviorType
    10.     {
    11.         None               = 0x00000,
    12.         Seek               = 0x00002,
    13.         Arrive             = 0x00004,
    14.         FollowPath         = 0x00008,
    15.         ObstacleAvoidance  = 0x00010,
    16.     };
    17.     public Transform target;
    18.     public VLCarMotor motorScript;
    19.     public VLObstacleSensor obstacleSensorScript;
    20.  
    21.     public string[] states;
    22.     public string initialState = "VLState_Dummy";
    23.  
    24.     public float maxForce = 100.0f;
    25.     public float maxSpeed = 100.0f;
    26.     public float minSpeed = 0.0f;
    27.     //
    28.     public float weightSeek = 1.0f;
    29.     public float weightArrive = 1.0f;
    30.     public float weightFollowPath = 1.0f;
    31.     public float weightObstacleAvoidance = 1.0f;
    32.     public float deceleration = 0.01f;
    33.     public float stoppingDistance = 1.0f;
    34.     public float waypointSeekDistance = 0.5f;
    35.     public float avoidanceMultiplier = 50;
    36.  
    37.     public Vector3 steeringForce = new Vector3(0,0,0);
    38.  
    39.     private VLCarAIController.BehaviorType behavior = VLCarAIController.BehaviorType.None;
    40.     private VLStateMachine stateMach;
    41.     private Vector3 dest;
    42.     private VLPath path;
    43.     //
    44.     public VLStateMachine stateMachine
    45.     {
    46.         get
    47.         {
    48.             return stateMach;
    49.         }
    50.     }
    51.  
    52.     public bool straightReachedDestination
    53.     {
    54.         get
    55.         {
    56.             return (destination - position).magnitude <= stoppingDistance;
    57.         }
    58.     }
    59.  
    60.     public float straightDistanceToDestination
    61.     {
    62.         get
    63.         {
    64.             return (destination - position).magnitude;
    65.         }
    66.     }
    67.  
    68.     public bool pathReachedDestination
    69.     {
    70.         get
    71.         {
    72.             return (path.GetCurrentLength() + (path.GetCurrentWaypoint()-position).magnitude) <= stoppingDistance && path.IsFinished();
    73.         }
    74.     }
    75.  
    76.     public float pathDistanceToDestination
    77.     {
    78.         get
    79.         {
    80.             return path.GetCurrentLength() + (path.GetCurrentWaypoint()-position).magnitude;
    81.         }
    82.     }
    83.  
    84.     public bool pathCalculationError
    85.     {
    86.         get
    87.         {
    88.             return pathReachedDestination && !path.IsFinished();
    89.         }
    90.     }
    91.  
    92.     public Vector3 destination
    93.     {
    94.         get
    95.         {
    96.             return dest;
    97.         }
    98.         set
    99.         {
    100.             dest = value;
    101.             if(!path.CalculatePath(position,dest))
    102.             {
    103.             //    Debug.Log("CalculatePath Failed");
    104.             }
    105.         }
    106.     }
    107.  
    108.     public Vector3 position
    109.     {
    110.         get
    111.         {
    112.             return transform.position;
    113.         }
    114.     }
    115.  
    116.     public Vector3 velocity
    117.     {
    118.         get
    119.         {
    120.             return rigidbody.velocity;
    121.         }
    122.     }
    123.     //
    124.     public bool IsActive(VLCarAIController.BehaviorType b){ return (b & behavior) == b; }
    125.     public void ResetBehavior(){ behavior = VLCarAIController.BehaviorType.None;   }
    126.     public void SeekOn(){ behavior |= VLCarAIController.BehaviorType.Seek; }
    127.     public void SeekOff(){if(IsActive(VLCarAIController.BehaviorType.Seek)) behavior ^= VLCarAIController.BehaviorType.Seek; }
    128.     public void ArriveOn(){ behavior |= VLCarAIController.BehaviorType.Arrive; }
    129.     public void ArriveOff(){if(IsActive(VLCarAIController.BehaviorType.Arrive)) behavior ^= VLCarAIController.BehaviorType.Arrive; }
    130.     public void FollowPathOn(){ behavior |= VLCarAIController.BehaviorType.FollowPath; }
    131.     public void FollowPathOff(){if(IsActive(VLCarAIController.BehaviorType.FollowPath)) behavior ^= VLCarAIController.BehaviorType.FollowPath; }
    132.     public void ObstacleAvoidanceOn(){ behavior |= VLCarAIController.BehaviorType.ObstacleAvoidance; }
    133.     public void ObstacleAvoidanceOff(){if(IsActive(VLCarAIController.BehaviorType.ObstacleAvoidance)) behavior ^= VLCarAIController.BehaviorType.ObstacleAvoidance; }
    134.     //
    135.     // Use this for initialization
    136.     void Awake ()
    137.     {
    138.         path = new VLPath();
    139.         destination = position;
    140.         stateMach = new VLStateMachine();
    141.         foreach (string st in states)
    142.         {
    143.             stateMach.AddState(st,GetComponent(st) as VLState);
    144.         }
    145.         //
    146.  
    147.         //
    148.         if(stateMach.GetState(initialState) != null)
    149.             stateMach.ChangeState(initialState);
    150.     }
    151.  
    152.     // Update is called once per frame
    153.     void Update ()
    154.     {
    155.  
    156.     }
    157.  
    158.     void FixedUpdate()
    159.     {
    160.         destination = destination;
    161.         CalculateSteeringForce ();
    162.         destination = target.position;
    163.         FollowPathOn ();
    164.         ObstacleAvoidanceOn ();
    165.         MoveForwardToDestination ();
    166.         //MoveBackwardToDestination ();
    167.         //
    168.         stateMachine.UpdateState();
    169.         motorScript.Process ();
    170.     }
    171.  
    172.     void MoveForwardToDestination()
    173.     {
    174.         motorScript.moveVector.z = Mathf.Clamp (steeringForce.magnitude,0,1);
    175.         motorScript.moveVector.x = Mathf.Clamp (VLHelper.FindAngleDegree(transform.forward, steeringForce, transform.up)/90f,-1f,1f);
    176.     }
    177.  
    178.     void MoveBackwardToDestination()
    179.     {
    180.         motorScript.moveVector.z = -Mathf.Clamp (steeringForce.magnitude,0,1);
    181.         motorScript.moveVector.x = -Mathf.Clamp (VLHelper.FindAngleDegree(-transform.forward, steeringForce, transform.up)/90f,-1f,1f);
    182.     }
    183.  
    184.     public Vector3 GetRandomPointOnNavMesh(float walkRadius, float height)
    185.     {
    186.         Vector2 randomPoint = Random.insideUnitCircle * walkRadius;
    187.         randomPoint.x += transform.position.x;
    188.         randomPoint.y += transform.position.z;
    189.         RaycastHit[] rhits = Physics.RaycastAll(new Vector3(randomPoint.x,height,randomPoint.y),-Vector3.up);
    190.         if (rhits.Length == 0)
    191.         {
    192.             return GetRandomPointOnNavMesh(walkRadius,height);
    193.         }
    194.         List<Vector3> checkPoints = new List<Vector3>();
    195.         foreach (RaycastHit rhit in rhits)
    196.         {
    197.             NavMeshPath nmp = new NavMeshPath();
    198.             if(NavMesh.CalculatePath(transform.position,rhit.point,-1, nmp))
    199.             {
    200.                 checkPoints.Add(rhit.point);
    201.             }
    202.         }
    203.         if (checkPoints.Count == 0)
    204.         {
    205.             return GetRandomPointOnNavMesh(walkRadius,height);
    206.         }
    207.      
    208.         return checkPoints[Random.Range(0,checkPoints.Count-1)];
    209.     }
    210.  
    211.     public Vector3 PredictFuturePosition(float predictionTime)
    212.     {
    213.         return transform.position + (velocity * predictionTime);
    214.     }
    215.  
    216.     Vector3 Seek(Vector3 targetPos)
    217.     {
    218.         Vector3 desiredVel = ((targetPos - position).normalized * maxSpeed);
    219.         Vector3 force = desiredVel - velocity;
    220.         return force;
    221.     }
    222.  
    223.     Vector3 Arrive(Vector3 targetPos)
    224.     {
    225.         Vector3 toTarget = targetPos - position;
    226.         float dist = toTarget.magnitude;
    227.         if (dist > stoppingDistance)
    228.         {
    229.             float decelerationTweaker = 0.3f;
    230.             float speed = dist / (deceleration * decelerationTweaker);
    231.             speed = Mathf.Min(speed, maxSpeed);
    232.          
    233.             Vector3 desiredVel = toTarget * speed / dist;
    234.             //if((desiredVel - aiAgent.Velocity).magnitude == 0)
    235.             //Debug.Log((desiredVel - aiAgent.Velocity).ToString());
    236.             Vector3 force = desiredVel - velocity;
    237.             return force;
    238.         }
    239.         return Vector3.zero;
    240.     }
    241.  
    242.     Vector3 FollowPath()
    243.     {
    244.         if ((path.GetCurrentWaypoint() - position).magnitude < waypointSeekDistance)
    245.         {
    246.             path.SetNextWaypoint();
    247.         }
    248.         if (!path.IsFinished())
    249.         {
    250.             return Seek(path.GetCurrentWaypoint());
    251.         }
    252.         else
    253.         {
    254.             return Arrive(path.GetCurrentWaypoint());
    255.         }
    256.         return Vector3.zero;
    257.     }
    258.  
    259.     Vector3 ObstacleAvoidance()
    260.     {
    261.         Vector3 avoidance = Vector3.zero;
    262.         Vector3 futurePosition = PredictFuturePosition(1.0f);
    263.         if(obstacleSensorScript == null)
    264.             return avoidance;
    265.         if(obstacleSensorScript.obstacles.Count == 0)
    266.             return avoidance;
    267.         foreach(GameObject obs in obstacleSensorScript.obstacles)
    268.         {
    269.             if(obs == null || obs.Equals(null))
    270.             {
    271.                 continue;
    272.             }
    273.             var distanceCurrent = position - obs.transform.position;
    274.             var distanceFuture = futurePosition - obs.transform.position;
    275.             avoidance += avoidanceMultiplier * distanceCurrent / distanceFuture.sqrMagnitude;
    276.         }
    277.         avoidance /= obstacleSensorScript.obstacles.Count;
    278.         var newDesired = Vector3.Reflect(velocity, avoidance);
    279.         return newDesired;
    280.     }
    281.  
    282.     bool AccumulateForce(ref Vector3 runningTot, Vector3 forceToAdd)
    283.     {
    284.         float magnitudeSoFar = runningTot.magnitude;
    285.         float magnitudeRemaining = maxForce - magnitudeSoFar;
    286.         if (magnitudeRemaining <= 0.0f)
    287.             return false;
    288.         float magnitudeToAdd = forceToAdd.magnitude;
    289.         if (magnitudeToAdd < magnitudeRemaining)
    290.         {
    291.             runningTot += forceToAdd;
    292.         }
    293.         else
    294.         {
    295.             runningTot += (forceToAdd.normalized * magnitudeRemaining);
    296.         }
    297.         return true;
    298.     }
    299.  
    300.     Vector3 CalculatePrioritized()
    301.     {
    302.         Vector3 force = new Vector3();
    303.      
    304.         if (IsActive(VLCarAIController.BehaviorType.Seek))
    305.         {
    306.             force = Seek(destination) * weightSeek;
    307.             if (!AccumulateForce(ref steeringForce, force))
    308.                 return steeringForce;
    309.         }
    310.         if (IsActive(VLCarAIController.BehaviorType.Arrive))
    311.         {
    312.             force = Arrive(destination) * weightArrive;
    313.             if (!AccumulateForce(ref steeringForce, force))
    314.                 return steeringForce;
    315.         }
    316.         if (IsActive(VLCarAIController.BehaviorType.FollowPath))
    317.         {
    318.             force = FollowPath() * weightFollowPath;
    319.             if (!AccumulateForce(ref steeringForce, force))
    320.                 return steeringForce;
    321.         }
    322.         if (IsActive(VLCarAIController.BehaviorType.ObstacleAvoidance))
    323.         {
    324.             force = ObstacleAvoidance() * weightObstacleAvoidance;
    325.             //force = Vector3.Lerp(force,ObstacleAvoidance() * weightObstacleAvoidance,0.1f);
    326.             if (!AccumulateForce(ref steeringForce, force))
    327.                 return steeringForce;
    328.         }
    329.         return steeringForce;
    330.     }
    331.     void CalculateSteeringForce()
    332.     {
    333.         steeringForce = Vector3.zero;
    334.         steeringForce = CalculatePrioritized();
    335.         Debug.DrawRay (
    336.             new Vector3(transform.position.x, transform.position.y + 5, transform.position.z),
    337.             steeringForce * 10, Color.blue);
    338.     }
    339. }
    340.  
    341.  
    The Code above depends on Path Script: VLPath.cs

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class VLPath
    7. {
    8.     private NavMeshPath path = new NavMeshPath();
    9.     private Vector3 currentWaypoint = new Vector3();
    10.     private int index;
    11.  
    12.     public VLPath()
    13.     {
    14.         index = 0;
    15.         //currentWaypoint = path.corners [index];
    16.     }
    17.     public float GetLength()
    18.     {
    19.         if (path.corners.Length < 2)
    20.             return 0;
    21.         Vector3 previousCorner = path.corners[0];
    22.         float lengthSoFar = 0.0f;
    23.    
    24.         // Calculate the total distance by adding up the lengths
    25.         // of the straight lines between corners.
    26.         for (int i = 1; i < path.corners.Length; i++)
    27.         {
    28.             Vector3 currentCorner = path.corners[i];
    29.             lengthSoFar += Vector3.Distance(previousCorner, currentCorner);
    30.             previousCorner = currentCorner;
    31.         }
    32.         return lengthSoFar;
    33.     }
    34.     public float GetCurrentLength()
    35.     {
    36.         if (path.corners.Length < 2)
    37.             return 0;
    38.         if (index >= path.corners.Length)
    39.             return 0;
    40.         if (IsFinished())
    41.             return 0;
    42.         Vector3 previousCorner = path.corners[index];
    43.         float lengthSoFar = 0.0f;
    44.    
    45.         // Calculate the total distance by adding up the lengths
    46.         // of the straight lines between corners.
    47.         for (int i = index + 1; i < path.corners.Length; i++)
    48.         {
    49.             Vector3 currentCorner = path.corners[i];
    50.             lengthSoFar += Vector3.Distance(previousCorner, currentCorner);
    51.             previousCorner = currentCorner;
    52.         }
    53.         return lengthSoFar;
    54.     }
    55.     public Vector3 GetCurrentWaypoint()
    56.     {
    57.         //if (path.corners.Length == 0)
    58.         //    Debug.LogError("MLPath.GetCurrentWaypoint() path corners length = 0");
    59.         return currentWaypoint;
    60.     }
    61.     public void SetNextWaypoint()
    62.     {
    63.         if (index >= path.corners.Length)
    64.         {
    65.             //index = path.corners.Length - 1;
    66.             //currentWaypoint = path.corners [index];
    67.             return;
    68.         }
    69.         currentWaypoint = path.corners [index];
    70.         ++index;
    71.     }
    72.     public bool IsFinished()
    73.     {
    74.         if (path.corners.Length == 0)
    75.             return true;
    76.         return currentWaypoint == path.corners[path.corners.Length-1];
    77.     }
    78.     public bool CalculatePath(Vector3 from, Vector3 to)
    79.     {
    80.         NavMeshPath tPath = new NavMeshPath();
    81.         bool ret = NavMesh.CalculatePath(from, to, -1, tPath);
    82.         if (ret)
    83.         {
    84.             path = tPath;
    85.             if(path.corners.Length > 1)
    86.             {
    87.                 index = 1;
    88.                 currentWaypoint = path.corners[index];
    89.             }
    90.             else
    91.             {
    92.                 index = 0;
    93.                 currentWaypoint = path.corners[index];
    94.             }
    95.             return true;
    96.         }
    97.         index = 0;
    98.         path = tPath;
    99.         return false;
    100.     }
    101. }
    102.  
    Part 8 : FSM: This Part is divided to 2 scripts:
    VLState and VLStateMachine. To use VLState as a state, inherit your class from VLState and MonoBehavior using multiple inheritance, then access your VLCarAIController using GetComponent<>().


    VLState.cs:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public interface VLState
    6. {
    7.     void Enter();
    8.     void Execute();
    9.     void Exit();
    10. }
    11.  
    VLStateMachine.cs:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class VLState_Dummy : VLState
    7. {
    8.     public void Enter()
    9.     {
    10.         Debug.Log("VLState_Dummy.Enter");
    11.     }
    12.     public void Execute()
    13.     {
    14.         //Debug.Log("VLState_Dummy.Execute");
    15.     }
    16.     public void Exit()
    17.     {
    18.         Debug.Log("VLState_Dummy.Exit");
    19.     }
    20. }
    21.  
    22. public class VLStateMachine
    23. {
    24.     private Dictionary<string,VLState> states;
    25.  
    26.     private VLState currentState;
    27.     private VLState previousState;
    28.     private VLState globalState;
    29.     private VLState dummyState;
    30.  
    31.     public VLState GetCurrentState()
    32.     {
    33.         return currentState;
    34.     }
    35.     public VLState GetPreviousState()
    36.     {
    37.         return previousState;
    38.     }
    39.     public VLState GetGlobalState()
    40.     {
    41.         return globalState;
    42.     }
    43.     public VLStateMachine()
    44.     {
    45.         dummyState = new VLState_Dummy();
    46.         states = new Dictionary<string,VLState>();
    47.         currentState = dummyState;
    48.         previousState = dummyState;
    49.         globalState = dummyState;
    50.         //Debug.Log(dummyState.ToString());
    51.         AddState("VLState_Dummy", dummyState);
    52.     }
    53.     public void AddState(string name, VLState st)
    54.     {
    55.         if (st == null)
    56.         {
    57.             Debug.LogError("VLStateMachine.AddState: FSM ERROR: Null reference is not allowed");
    58.             return;
    59.         }
    60.         if (states.ContainsKey(name))
    61.         {
    62.             Debug.LogError("VLStateMachine.AddState: FSM ERROR: duplicate name");
    63.             return;
    64.         }
    65.         states.Add(name, st);
    66.     }
    67.     public void RemoveState(string stName)
    68.     {
    69.         if (!states.ContainsKey(stName))
    70.         {
    71.             Debug.LogError("VLStateMachine.RemoveState: FSM ERROR: state with name: " + stName + "not exist");
    72.             return;
    73.         }
    74.         states.Remove(stName);
    75.     }
    76.     public VLState GetState(string stName)
    77.     {
    78.         if (!states.ContainsKey(stName))
    79.         {
    80.             Debug.LogError("VLStateMachine.GetState: FSM ERROR: state with name: " + stName + "not exist");
    81.             return null;
    82.         }
    83.         return states [stName];
    84.     }
    85.     public void UpdateState()
    86.     {
    87.         if (currentState != null)
    88.             currentState.Execute();
    89.         if (globalState != null)
    90.             globalState.Execute();
    91.     }
    92.     public void ChangeState(string newStateName)
    93.     {
    94.         VLState newState = GetState(newStateName);
    95.         if (newState != null)
    96.         {
    97.             ChangeState(newState);
    98.             return;
    99.         }
    100.         Debug.LogError("VLStateMachine.ChangeState : FSM ERROR: The state passed was not on the dictionary.");
    101.     }
    102.     void ChangeState(VLState newState)
    103.     {
    104.         previousState = currentState;
    105.    
    106.         //call the exit method of the existing state
    107.         currentState.Exit();
    108.    
    109.         //change state to the new state
    110.         currentState = newState;
    111.    
    112.         //call the entry method of the new state
    113.         currentState.Enter();
    114.     }
    115.     public void ChangeGlobalState(string stateName)
    116.     {
    117.         VLState newGlobalState = GetState(stateName);
    118.         if (newGlobalState != null)
    119.         {
    120.             globalState.Exit();
    121.             globalState = newGlobalState;
    122.             globalState.Enter();
    123.             return;
    124.         }
    125.         Debug.LogError("VLStateMachine.ChangeGlobalState : FSM ERROR: The state passed was not on the dictionary.");
    126.     }
    127.     public void RevertToPreviousState()
    128.     {
    129.         ChangeState(previousState);
    130.     }
    131.     public void GoToDummyState()
    132.     {
    133.         ChangeState(dummyState);
    134.     }
    135.     public void ReloadState()
    136.     {
    137.         if (IsInState("VLState_Dummy"))
    138.             return;
    139.         GoToDummyState();
    140.         RevertToPreviousState();
    141.     }
    142.     public bool IsInState(string stateName)
    143.     {
    144.         VLState st = GetState(stateName);
    145.         if (st == null)
    146.             return false;
    147.         if (st == currentState)
    148.             return true;
    149.         return false;
    150.     }
    151.     public string GetNameOfCurrentState()
    152.     {
    153.         foreach (KeyValuePair<string, VLState> pair in states)
    154.         {
    155.             if(pair.Value == currentState)
    156.                 return pair.Key;
    157.         }
    158.         Debug.LogError("VLStateMachine.GetNameOfCurrentState : Unknown State");
    159.         return "Unknown State";
    160.     }
    161.  
    162. }
    163.  
    Part 9: Obstacle Sensor Script:VLObstacleSensor.cs
    attach this code to a child GameObject with SphereCollider Component.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using TagFrenzy;
    6.  
    7. public class VLObstacleSensor : MonoBehaviour
    8. {
    9.     public HashSet<GameObject> obstacles;
    10.     public SphereCollider sensorCollider;
    11.     public Tags obstacleTag = Tags.Obstacle;
    12.  
    13.     void Start()
    14.     {
    15.         obstacles = new HashSet<GameObject> ();
    16.     }
    17.  
    18.     void FixedUpdate()
    19.     {
    20.         //Debug.Log (obstacles.Count);
    21.     }
    22.  
    23.     void OnTriggerStay(Collider other)
    24.     {
    25.         //if(other.gameObject.name == "Obs")
    26.         //    Debug.Log ("root collider hit");
    27.         if(other.gameObject.tagsEnum().Contains(obstacleTag))
    28.         {
    29.             float test = float.MaxValue;
    30.             GameObject closest = null;
    31.             RaycastHit closestHit = new RaycastHit();
    32.             Vector3 origin = transform.position + transform.up;
    33.             Vector3 direction = other.transform.position - transform.position;
    34.             RaycastHit[] rhits = Physics.RaycastAll(
    35.                 origin,
    36.                 direction.normalized,
    37.                 sensorCollider.radius);
    38.             foreach (RaycastHit rhit in rhits)
    39.             {
    40.                 if(rhit.transform.root.gameObject == this.transform.root.gameObject ||
    41.                   rhit.transform.root.gameObject.Equals(this.transform.root.gameObject))
    42.                 {
    43.                     continue;
    44.                 }
    45.                 float distance = (rhit.point - origin).magnitude;
    46.                 if(distance < test)
    47.                 {
    48.                     test = distance;
    49.                     closest = rhit.collider.gameObject;
    50.                     closestHit = rhit;
    51.                 }
    52.             }
    53.             if(closest == null)
    54.                 return;
    55.             Debug.DrawLine(origin, closestHit.point,Color.red);
    56.            
    57.             if(closest.tagsEnum().Contains(obstacleTag))
    58.             {
    59.                 obstacles.Add(closest);
    60.             }
    61.         }
    62.     }
    63.  
    64.     void OnTriggerExit (Collider other)
    65.     {
    66.         if(other.gameObject.tagsEnum().Contains(obstacleTag))
    67.         {
    68.             obstacles.Remove(other.gameObject);
    69.         }
    70.     }
    71. }
    72.  
     
    Last edited: Sep 3, 2014
  6. Kirlim

    Kirlim

    Joined:
    Aug 6, 2012
    Posts:
    126
    Didn't quite understand your pathfinding solution. I'd go with a steering algorithm using raycasts to detect close obstacles that avoid movement (assuming non-mobile game) when obstacles would cause collision.

    Heuristics:
    1)Can I move forward while turning? If yes, move forward turning at the best guessed forward and angular speed.
    2)If not, what about moving backwards?
    3)If not either, life is unfair and I can't target my desired enemy. Shame on easiness!

    My main reasons for a heuristical steering algorithm:
    - Lighter than pathfinding, if you don't abuse of the raycasts
    - Easer to consider any nearby obstacle, such as other vehicles with the same behavior
    - Automatic swarm behavior. If a crowd of vehicles are together, the innermost ones will automatically wait the outermost ones move away (will need to use "minimal space to start moving, though")
     
  7. rakifsul

    rakifsul

    Joined:
    Mar 30, 2014
    Posts:
    47
    My bad, i forgot to post the Obstacle Sensor script. It's using Trigger and raycast to detect the obstacle. With that script, the AI car should be able to avoid dynamic obstacle while following the path. I will edit and add the script to my post above.

    However, your heuristics is right, if the AI car is surrounded by obstacle(for example the other AI cars) and get stucked, the AI car should make a decision.

    To solve that, we can turn the Obstacle Avoidance behavior off to make the AI car run into the other AI cars and crash them. Or.. we can turn all behaviors off to make it stop moving and wait until the obstacle gone. We do this in the FSM state.