View difference between Paste ID: 5iRzeJix and enHVbev4
SHOW: | | - or go back to the newest paste.
1
//#define ASTARDEBUG
2
using UnityEngine;
3
using System.Collections;
4
using System.Collections.Generic;
5
using Pathfinding;
6
using Pathfinding.RVO;
7
8-
/** AI for 2D top down games.
8+
/** Base class for AIs.
9-
 * This is one of the default movement scripts which comes with the A* Pathfinding Project.
9+
10-
 * 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+
 * \see AIPath
11-
 * to set up movement for the characters in your game. 
11+
 * \see AI2DTopDown
12-
 * \n
12+
13-
 * This script will try to follow a target transform, and at regular intervals the path to that target will be recalculated.
13+
14
public abstract class AIPathBase : MonoBehaviour {
15-
 * \section variables Quick overview of the variables
15+
16-
 * 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
16+
	/** Determines how often it will search for new paths. 
17-
 * 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
17+
	 * If you have fast moving targets or AIs, you might want to set it to a lower value.
18-
 * 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.
18+
	 * The value is in seconds between path requests.
19-
 * Or it can be the player object in a zombie game.\n
19+
20-
 * The speed is self-explanatory, so is turningSpeed, however #slowdownDistance might require some explanation.
20+
	public float repathRate = 0.5F;
21-
 * 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
21+
22-
 * but also any intermediate points, so be sure to set #forwardLook and #pickNextWaypointDist to a higher value than this.\n
22+
	/** Target to move towards.
23-
 * #pickNextWaypointDist is simply determines within what range it will switch to target the next waypoint in the path.\n
23+
	 * The AI will try to follow/move towards this target.
24-
 * #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
24+
	 * 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.
25-
 * Below is an image illustrating several variables as well as some internal ones, but which are relevant for understanding how it works.
25+
26-
 * Note that the #forwardLook range will not match up exactly with the target point practically, even though that's the goal.
26+
	public Transform target;
27-
 * \shadowimage{aipath_variables.png}
27+
28-
 * This script can use a Rigidbody2D for movement or it can simply use transform.position.
28+
	/** Enables or disables searching for paths.
29-
 * If it finds a Rigidbody2D attached to the same GameObject, it will start to use it automatically.
29+
	 * Setting this to false does not stop any active path requests from being calculated or stop it from continuing to follow the current path.
30
	 * \see #canMove
31-
 * \ingroup movementscripts
31+
32
	public bool canSearch = true;
33
	
34-
[AddComponentMenu("Pathfinding/AI/AIPath (2D top down)")]
34+
	/** Enables or disables movement.
35-
public class AIPath2DTopDown : AIPathBase {
35+
	  * \see #canSearch */
36
	public bool canMove = true;
37-
	/** Cached Rigidbody component */
37+
38-
	protected Rigidbody2D rigid;
38+
	/** Maximum velocity.
39
	 * This is the maximum speed in world units per second.
40
	 */
41
	public float speed = 3;
42
	
