LegacyAIPath.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. #pragma warning disable 618
  2. using UnityEngine;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. namespace Pathfinding.Legacy {
  6. using Pathfinding;
  7. using Pathfinding.RVO;
  8. /// <summary>
  9. /// AI for following paths.
  10. /// This AI is the default movement script which comes with the A* Pathfinding Project.
  11. /// It is in no way required by the rest of the system, so feel free to write your own. But I hope this script will make it easier
  12. /// to set up movement for the characters in your game. This script is not written for high performance, so I do not recommend using it for large groups of units.
  13. ///
  14. /// This script will try to follow a target transform, in regular intervals, the path to that target will be recalculated.
  15. /// It will on FixedUpdate try to move towards the next point in the path.
  16. /// However it will only move in the forward direction, but it will rotate around it's Y-axis
  17. /// to make it reach the target.
  18. ///
  19. /// \section variables Quick overview of the variables
  20. /// In the inspector in Unity, you will see a bunch of variables. You can view detailed information further down, but here's a quick overview.
  21. /// The <see cref="repathRate"/> determines how often it will search for new paths, if you have fast moving targets, you might want to set it to a lower value.
  22. /// The <see cref="target"/> variable is where the AI will try to move, it can be a point on the ground where the player has clicked in an RTS for example.
  23. /// Or it can be the player object in a zombie game.
  24. /// The speed is self-explanatory, so is turningSpeed, however <see cref="slowdownDistance"/> might require some explanation.
  25. /// It is the approximate distance from the target where the AI will start to slow down. Note that this doesn't only affect the end point of the path
  26. /// but also any intermediate points, so be sure to set <see cref="forwardLook"/> and <see cref="pickNextWaypointDist"/> to a higher value than this.
  27. /// <see cref="pickNextWaypointDist"/> is simply determines within what range it will switch to target the next waypoint in the path.
  28. /// <see cref="forwardLook"/> will try to calculate an interpolated target point on the current segment in the path so that it has a distance of <see cref="forwardLook"/> from the AI
  29. /// Below is an image illustrating several variables as well as some internal ones, but which are relevant for understanding how it works.
  30. /// Note that the <see cref="forwardLook"/> range will not match up exactly with the target point practically, even though that's the goal.
  31. /// [Open online documentation to see images]
  32. /// This script has many movement fallbacks.
  33. /// If it finds a NavmeshController, it will use that, otherwise it will look for a character controller, then for a rigidbody and if it hasn't been able to find any
  34. /// it will use Transform.Translate which is guaranteed to always work.
  35. ///
  36. /// Deprecated: Use the AIPath class instead. This class only exists for compatibility reasons.
  37. /// </summary>
  38. [RequireComponent(typeof(Seeker))]
  39. [AddComponentMenu("Pathfinding/Legacy/AI/Legacy AIPath (3D)")]
  40. [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_legacy_1_1_legacy_a_i_path.php")]
  41. public class LegacyAIPath : AIPath {
  42. /// <summary>
  43. /// Target point is Interpolated on the current segment in the path so that it has a distance of <see cref="forwardLook"/> from the AI.
  44. /// See the detailed description of AIPath for an illustrative image
  45. /// </summary>
  46. public float forwardLook = 1;
  47. /// <summary>
  48. /// Do a closest point on path check when receiving path callback.
  49. /// Usually the AI has moved a bit between requesting the path, and getting it back, and there is usually a small gap between the AI
  50. /// and the closest node.
  51. /// If this option is enabled, it will simulate, when the path callback is received, movement between the closest node and the current
  52. /// AI position. This helps to reduce the moments when the AI just get a new path back, and thinks it ought to move backwards to the start of the new path
  53. /// even though it really should just proceed forward.
  54. /// </summary>
  55. public bool closestOnPathCheck = true;
  56. protected float minMoveScale = 0.05F;
  57. /// <summary>Current index in the path which is current target</summary>
  58. protected int currentWaypointIndex = 0;
  59. protected Vector3 lastFoundWaypointPosition;
  60. protected float lastFoundWaypointTime = -9999;
  61. protected override void Awake () {
  62. base.Awake();
  63. if (rvoController != null) {
  64. if (rvoController is LegacyRVOController) (rvoController as LegacyRVOController).enableRotation = false;
  65. else Debug.LogError("The LegacyAIPath component only works with the legacy RVOController, not the latest one. Please upgrade this component", this);
  66. }
  67. }
  68. /// <summary>
  69. /// Called when a requested path has finished calculation.
  70. /// A path is first requested by <see cref="SearchPath"/>, it is then calculated, probably in the same or the next frame.
  71. /// Finally it is returned to the seeker which forwards it to this function.
  72. /// </summary>
  73. protected override void OnPathComplete (Path _p) {
  74. ABPath p = _p as ABPath;
  75. if (p == null) throw new System.Exception("This function only handles ABPaths, do not use special path types");
  76. waitingForPathCalculation = false;
  77. //Claim the new path
  78. p.Claim(this);
  79. // Path couldn't be calculated of some reason.
  80. // More info in p.errorLog (debug string)
  81. if (p.error) {
  82. p.Release(this);
  83. return;
  84. }
  85. //Release the previous path
  86. if (path != null) path.Release(this);
  87. //Replace the old path
  88. path = p;
  89. //Reset some variables
  90. currentWaypointIndex = 0;
  91. reachedEndOfPath = false;
  92. //The next row can be used to find out if the path could be found or not
  93. //If it couldn't (error == true), then a message has probably been logged to the console
  94. //however it can also be got using p.errorLog
  95. //if (p.error)
  96. if (closestOnPathCheck) {
  97. // Simulate movement from the point where the path was requested
  98. // to where we are right now. This reduces the risk that the agent
  99. // gets confused because the first point in the path is far away
  100. // from the current position (possibly behind it which could cause
  101. // the agent to turn around, and that looks pretty bad).
  102. Vector3 p1 = Time.time - lastFoundWaypointTime < 0.3f ? lastFoundWaypointPosition : p.originalStartPoint;
  103. Vector3 p2 = GetFeetPosition();
  104. Vector3 dir = p2-p1;
  105. float magn = dir.magnitude;
  106. dir /= magn;
  107. int steps = (int)(magn/pickNextWaypointDist);
  108. #if ASTARDEBUG
  109. Debug.DrawLine(p1, p2, Color.red, 1);
  110. #endif
  111. for (int i = 0; i <= steps; i++) {
  112. CalculateVelocity(p1);
  113. p1 += dir;
  114. }
  115. }
  116. }
  117. protected override void Update () {
  118. if (!canMove) { return; }
  119. Vector3 dir = CalculateVelocity(GetFeetPosition());
  120. //Rotate towards targetDirection (filled in by CalculateVelocity)
  121. RotateTowards(targetDirection);
  122. if (rvoController != null) {
  123. rvoController.Move(dir);
  124. } else
  125. if (controller != null) {
  126. controller.SimpleMove(dir);
  127. } else if (rigid != null) {
  128. rigid.AddForce(dir);
  129. } else {
  130. tr.Translate(dir*Time.deltaTime, Space.World);
  131. }
  132. }
  133. /// <summary>
  134. /// Relative direction to where the AI is heading.
  135. /// Filled in by <see cref="CalculateVelocity"/>
  136. /// </summary>
  137. protected new Vector3 targetDirection;
  138. protected float XZSqrMagnitude (Vector3 a, Vector3 b) {
  139. float dx = b.x-a.x;
  140. float dz = b.z-a.z;
  141. return dx*dx + dz*dz;
  142. }
  143. /// <summary>
  144. /// Calculates desired velocity.
  145. /// Finds the target path segment and returns the forward direction, scaled with speed.
  146. /// A whole bunch of restrictions on the velocity is applied to make sure it doesn't overshoot, does not look too far ahead,
  147. /// and slows down when close to the target.
  148. /// /see speed
  149. /// /see endReachedDistance
  150. /// /see slowdownDistance
  151. /// /see CalculateTargetPoint
  152. /// /see targetPoint
  153. /// /see targetDirection
  154. /// /see currentWaypointIndex
  155. /// </summary>
  156. protected new Vector3 CalculateVelocity (Vector3 currentPosition) {
  157. if (path == null || path.vectorPath == null || path.vectorPath.Count == 0) return Vector3.zero;
  158. List<Vector3> vPath = path.vectorPath;
  159. if (vPath.Count == 1) {
  160. vPath.Insert(0, currentPosition);
  161. }
  162. if (currentWaypointIndex >= vPath.Count) { currentWaypointIndex = vPath.Count-1; }
  163. if (currentWaypointIndex <= 1) currentWaypointIndex = 1;
  164. while (true) {
  165. if (currentWaypointIndex < vPath.Count-1) {
  166. //There is a "next path segment"
  167. float dist = XZSqrMagnitude(vPath[currentWaypointIndex], currentPosition);
  168. //Mathfx.DistancePointSegmentStrict (vPath[currentWaypointIndex+1],vPath[currentWaypointIndex+2],currentPosition);
  169. if (dist < pickNextWaypointDist*pickNextWaypointDist) {
  170. lastFoundWaypointPosition = currentPosition;
  171. lastFoundWaypointTime = Time.time;
  172. currentWaypointIndex++;
  173. } else {
  174. break;
  175. }
  176. } else {
  177. break;
  178. }
  179. }
  180. Vector3 dir = vPath[currentWaypointIndex] - vPath[currentWaypointIndex-1];
  181. Vector3 targetPosition = CalculateTargetPoint(currentPosition, vPath[currentWaypointIndex-1], vPath[currentWaypointIndex]);
  182. dir = targetPosition-currentPosition;
  183. dir.y = 0;
  184. float targetDist = dir.magnitude;
  185. float slowdown = Mathf.Clamp01(targetDist / slowdownDistance);
  186. this.targetDirection = dir;
  187. if (currentWaypointIndex == vPath.Count-1 && targetDist <= endReachedDistance) {
  188. if (!reachedEndOfPath) { reachedEndOfPath = true; OnTargetReached(); }
  189. //Send a move request, this ensures gravity is applied
  190. return Vector3.zero;
  191. }
  192. Vector3 forward = tr.forward;
  193. float dot = Vector3.Dot(dir.normalized, forward);
  194. float sp = maxSpeed * Mathf.Max(dot, minMoveScale) * slowdown;
  195. #if ASTARDEBUG
  196. Debug.DrawLine(vPath[currentWaypointIndex-1], vPath[currentWaypointIndex], Color.black);
  197. Debug.DrawLine(GetFeetPosition(), targetPosition, Color.red);
  198. Debug.DrawRay(targetPosition, Vector3.up, Color.red);
  199. Debug.DrawRay(GetFeetPosition(), dir, Color.yellow);
  200. Debug.DrawRay(GetFeetPosition(), forward*sp, Color.cyan);
  201. #endif
  202. if (Time.deltaTime > 0) {
  203. sp = Mathf.Clamp(sp, 0, targetDist/(Time.deltaTime*2));
  204. }
  205. return forward*sp;
  206. }
  207. /// <summary>
  208. /// Rotates in the specified direction.
  209. /// Rotates around the Y-axis.
  210. /// See: turningSpeed
  211. /// </summary>
  212. protected void RotateTowards (Vector3 dir) {
  213. if (dir == Vector3.zero) return;
  214. Quaternion rot = tr.rotation;
  215. Quaternion toTarget = Quaternion.LookRotation(dir);
  216. rot = Quaternion.Slerp(rot, toTarget, turningSpeed*Time.deltaTime);
  217. Vector3 euler = rot.eulerAngles;
  218. euler.z = 0;
  219. euler.x = 0;
  220. rot = Quaternion.Euler(euler);
  221. tr.rotation = rot;
  222. }
  223. /// <summary>
  224. /// Calculates target point from the current line segment.
  225. /// See: <see cref="forwardLook"/>
  226. /// TODO: This function uses .magnitude quite a lot, can it be optimized?
  227. /// </summary>
  228. /// <param name="p">Current position</param>
  229. /// <param name="a">Line segment start</param>
  230. /// <param name="b">Line segment end
  231. /// The returned point will lie somewhere on the line segment.</param>
  232. protected Vector3 CalculateTargetPoint (Vector3 p, Vector3 a, Vector3 b) {
  233. a.y = p.y;
  234. b.y = p.y;
  235. float magn = (a-b).magnitude;
  236. if (magn == 0) return a;
  237. float closest = Mathf.Clamp01(VectorMath.ClosestPointOnLineFactor(a, b, p));
  238. Vector3 point = (b-a)*closest + a;
  239. float distance = (point-p).magnitude;
  240. float lookAhead = Mathf.Clamp(forwardLook - distance, 0.0F, forwardLook);
  241. float offset = lookAhead / magn;
  242. offset = Mathf.Clamp(offset+closest, 0.0F, 1.0F);
  243. return (b-a)*offset + a;
  244. }
  245. }
  246. }