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:
- Get Target position
- Subtract it with player position
- Do Pathfinding at a point on the line between Target and Player.
- 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.
- 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:
//File.WriteAllLines(tagClassName, contents.ToArray());
FileStream fs = new FileStream(tagClassName, FileMode.Create);
//BinaryWriter w = new BinaryWriter(fs);
StreamWriter w = new StreamWriter(fs);
for(int i = 0; i < contents.ToArray().Count(); ++i)
{
w.Write(contents.ToArray()[i]);//for loop through these
//w.WriteLine();
//w.Write(,System.Text.Encoding.UTF8.GetBytes(contents.ToArray()[i]));
//w.WriteLine(contents.ToArray()[i]);
}
w.Close();
fs.Close();
Part 2: N-Wheeled Car: VLCarMotor.cs:
using UnityEngine;
using System.Collections;
public class VLCarMotor : MonoBehaviour
{
public enum DriveMode
{
NonSteeringWheelDrive,
SteeringWheelDrive,
AllWheelDrive
}
public Transform centerOfMass;
public WheelCollider[] steeringWheels;
public WheelCollider[] driveWheels;
public DriveMode driveMode;
public float maxTorque = 50f;
public float maxSpeed = 80f;
public float maxReverseSpeed = 50;
public float decceleration = 30f;
public float maxBrakeTorque = 70f;
public float maxSteerAngle = 20f;
public int gear;
public int[] gearRatio;
public bool processSlip = false;
private float myForwardFriction;
private float mySidewayFriction;
public float slipForwardFriction = 0.04f;
public float slipSidewayFriction = 0.02f;
public float currentSpeed;
public float motorTorque;
public bool braked = false;
public Vector3 moveVector = new Vector3(0,0,0);
// Use this for initialization
void Start ()
{
rigidbody.centerOfMass = centerOfMass.localPosition;
InitializeFrictionValues ();
}
void InitializeFrictionValues()
{
if(driveWheels.Length == 0)
return;
myForwardFriction = driveWheels[0].forwardFriction.stiffness;
mySidewayFriction = driveWheels[0].sidewaysFriction.stiffness;
}
public void Process()
{
ProcessMotion ();
ProcessGear ();
}
protected void ProcessMotion()
{
if(steeringWheels.Length == 0 || driveWheels.Length == 0)
return;
//
moveVector.x = Mathf.Clamp (moveVector.x, -1, 1);
moveVector.y = Mathf.Clamp (moveVector.y, -1, 1);
moveVector.z = Mathf.Clamp (moveVector.z, -1, 1);
//
currentSpeed = 2 * 22 / 7 * driveWheels[0].radius * driveWheels[0].rpm * 60 / 1000;
//currentSpeed = 2 * 22 / 7 * steeringWheels[0].radius * steeringWheels[0].rpm * 60 / 1000;
//currentSpeed = Mathf.Round(currentSpeed);
//
foreach(WheelCollider wc in steeringWheels)
{
wc.brakeTorque = 0;
wc.motorTorque = 0;
}
foreach(WheelCollider wc in driveWheels)
{
wc.brakeTorque = 0;
wc.motorTorque = 0;
}
//
if(driveMode == DriveMode.NonSteeringWheelDrive)
ProcessNonSteeringWheelDrive();
else if(driveMode == DriveMode.SteeringWheelDrive)
ProcessSteeringWheelDrive();
else if(driveMode == DriveMode.AllWheelDrive)
ProcessAllWheelDrive();
//
if(processSlip)
ProcessSlip ();
//
ProcessSteer ();
//
motorTorque = driveWheels[0].motorTorque;
//
moveVector = Vector3.zero;
braked = false;
}
protected void ProcessNonSteeringWheelDrive()
{
if(currentSpeed < maxSpeed && currentSpeed > -Mathf.Abs(maxReverseSpeed))
{
foreach(WheelCollider wc in driveWheels)
{
wc.motorTorque = moveVector.z * maxTorque;
}
}
else
{
foreach(WheelCollider wc in driveWheels)
{
wc.motorTorque = 0;
}
}
//bug fix: exceed max speed when turning
if(currentSpeed > maxSpeed)
{
foreach(WheelCollider wc in driveWheels)
{
wc.motorTorque = 0;
wc.brakeTorque = 10 * (currentSpeed-maxSpeed);
}
}
//
if(moveVector.z == 0 && !braked)
{
foreach(WheelCollider wc in driveWheels)
{
wc.brakeTorque = decceleration;
}
}
else if(braked)
{
foreach(WheelCollider wc in driveWheels)
{
wc.brakeTorque = maxBrakeTorque;
wc.motorTorque = 0;
}
foreach(WheelCollider wc in steeringWheels)
{
wc.brakeTorque = maxBrakeTorque;
}
}
}
protected void ProcessSteeringWheelDrive()
{
if(currentSpeed < maxSpeed && currentSpeed > -Mathf.Abs(maxReverseSpeed))
{
foreach(WheelCollider wc in steeringWheels)
{
wc.motorTorque = moveVector.z * maxTorque;
}
}
else
{
foreach(WheelCollider wc in steeringWheels)
{
wc.motorTorque = 0;
}
}
//bug fix: exceed max speed when turning
if((2 * 22 / 7 * steeringWheels[0].radius * steeringWheels[0].rpm * 60 / 1000) > maxSpeed)
{
foreach(WheelCollider wc in steeringWheels)
{
wc.motorTorque = 0;
wc.brakeTorque = 10 * ((2 * 22 / 7 * steeringWheels[0].radius * steeringWheels[0].rpm * 60 / 1000)-maxSpeed);
}
}
//
if(moveVector.z == 0 && !braked)
{
foreach(WheelCollider wc in steeringWheels)
{
wc.brakeTorque = decceleration;
}
}
else if(braked)
{
foreach(WheelCollider wc in driveWheels)
{
wc.brakeTorque = maxBrakeTorque;
wc.motorTorque = 0;
}
foreach(WheelCollider wc in steeringWheels)
{
wc.brakeTorque = maxBrakeTorque;
wc.motorTorque = 0;
}
}
}
protected void ProcessAllWheelDrive()
{
if(currentSpeed < maxSpeed && currentSpeed > -Mathf.Abs(maxReverseSpeed))
{
foreach(WheelCollider wc in steeringWheels)
{
wc.motorTorque = moveVector.z * maxTorque;
}
foreach(WheelCollider wc in driveWheels)
{
wc.motorTorque = moveVector.z * maxTorque;
}
}
else
{
foreach(WheelCollider wc in steeringWheels)
{
wc.motorTorque = 0;
}
foreach(WheelCollider wc in driveWheels)
{
wc.motorTorque = 0;
}
}
//bug fix: exceed max speed when turning
if((2 * 22 / 7 * steeringWheels[0].radius * steeringWheels[0].rpm * 60 / 1000) > maxSpeed)
{
foreach(WheelCollider wc in steeringWheels)
{
wc.motorTorque = 0;
wc.brakeTorque = 10 * ((2 * 22 / 7 * steeringWheels[0].radius * steeringWheels[0].rpm * 60 / 1000)-maxSpeed);
}
}
if((2 * 22 / 7 * driveWheels[0].radius * driveWheels[0].rpm * 60 / 1000) > maxSpeed)
{
foreach(WheelCollider wc in driveWheels)
{
wc.motorTorque = 0;
wc.brakeTorque = 10 * ((2 * 22 / 7 * driveWheels[0].radius * driveWheels[0].rpm * 60 / 1000)-maxSpeed);
}
}
//
if(moveVector.z == 0 && !braked)
{
foreach(WheelCollider wc in steeringWheels)
{
wc.brakeTorque = decceleration;
}
foreach(WheelCollider wc in driveWheels)
{
wc.brakeTorque = decceleration;
}
}
else if(braked)
{
foreach(WheelCollider wc in driveWheels)
{
wc.brakeTorque = maxBrakeTorque;
wc.motorTorque = 0;
}
foreach(WheelCollider wc in steeringWheels)
{
wc.brakeTorque = maxBrakeTorque;
wc.motorTorque = 0;
}
}
}
protected void ProcessSlip()
{
if(braked)
{
if(rigidbody.velocity.magnitude > 1)
{
SetSlip(slipForwardFriction, slipSidewayFriction);
}
else
{
SetSlip(1, 1);
}
}
else
{
SetSlip(myForwardFriction, mySidewayFriction);
}
}
protected void ProcessSteer()
{
float newSteer = moveVector.x * maxSteerAngle;
foreach(WheelCollider wc in steeringWheels)
{
wc.steerAngle = newSteer;
}
}
protected void ProcessGear()
{
if(gearRatio.Length <= 0)
return;
if(maxSpeed > gearRatio [gearRatio.Length - 1])
gearRatio [gearRatio.Length - 1] = (int)maxSpeed + 1;
int c = 0;
for(c = 0; c < gearRatio.Length; c++)
{
if(gearRatio[c] > currentSpeed)
{
gear = c;
break;
}
}
if(c >= gearRatio.Length)
c = gearRatio.Length-1;
float gearMinValue = 0.00f;
float gearMaxValue = 0.00f;
if(c == 0)
{
gearMinValue = 0;
gearMaxValue = gearRatio[c];
}
else
{
//if(c > 0)
{
gearMinValue = gearRatio[c - 1];
}
//if(c >= 0 && c < gearRatio.length)
if(c < gearRatio.Length)
{
gearMaxValue = gearRatio[c];
}
else
{
Debug.Log(c + "---" + gearRatio.Length );
}
//if(i > gearRatio.length - 1)
//{
// Debug.Log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
//}
}
float enginePitch = ((currentSpeed - gearMinValue) / (gearMaxValue - gearMinValue)) + 1;
audio.pitch = enginePitch;
}
void SetSlip(float currentForwardFriction, float currentSidewayFriction)
{
foreach(WheelCollider wc in driveWheels)
{
WheelFrictionCurve wfc = wc.forwardFriction;
wfc.stiffness = currentForwardFriction;
wc.forwardFriction = wfc;
WheelFrictionCurve wfcs = wc.sidewaysFriction;
wfcs.stiffness = currentSidewayFriction;
wc.sidewaysFriction = wfcs;
}
foreach(WheelCollider wc in steeringWheels)
{
WheelFrictionCurve wfc = wc.forwardFriction;
wfc.stiffness = currentForwardFriction;
wc.forwardFriction = wfc;
WheelFrictionCurve wfcs = wc.sidewaysFriction;
wfcs.stiffness = currentSidewayFriction;
wc.sidewaysFriction = wfcs;
}
}
}
Part 3: Anti Roll:VLAntiRoll.cs:
using UnityEngine;
using System.Collections;
public class VLAntiRoll : MonoBehaviour
{
public WheelCollider wheelL;
public WheelCollider wheelR;
public float antiRoll = 10000f;
void FixedUpdate()
{
WheelHit hit;
float travelL = 1.0f;
float travelR = 1.0f;
bool groundedL = wheelL.GetGroundHit (out hit);
if (groundedL)
travelL = (-wheelL.transform.InverseTransformPoint(hit.point).y - wheelL.radius) / wheelL.suspensionDistance;
bool groundedR = wheelR.GetGroundHit(out hit);
if (groundedR)
travelR = (-wheelR.transform.InverseTransformPoint(hit.point).y - wheelR.radius) / wheelR.suspensionDistance;
var antiRollForce = (travelL - travelR) * antiRoll;
if (groundedL)
rigidbody.AddForceAtPosition(wheelL.transform.up * -antiRollForce,
wheelL.transform.position);
if (groundedR)
rigidbody.AddForceAtPosition(wheelR.transform.up * antiRollForce,
wheelR.transform.position);
}
}
Part 4: Down Force: VLDownForce:
using UnityEngine;
using System.Collections;
public class VLDownForce : MonoBehaviour
{
public float forceFactor = 1000;
void FixedUpdate ()
{
rigidbody.AddForce(-transform.up * rigidbody.velocity.magnitude * forceFactor);
}
}
Part 5: Turret: VLTurret.cs:
This turret can pitch and yaw to a direction wherever your car roll without rolling itself.
using UnityEngine;
using System.Collections;
public class VLTurret : MonoBehaviour
{
public Transform turretHead;
public float turretRotationDamping = 20f;
public Vector3 targetDirection = Vector3.zero;
private Quaternion tmpRotation;
private Quaternion targetRotationYaw;
private Quaternion targetRotationPitch;
private Vector3 tgtPos;
public Vector3 targetPosition
{
get
{
return tgtPos;
}
set
{
tgtPos = value;
targetDirection = tgtPos - turretHead.position;
}
}
void Start()
{
}
void FixedUpdate()
{
if(turretHead.parent.parent == null)
{
Debug.LogError("turretHead.parent.parent == null");
return;
}
tmpRotation = turretHead.parent.localRotation;
targetRotationYaw = Quaternion.LookRotation (targetDirection, turretHead.parent.parent.up);
turretHead.parent.rotation = Quaternion.Slerp(turretHead.parent.rotation, targetRotationYaw, Time.deltaTime * turretRotationDamping);
turretHead.parent.localRotation = Quaternion.Euler( tmpRotation.eulerAngles.x, turretHead.parent.localRotation.eulerAngles.y, tmpRotation.eulerAngles.z);
tmpRotation = turretHead.localRotation;
targetRotationPitch = Quaternion.LookRotation (targetDirection, turretHead.parent.transform.up);
turretHead.rotation = Quaternion.Slerp(turretHead.rotation, targetRotationPitch, Time.deltaTime * turretRotationDamping);
turretHead.localRotation = Quaternion.Euler( turretHead.localRotation.eulerAngles.x, tmpRotation.eulerAngles.y, tmpRotation.eulerAngles.z);
}
}
Part 6: How to convert mouse point to world (accurate) : Code Snippet:
VLTurret turretScript = GetComponent<VLTurret>();
RaycastHit hit = new RaycastHit();
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray, out hit, 9999f))
{
turretScript.targetPosition = hit.point;
}
else
{
float t = ray.origin.y / ray.direction.y;
turretScript.targetPosition = ray.GetPoint(t);
}
Part 7: Pathfinding for car (Pro Only) with custom navmeshagent script including Obstacle Avoidance: VLCarAIController.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using TagFrenzy;
public class VLCarAIController : MonoBehaviour
{
public enum BehaviorType
{
None = 0x00000,
Seek = 0x00002,
Arrive = 0x00004,
FollowPath = 0x00008,
ObstacleAvoidance = 0x00010,
};
public Transform target;
public VLCarMotor motorScript;
public VLObstacleSensor obstacleSensorScript;
public string[] states;
public string initialState = "VLState_Dummy";
public float maxForce = 100.0f;
public float maxSpeed = 100.0f;
public float minSpeed = 0.0f;
//
public float weightSeek = 1.0f;
public float weightArrive = 1.0f;
public float weightFollowPath = 1.0f;
public float weightObstacleAvoidance = 1.0f;
public float deceleration = 0.01f;
public float stoppingDistance = 1.0f;
public float waypointSeekDistance = 0.5f;
public float avoidanceMultiplier = 50;
public Vector3 steeringForce = new Vector3(0,0,0);
private VLCarAIController.BehaviorType behavior = VLCarAIController.BehaviorType.None;
private VLStateMachine stateMach;
private Vector3 dest;
private VLPath path;
//
public VLStateMachine stateMachine
{
get
{
return stateMach;
}
}
public bool straightReachedDestination
{
get
{
return (destination - position).magnitude <= stoppingDistance;
}
}
public float straightDistanceToDestination
{
get
{
return (destination - position).magnitude;
}
}
public bool pathReachedDestination
{
get
{
return (path.GetCurrentLength() + (path.GetCurrentWaypoint()-position).magnitude) <= stoppingDistance && path.IsFinished();
}
}
public float pathDistanceToDestination
{
get
{
return path.GetCurrentLength() + (path.GetCurrentWaypoint()-position).magnitude;
}
}
public bool pathCalculationError
{
get
{
return pathReachedDestination && !path.IsFinished();
}
}
public Vector3 destination
{
get
{
return dest;
}
set
{
dest = value;
if(!path.CalculatePath(position,dest))
{
// Debug.Log("CalculatePath Failed");
}
}
}
public Vector3 position
{
get
{
return transform.position;
}
}
public Vector3 velocity
{
get
{
return rigidbody.velocity;
}
}
//
public bool IsActive(VLCarAIController.BehaviorType b){ return (b & behavior) == b; }
public void ResetBehavior(){ behavior = VLCarAIController.BehaviorType.None; }
public void SeekOn(){ behavior |= VLCarAIController.BehaviorType.Seek; }
public void SeekOff(){if(IsActive(VLCarAIController.BehaviorType.Seek)) behavior ^= VLCarAIController.BehaviorType.Seek; }
public void ArriveOn(){ behavior |= VLCarAIController.BehaviorType.Arrive; }
public void ArriveOff(){if(IsActive(VLCarAIController.BehaviorType.Arrive)) behavior ^= VLCarAIController.BehaviorType.Arrive; }
public void FollowPathOn(){ behavior |= VLCarAIController.BehaviorType.FollowPath; }
public void FollowPathOff(){if(IsActive(VLCarAIController.BehaviorType.FollowPath)) behavior ^= VLCarAIController.BehaviorType.FollowPath; }
public void ObstacleAvoidanceOn(){ behavior |= VLCarAIController.BehaviorType.ObstacleAvoidance; }
public void ObstacleAvoidanceOff(){if(IsActive(VLCarAIController.BehaviorType.ObstacleAvoidance)) behavior ^= VLCarAIController.BehaviorType.ObstacleAvoidance; }
//
// Use this for initialization
void Awake ()
{
path = new VLPath();
destination = position;
stateMach = new VLStateMachine();
foreach (string st in states)
{
stateMach.AddState(st,GetComponent(st) as VLState);
}
//
//
if(stateMach.GetState(initialState) != null)
stateMach.ChangeState(initialState);
}
// Update is called once per frame
void Update ()
{
}
void FixedUpdate()
{
destination = destination;
CalculateSteeringForce ();
destination = target.position;
FollowPathOn ();
ObstacleAvoidanceOn ();
MoveForwardToDestination ();
//MoveBackwardToDestination ();
//
stateMachine.UpdateState();
motorScript.Process ();
}
void MoveForwardToDestination()
{
motorScript.moveVector.z = Mathf.Clamp (steeringForce.magnitude,0,1);
motorScript.moveVector.x = Mathf.Clamp (VLHelper.FindAngleDegree(transform.forward, steeringForce, transform.up)/90f,-1f,1f);
}
void MoveBackwardToDestination()
{
motorScript.moveVector.z = -Mathf.Clamp (steeringForce.magnitude,0,1);
motorScript.moveVector.x = -Mathf.Clamp (VLHelper.FindAngleDegree(-transform.forward, steeringForce, transform.up)/90f,-1f,1f);
}
public Vector3 GetRandomPointOnNavMesh(float walkRadius, float height)
{
Vector2 randomPoint = Random.insideUnitCircle * walkRadius;
randomPoint.x += transform.position.x;
randomPoint.y += transform.position.z;
RaycastHit[] rhits = Physics.RaycastAll(new Vector3(randomPoint.x,height,randomPoint.y),-Vector3.up);
if (rhits.Length == 0)
{
return GetRandomPointOnNavMesh(walkRadius,height);
}
List<Vector3> checkPoints = new List<Vector3>();
foreach (RaycastHit rhit in rhits)
{
NavMeshPath nmp = new NavMeshPath();
if(NavMesh.CalculatePath(transform.position,rhit.point,-1, nmp))
{
checkPoints.Add(rhit.point);
}
}
if (checkPoints.Count == 0)
{
return GetRandomPointOnNavMesh(walkRadius,height);
}
return checkPoints[Random.Range(0,checkPoints.Count-1)];
}
public Vector3 PredictFuturePosition(float predictionTime)
{
return transform.position + (velocity * predictionTime);
}
Vector3 Seek(Vector3 targetPos)
{
Vector3 desiredVel = ((targetPos - position).normalized * maxSpeed);
Vector3 force = desiredVel - velocity;
return force;
}
Vector3 Arrive(Vector3 targetPos)
{
Vector3 toTarget = targetPos - position;
float dist = toTarget.magnitude;
if (dist > stoppingDistance)
{
float decelerationTweaker = 0.3f;
float speed = dist / (deceleration * decelerationTweaker);
speed = Mathf.Min(speed, maxSpeed);
Vector3 desiredVel = toTarget * speed / dist;
//if((desiredVel - aiAgent.Velocity).magnitude == 0)
//Debug.Log((desiredVel - aiAgent.Velocity).ToString());
Vector3 force = desiredVel - velocity;
return force;
}
return Vector3.zero;
}
Vector3 FollowPath()
{
if ((path.GetCurrentWaypoint() - position).magnitude < waypointSeekDistance)
{
path.SetNextWaypoint();
}
if (!path.IsFinished())
{
return Seek(path.GetCurrentWaypoint());
}
else
{
return Arrive(path.GetCurrentWaypoint());
}
return Vector3.zero;
}
Vector3 ObstacleAvoidance()
{
Vector3 avoidance = Vector3.zero;
Vector3 futurePosition = PredictFuturePosition(1.0f);
if(obstacleSensorScript == null)
return avoidance;
if(obstacleSensorScript.obstacles.Count == 0)
return avoidance;
foreach(GameObject obs in obstacleSensorScript.obstacles)
{
if(obs == null || obs.Equals(null))
{
continue;
}
var distanceCurrent = position - obs.transform.position;
var distanceFuture = futurePosition - obs.transform.position;
avoidance += avoidanceMultiplier * distanceCurrent / distanceFuture.sqrMagnitude;
}
avoidance /= obstacleSensorScript.obstacles.Count;
var newDesired = Vector3.Reflect(velocity, avoidance);
return newDesired;
}
bool AccumulateForce(ref Vector3 runningTot, Vector3 forceToAdd)
{
float magnitudeSoFar = runningTot.magnitude;
float magnitudeRemaining = maxForce - magnitudeSoFar;
if (magnitudeRemaining <= 0.0f)
return false;
float magnitudeToAdd = forceToAdd.magnitude;
if (magnitudeToAdd < magnitudeRemaining)
{
runningTot += forceToAdd;
}
else
{
runningTot += (forceToAdd.normalized * magnitudeRemaining);
}
return true;
}
Vector3 CalculatePrioritized()
{
Vector3 force = new Vector3();
if (IsActive(VLCarAIController.BehaviorType.Seek))
{
force = Seek(destination) * weightSeek;
if (!AccumulateForce(ref steeringForce, force))
return steeringForce;
}
if (IsActive(VLCarAIController.BehaviorType.Arrive))
{
force = Arrive(destination) * weightArrive;
if (!AccumulateForce(ref steeringForce, force))
return steeringForce;
}
if (IsActive(VLCarAIController.BehaviorType.FollowPath))
{
force = FollowPath() * weightFollowPath;
if (!AccumulateForce(ref steeringForce, force))
return steeringForce;
}
if (IsActive(VLCarAIController.BehaviorType.ObstacleAvoidance))
{
force = ObstacleAvoidance() * weightObstacleAvoidance;
//force = Vector3.Lerp(force,ObstacleAvoidance() * weightObstacleAvoidance,0.1f);
if (!AccumulateForce(ref steeringForce, force))
return steeringForce;
}
return steeringForce;
}
void CalculateSteeringForce()
{
steeringForce = Vector3.zero;
steeringForce = CalculatePrioritized();
Debug.DrawRay (
new Vector3(transform.position.x, transform.position.y + 5, transform.position.z),
steeringForce * 10, Color.blue);
}
}
The Code above depends on Path Script: VLPath.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class VLPath
{
private NavMeshPath path = new NavMeshPath();
private Vector3 currentWaypoint = new Vector3();
private int index;
public VLPath()
{
index = 0;
//currentWaypoint = path.corners [index];
}
public float GetLength()
{
if (path.corners.Length < 2)
return 0;
Vector3 previousCorner = path.corners[0];
float lengthSoFar = 0.0f;
// Calculate the total distance by adding up the lengths
// of the straight lines between corners.
for (int i = 1; i < path.corners.Length; i++)
{
Vector3 currentCorner = path.corners[i];
lengthSoFar += Vector3.Distance(previousCorner, currentCorner);
previousCorner = currentCorner;
}
return lengthSoFar;
}
public float GetCurrentLength()
{
if (path.corners.Length < 2)
return 0;
if (index >= path.corners.Length)
return 0;
if (IsFinished())
return 0;
Vector3 previousCorner = path.corners[index];
float lengthSoFar = 0.0f;
// Calculate the total distance by adding up the lengths
// of the straight lines between corners.
for (int i = index + 1; i < path.corners.Length; i++)
{
Vector3 currentCorner = path.corners[i];
lengthSoFar += Vector3.Distance(previousCorner, currentCorner);
previousCorner = currentCorner;
}
return lengthSoFar;
}
public Vector3 GetCurrentWaypoint()
{
//if (path.corners.Length == 0)
// Debug.LogError("MLPath.GetCurrentWaypoint() path corners length = 0");
return currentWaypoint;
}
public void SetNextWaypoint()
{
if (index >= path.corners.Length)
{
//index = path.corners.Length - 1;
//currentWaypoint = path.corners [index];
return;
}
currentWaypoint = path.corners [index];
++index;
}
public bool IsFinished()
{
if (path.corners.Length == 0)
return true;
return currentWaypoint == path.corners[path.corners.Length-1];
}
public bool CalculatePath(Vector3 from, Vector3 to)
{
NavMeshPath tPath = new NavMeshPath();
bool ret = NavMesh.CalculatePath(from, to, -1, tPath);
if (ret)
{
path = tPath;
if(path.corners.Length > 1)
{
index = 1;
currentWaypoint = path.corners[index];
}
else
{
index = 0;
currentWaypoint = path.corners[index];
}
return true;
}
index = 0;
path = tPath;
return false;
}
}
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:
using UnityEngine;
using System.Collections;
public interface VLState
{
void Enter();
void Execute();
void Exit();
}
VLStateMachine.cs:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class VLState_Dummy : VLState
{
public void Enter()
{
Debug.Log("VLState_Dummy.Enter");
}
public void Execute()
{
//Debug.Log("VLState_Dummy.Execute");
}
public void Exit()
{
Debug.Log("VLState_Dummy.Exit");
}
}
public class VLStateMachine
{
private Dictionary<string,VLState> states;
private VLState currentState;
private VLState previousState;
private VLState globalState;
private VLState dummyState;
public VLState GetCurrentState()
{
return currentState;
}
public VLState GetPreviousState()
{
return previousState;
}
public VLState GetGlobalState()
{
return globalState;
}
public VLStateMachine()
{
dummyState = new VLState_Dummy();
states = new Dictionary<string,VLState>();
currentState = dummyState;
previousState = dummyState;
globalState = dummyState;
//Debug.Log(dummyState.ToString());
AddState("VLState_Dummy", dummyState);
}
public void AddState(string name, VLState st)
{
if (st == null)
{
Debug.LogError("VLStateMachine.AddState: FSM ERROR: Null reference is not allowed");
return;
}
if (states.ContainsKey(name))
{
Debug.LogError("VLStateMachine.AddState: FSM ERROR: duplicate name");
return;
}
states.Add(name, st);
}
public void RemoveState(string stName)
{
if (!states.ContainsKey(stName))
{
Debug.LogError("VLStateMachine.RemoveState: FSM ERROR: state with name: " + stName + "not exist");
return;
}
states.Remove(stName);
}
public VLState GetState(string stName)
{
if (!states.ContainsKey(stName))
{
Debug.LogError("VLStateMachine.GetState: FSM ERROR: state with name: " + stName + "not exist");
return null;
}
return states [stName];
}
public void UpdateState()
{
if (currentState != null)
currentState.Execute();
if (globalState != null)
globalState.Execute();
}
public void ChangeState(string newStateName)
{
VLState newState = GetState(newStateName);
if (newState != null)
{
ChangeState(newState);
return;
}
Debug.LogError("VLStateMachine.ChangeState : FSM ERROR: The state passed was not on the dictionary.");
}
void ChangeState(VLState newState)
{
previousState = currentState;
//call the exit method of the existing state
currentState.Exit();
//change state to the new state
currentState = newState;
//call the entry method of the new state
currentState.Enter();
}
public void ChangeGlobalState(string stateName)
{
VLState newGlobalState = GetState(stateName);
if (newGlobalState != null)
{
globalState.Exit();
globalState = newGlobalState;
globalState.Enter();
return;
}
Debug.LogError("VLStateMachine.ChangeGlobalState : FSM ERROR: The state passed was not on the dictionary.");
}
public void RevertToPreviousState()
{
ChangeState(previousState);
}
public void GoToDummyState()
{
ChangeState(dummyState);
}
public void ReloadState()
{
if (IsInState("VLState_Dummy"))
return;
GoToDummyState();
RevertToPreviousState();
}
public bool IsInState(string stateName)
{
VLState st = GetState(stateName);
if (st == null)
return false;
if (st == currentState)
return true;
return false;
}
public string GetNameOfCurrentState()
{
foreach (KeyValuePair<string, VLState> pair in states)
{
if(pair.Value == currentState)
return pair.Key;
}
Debug.LogError("VLStateMachine.GetNameOfCurrentState : Unknown State");
return "Unknown State";
}
}
Part 9: Obstacle Sensor Script:VLObstacleSensor.cs
attach this code to a child GameObject with SphereCollider Component.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using TagFrenzy;
public class VLObstacleSensor : MonoBehaviour
{
public HashSet<GameObject> obstacles;
public SphereCollider sensorCollider;
public Tags obstacleTag = Tags.Obstacle;
void Start()
{
obstacles = new HashSet<GameObject> ();
}
void FixedUpdate()
{
//Debug.Log (obstacles.Count);
}
void OnTriggerStay(Collider other)
{
//if(other.gameObject.name == "Obs")
// Debug.Log ("root collider hit");
if(other.gameObject.tagsEnum().Contains(obstacleTag))
{
float test = float.MaxValue;
GameObject closest = null;
RaycastHit closestHit = new RaycastHit();
Vector3 origin = transform.position + transform.up;
Vector3 direction = other.transform.position - transform.position;
RaycastHit[] rhits = Physics.RaycastAll(
origin,
direction.normalized,
sensorCollider.radius);
foreach (RaycastHit rhit in rhits)
{
if(rhit.transform.root.gameObject == this.transform.root.gameObject ||
rhit.transform.root.gameObject.Equals(this.transform.root.gameObject))
{
continue;
}
float distance = (rhit.point - origin).magnitude;
if(distance < test)
{
test = distance;
closest = rhit.collider.gameObject;
closestHit = rhit;
}
}
if(closest == null)
return;
Debug.DrawLine(origin, closestHit.point,Color.red);
if(closest.tagsEnum().Contains(obstacleTag))
{
obstacles.Add(closest);
}
}
}
void OnTriggerExit (Collider other)
{
if(other.gameObject.tagsEnum().Contains(obstacleTag))
{
obstacles.Remove(other.gameObject);
}
}
}