AIPath.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. namespace Pathfinding {
  5. using Pathfinding.RVO;
  6. using Pathfinding.Util;
  7. /// <summary>
  8. /// AI for following paths.
  9. ///
  10. /// [Open online documentation to see images]
  11. ///
  12. /// This AI is the default movement script which comes with the A* Pathfinding Project.
  13. /// 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
  14. /// to set up movement for the characters in your game.
  15. /// This script works well for many types of units, but if you need the highest performance (for example if you are moving hundreds of characters) you
  16. /// may want to customize this script or write a custom movement script to be able to optimize it specifically for your game.
  17. ///
  18. /// This script will try to move to a given <see cref="destination"/>. At <see cref="repathRate regular"/>, the path to the destination will be recalculated.
  19. /// If you want to make the AI to follow a particular object you can attach the <see cref="Pathfinding.AIDestinationSetter"/> component.
  20. /// Take a look at the getstarted (view in online documentation for working links) tutorial for more instructions on how to configure this script.
  21. ///
  22. /// Here is a video of this script being used move an agent around (technically it uses the <see cref="Pathfinding.Examples.MineBotAI"/> script that inherits from this one but adds a bit of animation support for the example scenes):
  23. /// [Open online documentation to see videos]
  24. ///
  25. /// \section variables Quick overview of the variables
  26. /// 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.
  27. ///
  28. /// 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.
  29. /// The <see cref="destination"/> field 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.
  30. /// Or it can be the player object in a zombie game.
  31. /// The <see cref="maxSpeed"/> is self-explanatory, as is <see cref="rotationSpeed"/>. however <see cref="slowdownDistance"/> might require some explanation:
  32. /// It is the approximate distance from the target where the AI will start to slow down. Setting it to a large value will make the AI slow down very gradually.
  33. /// <see cref="pickNextWaypointDist"/> determines the distance to the point the AI will move to (see image below).
  34. ///
  35. /// Below is an image illustrating several variables that are exposed by this class (<see cref="pickNextWaypointDist"/>, <see cref="steeringTarget"/>, <see cref="desiredVelocity)"/>
  36. /// [Open online documentation to see images]
  37. ///
  38. /// This script has many movement fallbacks.
  39. /// If it finds an RVOController attached to the same GameObject as this component, it will use that. If it finds a character controller it will also use that.
  40. /// If it finds a rigidbody it will use that. Lastly it will fall back to simply modifying Transform.position which is guaranteed to always work and is also the most performant option.
  41. ///
  42. /// \section how-aipath-works How it works
  43. /// In this section I'm going to go over how this script is structured and how information flows.
  44. /// This is useful if you want to make changes to this script or if you just want to understand how it works a bit more deeply.
  45. /// However you do not need to read this section if you are just going to use the script as-is.
  46. ///
  47. /// This script inherits from the <see cref="AIBase"/> class. The movement happens either in Unity's standard <see cref="Update"/> or <see cref="FixedUpdate"/> method.
  48. /// They are both defined in the AIBase class. Which one is actually used depends on if a rigidbody is used for movement or not.
  49. /// Rigidbody movement has to be done inside the FixedUpdate method while otherwise it is better to do it in Update.
  50. ///
  51. /// From there a call is made to the <see cref="MovementUpdate"/> method (which in turn calls <see cref="MovementUpdateInternal)"/>.
  52. /// This method contains the main bulk of the code and calculates how the AI *wants* to move. However it doesn't do any movement itself.
  53. /// Instead it returns the position and rotation it wants the AI to move to have at the end of the frame.
  54. /// The <see cref="Update"/> (or <see cref="FixedUpdate)"/> method then passes these values to the <see cref="FinalizeMovement"/> method which is responsible for actually moving the character.
  55. /// That method also handles things like making sure the AI doesn't fall through the ground using raycasting.
  56. ///
  57. /// The AI recalculates its path regularly. This happens in the Update method which checks <see cref="shouldRecalculatePath"/> and if that returns true it will call <see cref="SearchPath"/>.
  58. /// The <see cref="SearchPath"/> method will prepare a path request and send it to the <see cref="Pathfinding.Seeker"/> component which should be attached to the same GameObject as this script.
  59. /// Since this script will when waking up register to the <see cref="Pathfinding.Seeker.pathCallback"/> delegate this script will be notified every time a new path is calculated by the <see cref="OnPathComplete"/> method being called.
  60. /// It may take one or sometimes multiple frames for the path to be calculated, but finally the <see cref="OnPathComplete"/> method will be called and the current path that the AI is following will be replaced.
  61. /// </summary>
  62. [AddComponentMenu("Pathfinding/AI/AIPath (2D,3D)")]
  63. public partial class AIPath : AIBase, IAstarAI {
  64. /// <summary>
  65. /// How quickly the agent accelerates.
  66. /// Positive values represent an acceleration in world units per second squared.
  67. /// Negative values are interpreted as an inverse time of how long it should take for the agent to reach its max speed.
  68. /// For example if it should take roughly 0.4 seconds for the agent to reach its max speed then this field should be set to -1/0.4 = -2.5.
  69. /// For a negative value the final acceleration will be: -acceleration*maxSpeed.
  70. /// This behaviour exists mostly for compatibility reasons.
  71. ///
  72. /// In the Unity inspector there are two modes: Default and Custom. In the Default mode this field is set to -2.5 which means that it takes about 0.4 seconds for the agent to reach its top speed.
  73. /// In the Custom mode you can set the acceleration to any positive value.
  74. /// </summary>
  75. public float maxAcceleration = -2.5f;
  76. /// <summary>
  77. /// Rotation speed in degrees per second.
  78. /// Rotation is calculated using Quaternion.RotateTowards. This variable represents the rotation speed in degrees per second.
  79. /// The higher it is, the faster the character will be able to rotate.
  80. /// </summary>
  81. [UnityEngine.Serialization.FormerlySerializedAs("turningSpeed")]
  82. public float rotationSpeed = 360;
  83. /// <summary>Distance from the end of the path where the AI will start to slow down</summary>
  84. public float slowdownDistance = 0.6F;
  85. /// <summary>
  86. /// How far the AI looks ahead along the path to determine the point it moves to.
  87. /// In world units.
  88. /// If you enable the <see cref="alwaysDrawGizmos"/> toggle this value will be visualized in the scene view as a blue circle around the agent.
  89. /// [Open online documentation to see images]
  90. ///
  91. /// Here are a few example videos showing some typical outcomes with good values as well as how it looks when this value is too low and too high.
  92. /// <table>
  93. /// <tr><td>[Open online documentation to see videos]</td><td>\xmlonly <verbatim><span class="label label-danger">Too low</span><br/></verbatim>\endxmlonly A too low value and a too low acceleration will result in the agent overshooting a lot and not managing to follow the path well.</td></tr>
  94. /// <tr><td>[Open online documentation to see videos]</td><td>\xmlonly <verbatim><span class="label label-warning">Ok</span><br/></verbatim>\endxmlonly A low value but a high acceleration works decently to make the AI follow the path more closely. Note that the <see cref="Pathfinding.AILerp"/> component is better suited if you want the agent to follow the path without any deviations.</td></tr>
  95. /// <tr><td>[Open online documentation to see videos]</td><td>\xmlonly <verbatim><span class="label label-success">Ok</span><br/></verbatim>\endxmlonly A reasonable value in this example.</td></tr>
  96. /// <tr><td>[Open online documentation to see videos]</td><td>\xmlonly <verbatim><span class="label label-success">Ok</span><br/></verbatim>\endxmlonly A reasonable value in this example, but the path is followed slightly more loosely than in the previous video.</td></tr>
  97. /// <tr><td>[Open online documentation to see videos]</td><td>\xmlonly <verbatim><span class="label label-danger">Too high</span><br/></verbatim>\endxmlonly A too high value will make the agent follow the path too loosely and may cause it to try to move through obstacles.</td></tr>
  98. /// </table>
  99. /// </summary>
  100. public float pickNextWaypointDist = 2;
  101. /// <summary>
  102. /// Distance to the end point to consider the end of path to be reached.
  103. /// When the end is within this distance then <see cref="OnTargetReached"/> will be called and <see cref="reachedEndOfPath"/> will return true.
  104. /// </summary>
  105. public float endReachedDistance = 0.2F;
  106. /// <summary>Draws detailed gizmos constantly in the scene view instead of only when the agent is selected and settings are being modified</summary>
  107. public bool alwaysDrawGizmos;
  108. /// <summary>
  109. /// Slow down when not facing the target direction.
  110. /// Incurs at a small performance overhead.
  111. /// </summary>
  112. public bool slowWhenNotFacingTarget = true;
  113. /// <summary>
  114. /// What to do when within <see cref="endReachedDistance"/> units from the destination.
  115. /// The character can either stop immediately when it comes within that distance, which is useful for e.g archers
  116. /// or other ranged units that want to fire on a target. Or the character can continue to try to reach the exact
  117. /// destination point and come to a full stop there. This is useful if you want the character to reach the exact
  118. /// point that you specified.
  119. ///
  120. /// Note: <see cref="reachedEndOfPath"/> will become true when the character is within <see cref="endReachedDistance"/> units from the destination
  121. /// regardless of what this field is set to.
  122. /// </summary>
  123. public CloseToDestinationMode whenCloseToDestination = CloseToDestinationMode.Stop;
  124. /// <summary>
  125. /// Ensure that the character is always on the traversable surface of the navmesh.
  126. /// When this option is enabled a <see cref="AstarPath.GetNearest"/> query will be done every frame to find the closest node that the agent can walk on
  127. /// and if the agent is not inside that node, then the agent will be moved to it.
  128. ///
  129. /// This is especially useful together with local avoidance in order to avoid agents pushing each other into walls.
  130. /// See: local-avoidance (view in online documentation for working links) for more info about this.
  131. ///
  132. /// This option also integrates with local avoidance so that if the agent is say forced into a wall by other agents the local avoidance
  133. /// system will be informed about that wall and can take that into account.
  134. ///
  135. /// Enabling this has some performance impact depending on the graph type (pretty fast for grid graphs, slightly slower for navmesh/recast graphs).
  136. /// If you are using a navmesh/recast graph you may want to switch to the <see cref="Pathfinding.RichAI"/> movement script which is specifically written for navmesh/recast graphs and
  137. /// does this kind of clamping out of the box. In many cases it can also follow the path more smoothly around sharp bends in the path.
  138. ///
  139. /// It is not recommended that you use this option together with the funnel modifier on grid graphs because the funnel modifier will make the path
  140. /// go very close to the border of the graph and this script has a tendency to try to cut corners a bit. This may cause it to try to go slightly outside the
  141. /// traversable surface near corners and that will look bad if this option is enabled.
  142. ///
  143. /// Warning: This option makes no sense to use on point graphs because point graphs do not have a surface.
  144. /// Enabling this option when using a point graph will lead to the agent being snapped to the closest node every frame which is likely not what you want.
  145. ///
  146. /// Below you can see an image where several agents using local avoidance were ordered to go to the same point in a corner.
  147. /// When not constraining the agents to the graph they are easily pushed inside obstacles.
  148. /// [Open online documentation to see images]
  149. /// </summary>
  150. public bool constrainInsideGraph = false;
  151. /// <summary>Current path which is followed</summary>
  152. protected Path path;
  153. /// <summary>Helper which calculates points along the current path</summary>
  154. protected PathInterpolator interpolator = new PathInterpolator();
  155. #region IAstarAI implementation
  156. /// <summary>\copydoc Pathfinding::IAstarAI::Teleport</summary>
  157. public override void Teleport (Vector3 newPosition, bool clearPath = true) {
  158. reachedEndOfPath = false;
  159. base.Teleport(newPosition, clearPath);
  160. }
  161. /// <summary>\copydoc Pathfinding::IAstarAI::remainingDistance</summary>
  162. public float remainingDistance {
  163. get {
  164. return interpolator.valid ? interpolator.remainingDistance + movementPlane.ToPlane(interpolator.position - position).magnitude : float.PositiveInfinity;
  165. }
  166. }
  167. /// <summary>\copydoc Pathfinding::IAstarAI::reachedDestination</summary>
  168. public bool reachedDestination {
  169. get {
  170. if (!reachedEndOfPath) return false;
  171. if (!interpolator.valid || remainingDistance + movementPlane.ToPlane(destination - interpolator.endPoint).magnitude > endReachedDistance) return false;
  172. // Don't do height checks in 2D mode
  173. if (orientation != OrientationMode.YAxisForward) {
  174. // Check if the destination is above the head of the character or far below the feet of it
  175. float yDifference;
  176. movementPlane.ToPlane(destination - position, out yDifference);
  177. var h = tr.localScale.y * height;
  178. if (yDifference > h || yDifference < -h*0.5) return false;
  179. }
  180. return true;
  181. }
  182. }
  183. /// <summary>\copydoc Pathfinding::IAstarAI::reachedEndOfPath</summary>
  184. public bool reachedEndOfPath { get; protected set; }
  185. /// <summary>\copydoc Pathfinding::IAstarAI::hasPath</summary>
  186. public bool hasPath {
  187. get {
  188. return interpolator.valid;
  189. }
  190. }
  191. /// <summary>\copydoc Pathfinding::IAstarAI::pathPending</summary>
  192. public bool pathPending {
  193. get {
  194. return waitingForPathCalculation;
  195. }
  196. }
  197. /// <summary>\copydoc Pathfinding::IAstarAI::steeringTarget</summary>
  198. public Vector3 steeringTarget {
  199. get {
  200. return interpolator.valid ? interpolator.position : position;
  201. }
  202. }
  203. /// <summary>\copydoc Pathfinding::IAstarAI::radius</summary>
  204. float IAstarAI.radius { get { return radius; } set { radius = value; } }
  205. /// <summary>\copydoc Pathfinding::IAstarAI::height</summary>
  206. float IAstarAI.height { get { return height; } set { height = value; } }
  207. /// <summary>\copydoc Pathfinding::IAstarAI::maxSpeed</summary>
  208. float IAstarAI.maxSpeed { get { return maxSpeed; } set { maxSpeed = value; } }
  209. /// <summary>\copydoc Pathfinding::IAstarAI::canSearch</summary>
  210. bool IAstarAI.canSearch { get { return canSearch; } set { canSearch = value; } }
  211. /// <summary>\copydoc Pathfinding::IAstarAI::canMove</summary>
  212. bool IAstarAI.canMove { get { return canMove; } set { canMove = value; } }
  213. #endregion
  214. /// <summary>\copydoc Pathfinding::IAstarAI::GetRemainingPath</summary>
  215. public void GetRemainingPath (List<Vector3> buffer, out bool stale) {
  216. buffer.Clear();
  217. buffer.Add(position);
  218. if (!interpolator.valid) {
  219. stale = true;
  220. return;
  221. }
  222. stale = false;
  223. interpolator.GetRemainingPath(buffer);
  224. }
  225. protected override void OnDisable () {
  226. base.OnDisable();
  227. // Release current path so that it can be pooled
  228. if (path != null) path.Release(this);
  229. path = null;
  230. interpolator.SetPath(null);
  231. reachedEndOfPath = false;
  232. }
  233. /// <summary>
  234. /// The end of the path has been reached.
  235. /// If you want custom logic for when the AI has reached it's destination add it here. You can
  236. /// also create a new script which inherits from this one and override the function in that script.
  237. ///
  238. /// This method will be called again if a new path is calculated as the destination may have changed.
  239. /// So when the agent is close to the destination this method will typically be called every <see cref="repathRate"/> seconds.
  240. /// </summary>
  241. public virtual void OnTargetReached () {
  242. }
  243. /// <summary>
  244. /// Called when a requested path has been calculated.
  245. /// A path is first requested by <see cref="UpdatePath"/>, it is then calculated, probably in the same or the next frame.
  246. /// Finally it is returned to the seeker which forwards it to this function.
  247. /// </summary>
  248. protected override void OnPathComplete (Path newPath) {
  249. ABPath p = newPath as ABPath;
  250. if (p == null) throw new System.Exception("This function only handles ABPaths, do not use special path types");
  251. waitingForPathCalculation = false;
  252. // Increase the reference count on the new path.
  253. // This is used for object pooling to reduce allocations.
  254. p.Claim(this);
  255. // Path couldn't be calculated of some reason.
  256. // More info in p.errorLog (debug string)
  257. if (p.error) {
  258. p.Release(this);
  259. SetPath(null);
  260. return;
  261. }
  262. // Release the previous path.
  263. if (path != null) path.Release(this);
  264. // Replace the old path
  265. path = p;
  266. // The RandomPath and MultiTargetPath do not have a well defined destination that could have been
  267. // set before the paths were calculated. So we instead set the destination here so that some properties
  268. // like #reachedDestination and #remainingDistance work correctly.
  269. if (path is RandomPath rpath) {
  270. destination = rpath.originalEndPoint;
  271. } else if (path is MultiTargetPath mpath) {
  272. destination = mpath.originalEndPoint;
  273. }
  274. // Make sure the path contains at least 2 points
  275. if (path.vectorPath.Count == 1) path.vectorPath.Add(path.vectorPath[0]);
  276. interpolator.SetPath(path.vectorPath);
  277. var graph = path.path.Count > 0 ? AstarData.GetGraph(path.path[0]) as ITransformedGraph : null;
  278. movementPlane = graph != null ? graph.transform : (orientation == OrientationMode.YAxisForward ? new GraphTransform(Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(-90, 270, 90), Vector3.one)) : GraphTransform.identityTransform);
  279. // Reset some variables
  280. reachedEndOfPath = false;
  281. // Simulate movement from the point where the path was requested
  282. // to where we are right now. This reduces the risk that the agent
  283. // gets confused because the first point in the path is far away
  284. // from the current position (possibly behind it which could cause
  285. // the agent to turn around, and that looks pretty bad).
  286. interpolator.MoveToLocallyClosestPoint((GetFeetPosition() + p.originalStartPoint) * 0.5f);
  287. interpolator.MoveToLocallyClosestPoint(GetFeetPosition());
  288. // Update which point we are moving towards.
  289. // Note that we need to do this here because otherwise the remainingDistance field might be incorrect for 1 frame.
  290. // (due to interpolator.remainingDistance being incorrect).
  291. interpolator.MoveToCircleIntersection2D(position, pickNextWaypointDist, movementPlane);
  292. var distanceToEnd = remainingDistance;
  293. if (distanceToEnd <= endReachedDistance) {
  294. reachedEndOfPath = true;
  295. OnTargetReached();
  296. }
  297. }
  298. protected override void ClearPath () {
  299. CancelCurrentPathRequest();
  300. if (path != null) path.Release(this);
  301. path = null;
  302. interpolator.SetPath(null);
  303. reachedEndOfPath = false;
  304. }
  305. /// <summary>Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not</summary>
  306. protected override void MovementUpdateInternal (float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation) {
  307. float currentAcceleration = maxAcceleration;
  308. // If negative, calculate the acceleration from the max speed
  309. if (currentAcceleration < 0) currentAcceleration *= -maxSpeed;
  310. if (updatePosition) {
  311. // Get our current position. We read from transform.position as few times as possible as it is relatively slow
  312. // (at least compared to a local variable)
  313. simulatedPosition = tr.position;
  314. }
  315. if (updateRotation) simulatedRotation = tr.rotation;
  316. var currentPosition = simulatedPosition;
  317. // Update which point we are moving towards
  318. interpolator.MoveToCircleIntersection2D(currentPosition, pickNextWaypointDist, movementPlane);
  319. var dir = movementPlane.ToPlane(steeringTarget - currentPosition);
  320. // Calculate the distance to the end of the path
  321. float distanceToEnd = dir.magnitude + Mathf.Max(0, interpolator.remainingDistance);
  322. // Check if we have reached the target
  323. var prevTargetReached = reachedEndOfPath;
  324. reachedEndOfPath = distanceToEnd <= endReachedDistance && interpolator.valid;
  325. if (!prevTargetReached && reachedEndOfPath) OnTargetReached();
  326. float slowdown;
  327. // Normalized direction of where the agent is looking
  328. var forwards = movementPlane.ToPlane(simulatedRotation * (orientation == OrientationMode.YAxisForward ? Vector3.up : Vector3.forward));
  329. // Check if we have a valid path to follow and some other script has not stopped the character
  330. bool stopped = isStopped || (reachedDestination && whenCloseToDestination == CloseToDestinationMode.Stop);
  331. if (interpolator.valid && !stopped) {
  332. // How fast to move depending on the distance to the destination.
  333. // Move slower as the character gets closer to the destination.
  334. // This is always a value between 0 and 1.
  335. slowdown = distanceToEnd < slowdownDistance? Mathf.Sqrt(distanceToEnd / slowdownDistance) : 1;
  336. if (reachedEndOfPath && whenCloseToDestination == CloseToDestinationMode.Stop) {
  337. // Slow down as quickly as possible
  338. velocity2D -= Vector2.ClampMagnitude(velocity2D, currentAcceleration * deltaTime);
  339. } else {
  340. velocity2D += MovementUtilities.CalculateAccelerationToReachPoint(dir, dir.normalized*maxSpeed, velocity2D, currentAcceleration, rotationSpeed, maxSpeed, forwards) * deltaTime;
  341. }
  342. } else {
  343. slowdown = 1;
  344. // Slow down as quickly as possible
  345. velocity2D -= Vector2.ClampMagnitude(velocity2D, currentAcceleration * deltaTime);
  346. }
  347. velocity2D = MovementUtilities.ClampVelocity(velocity2D, maxSpeed, slowdown, slowWhenNotFacingTarget && enableRotation, forwards);
  348. ApplyGravity(deltaTime);
  349. if (rvoController != null && rvoController.enabled) {
  350. // Send a message to the RVOController that we want to move
  351. // with this velocity. In the next simulation step, this
  352. // velocity will be processed and it will be fed back to the
  353. // rvo controller and finally it will be used by this script
  354. // when calling the CalculateMovementDelta method below
  355. // Make sure that we don't move further than to the end point
  356. // of the path. If the RVO simulation FPS is low and we did
  357. // not do this, the agent might overshoot the target a lot.
  358. var rvoTarget = currentPosition + movementPlane.ToWorld(Vector2.ClampMagnitude(velocity2D, distanceToEnd), 0f);
  359. rvoController.SetTarget(rvoTarget, velocity2D.magnitude, maxSpeed);
  360. }
  361. // Set how much the agent wants to move during this frame
  362. var delta2D = lastDeltaPosition = CalculateDeltaToMoveThisFrame(movementPlane.ToPlane(currentPosition), distanceToEnd, deltaTime);
  363. nextPosition = currentPosition + movementPlane.ToWorld(delta2D, verticalVelocity * lastDeltaTime);
  364. CalculateNextRotation(slowdown, out nextRotation);
  365. }
  366. protected virtual void CalculateNextRotation (float slowdown, out Quaternion nextRotation) {
  367. if (lastDeltaTime > 0.00001f && enableRotation) {
  368. Vector2 desiredRotationDirection;
  369. if (rvoController != null && rvoController.enabled) {
  370. // When using local avoidance, use the actual velocity we are moving with if that velocity
  371. // is high enough, otherwise fall back to the velocity that we want to move with (velocity2D).
  372. // The local avoidance velocity can be very jittery when the character is close to standing still
  373. // as it constantly makes small corrections. We do not want the rotation of the character to be jittery.
  374. var actualVelocity = lastDeltaPosition/lastDeltaTime;
  375. desiredRotationDirection = Vector2.Lerp(velocity2D, actualVelocity, 4 * actualVelocity.magnitude / (maxSpeed + 0.0001f));
  376. } else {
  377. desiredRotationDirection = velocity2D;
  378. }
  379. // Rotate towards the direction we are moving in.
  380. // Don't rotate when we are very close to the target.
  381. var currentRotationSpeed = rotationSpeed * Mathf.Max(0, (slowdown - 0.3f) / 0.7f);
  382. nextRotation = SimulateRotationTowards(desiredRotationDirection, currentRotationSpeed * lastDeltaTime);
  383. } else {
  384. // TODO: simulatedRotation
  385. nextRotation = rotation;
  386. }
  387. }
  388. static NNConstraint cachedNNConstraint = NNConstraint.Default;
  389. protected override Vector3 ClampToNavmesh (Vector3 position, out bool positionChanged) {
  390. if (constrainInsideGraph) {
  391. cachedNNConstraint.tags = seeker.traversableTags;
  392. cachedNNConstraint.graphMask = seeker.graphMask;
  393. cachedNNConstraint.distanceXZ = true;
  394. var clampedPosition = AstarPath.active.GetNearest(position, cachedNNConstraint).position;
  395. // We cannot simply check for equality because some precision may be lost
  396. // if any coordinate transformations are used.
  397. var difference = movementPlane.ToPlane(clampedPosition - position);
  398. float sqrDifference = difference.sqrMagnitude;
  399. if (sqrDifference > 0.001f*0.001f) {
  400. // The agent was outside the navmesh. Remove that component of the velocity
  401. // so that the velocity only goes along the direction of the wall, not into it
  402. velocity2D -= difference * Vector2.Dot(difference, velocity2D) / sqrDifference;
  403. // Make sure the RVO system knows that there was a collision here
  404. // Otherwise other agents may think this agent continued
  405. // to move forwards and avoidance quality may suffer
  406. if (rvoController != null && rvoController.enabled) {
  407. rvoController.SetCollisionNormal(difference);
  408. }
  409. positionChanged = true;
  410. // Return the new position, but ignore any changes in the y coordinate from the ClampToNavmesh method as the y coordinates in the navmesh are rarely very accurate
  411. return position + movementPlane.ToWorld(difference);
  412. }
  413. }
  414. positionChanged = false;
  415. return position;
  416. }
  417. #if UNITY_EDITOR
  418. [System.NonSerialized]
  419. int gizmoHash = 0;
  420. [System.NonSerialized]
  421. float lastChangedTime = float.NegativeInfinity;
  422. protected static readonly Color GizmoColor = new Color(46.0f/255, 104.0f/255, 201.0f/255);
  423. protected override void OnDrawGizmos () {
  424. base.OnDrawGizmos();
  425. if (alwaysDrawGizmos) OnDrawGizmosInternal();
  426. }
  427. protected override void OnDrawGizmosSelected () {
  428. base.OnDrawGizmosSelected();
  429. if (!alwaysDrawGizmos) OnDrawGizmosInternal();
  430. }
  431. void OnDrawGizmosInternal () {
  432. var newGizmoHash = pickNextWaypointDist.GetHashCode() ^ slowdownDistance.GetHashCode() ^ endReachedDistance.GetHashCode();
  433. if (newGizmoHash != gizmoHash && gizmoHash != 0) lastChangedTime = Time.realtimeSinceStartup;
  434. gizmoHash = newGizmoHash;
  435. float alpha = alwaysDrawGizmos ? 1 : Mathf.SmoothStep(1, 0, (Time.realtimeSinceStartup - lastChangedTime - 5f)/0.5f) * (UnityEditor.Selection.gameObjects.Length == 1 ? 1 : 0);
  436. if (alpha > 0) {
  437. // Make sure the scene view is repainted while the gizmos are visible
  438. if (!alwaysDrawGizmos) UnityEditor.SceneView.RepaintAll();
  439. Draw.Gizmos.Line(position, steeringTarget, GizmoColor * new Color(1, 1, 1, alpha));
  440. Gizmos.matrix = Matrix4x4.TRS(position, transform.rotation * (orientation == OrientationMode.YAxisForward ? Quaternion.Euler(-90, 0, 0) : Quaternion.identity), Vector3.one);
  441. Draw.Gizmos.CircleXZ(Vector3.zero, pickNextWaypointDist, GizmoColor * new Color(1, 1, 1, alpha));
  442. Draw.Gizmos.CircleXZ(Vector3.zero, slowdownDistance, Color.Lerp(GizmoColor, Color.red, 0.5f) * new Color(1, 1, 1, alpha));
  443. Draw.Gizmos.CircleXZ(Vector3.zero, endReachedDistance, Color.Lerp(GizmoColor, Color.red, 0.8f) * new Color(1, 1, 1, alpha));
  444. }
  445. }
  446. #endif
  447. protected override int OnUpgradeSerializedData (int version, bool unityThread) {
  448. // Approximately convert from a damping value to a degrees per second value.
  449. if (version < 1) rotationSpeed *= 90;
  450. return base.OnUpgradeSerializedData(version, unityThread);
  451. }
  452. }
  453. }