AILerp.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. namespace Pathfinding {
  5. using Pathfinding.Util;
  6. /// <summary>
  7. /// Linearly interpolating movement script.
  8. /// This movement script will follow the path exactly, it uses linear interpolation to move between the waypoints in the path.
  9. /// This is desirable for some types of games.
  10. /// It also works in 2D.
  11. ///
  12. /// See: You can see an example of this script in action in the example scene called Example15_2D.
  13. ///
  14. /// \section rec Configuration
  15. /// \subsection rec-snapped Recommended setup for movement along connections
  16. ///
  17. /// This depends on what type of movement you are aiming for.
  18. /// If you are aiming for movement where the unit follows the path exactly and move only along the graph connections on a grid/point graph.
  19. /// I recommend that you adjust the StartEndModifier on the Seeker component: set the 'Start Point Snapping' field to 'NodeConnection' and the 'End Point Snapping' field to 'SnapToNode'.
  20. /// [Open online documentation to see images]
  21. /// [Open online documentation to see images]
  22. ///
  23. /// \subsection rec-smooth Recommended setup for smooth movement
  24. /// If you on the other hand want smoother movement I recommend setting 'Start Point Snapping' and 'End Point Snapping' to 'ClosestOnNode' and to add the Simple Smooth Modifier to the GameObject as well.
  25. /// Alternatively you can use the <see cref="Pathfinding.FunnelModifier Funnel"/> which works better on navmesh/recast graphs or the <see cref="Pathfinding.RaycastModifier"/>.
  26. ///
  27. /// You should not combine the Simple Smooth Modifier or the Funnel Modifier with the NodeConnection snapping mode. This may lead to very odd behavior.
  28. ///
  29. /// [Open online documentation to see images]
  30. /// [Open online documentation to see images]
  31. /// You may also want to tweak the <see cref="rotationSpeed"/>.
  32. ///
  33. /// \ingroup movementscripts
  34. /// </summary>
  35. [RequireComponent(typeof(Seeker))]
  36. [AddComponentMenu("Pathfinding/AI/AILerp (2D,3D)")]
  37. [HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_a_i_lerp.php")]
  38. public class AILerp : VersionedMonoBehaviour, IAstarAI {
  39. /// <summary>
  40. /// Determines how often it will search for new paths.
  41. /// If you have fast moving targets or AIs, you might want to set it to a lower value.
  42. /// The value is in seconds between path requests.
  43. /// </summary>
  44. public float repathRate = 0.5F;
  45. /// <summary>\copydoc Pathfinding::IAstarAI::canSearch</summary>
  46. public bool canSearch = true;
  47. /// <summary>\copydoc Pathfinding::IAstarAI::canMove</summary>
  48. public bool canMove = true;
  49. /// <summary>Speed in world units</summary>
  50. public float speed = 3;
  51. /// <summary>
  52. /// Determines which direction the agent moves in.
  53. /// For 3D games you most likely want the ZAxisIsForward option as that is the convention for 3D games.
  54. /// For 2D games you most likely want the YAxisIsForward option as that is the convention for 2D games.
  55. ///
  56. /// Using the YAxisForward option will also allow the agent to assume that the movement will happen in the 2D (XY) plane instead of the XZ plane
  57. /// if it does not know. This is important only for the point graph which does not have a well defined up direction. The other built-in graphs (e.g the grid graph)
  58. /// will all tell the agent which movement plane it is supposed to use.
  59. ///
  60. /// [Open online documentation to see images]
  61. /// </summary>
  62. [UnityEngine.Serialization.FormerlySerializedAs("rotationIn2D")]
  63. public OrientationMode orientation = OrientationMode.ZAxisForward;
  64. /// <summary>
  65. /// If true, the forward axis of the character will be along the Y axis instead of the Z axis.
  66. ///
  67. /// Deprecated: Use <see cref="orientation"/> instead
  68. /// </summary>
  69. [System.Obsolete("Use orientation instead")]
  70. public bool rotationIn2D {
  71. get { return orientation == OrientationMode.YAxisForward; }
  72. set { orientation = value ? OrientationMode.YAxisForward : OrientationMode.ZAxisForward; }
  73. }
  74. /// <summary>
  75. /// If true, the AI will rotate to face the movement direction.
  76. /// See: <see cref="orientation"/>
  77. /// </summary>
  78. public bool enableRotation = true;
  79. /// <summary>How quickly to rotate</summary>
  80. public float rotationSpeed = 10;
  81. /// <summary>
  82. /// If true, some interpolation will be done when a new path has been calculated.
  83. /// This is used to avoid short distance teleportation.
  84. /// </summary>
  85. public bool interpolatePathSwitches = true;
  86. /// <summary>How quickly to interpolate to the new path</summary>
  87. public float switchPathInterpolationSpeed = 5;
  88. /// <summary>True if the end of the current path has been reached</summary>
  89. public bool reachedEndOfPath { get; private set; }
  90. /// <summary>\copydoc Pathfinding::IAstarAI::reachedDestination</summary>
  91. public bool reachedDestination {
  92. get {
  93. if (!reachedEndOfPath) return false;
  94. // Note: distanceToSteeringTarget is the distance to the end of the path when approachingPathEndpoint is true
  95. var dir = destination - interpolator.endPoint;
  96. // Ignore either the y or z coordinate depending on if we are using 2D mode or not
  97. if (orientation == OrientationMode.YAxisForward) dir.z = 0;
  98. else dir.y = 0;
  99. // Check against using a very small margin
  100. // In theory a check against 0 should be done, but this will be a bit more resilient against targets that move slowly or maybe jitter around due to floating point errors.
  101. if (remainingDistance + dir.magnitude >= 0.05f) return false;
  102. return true;
  103. }
  104. }
  105. public Vector3 destination { get; set; }
  106. /// <summary>
  107. /// Determines if the character's position should be coupled to the Transform's position.
  108. /// If false then all movement calculations will happen as usual, but the object that this component is attached to will not move
  109. /// instead only the <see cref="position"/> property will change.
  110. ///
  111. /// See: <see cref="canMove"/> which in contrast to this field will disable all movement calculations.
  112. /// See: <see cref="updateRotation"/>
  113. /// </summary>
  114. [System.NonSerialized]
  115. public bool updatePosition = true;
  116. /// <summary>
  117. /// Determines if the character's rotation should be coupled to the Transform's rotation.
  118. /// If false then all movement calculations will happen as usual, but the object that this component is attached to will not rotate
  119. /// instead only the <see cref="rotation"/> property will change.
  120. ///
  121. /// See: <see cref="updatePosition"/>
  122. /// </summary>
  123. [System.NonSerialized]
  124. public bool updateRotation = true;
  125. /// <summary>
  126. /// Target to move towards.
  127. /// The AI will try to follow/move towards this target.
  128. /// It can be a point on the ground where the player has clicked in an RTS for example, or it can be the player object in a zombie game.
  129. ///
  130. /// Deprecated: In 4.0 this will automatically add a <see cref="Pathfinding.AIDestinationSetter"/> component and set the target on that component.
  131. /// Try instead to use the <see cref="destination"/> property which does not require a transform to be created as the target or use
  132. /// the AIDestinationSetter component directly.
  133. /// </summary>
  134. [System.Obsolete("Use the destination property or the AIDestinationSetter component instead")]
  135. public Transform target {
  136. get {
  137. var setter = GetComponent<AIDestinationSetter>();
  138. return setter != null ? setter.target : null;
  139. }
  140. set {
  141. targetCompatibility = null;
  142. var setter = GetComponent<AIDestinationSetter>();
  143. if (setter == null) setter = gameObject.AddComponent<AIDestinationSetter>();
  144. setter.target = value;
  145. destination = value != null ? value.position : new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
  146. }
  147. }
  148. /// <summary>\copydoc Pathfinding::IAstarAI::position</summary>
  149. public Vector3 position { get { return updatePosition ? tr.position : simulatedPosition; } }
  150. /// <summary>\copydoc Pathfinding::IAstarAI::rotation</summary>
  151. public Quaternion rotation { get { return updateRotation ? tr.rotation : simulatedRotation; } }
  152. #region IAstarAI implementation
  153. /// <summary>\copydoc Pathfinding::IAstarAI::Move</summary>
  154. void IAstarAI.Move (Vector3 deltaPosition) {
  155. // This script does not know the concept of being away from the path that it is following
  156. // so this call will be ignored (as is also mentioned in the documentation).
  157. }
  158. /// <summary>\copydoc Pathfinding::IAstarAI::radius</summary>
  159. float IAstarAI.radius { get { return 0; } set {} }
  160. /// <summary>\copydoc Pathfinding::IAstarAI::height</summary>
  161. float IAstarAI.height { get { return 0; } set {} }
  162. /// <summary>\copydoc Pathfinding::IAstarAI::maxSpeed</summary>
  163. float IAstarAI.maxSpeed { get { return speed; } set { speed = value; } }
  164. /// <summary>\copydoc Pathfinding::IAstarAI::canSearch</summary>
  165. bool IAstarAI.canSearch { get { return canSearch; } set { canSearch = value; } }
  166. /// <summary>\copydoc Pathfinding::IAstarAI::canMove</summary>
  167. bool IAstarAI.canMove { get { return canMove; } set { canMove = value; } }
  168. Vector3 IAstarAI.velocity {
  169. get {
  170. return Time.deltaTime > 0.00001f ? (previousPosition1 - previousPosition2) / Time.deltaTime : Vector3.zero;
  171. }
  172. }
  173. Vector3 IAstarAI.desiredVelocity {
  174. get {
  175. // The AILerp script sets the position every frame. It does not take into account physics
  176. // or other things. So the velocity should always be the same as the desired velocity.
  177. return (this as IAstarAI).velocity;
  178. }
  179. }
  180. /// <summary>\copydoc Pathfinding::IAstarAI::steeringTarget</summary>
  181. Vector3 IAstarAI.steeringTarget {
  182. get {
  183. // AILerp doesn't use steering at all, so we will just return a point ahead of the agent in the direction it is moving.
  184. return interpolator.valid ? interpolator.position + interpolator.tangent : simulatedPosition;
  185. }
  186. }
  187. #endregion
  188. /// <summary>\copydoc Pathfinding::IAstarAI::remainingDistance</summary>
  189. public float remainingDistance {
  190. get {
  191. return Mathf.Max(interpolator.remainingDistance, 0);
  192. }
  193. set {
  194. interpolator.remainingDistance = Mathf.Max(value, 0);
  195. }
  196. }
  197. /// <summary>\copydoc Pathfinding::IAstarAI::hasPath</summary>
  198. public bool hasPath {
  199. get {
  200. return interpolator.valid;
  201. }
  202. }
  203. /// <summary>\copydoc Pathfinding::IAstarAI::pathPending</summary>
  204. public bool pathPending {
  205. get {
  206. return !canSearchAgain;
  207. }
  208. }
  209. /// <summary>\copydoc Pathfinding::IAstarAI::isStopped</summary>
  210. public bool isStopped { get; set; }
  211. /// <summary>\copydoc Pathfinding::IAstarAI::onSearchPath</summary>
  212. public System.Action onSearchPath { get; set; }
  213. /// <summary>Cached Seeker component</summary>
  214. protected Seeker seeker;
  215. /// <summary>Cached Transform component</summary>
  216. protected Transform tr;
  217. /// <summary>Time when the last path request was sent</summary>
  218. protected float lastRepath = -9999;
  219. /// <summary>Current path which is followed</summary>
  220. protected ABPath path;
  221. /// <summary>Only when the previous path has been returned should a search for a new path be done</summary>
  222. protected bool canSearchAgain = true;
  223. /// <summary>
  224. /// When a new path was returned, the AI was moving along this ray.
  225. /// Used to smoothly interpolate between the previous movement and the movement along the new path.
  226. /// The speed is equal to movement direction.
  227. /// </summary>
  228. protected Vector3 previousMovementOrigin;
  229. protected Vector3 previousMovementDirection;
  230. /// <summary>
  231. /// Time since the path was replaced by a new path.
  232. /// See: <see cref="interpolatePathSwitches"/>
  233. /// </summary>
  234. protected float pathSwitchInterpolationTime = 0;
  235. protected PathInterpolator interpolator = new PathInterpolator();
  236. /// <summary>
  237. /// Holds if the Start function has been run.
  238. /// Used to test if coroutines should be started in OnEnable to prevent calculating paths
  239. /// in the awake stage (or rather before start on frame 0).
  240. /// </summary>
  241. bool startHasRun = false;
  242. Vector3 previousPosition1, previousPosition2, simulatedPosition;
  243. Quaternion simulatedRotation;
  244. /// <summary>Required for serialization backward compatibility</summary>
  245. [UnityEngine.Serialization.FormerlySerializedAs("target")][SerializeField][HideInInspector]
  246. Transform targetCompatibility;
  247. protected AILerp () {
  248. // Note that this needs to be set here in the constructor and not in e.g Awake
  249. // because it is possible that other code runs and sets the destination property
  250. // before the Awake method on this script runs.
  251. destination = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
  252. }
  253. /// <summary>
  254. /// Initializes reference variables.
  255. /// If you override this function you should in most cases call base.Awake () at the start of it.
  256. /// </summary>
  257. protected override void Awake () {
  258. base.Awake();
  259. //This is a simple optimization, cache the transform component lookup
  260. tr = transform;
  261. seeker = GetComponent<Seeker>();
  262. // Tell the StartEndModifier to ask for our exact position when post processing the path This
  263. // is important if we are using prediction and requesting a path from some point slightly ahead
  264. // of us since then the start point in the path request may be far from our position when the
  265. // path has been calculated. This is also good because if a long path is requested, it may take
  266. // a few frames for it to be calculated so we could have moved some distance during that time
  267. seeker.startEndModifier.adjustStartPoint = () => simulatedPosition;
  268. }
  269. /// <summary>
  270. /// Starts searching for paths.
  271. /// If you override this function you should in most cases call base.Start () at the start of it.
  272. /// See: <see cref="Init"/>
  273. /// See: <see cref="RepeatTrySearchPath"/>
  274. /// </summary>
  275. protected virtual void Start () {
  276. startHasRun = true;
  277. Init();
  278. }
  279. /// <summary>Called when the component is enabled</summary>
  280. protected virtual void OnEnable () {
  281. // Make sure we receive callbacks when paths complete
  282. seeker.pathCallback += OnPathComplete;
  283. Init();
  284. }
  285. void Init () {
  286. if (startHasRun) {
  287. // The Teleport call will make sure some variables are properly initialized (like #prevPosition1 and #prevPosition2)
  288. Teleport(position, false);
  289. lastRepath = float.NegativeInfinity;
  290. if (shouldRecalculatePath) SearchPath();
  291. }
  292. }
  293. public void OnDisable () {
  294. // Abort any calculations in progress
  295. if (seeker != null) seeker.CancelCurrentPathRequest();
  296. canSearchAgain = true;
  297. // Release current path so that it can be pooled
  298. if (path != null) path.Release(this);
  299. path = null;
  300. interpolator.SetPath(null);
  301. // Make sure we no longer receive callbacks when paths complete
  302. seeker.pathCallback -= OnPathComplete;
  303. }
  304. public void Teleport (Vector3 position, bool clearPath = true) {
  305. if (clearPath) interpolator.SetPath(null);
  306. simulatedPosition = previousPosition1 = previousPosition2 = position;
  307. if (updatePosition) tr.position = position;
  308. reachedEndOfPath = false;
  309. if (clearPath) SearchPath();
  310. }
  311. /// <summary>True if the path should be automatically recalculated as soon as possible</summary>
  312. protected virtual bool shouldRecalculatePath {
  313. get {
  314. return Time.time - lastRepath >= repathRate && canSearchAgain && canSearch && !float.IsPositiveInfinity(destination.x);
  315. }
  316. }
  317. /// <summary>
  318. /// Requests a path to the target.
  319. /// Deprecated: Use <see cref="SearchPath"/> instead.
  320. /// </summary>
  321. [System.Obsolete("Use SearchPath instead")]
  322. public virtual void ForceSearchPath () {
  323. SearchPath();
  324. }
  325. /// <summary>Requests a path to the target.</summary>
  326. public virtual void SearchPath () {
  327. if (float.IsPositiveInfinity(destination.x)) return;
  328. if (onSearchPath != null) onSearchPath();
  329. lastRepath = Time.time;
  330. // This is where the path should start to search from
  331. var currentPosition = GetFeetPosition();
  332. // If we are following a path, start searching from the node we will
  333. // reach next this can prevent odd turns right at the start of the path
  334. /*if (interpolator.valid) {
  335. var prevDist = interpolator.distance;
  336. // Move to the end of the current segment
  337. interpolator.MoveToSegment(interpolator.segmentIndex, 1);
  338. currentPosition = interpolator.position;
  339. // Move back to the original position
  340. interpolator.distance = prevDist;
  341. }*/
  342. canSearchAgain = false;
  343. // Alternative way of creating a path request
  344. //ABPath p = ABPath.Construct(currentPosition, targetPoint, null);
  345. //seeker.StartPath(p);
  346. // Create a new path request
  347. // The OnPathComplete method will later be called with the result
  348. seeker.StartPath(currentPosition, destination);
  349. }
  350. /// <summary>
  351. /// The end of the path has been reached.
  352. /// If you want custom logic for when the AI has reached it's destination
  353. /// add it here.
  354. /// You can also create a new script which inherits from this one
  355. /// and override the function in that script.
  356. /// </summary>
  357. public virtual void OnTargetReached () {
  358. }
  359. /// <summary>
  360. /// Called when a requested path has finished calculation.
  361. /// A path is first requested by <see cref="SearchPath"/>, it is then calculated, probably in the same or the next frame.
  362. /// Finally it is returned to the seeker which forwards it to this function.
  363. /// </summary>
  364. protected virtual void OnPathComplete (Path _p) {
  365. ABPath p = _p as ABPath;
  366. if (p == null) throw new System.Exception("This function only handles ABPaths, do not use special path types");
  367. canSearchAgain = true;
  368. // Increase the reference count on the path.
  369. // This is used for path pooling
  370. p.Claim(this);
  371. // Path couldn't be calculated of some reason.
  372. // More info in p.errorLog (debug string)
  373. if (p.error) {
  374. p.Release(this);
  375. return;
  376. }
  377. if (interpolatePathSwitches) {
  378. ConfigurePathSwitchInterpolation();
  379. }
  380. // Replace the old path
  381. var oldPath = path;
  382. path = p;
  383. reachedEndOfPath = false;
  384. // Just for the rest of the code to work, if there
  385. // is only one waypoint in the path add another one
  386. if (path.vectorPath != null && path.vectorPath.Count == 1) {
  387. path.vectorPath.Insert(0, GetFeetPosition());
  388. }
  389. // Reset some variables
  390. ConfigureNewPath();
  391. // Release the previous path
  392. // This is used for path pooling.
  393. // This is done after the interpolator has been configured in the ConfigureNewPath method
  394. // as this method would otherwise invalidate the interpolator
  395. // since the vectorPath list (which the interpolator uses) will be pooled.
  396. if (oldPath != null) oldPath.Release(this);
  397. if (interpolator.remainingDistance < 0.0001f && !reachedEndOfPath) {
  398. reachedEndOfPath = true;
  399. OnTargetReached();
  400. }
  401. }
  402. /// <summary>\copydoc Pathfinding::IAstarAI::SetPath</summary>
  403. public void SetPath (Path path) {
  404. if (path.PipelineState == PathState.Created) {
  405. // Path has not started calculation yet
  406. lastRepath = Time.time;
  407. canSearchAgain = false;
  408. seeker.CancelCurrentPathRequest();
  409. seeker.StartPath(path);
  410. } else if (path.PipelineState == PathState.Returned) {
  411. // Path has already been calculated
  412. // We might be calculating another path at the same time, and we don't want that path to override this one. So cancel it.
  413. if (seeker.GetCurrentPath() != path) seeker.CancelCurrentPathRequest();
  414. else throw new System.ArgumentException("If you calculate the path using seeker.StartPath then this script will pick up the calculated path anyway as it listens for all paths the Seeker finishes calculating. You should not call SetPath in that case.");
  415. OnPathComplete(path);
  416. } else {
  417. // Path calculation has been started, but it is not yet complete. Cannot really handle this.
  418. throw new System.ArgumentException("You must call the SetPath method with a path that either has been completely calculated or one whose path calculation has not been started at all. It looks like the path calculation for the path you tried to use has been started, but is not yet finished.");
  419. }
  420. }
  421. protected virtual void ConfigurePathSwitchInterpolation () {
  422. bool reachedEndOfPreviousPath = interpolator.valid && interpolator.remainingDistance < 0.0001f;
  423. if (interpolator.valid && !reachedEndOfPreviousPath) {
  424. previousMovementOrigin = interpolator.position;
  425. previousMovementDirection = interpolator.tangent.normalized * interpolator.remainingDistance;
  426. pathSwitchInterpolationTime = 0;
  427. } else {
  428. previousMovementOrigin = Vector3.zero;
  429. previousMovementDirection = Vector3.zero;
  430. pathSwitchInterpolationTime = float.PositiveInfinity;
  431. }
  432. }
  433. public virtual Vector3 GetFeetPosition () {
  434. return position;
  435. }
  436. /// <summary>Finds the closest point on the current path and configures the <see cref="interpolator"/></summary>
  437. protected virtual void ConfigureNewPath () {
  438. var hadValidPath = interpolator.valid;
  439. var prevTangent = hadValidPath ? interpolator.tangent : Vector3.zero;
  440. interpolator.SetPath(path.vectorPath);
  441. interpolator.MoveToClosestPoint(GetFeetPosition());
  442. if (interpolatePathSwitches && switchPathInterpolationSpeed > 0.01f && hadValidPath) {
  443. var correctionFactor = Mathf.Max(-Vector3.Dot(prevTangent.normalized, interpolator.tangent.normalized), 0);
  444. interpolator.distance -= speed*correctionFactor*(1f/switchPathInterpolationSpeed);
  445. }
  446. }
  447. protected virtual void Update () {
  448. if (shouldRecalculatePath) SearchPath();
  449. if (canMove) {
  450. Vector3 nextPosition;
  451. Quaternion nextRotation;
  452. MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation);
  453. FinalizeMovement(nextPosition, nextRotation);
  454. }
  455. }
  456. /// <summary>\copydoc Pathfinding::IAstarAI::MovementUpdate</summary>
  457. public void MovementUpdate (float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation) {
  458. if (updatePosition) simulatedPosition = tr.position;
  459. if (updateRotation) simulatedRotation = tr.rotation;
  460. Vector3 direction;
  461. nextPosition = CalculateNextPosition(out direction, isStopped ? 0f : deltaTime);
  462. if (enableRotation) nextRotation = SimulateRotationTowards(direction, deltaTime);
  463. else nextRotation = simulatedRotation;
  464. }
  465. /// <summary>\copydoc Pathfinding::IAstarAI::FinalizeMovement</summary>
  466. public void FinalizeMovement (Vector3 nextPosition, Quaternion nextRotation) {
  467. previousPosition2 = previousPosition1;
  468. previousPosition1 = simulatedPosition = nextPosition;
  469. simulatedRotation = nextRotation;
  470. if (updatePosition) tr.position = nextPosition;
  471. if (updateRotation) tr.rotation = nextRotation;
  472. }
  473. Quaternion SimulateRotationTowards (Vector3 direction, float deltaTime) {
  474. // Rotate unless we are really close to the target
  475. if (direction != Vector3.zero) {
  476. Quaternion targetRotation = Quaternion.LookRotation(direction, orientation == OrientationMode.YAxisForward ? Vector3.back : Vector3.up);
  477. // This causes the character to only rotate around the Z axis
  478. if (orientation == OrientationMode.YAxisForward) targetRotation *= Quaternion.Euler(90, 0, 0);
  479. return Quaternion.Slerp(simulatedRotation, targetRotation, deltaTime * rotationSpeed);
  480. }
  481. return simulatedRotation;
  482. }
  483. /// <summary>Calculate the AI's next position (one frame in the future).</summary>
  484. /// <param name="direction">The tangent of the segment the AI is currently traversing. Not normalized.</param>
  485. protected virtual Vector3 CalculateNextPosition (out Vector3 direction, float deltaTime) {
  486. if (!interpolator.valid) {
  487. direction = Vector3.zero;
  488. return simulatedPosition;
  489. }
  490. interpolator.distance += deltaTime * speed;
  491. if (interpolator.remainingDistance < 0.0001f && !reachedEndOfPath) {
  492. reachedEndOfPath = true;
  493. OnTargetReached();
  494. }
  495. direction = interpolator.tangent;
  496. pathSwitchInterpolationTime += deltaTime;
  497. var alpha = switchPathInterpolationSpeed * pathSwitchInterpolationTime;
  498. if (interpolatePathSwitches && alpha < 1f) {
  499. // Find the approximate position we would be at if we
  500. // would have continued to follow the previous path
  501. Vector3 positionAlongPreviousPath = previousMovementOrigin + Vector3.ClampMagnitude(previousMovementDirection, speed * pathSwitchInterpolationTime);
  502. // Interpolate between the position on the current path and the position
  503. // we would have had if we would have continued along the previous path.
  504. return Vector3.Lerp(positionAlongPreviousPath, interpolator.position, alpha);
  505. } else {
  506. return interpolator.position;
  507. }
  508. }
  509. protected override int OnUpgradeSerializedData (int version, bool unityThread) {
  510. #pragma warning disable 618
  511. if (unityThread && targetCompatibility != null) target = targetCompatibility;
  512. #pragma warning restore 618
  513. return 2;
  514. }
  515. }
  516. }