123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- // Traffic Simulation
- // https://github.com/mchrbn/unity-traffic-simulation
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- namespace TrafficSimulation {
- /*
- [-] Check prefab #6 issue
- [-] Deaccelerate when see stop in front
- [-] Smooth sharp turns when two segments are linked
-
- */
- public struct Target{
- public int segment;
- public int waypoint;
- }
- public enum Status{
- GO,
- STOP,
- SLOW_DOWN
- }
- public class VehicleAI : MonoBehaviour
- {
- [Header("Traffic System")]
- [Tooltip("Current active traffic system")]
- public TrafficSystem trafficSystem;
- [Tooltip("Determine when the vehicle has reached its target. Can be used to \"anticipate\" earlier the next waypoint (the higher this number his, the earlier it will anticipate the next waypoint)")]
- public float waypointThresh = 6;
- [Header("Radar")]
- [Tooltip("Empty gameobject from where the rays will be casted")]
- public Transform raycastAnchor;
- [Tooltip("Length of the casted rays")]
- public float raycastLength = 5;
- [Tooltip("Spacing between each rays")]
- public int raySpacing = 2;
- [Tooltip("Number of rays to be casted")]
- public int raysNumber = 6;
- [Tooltip("If detected vehicle is below this distance, ego vehicle will stop")]
- public float emergencyBrakeThresh = 2f;
- [Tooltip("If detected vehicle is below this distance (and above, above distance), ego vehicle will slow down")]
- public float slowDownThresh = 4f;
- [HideInInspector] public Status vehicleStatus = Status.GO;
- private WheelDrive wheelDrive;
- private float initMaxSpeed = 0;
- private int pastTargetSegment = -1;
- private Target currentTarget;
- private Target futureTarget;
- void Start()
- {
- wheelDrive = this.GetComponent<WheelDrive>();
- if(trafficSystem == null)
- return;
- initMaxSpeed = wheelDrive.maxSpeed;
- SetWaypointVehicleIsOn();
- }
- private void FixedUpdate()
- {
- if (trafficSystem == null)
- return;
- WaypointChecker();
- MoveVehicle();
- }
- void WaypointChecker()
- {
- GameObject waypoint = trafficSystem.segments[currentTarget.segment].waypoints[currentTarget.waypoint].gameObject;
- //Position of next waypoint relative to the car
- Vector3 wpDist = this.transform.InverseTransformPoint(new Vector3(waypoint.transform.position.x, this.transform.position.y, waypoint.transform.position.z));
- //Go to next waypoint if arrived to current
- if(wpDist.magnitude < waypointThresh)
- {
- //Get next target
- currentTarget.waypoint++;
- if(currentTarget.waypoint >= trafficSystem.segments[currentTarget.segment].waypoints.Count)
- {
- pastTargetSegment = currentTarget.segment;
- currentTarget.segment = futureTarget.segment;
- currentTarget.waypoint = 0;
- }
- //Get future target
- futureTarget.waypoint = currentTarget.waypoint + 1;
- if(futureTarget.waypoint >= trafficSystem.segments[currentTarget.segment].waypoints.Count)
- {
- futureTarget.waypoint = 0;
- futureTarget.segment = GetNextSegmentId();
- }
- }
- }
- void MoveVehicle()
- {
- //Default, full acceleration, no break and no steering
- float acc = 1;
- float brake = 0;
- float steering = 0;
- wheelDrive.maxSpeed = initMaxSpeed;
- //Calculate if there is a planned turn
- Transform targetTransform = trafficSystem.segments[currentTarget.segment].waypoints[currentTarget.waypoint].transform;
- Transform futureTargetTransform = trafficSystem.segments[futureTarget.segment].waypoints[futureTarget.waypoint].transform;
- Vector3 futureVel = futureTargetTransform.position - targetTransform.position;
- float futureSteering = Mathf.Clamp(this.transform.InverseTransformDirection(futureVel.normalized).x, -1, 1);
- //Check if the car has to stop
- if(vehicleStatus == Status.STOP)
- {
- //Debug.Log(gameObject.name + ":STOP");
- acc = 0;
- brake = 1;
- //wheelDrive.maxSpeed = Mathf.Min(wheelDrive.maxSpeed / 2f, 5f);
- wheelDrive.maxSpeed = 0f;
- }
- else{
-
- //Not full acceleration if have to slow down
- if(vehicleStatus == Status.SLOW_DOWN){
- //Debug.Log(gameObject.name + ": SLOW DOWN");
- acc = .3f;
- brake = 0f;
- }
- //If planned to steer, decrease the speed
- if(futureSteering > .3f || futureSteering < -.3f)
- {
- //Debug.Log( gameObject.name + " :Future Steering. Decrease Speed");
- wheelDrive.maxSpeed = Mathf.Min(wheelDrive.maxSpeed, wheelDrive.steeringSpeedMax);
- }
- //2. Check if there are obstacles which are detected by the radar
- float hitDist;
- GameObject obstacle = GetDetectedObstacles(out hitDist);
- //Check if we hit something
- if(obstacle != null){
- //Debug.Log(gameObject.name + ": Obstacle detected " + obstacle.name + "!");
- WheelDrive otherVehicle = null;
- otherVehicle = obstacle.GetComponent<WheelDrive>();
- ///////////////////////////////////////////////////////////////
- //Differenciate between other vehicles AI and generic obstacles (including controlled vehicle, if any)
- if(otherVehicle != null){
- //Check if it's front vehicle
- float dotFront = Vector3.Dot(this.transform.forward, otherVehicle.transform.forward);
- //If detected front vehicle max speed is lower than ego vehicle, then decrease ego vehicle max speed
- //if(otherVehicle.maxSpeed < wheelDrive.maxSpeed && otherVehicle.maxSpeed > 0 && dotFront > .8f)
- if (otherVehicle.maxSpeed < wheelDrive.maxSpeed && otherVehicle.maxSpeed > 0 && dotFront > .8f)
- {
- float ms = Mathf.Max(wheelDrive.GetSpeedMS(otherVehicle.maxSpeed) - .5f, .1f);
- //Debug.Log(obstacle.name + " slower. lower speed of " +gameObject.name + " to " + wheelDrive.GetSpeedUnit(ms) + "!");
- wheelDrive.maxSpeed = wheelDrive.GetSpeedUnit(ms);
- }
- else if(otherVehicle.maxSpeed == 0f)
- {
- //Debug.Log(gameObject.name + ": Stopping because of " + obstacle.name +"!");
- acc = 0;
- brake = 1;
- wheelDrive.maxSpeed = 0f;
- }
-
- //If the two vehicles are too close, and facing the same direction, brake the ego vehicle
- if(hitDist < emergencyBrakeThresh && dotFront > .8f)
- {
- //Debug.Log(gameObject.name + "Two close, same direction. Breaking");
- acc = 0;
- brake = 1;
- wheelDrive.maxSpeed = Mathf.Max(wheelDrive.maxSpeed / 2f, wheelDrive.minSpeed);
- }
- //If the two vehicles are too close, and not facing same direction, slight make the ego vehicle go backward
- else if(hitDist < emergencyBrakeThresh && dotFront <= .8f)
- {
- //Debug.Log(gameObject.name + "Too Close, not same direction. Go Backward");
- acc = -.3f;
- brake = 0f;
- wheelDrive.maxSpeed = Mathf.Max(wheelDrive.maxSpeed / 2f, wheelDrive.minSpeed);
- //Check if the vehicle we are close to is located on the right or left then apply according steering to try to make it move
- float dotRight = Vector3.Dot(this.transform.forward, otherVehicle.transform.right);
- if(dotRight > 0.1f)//Right
- {
- //Debug.Log("Steer left");
- steering = -.2f;
- }else if(dotRight < -0.1f) //Left
- {
- //Debug.Log("Steer right");
- steering = .2f;
- }else //Middle
- {
- //Debug.Log("Steer Middle");
- steering = -.7f;
- }
- }
- //If the two vehicles are getting close, slow down their speed
- else if(hitDist < slowDownThresh && acc > 0)
- {
- //Debug.Log(gameObject.name + ": Too Close, Slow Down!");
- acc = .5f;
- brake = 0f;
- //wheelDrive.maxSpeed = Mathf.Max(wheelDrive.maxSpeed / 1.5f, wheelDrive.minSpeed);
- }
- }
- ///////////////////////////////////////////////////////////////////
- // Generic obstacles
- else
- {
- //Emergency brake if getting too close
- if(hitDist < emergencyBrakeThresh)
- {
- acc = 0;
- brake = 1;
- wheelDrive.maxSpeed = Mathf.Max(wheelDrive.maxSpeed / 2f, wheelDrive.minSpeed);
- }
- //Otherwise if getting relatively close decrease speed
- else if(hitDist < slowDownThresh)
- {
- acc = .5f;
- brake = 0f;
- }
- }
- }
- //Check if we need to steer to follow path
- if(acc > 0f)
- {
- Vector3 desiredVel = trafficSystem.segments[currentTarget.segment].waypoints[currentTarget.waypoint].transform.position - this.transform.position;
- steering = Mathf.Clamp(this.transform.InverseTransformDirection(desiredVel.normalized).x, -1f, 1f);
- }
- }
- //Move the car
- wheelDrive.Move(acc, steering, brake);
- }
- GameObject GetDetectedObstacles(out float _hitDist)
- {
- GameObject detectedObstacle = null;
- float minDist = 1000f;
- float initRay = (raysNumber / 2f) * raySpacing;
- float hitDist = -1f;
- for(float a=-initRay; a<=initRay; a+=raySpacing)
- {
- CastRay(raycastAnchor.transform.position, a, this.transform.forward, raycastLength, out detectedObstacle, out hitDist);
- if(detectedObstacle == null){
- continue;
- }
- float dist = Vector3.Distance(this.transform.position, detectedObstacle.transform.position);
- if(dist < minDist)
- {
- minDist = dist;
- break;
- }
- }
- _hitDist = hitDist;
- return detectedObstacle;
- }
-
- void CastRay(Vector3 _anchor, float _angle, Vector3 _dir, float _length, out GameObject _outObstacle, out float _outHitDistance){
- _outObstacle = null;
- _outHitDistance = -1f;
- //Draw raycast
- Debug.DrawRay(_anchor, Quaternion.Euler(0, _angle, 0) * _dir * _length, new Color(1, 0, 0, 0.5f));
- //Detect hit only on the autonomous vehicle layer
- int layer = 1 << LayerMask.NameToLayer("AutonomousVehicle");
- int finalMask = layer;
- foreach(string layerName in trafficSystem.collisionLayers)
- {
- int id = 1 << LayerMask.NameToLayer(layerName);
- finalMask = finalMask | id;
- }
- RaycastHit hit;
- if(Physics.Raycast(_anchor, Quaternion.Euler(0, _angle, 0) * _dir, out hit, _length, finalMask))
- {
- _outObstacle = hit.collider.gameObject;
- _outHitDistance = hit.distance;
- }
- }
- int GetNextSegmentId()
- {
- if(trafficSystem.segments[currentTarget.segment].nextSegments.Count == 0)
- {
- return 0;
- }
- int c = Random.Range(0, trafficSystem.segments[currentTarget.segment].nextSegments.Count);
- return trafficSystem.segments[currentTarget.segment].nextSegments[c].id;
- }
- void SetWaypointVehicleIsOn()
- {
- //Find current target
- foreach(Segment segment in trafficSystem.segments)
- {
- if(segment.IsOnSegment(this.transform.position))
- {
- currentTarget.segment = segment.id;
- //Find nearest waypoint to start within the segment
- float minDist = float.MaxValue;
- for(int j=0; j<trafficSystem.segments[currentTarget.segment].waypoints.Count; j++)
- {
- float d = Vector3.Distance(this.transform.position, trafficSystem.segments[currentTarget.segment].waypoints[j].transform.position);
- //Only take in front points
- Vector3 lSpace = this.transform.InverseTransformPoint(trafficSystem.segments[currentTarget.segment].waypoints[j].transform.position);
- if(d < minDist && lSpace.z > 0)
- {
- minDist = d;
- currentTarget.waypoint = j;
- }
- }
- break;
- }
- }
- //Get future target
- futureTarget.waypoint = currentTarget.waypoint + 1;
- futureTarget.segment = currentTarget.segment;
- if(futureTarget.waypoint >= trafficSystem.segments[currentTarget.segment].waypoints.Count)
- {
- futureTarget.waypoint = 0;
- futureTarget.segment = GetNextSegmentId();
- }
- }
- public int GetSegmentVehicleIsIn()
- {
- int vehicleSegment = currentTarget.segment;
- bool isOnSegment = trafficSystem.segments[vehicleSegment].IsOnSegment(this.transform.position);
- if(!isOnSegment)
- {
- bool isOnPSegement = trafficSystem.segments[pastTargetSegment].IsOnSegment(this.transform.position);
- if(isOnPSegement)
- vehicleSegment = pastTargetSegment;
- }
- return vehicleSegment;
- }
- }
- }
|