43-
	protected override void Awake () {
43+
	/** Rotation speed.
44-
		base.Awake ();
44+
	 * Rotation is calculated using Quaternion.SLerp. This variable represents the damping, the higher, the faster it will be able to rotate.
45
	 */
46-
		//Cache some other components (not all are necessarily there)
46+
	public float turningSpeed = 5;
47-
		rigid = GetComponent<Rigidbody2D>();
47+
48
	/** Distance from the target point where the AI will start to slow down.
49
	 * Note that this doesn't only affect the end point of the path
50-
	public override Vector3 GetFeetPosition () {
50+
 	 * but also any intermediate points, so be sure to set #forwardLook and #pickNextWaypointDist to a higher value than this
51
 	 */
52
	public float slowdownDistance = 0.6F;
53
	
54-
	public virtual void Update () {
54+
	/** Determines within what range it will switch to target the next waypoint in the path */
55
	public float pickNextWaypointDist = 2;
56-
		if (!canMove) { return; }
56+
57
	/** Target point is Interpolated on the current segment in the path so that it has a distance of #forwardLook from the AI.
58-
		Vector2 dir = CalculateVelocity (GetFeetPosition());
58+
	  * See the detailed description of AIPath for an illustrative image */
59
	public float forwardLook = 1;
60-
		//Rotate towards targetDirection (filled in by CalculateVelocity)
60+
61-
		RotateTowards (targetDirection);
61+
	/** Distance to the end point to consider the end of path to be reached.
62
	 * When this has been reached, the AI will not move anymore until the target changes and OnTargetReached will be called.
63-
		if (rigid != null) {
63+
64-
			//rigid.AddForce (dir, ForceMode2D.Impulse);
64+
	public float endReachedDistance = 0.2F;
65-
			rigid.MovePosition (rigid.position + dir*Time.deltaTime);
65+
66
	/** Do a closest point on path check when receiving path callback.
67-
			transform.Translate (dir*Time.deltaTime, Space.World);
67+
	 * 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
68
	 * and the closest node.
69
	 * If this option is enabled, it will simulate, when the path callback is received, movement between the closest node and the current
70
	 * 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
71-
	/** Point to where the AI is heading.
71+
	 * even though it really should just proceed forward.
72-
	  * Filled in by #CalculateVelocity */
72+
73-
	protected Vector2 targetPoint;
73+
	public bool closestOnPathCheck = true;
74-
	/** Relative direction to where the AI is heading.
74+
75-
	 * Filled in by #CalculateVelocity */
75+
	protected float minMoveScale = 0.05F;
76-
	protected Vector2 targetDirection;
76+
77
	/** Cached Seeker component */
78-
	/** Calculates desired velocity.
78+
	protected Seeker seeker;
79-
	 * Finds the target path segment and returns the forward direction, scaled with speed.
79+
80-
	 * A whole bunch of restrictions on the velocity is applied to make sure it doesn't overshoot, does not look too far ahead,
80+
	/** Cached Transform component */
81-
	 * and slows down when close to the target.
81+
	protected Transform tr;
82-
	 * /see speed
82+
83-
	 * /see endReachedDistance
83+
	/** Time when the last path request was sent */
84-
	 * /see slowdownDistance
84+
	protected float lastRepath = -9999;
85-
	 * /see CalculateTargetPoint
85+
86-
	 * /see targetPoint
86+
	/** Current path which is followed */
87-
	 * /see targetDirection
87+
	protected Path path;
88-
	 * /see currentWaypointIndex
88+
89
	/** Current index in the path which is current target */
90-
	protected Vector2 CalculateVelocity (Vector2 currentPosition) {
90+
	protected int currentWaypointIndex = 0;
91-
		if (path == null || path.vectorPath == null || path.vectorPath.Count == 0) return Vector3.zero; 
91+
92
	/** Holds if the end-of-path is reached
93-
		List<Vector3> vPath = path.vectorPath;
93+
	 * \see TargetReached */
94
	protected bool targetReached = false;
95-
		// Just for the rest of the code to work, if there is only one waypoint in the path
95+
96-
		// add another one
96+
	/** Only when the previous path has been returned should be search for a new path */
97-
		if (vPath.Count == 1) {
97+
	protected bool canSearchAgain = true;
98-
			vPath.Insert (0,currentPosition);
98+
99
	protected Vector3 lastFoundWaypointPosition;
100
	protected float lastFoundWaypointTime = -9999;
101-
		// Make sure we stay inside valid ranges
101+
102-
		currentWaypointIndex = Mathf.Clamp (currentWaypointIndex, 1, vPath.Count - 1);
102+
	/** Returns if the end-of-path has been reached
103
	 * \see targetReached */
104-
		// Possibly pick the next segment
104+
	public bool TargetReached {
105
		get {
106-
			if (currentWaypointIndex < vPath.Count-1) {
106+
			return targetReached;
107-
				//There is a "next path segment"
107+
108-
				Vector2 nextWaypointDir = (Vector2)vPath[currentWaypointIndex] - currentPosition;
108+
109-
				float dist = nextWaypointDir.sqrMagnitude;
109+
110-
					//Mathfx.DistancePointSegmentStrict (vPath[currentWaypointIndex+1],vPath[currentWaypointIndex+2],currentPosition);
110+
	/** Holds if the Start function has been run.
111-
				if (dist < pickNextWaypointDist*pickNextWaypointDist) {
111+
	 * Used to test if coroutines should be started in OnEnable to prevent calculating paths
112-
					lastFoundWaypointPosition = currentPosition;
112+
	 * in the awake stage (or rather before start on frame 0).
113-
					lastFoundWaypointTime = Time.time;
113+
114-
					currentWaypointIndex++;
114+
	private bool startHasRun = false;
115-
				} else {
115+
116-
					break;
116+
117-
				}
117+
118-
			} else {
118+
119-
				break;
119+
	protected virtual void Awake () {
120-
			}
120+
		seeker = GetComponent<Seeker>();
121
		
122
		//This is a simple optimization, cache the transform component lookup
123-
		// Calculate the point we should move towards
123+
		tr = transform;
124-
		Vector2 targetPosition = CalculateTargetPoint (currentPosition,vPath[currentWaypointIndex-1] , vPath[currentWaypointIndex]);
124+
125
	
126-
		// Vector to the target position
126+
	/** Starts searching for paths.
127-
		Vector2 dir = targetPosition-currentPosition;
127+
	 * If you override this function you should in most cases call base.Start () at the start of it.
128-
		float targetDist = dir.magnitude;
128+
	 * \see OnEnable
129
	 * \see RepeatTrySearchPath
130-
		float slowdown = Mathf.Clamp01 (targetDist / slowdownDistance);
130+
131
	protected virtual void Start () {
132-
		this.targetDirection = dir;
132+
		startHasRun = true;
133-
		this.targetPoint = targetPosition;
133+
		OnEnable ();
134
	}
135-
		if (currentWaypointIndex == vPath.Count-1 && targetDist <= endReachedDistance) {
135+
136-
			if (!targetReached) { targetReached = true; OnTargetReached (); }
136+
	/** Run at start and when reenabled.
137
	 * Starts RepeatTrySearchPath.
138-
			//Send a move request, this ensures gravity is applied
138+
	 * 
139-
			return Vector3.zero;
139+
	 * \see Start
140
	 */
141
	protected virtual void OnEnable () {
142-
		// The Y axis is forward in 2D space
142+
143-
		Vector2 forward = -tr.up;
143+
		lastRepath = -9999;
144-
		float dot = Vector2.Dot (dir.normalized,forward);
144+
		canSearchAgain = true;
145-
		float sp = speed * Mathf.Max (dot,minMoveScale) * slowdown;
145+
146
		lastFoundWaypointPosition = GetFeetPosition ();
147-
#if ASTARDEBUG
147+
148-
		Debug.DrawLine (vPath[currentWaypointIndex-1] , vPath[currentWaypointIndex],Color.black);
148+
		if (startHasRun) {
149-
		Debug.DrawLine (GetFeetPosition(),targetPosition,Color.red);
149+
			//Make sure we receive callbacks when paths complete
150-
		Debug.DrawRay (targetPosition,Vector3.up, Color.red);
150+
			seeker.pathCallback += OnPathComplete;
151-
		Debug.DrawRay (GetFeetPosition(),dir,Color.yellow);
151+
152-
		Debug.DrawRay (GetFeetPosition(),forward*sp,Color.cyan);
152+
			StartCoroutine (RepeatTrySearchPath ());
153-
#endif
153+
154
	}
155-
		// Make sure we don't overshoot the target
155+
156-
		// when the framerate is low
156+
	public void OnDisable () {
157-
		if (Time.deltaTime	> 0) {
157+
		// Abort calculation of path
158-
			sp = Mathf.Clamp (sp,0,targetDist/(Time.deltaTime*2));
158+
		if (seeker != null && !seeker.IsDone()) seeker.GetCurrentPath().Error();
159
		
160-
		return forward*sp;
160+
		// Release current path
161
		if (path != null) path.Release (this);
162
		path = null;
163-
	/** Rotates in the specified direction.
163+
164-
	 * Rotates around the Y-axis.
164+
		//Make sure we receive callbacks when paths complete
165-
	 * \see turningSpeed
165+
		seeker.pathCallback -= OnPathComplete;
166
	}
167-
	protected void RotateTowards (Vector2 dir) {
167+
168
	/** Tries to search for a path every #repathRate seconds.
169-
		if (dir == Vector2.zero) return;
169+
	  * \see TrySearchPath
170
	  */
171-
		// Figure out the angle so that the Y axis faces dir
171+
	protected IEnumerator RepeatTrySearchPath () {
172-
		float angle = Mathf.Atan2 (dir.x, -dir.y)*Mathf.Rad2Deg;
172+
173
			float v = TrySearchPath ();
174-
		Vector3 rot = tr.eulerAngles;
174+
			yield return new WaitForSeconds (v);
175-
		rot.z = Mathf.LerpAngle (rot.z, angle, turningSpeed * Time.deltaTime);
175+
176-
		tr.eulerAngles = rot;
176+
177
	
178
	/** Tries to search for a path.
179-
	/** Calculates target point from the current line segment.
179+
	 * Will search for a new path if there was a sufficient time since the last repath and both
180-
	 * \param p Current position
180+
	 * #canSearchAgain and #canSearch are true and there is a target.
181-
	 * \param a Line segment start
181+
	 * 
182-
	 * \param b Line segment end
182+
	 * \returns The time to wait until calling this function again (based on #repathRate) 
183-
	 * The returned point will lie somewhere on the line segment.
183+
184-
	 * \see #forwardLook
184+
	public float TrySearchPath () {
185-
	 * \todo This function uses .magnitude quite a lot, can it be optimized?
185+
		if (Time.time - lastRepath >= repathRate && canSearchAgain && canSearch && target != null) {
186
			SearchPath ();
187-
	protected Vector2 CalculateTargetPoint (Vector2 p, Vector2 a, Vector2 b) {
187+
			return repathRate;
188
		} else {
189-
		float magn = (a-b).magnitude;
189+
			//StartCoroutine (WaitForRepath ());
190-
		if (magn == 0) return a;
190+
			float v = repathRate - (Time.time-lastRepath);
191
			return v < 0 ? 0 : v;
192-
		float closest = AstarMath.Clamp01 (AstarMath.NearestPointFactor (a, b, p));
192+
193-
		Vector2 point = (b-a)*closest + a;
193+
194-
		float distance = (point-p).magnitude;
194+
195
	/** Requests a path to the target */
196-
		float lookAhead = Mathf.Clamp (forwardLook - distance, 0.0F, forwardLook);
196+
	public virtual void SearchPath () {
197
		
198-
		float offset = lookAhead / magn;
198+
		if (target == null) throw new System.InvalidOperationException ("Target is null");
199-
		offset = Mathf.Clamp (offset+closest,0.0F,1.0F);
199+
200-
		return (b-a)*offset + a;
200+
		lastRepath = Time.time;
201
		//This is where we should search to
202
		Vector3 targetPosition = target.position;
203
		
204
		canSearchAgain = false;
205
		
206
		//Alternative way of requesting the path
207
		//ABPath p = ABPath.Construct (GetFeetPosition(),targetPoint,null);
208
		//seeker.StartPath (p);
209
		
210
		//We should search from the current position
211
		seeker.StartPath (GetFeetPosition(), targetPosition);
212
	}
213
	
214
	public virtual void OnTargetReached () {
215
		//End of path has been reached
216
		//If you want custom logic for when the AI has reached it's destination
217
		//add it here
218
		//You can also create a new script which inherits from this one
219
		//and override the function in that script
220
	}
221
	
222
	/** Called when a requested path has finished calculation.
223
	  * A path is first requested by #SearchPath, it is then calculated, probably in the same or the next frame.
224
	  * Finally it is returned to the seeker which forwards it to this function.\n
225
	  */
226
	public virtual void OnPathComplete (Path _p) {
227
		ABPath p = _p as ABPath;
228
		if (p == null) throw new System.Exception ("This function only handles ABPaths, do not use special path types");
229
		
230
		canSearchAgain = true;
231
		
232
		//Claim the new path
233
		p.Claim (this);
234
		
235
		// Path couldn't be calculated of some reason.
236
		// More info in p.errorLog (debug string)
237
		if (p.error) {
238
			p.Release (this);
239
			return;
240
		}
241
		
242
		//Release the previous path
243
		if (path != null) path.Release (this);
244
		
245
		//Replace the old path
246
		path = p;
247
		
248
		//Reset some variables
249
		currentWaypointIndex = 0;
250
		targetReached = false;
251
	}
252
	
253
	public virtual Vector3 GetFeetPosition () {
254
		return tr.position;
255
	}
256
}