Advertisement
Guest User

Untitled

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