VehicleAI.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. // Traffic Simulation
  2. // https://github.com/mchrbn/unity-traffic-simulation
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. namespace TrafficSimulation {
  7. /*
  8. [-] Check prefab #6 issue
  9. [-] Deaccelerate when see stop in front
  10. [-] Smooth sharp turns when two segments are linked
  11. */
  12. public struct Target{
  13. public int segment;
  14. public int waypoint;
  15. }
  16. public enum Status{
  17. GO,
  18. STOP,
  19. SLOW_DOWN
  20. }
  21. public class VehicleAI : MonoBehaviour
  22. {
  23. [Header("Traffic System")]
  24. [Tooltip("Current active traffic system")]
  25. public TrafficSystem trafficSystem;
  26. [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)")]
  27. public float waypointThresh = 6;
  28. [Header("Radar")]
  29. [Tooltip("Empty gameobject from where the rays will be casted")]
  30. public Transform raycastAnchor;
  31. [Tooltip("Length of the casted rays")]
  32. public float raycastLength = 5;
  33. [Tooltip("Spacing between each rays")]
  34. public int raySpacing = 2;
  35. [Tooltip("Number of rays to be casted")]
  36. public int raysNumber = 6;
  37. [Tooltip("If detected vehicle is below this distance, ego vehicle will stop")]
  38. public float emergencyBrakeThresh = 2f;
  39. [Tooltip("If detected vehicle is below this distance (and above, above distance), ego vehicle will slow down")]
  40. public float slowDownThresh = 4f;
  41. [HideInInspector] public Status vehicleStatus = Status.GO;
  42. private WheelDrive wheelDrive;
  43. private float initMaxSpeed = 0;
  44. private int pastTargetSegment = -1;
  45. private Target currentTarget;
  46. private Target futureTarget;
  47. void Start()
  48. {
  49. this.wheelDrive = this.GetComponent<WheelDrive>();
  50. if(trafficSystem == null)
  51. return;
  52. this.initMaxSpeed = wheelDrive.maxSpeed;
  53. SetWaypointVehicleIsOn();
  54. }
  55. private void OnEnable()
  56. {
  57. SetWaypointVehicleIsOn();
  58. }
  59. private void FixedUpdate()
  60. {
  61. if (trafficSystem == null)
  62. return;
  63. WaypointChecker();
  64. MoveVehicle();
  65. }
  66. void WaypointChecker()
  67. {
  68. GameObject waypoint = trafficSystem.segments[currentTarget.segment].waypoints[currentTarget.waypoint].gameObject;
  69. //Position of next waypoint relative to the car
  70. Vector3 wpDist = this.transform.InverseTransformPoint(new Vector3(waypoint.transform.position.x, this.transform.position.y, waypoint.transform.position.z));
  71. //Go to next waypoint if arrived to current
  72. if(wpDist.magnitude < waypointThresh)
  73. {
  74. //Get next target
  75. currentTarget.waypoint++;
  76. if(currentTarget.waypoint >= trafficSystem.segments[currentTarget.segment].waypoints.Count)
  77. {
  78. pastTargetSegment = currentTarget.segment;
  79. currentTarget.segment = futureTarget.segment;
  80. currentTarget.waypoint = 0;
  81. }
  82. //Get future target
  83. futureTarget.waypoint = currentTarget.waypoint + 1;
  84. if(futureTarget.waypoint >= trafficSystem.segments[currentTarget.segment].waypoints.Count)
  85. {
  86. futureTarget.waypoint = 0;
  87. futureTarget.segment = GetNextSegmentId();
  88. }
  89. }
  90. }
  91. void MoveVehicle()
  92. {
  93. //Default, full acceleration, no break and no steering
  94. float acc = 1;
  95. float brake = 0;
  96. float steering = 0;
  97. wheelDrive.maxSpeed = initMaxSpeed;
  98. //Calculate if there is a planned turn
  99. Transform targetTransform = trafficSystem.segments[currentTarget.segment].waypoints[currentTarget.waypoint].transform;
  100. Transform futureTargetTransform = trafficSystem.segments[futureTarget.segment].waypoints[futureTarget.waypoint].transform;
  101. Vector3 futureVel = futureTargetTransform.position - targetTransform.position;
  102. float futureSteering = Mathf.Clamp(this.transform.InverseTransformDirection(futureVel.normalized).x, -1, 1);
  103. //Check if the car has to stop
  104. if(vehicleStatus == Status.STOP)
  105. {
  106. //Debug.Log(gameObject.name + ":STOP");
  107. acc = 0;
  108. brake = 1;
  109. //wheelDrive.maxSpeed = Mathf.Min(wheelDrive.maxSpeed / 2f, 5f);
  110. wheelDrive.maxSpeed = 0f;
  111. }
  112. else{
  113. //Not full acceleration if have to slow down
  114. if(vehicleStatus == Status.SLOW_DOWN){
  115. //Debug.Log(gameObject.name + ": SLOW DOWN");
  116. acc = .3f;
  117. brake = 0f;
  118. }
  119. //If planned to steer, decrease the speed
  120. if(futureSteering > .3f || futureSteering < -.3f)
  121. {
  122. //Debug.Log( gameObject.name + " :Future Steering. Decrease Speed");
  123. wheelDrive.maxSpeed = Mathf.Min(wheelDrive.maxSpeed, wheelDrive.steeringSpeedMax);
  124. }
  125. //2. Check if there are obstacles which are detected by the radar
  126. float hitDist;
  127. GameObject obstacle = GetDetectedObstacles(out hitDist);
  128. //Check if we hit something
  129. if(obstacle != null){
  130. //Debug.Log(gameObject.name + ": Obstacle detected " + obstacle.name + "!");
  131. WheelDrive otherVehicle = null;
  132. otherVehicle = obstacle.GetComponent<WheelDrive>();
  133. ///////////////////////////////////////////////////////////////
  134. //Differenciate between other vehicles AI and generic obstacles (including controlled vehicle, if any)
  135. if(otherVehicle != null){
  136. //Check if it's front vehicle
  137. float dotFront = Vector3.Dot(this.transform.forward, otherVehicle.transform.forward);
  138. //If detected front vehicle max speed is lower than ego vehicle, then decrease ego vehicle max speed
  139. //if(otherVehicle.maxSpeed < wheelDrive.maxSpeed && otherVehicle.maxSpeed > 0 && dotFront > .8f)
  140. if (otherVehicle.maxSpeed < wheelDrive.maxSpeed && otherVehicle.maxSpeed > 0 && dotFront > .8f)
  141. {
  142. float ms = Mathf.Max(wheelDrive.GetSpeedMS(otherVehicle.maxSpeed) - .5f, .1f);
  143. //Debug.Log(obstacle.name + " slower. lower speed of " +gameObject.name + " to " + wheelDrive.GetSpeedUnit(ms) + "!");
  144. wheelDrive.maxSpeed = wheelDrive.GetSpeedUnit(ms);
  145. }
  146. else if(otherVehicle.maxSpeed == 0f)
  147. {
  148. //Debug.Log(gameObject.name + ": Stopping because of " + obstacle.name +"!");
  149. acc = 0;
  150. brake = 1;
  151. wheelDrive.maxSpeed = 0f;
  152. }
  153. //If the two vehicles are too close, and facing the same direction, brake the ego vehicle
  154. if(hitDist < emergencyBrakeThresh && dotFront > .8f)
  155. {
  156. //Debug.Log(gameObject.name + "Two close, same direction. Breaking");
  157. acc = 0;
  158. brake = 1;
  159. wheelDrive.maxSpeed = Mathf.Max(wheelDrive.maxSpeed / 2f, wheelDrive.minSpeed);
  160. }
  161. //If the two vehicles are too close, and not facing same direction, slight make the ego vehicle go backward
  162. else if(hitDist < emergencyBrakeThresh && dotFront <= .8f)
  163. {
  164. //Debug.Log(gameObject.name + "Too Close, not same direction. Go Backward");
  165. acc = -.3f;
  166. brake = 0f;
  167. wheelDrive.maxSpeed = Mathf.Max(wheelDrive.maxSpeed / 2f, wheelDrive.minSpeed);
  168. //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
  169. float dotRight = Vector3.Dot(this.transform.forward, otherVehicle.transform.right);
  170. if(dotRight > 0.1f)//Right
  171. {
  172. //Debug.Log("Steer left");
  173. steering = -.2f;
  174. }else if(dotRight < -0.1f) //Left
  175. {
  176. //Debug.Log("Steer right");
  177. steering = .2f;
  178. }else //Middle
  179. {
  180. //Debug.Log("Steer Middle");
  181. steering = -.7f;
  182. }
  183. }
  184. //If the two vehicles are getting close, slow down their speed
  185. else if(hitDist < slowDownThresh && acc > 0)
  186. {
  187. //Debug.Log(gameObject.name + ": Too Close, Slow Down!");
  188. acc = .5f;
  189. brake = 0f;
  190. //wheelDrive.maxSpeed = Mathf.Max(wheelDrive.maxSpeed / 1.5f, wheelDrive.minSpeed);
  191. }
  192. }
  193. ///////////////////////////////////////////////////////////////////
  194. // Generic obstacles
  195. else
  196. {
  197. //Emergency brake if getting too close
  198. if(hitDist < emergencyBrakeThresh)
  199. {
  200. acc = 0;
  201. brake = 1;
  202. wheelDrive.maxSpeed = Mathf.Max(wheelDrive.maxSpeed / 2f, wheelDrive.minSpeed);
  203. }
  204. //Otherwise if getting relatively close decrease speed
  205. else if(hitDist < slowDownThresh)
  206. {
  207. acc = .5f;
  208. brake = 0f;
  209. }
  210. }
  211. }
  212. //Check if we need to steer to follow path
  213. if(acc > 0f)
  214. {
  215. Vector3 desiredVel = trafficSystem.segments[currentTarget.segment].waypoints[currentTarget.waypoint].transform.position - this.transform.position;
  216. steering = Mathf.Clamp(this.transform.InverseTransformDirection(desiredVel.normalized).x, -1f, 1f);
  217. }
  218. }
  219. //Move the car
  220. wheelDrive.Move(acc, steering, brake);
  221. }
  222. GameObject GetDetectedObstacles(out float _hitDist)
  223. {
  224. GameObject detectedObstacle = null;
  225. float minDist = 1000f;
  226. float initRay = (raysNumber / 2f) * raySpacing;
  227. float hitDist = -1f;
  228. for(float a=-initRay; a<=initRay; a+=raySpacing)
  229. {
  230. CastRay(raycastAnchor.transform.position, a, this.transform.forward, raycastLength, out detectedObstacle, out hitDist);
  231. if(detectedObstacle == null){
  232. continue;
  233. }
  234. float dist = Vector3.Distance(this.transform.position, detectedObstacle.transform.position);
  235. if(dist < minDist)
  236. {
  237. minDist = dist;
  238. break;
  239. }
  240. }
  241. _hitDist = hitDist;
  242. return detectedObstacle;
  243. }
  244. void CastRay(Vector3 _anchor, float _angle, Vector3 _dir, float _length, out GameObject _outObstacle, out float _outHitDistance){
  245. _outObstacle = null;
  246. _outHitDistance = -1f;
  247. //Draw raycast
  248. Debug.DrawRay(_anchor, Quaternion.Euler(0, _angle, 0) * _dir * _length, new Color(1, 0, 0, 0.5f));
  249. //Detect hit only on the autonomous vehicle layer
  250. int layer = 1 << LayerMask.NameToLayer("AutonomousVehicle");
  251. int finalMask = layer;
  252. foreach(string layerName in trafficSystem.collisionLayers)
  253. {
  254. int id = 1 << LayerMask.NameToLayer(layerName);
  255. finalMask = finalMask | id;
  256. }
  257. RaycastHit hit;
  258. if(Physics.Raycast(_anchor, Quaternion.Euler(0, _angle, 0) * _dir, out hit, _length, finalMask))
  259. {
  260. _outObstacle = hit.collider.gameObject;
  261. _outHitDistance = hit.distance;
  262. }
  263. }
  264. int GetNextSegmentId()
  265. {
  266. if(trafficSystem.segments[currentTarget.segment].nextSegments.Count == 0)
  267. {
  268. return 0;
  269. }
  270. int c = Random.Range(0, trafficSystem.segments[currentTarget.segment].nextSegments.Count);
  271. return trafficSystem.segments[currentTarget.segment].nextSegments[c].id;
  272. }
  273. void SetWaypointVehicleIsOn()
  274. {
  275. if(trafficSystem.segments == null || trafficSystem.segments.Count == 0)
  276. {
  277. Debug.Log("Segments are null or count is 0");
  278. return;
  279. }
  280. //Find current target
  281. foreach(Segment segment in trafficSystem.segments)
  282. {
  283. if(segment.IsOnSegment(this.transform.position))
  284. {
  285. currentTarget.segment = segment.id;
  286. //Find nearest waypoint to start within the segment
  287. float minDist = float.MaxValue;
  288. for(int j=0; j<trafficSystem.segments[currentTarget.segment].waypoints.Count; j++)
  289. {
  290. float d = Vector3.Distance(this.transform.position, trafficSystem.segments[currentTarget.segment].waypoints[j].transform.position);
  291. //Only take in front points
  292. Vector3 lSpace = this.transform.InverseTransformPoint(trafficSystem.segments[currentTarget.segment].waypoints[j].transform.position);
  293. if(d < minDist && lSpace.z > 0)
  294. {
  295. minDist = d;
  296. currentTarget.waypoint = j;
  297. }
  298. }
  299. break;
  300. }
  301. }
  302. //Get future target
  303. futureTarget.waypoint = currentTarget.waypoint + 1;
  304. futureTarget.segment = currentTarget.segment;
  305. if(futureTarget.waypoint >= trafficSystem.segments[currentTarget.segment].waypoints.Count)
  306. {
  307. futureTarget.waypoint = 0;
  308. futureTarget.segment = GetNextSegmentId();
  309. }
  310. }
  311. public int GetSegmentVehicleIsIn()
  312. {
  313. int vehicleSegment = currentTarget.segment;
  314. bool isOnSegment = trafficSystem.segments[vehicleSegment].IsOnSegment(this.transform.position);
  315. if(!isOnSegment)
  316. {
  317. bool isOnPSegement = trafficSystem.segments[pastTargetSegment].IsOnSegment(this.transform.position);
  318. if(isOnPSegement)
  319. vehicleSegment = pastTargetSegment;
  320. }
  321. return vehicleSegment;
  322. }
  323. }
  324. }