VehicleAI.cs 16 KB

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