Guest User

AIPath.cs for development of A* Pathfinding Project 3.2

a guest
Oct 25th, 2012
160
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 15.13 KB | None | 0 0
  1. //#define DEBUG
  2. using UnityEngine;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using Pathfinding;
  6.  
  7. /** AI for following paths.
  8.  * This AI is the default movement script which comes with the A* Pathfinding Project.
  9.  * 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
  10.  * 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.
  11.  * \n
  12.  * \n
  13.  * This script will try to follow a target transform, in regular intervals, the path to that target will be recalculated.
  14.  * It will on FixedUpdate try to move towards the next point in the path.
  15.  * However it will only move in the forward direction, but it will rotate around it's Y-axis
  16.  * to make it reach the target.
  17.  *
  18.  * \section variables Quick overview of the variables
  19.  * 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.\n
  20.  * The #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.\n
  21.  * The #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.
  22.  * Or it can be the player object in a zombie game.\n
  23.  * The speed is self-explanatory, so is turningSpeed, however #slowdownDistance might require some explanation.
  24.  * 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
  25.  * but also any intermediate points, so be sure to set #forwardLook and #pickNextWaypointDist to a higher value than this.\n
  26.  * #pickNextWaypointDist is simply determines within what range it will switch to target the next waypoint in the path.\n
  27.  * #forwardLook will try to calculate an interpolated target point on the current segment in the path so that it has a distance of #forwardLook from the AI\n
  28.  * Below is an image illustrating several variables as well as some internal ones, but which are relevant for understanding how it works.
  29.  * Note that the #forwardLook range will not match up exactly with the target point practically, even though that's the goal.
  30.  * \shadowimage{aipath_variables.png}
  31.  * This script has many movement fallbacks.
  32.  * 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
  33.  * it will use Transform.Translate which is guaranteed to always work.
  34.  */
  35. [RequireComponent(typeof(Seeker))]
  36. public class AIPath : MonoBehaviour {
  37.    
  38.     /** Determines how often it will search for new paths.
  39.      * If you have fast moving targets or AIs, you might want to set it to a lower value.
  40.      * The value is in seconds between path requests.
  41.      */
  42.     public float repathRate = 0.5F;
  43.    
  44.     /** Target to move towards.
  45.      * The AI will try to follow/move towards this target.
  46.      * 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.
  47.      */
  48.     public Transform target;
  49.    
  50.     /** Enables or disables searching for paths.
  51.      * Setting this to false does not stop any active path requests from being calculated or stop it from continuing to follow the current path.
  52.      * \see #canMove
  53.      */
  54.     public bool canSearch = true;
  55.    
  56.     /** Enables or disables movement.
  57.       * \see #canSearch */
  58.     public bool canMove = true;
  59.    
  60.     /** Maximum velocity.
  61.      * This is the maximum speed in world units per second.
  62.      */
  63.     public float speed = 3;
  64.    
  65.     /** Rotation speed.
  66.      * Rotation is calculated using Quaternion.SLerp. This variable represents the damping, the higher, the faster it will be able to rotate.
  67.      */
  68.     public float turningSpeed = 5;
  69.    
  70.     /** Distance from the target point where the AI will start to slow down.
  71.      * Note that this doesn't only affect the end point of the path
  72.      * but also any intermediate points, so be sure to set #forwardLook and #pickNextWaypointDist to a higher value than this
  73.      */
  74.     public float slowdownDistance = 0.6F;
  75.    
  76.     /** Determines within what range it will switch to target the next waypoint in the path */
  77.     public float pickNextWaypointDist = 2;
  78.    
  79.     /** Target point is Interpolated on the current segment in the path so that it has a distance of #forwardLook from the AI.
  80.       * See the detailed description of AIPath for an illustrative image */
  81.     public float forwardLook = 1;
  82.    
  83.     /** Distance to the end point to consider the end of path to be reached.
  84.      * When this has been reached, the AI will not move anymore until the target changes and OnTargetReached will be called.
  85.      */
  86.     public float endReachedDistance = 0.2F;
  87.    
  88.     /** Do a closest point on path check when receiving path callback.
  89.      * 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
  90.      * and the closest node.
  91.      * If this option is enabled, it will simulate, when the path callback is received, movement between the closest node and the current
  92.      * 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
  93.      * even though it really should just proceed forward.
  94.      */
  95.     public bool closestOnPathCheck = true;
  96.    
  97.     public bool recyclePaths = true;
  98.    
  99.     protected float minMoveScale = 0.05F;
  100.    
  101.     /** Cached Seeker component */
  102.     protected Seeker seeker;
  103.    
  104.     /** Cached Transform component */
  105.     protected Transform tr;
  106.    
  107.     /** Time when the last path request was sent */
  108.     private float lastRepath = -9999;
  109.    
  110.     /** Current path which is followed */
  111.     protected Path path;
  112.    
  113.     /** Cached CharacterController component */
  114.     protected CharacterController controller;
  115.    
  116.     /** Cached NavmeshController component */
  117.     protected NavmeshController navController;
  118.    
  119.     /** Cached Rigidbody component */
  120.     protected Rigidbody rigid;
  121.    
  122.     /** Current index in the path which is current target */
  123.     protected int currentWaypointIndex = 0;
  124.    
  125.     /** Holds if the end-of-path is reached
  126.      * \see TargetReached */
  127.     protected bool targetReached = false;
  128.    
  129.     /** Only when the previous path has been returned should be search for a new path */
  130.     protected bool canSearchAgain = true;
  131.    
  132.     /** Returns if the end-of-path has been reached
  133.      * \see targetReached */
  134.     public bool TargetReached {
  135.         get {
  136.             return targetReached;
  137.         }
  138.     }
  139.    
  140.     // Use this for initialization
  141.     void Awake () {
  142.         seeker = GetComponent<Seeker>();
  143.        
  144.         //This is a simple optimization, cache the transform component lookup
  145.         tr = transform;
  146.        
  147.         //Make sure we receive callbacks when paths complete
  148.         seeker.pathCallback += OnPathComplete;
  149.        
  150.         //Cache some other components (not all are necessarily there)
  151.         controller = GetComponent<CharacterController>();
  152.         navController = GetComponent<NavmeshController>();
  153.         rigid = rigidbody;
  154.     }
  155.    
  156.     public void Start () {
  157.         StartCoroutine (RepeatTrySearchPath ());
  158.     }
  159.        
  160.     public IEnumerator RepeatTrySearchPath () {
  161.         while (true) {
  162.             TrySearchPath ();
  163.             yield return new WaitForSeconds (repathRate);
  164.         }
  165.     }
  166.        
  167.     public void TrySearchPath () {
  168.         if (Time.time - lastRepath >= repathRate && canSearchAgain && canSearch) {
  169.             SearchPath ();
  170.         } else {
  171.             StartCoroutine (WaitForRepath ());
  172.         }
  173.     }
  174.    
  175.     private bool waitingForRepath = false;
  176.     protected IEnumerator WaitForRepath () {
  177.         if (waitingForRepath) yield break; //A coroutine is already running
  178.        
  179.         waitingForRepath = true;
  180.         //Wait until it is predicted that the AI should search for a path again
  181.         yield return new WaitForSeconds (repathRate - (Time.time-lastRepath));
  182.        
  183.         waitingForRepath = false;
  184.         //Try to search for a path again
  185.         TrySearchPath ();
  186.     }
  187.    
  188.     public void SearchPath () {
  189.        
  190.         if (target == null) { Debug.LogError ("Target is null, aborting all search"); return; }
  191.        
  192.         lastRepath = Time.time;
  193.         //This is where we should search to
  194.         Vector3 targetPoint = target.position;
  195.        
  196.         canSearchAgain = false;
  197.        
  198.         //Alternative way of requesting the path
  199.         //Path p = PathPool<Path>.GetPath().Setup(GetFeetPosition(),targetPoint,null);
  200.         //seeker.StartPath (p);
  201.        
  202.         //We should search from the current position
  203.         seeker.StartPath (GetFeetPosition(), targetPoint);
  204.     }
  205.    
  206.     public virtual void OnTargetReached () {
  207.         //End of path has been reached
  208.         //If you want custom logic for when the AI has reached it's destination
  209.         //add it here
  210.         //You can also create a new script which inherits from this one
  211.         //and override the function in that script
  212.     }
  213.    
  214.     public void OnDestroy () {
  215.         if (path != null) path.Release (this);
  216.     }
  217.    
  218.     /** Called when a requested path has finished calculation.
  219.       * A path is first requested by #SearchPath(), it is then calculated, probably in the same or the next frame.
  220.       * Finally it is returned to the seeker which forwards it to this function.\n
  221.       */
  222.     public void OnPathComplete (Path _p) {
  223.         ABPath p = _p as ABPath;
  224.         if (p == null) throw new System.Exception ("This function only handles ABPaths, do not use special path types");
  225.        
  226.         //Release the previous path
  227.         if (path != null) path.Release (this);
  228.        
  229.         //Claim the new path
  230.         p.Claim (this);
  231.        
  232.         //Replace the old path
  233.         path = p;
  234.        
  235.         //Reset some variables
  236.         currentWaypointIndex = 0;
  237.         targetReached = false;
  238.         canSearchAgain = true;
  239.        
  240.         //The next row can be used to find out if the path could be found or not
  241.         //If it couldn't (error == true), then a message has probably been logged to the console
  242.         //however it can also be got using p.errorLog
  243.         //if (p.error)
  244.        
  245.         if (closestOnPathCheck) {
  246.             Vector3 p1 = p.startPoint;
  247.             Vector3 p2 = GetFeetPosition ();
  248.             float magn = Vector3.Distance (p1,p2);
  249.             Vector3 dir = p2-p1;
  250.             dir /= magn;
  251.             int steps = (int)(magn/pickNextWaypointDist);
  252.             for (int i=0;i<steps;i++) {
  253.                 CalculateVelocity (p1);
  254.                 p1 += dir;
  255.             }
  256. #if DEBUG
  257.             Debug.DrawLine (p1,p2,Color.red,1);
  258. #endif
  259.         }
  260.     }
  261.    
  262.     public virtual Vector3 GetFeetPosition () {
  263.         if (controller != null)
  264.             return tr.position - Vector3.up*controller.height*0.5F;
  265.        
  266.         return tr.position;
  267.     }
  268.    
  269.     public void Update () {
  270.        
  271.         if (!canMove) { return; }
  272.        
  273.         Vector3 dir = CalculateVelocity (GetFeetPosition());
  274.        
  275.         //Rotate towards targetDirection (filled in by CalculateVelocity)
  276.         if (targetDirection != Vector3.zero) {
  277.             RotateTowards (targetDirection);
  278.         }
  279.        
  280.         if (navController != null) {
  281.             navController.SimpleMove (GetFeetPosition(),dir);
  282.         } else if (controller != null) {
  283.             controller.SimpleMove (dir);
  284.         } else if (rigid != null) {
  285.             rigid.AddForce (dir);
  286.         } else {
  287.             transform.Translate (dir*Time.deltaTime);
  288.         }
  289.     }
  290.    
  291.     /** Point to where the AI is heading.
  292.       * Filled in by #CalculateVelocity */
  293.     protected Vector3 targetPoint;
  294.     /** Relative direction to where the AI is heading.
  295.      * Filled in by #CalculateVelocity */
  296.     protected Vector3 targetDirection;
  297.    
  298.     protected float XZSqrMagnitude (Vector3 a, Vector3 b) {
  299.         float dx = b.x-a.x;
  300.         float dz = b.z-a.z;
  301.         return dx*dx + dz*dz;
  302.     }
  303.    
  304.     /** Calculates desired velocity.
  305.      * Finds the target path segment and returns the forward direction, scaled with speed.
  306.      * A whole bunch of restrictions on the velocity is applied to make sure it doesn't overshoot, does not look too far ahead,
  307.      * and slows down when close to the target.
  308.      * /see speed
  309.      * /see endReachedDistance
  310.      * /see slowdownDistance
  311.      * /see CalculateTargetPoint
  312.      * /see targetPoint
  313.      * /see targetDirection
  314.      * /see currentWaypointIndex
  315.      */
  316.     protected Vector3 CalculateVelocity (Vector3 currentPosition) {
  317.         if (path == null || path.vectorPath == null || path.vectorPath.Count == 0) return Vector3.zero;
  318.        
  319.         List<Vector3> vPath = path.vectorPath;
  320.         //Vector3 currentPosition = GetFeetPosition();
  321.        
  322.         if (vPath.Count == 1) {
  323.             vPath.Insert (0,currentPosition);
  324.         }
  325.        
  326.         if (currentWaypointIndex >= vPath.Count) { currentWaypointIndex = vPath.Count-1; }
  327.        
  328.         if (currentWaypointIndex <= 1) currentWaypointIndex = 1;
  329.        
  330.         while (true) {
  331.             if (currentWaypointIndex < vPath.Count-1) {
  332.                 //There is a "next path segment"
  333.                 float dist = XZSqrMagnitude (vPath[currentWaypointIndex], currentPosition);
  334.                     //Mathfx.DistancePointSegmentStrict (vPath[currentWaypointIndex+1],vPath[currentWaypointIndex+2],currentPosition);
  335.                 if (dist < pickNextWaypointDist*pickNextWaypointDist) {
  336.                     currentWaypointIndex++;
  337.                 } else {
  338.                     break;
  339.                 }
  340.             } else {
  341.                 break;
  342.             }
  343.         }
  344.        
  345.         Vector3 dir = vPath[currentWaypointIndex] - vPath[currentWaypointIndex-1];
  346.         Vector3 targetPoint = CalculateTargetPoint (currentPosition,vPath[currentWaypointIndex-1] , vPath[currentWaypointIndex]);
  347.             //vPath[currentWaypointIndex] + Vector3.ClampMagnitude (dir,forwardLook);
  348.        
  349.        
  350.        
  351.         dir = targetPoint-currentPosition;
  352.         dir.y = 0;
  353.         float targetDist = dir.magnitude;
  354.        
  355.         float slowdown = Mathf.Clamp01 (targetDist / slowdownDistance);
  356.        
  357.         this.targetDirection = dir;
  358.         this.targetPoint = targetPoint;
  359.        
  360.         if (currentWaypointIndex == vPath.Count-1 && targetDist <= endReachedDistance) {
  361.             if (!targetReached) { targetReached = true; OnTargetReached (); }
  362.            
  363.             //Send a move request, this ensures gravity is applied
  364.             return Vector3.zero;
  365.         }
  366.        
  367.         Vector3 forward = tr.forward;
  368.         float dot = Vector3.Dot (dir.normalized,forward);
  369.         float sp = speed * Mathf.Max (dot,minMoveScale) * slowdown;
  370.        
  371. #if DEBUG
  372.         Debug.DrawLine (vPath[currentWaypointIndex-1] , vPath[currentWaypointIndex],Color.black);
  373.         Debug.DrawLine (GetFeetPosition(),targetPoint,Color.red);
  374.         Debug.DrawRay (targetPoint,Vector3.up, Color.red);
  375.         Debug.DrawRay (GetFeetPosition(),dir,Color.yellow);
  376.         Debug.DrawRay (GetFeetPosition(),forward*sp,Color.cyan);
  377. #endif
  378.        
  379.         if (Time.deltaTime  > 0) {
  380.             sp = Mathf.Clamp (sp,0,targetDist/(Time.deltaTime*2));
  381.         }
  382.         return forward*sp;
  383.     }
  384.    
  385.     /** Rotates in the specified direction.
  386.      * Rotates around the Y-axis.
  387.      * \see turningSpeed
  388.      */
  389.     protected virtual void RotateTowards (Vector3 dir) {
  390.         Quaternion rot = tr.rotation;
  391.         Quaternion target = Quaternion.LookRotation (dir);
  392.        
  393.         rot = Quaternion.Slerp (rot,target,turningSpeed*Time.fixedDeltaTime);
  394.         Vector3 euler = rot.eulerAngles;
  395.         euler.z = 0;
  396.         euler.x = 0;
  397.         rot = Quaternion.Euler (euler);
  398.        
  399.         tr.rotation = rot;
  400.     }
  401.    
  402.     /** Calculates target point from the current line segment.
  403.      * \param p Current position
  404.      * \param a Line segment start
  405.      * \param b Line segment end
  406.      * The returned point will lie somewhere on the line segment.
  407.      * \see #forwardLook
  408.      * \todo This function uses .magnitude quite a lot, can it be optimized?
  409.      */
  410.     protected Vector3 CalculateTargetPoint (Vector3 p, Vector3 a, Vector3 b) {
  411.         a.y = p.y;
  412.         b.y = p.y;
  413.        
  414.         float magn = (a-b).magnitude;
  415.         if (magn == 0) return a;
  416.        
  417.         float closest = Mathfx.Clamp01 (Mathfx.NearestPointFactor (a, b, p));
  418.         Vector3 point = (b-a)*closest + a;
  419.         float distance = (point-p).magnitude;
  420.        
  421.         float lookAhead = Mathf.Clamp (forwardLook - distance, 0.0F, forwardLook);
  422.        
  423.         float offset = lookAhead / magn;
  424.         offset = Mathf.Clamp (offset+closest,0.0F,1.0F);
  425.         return (b-a)*offset + a;
  426.     }
  427. }
Advertisement
Add Comment
Please, Sign In to add comment