Waypoints are often used as a guide to make autonomously moving NPCs and enemies follow a path in a general way (but be able to respond with other directional behaviors, such as flee or seek, if friends/predators/prey are sensed nearby). The waypoints are arranged in a sequence, so that when the character reaches, or gets close to a waypoint, it will then select the next waypoint in the sequence as the target location to move towards. This recipe demonstrates an arrow object moving towards a waypoint, and then, when it gets close enough, it will choose the next waypoint in the sequence as the new target destination. When the last waypoint has been reached, it again starts heading towards the first waypoint.
Since Unity's NavMeshAgent has simplified coding NPC behavior, our work in this recipe becomes basically finding the position of the next waypoint, and then telling the NavMeshAgent that this waypoint is its new destination.
This recipe builds upon the player-controlled 3D cube Unity project that you created at the beginning of this chapter. So, make a copy of this project, open it, and then follow the steps for this recipe.
For this recipe, we have prepared the yellow brick texture image that you need in a folder named Textures
in the 1362_08_06
folder.
To instruct an object to follow a sequence of waypoints, follow these steps:
2
.ArrowNPCMovement
to the Sphere-arrow GameObject:using UnityEngine; using System.Collections; public class ArrowNPCMovement : MonoBehaviour { private GameObject targetGO = null; private WaypointManager waypointManager; private NavMeshAgent navMeshAgent; void Start (){ navMeshAgent = GetComponent<NavMeshAgent>(); waypointManager = GetComponent<WaypointManager>(); HeadForNextWayPoint(); } void Update (){ float closeToDestinaton = navMeshAgent.stoppingDistance * 2; if (navMeshAgent.remainingDistance < closeToDestinaton){ HeadForNextWayPoint (); } } private void HeadForNextWayPoint (){ targetGO = waypointManager.NextWaypoint (targetGO); navMeshAgent.SetDestination (targetGO.transform.position); } }
WaypointManager
to the Sphere-arrow GameObject:using UnityEngine; public class WaypointManager : MonoBehaviour { public GameObject wayPoint0; public GameObject wayPoint3; public GameObject NextWaypoint(GameObject current){ if(current == wayPoint0) return wayPoint3; else return wayPoint0; } }
WaypointManager
scripted component. Drag Capsule-waypoint-0 and Capsule-waypoint-3 over the public variable projectile called Way Point 0 and Way Point 3, respectively.The NavMeshAgent component that we added to the Sphere-arrow GameObject does most of the work for us. NavMeshAgents need two things: a destination location to head towards, and a NavMesh, so that it can plan a path, avoiding obstacles.
We created two possible waypoints to be the location for our NPC to move towards: Capsule-waypoint-0 and Capsule-waypoint-3.
The C# script class called WaypointManager
has one job — to return a reference to the next waypoint that our NPC should head towards. There are two variables: wayPoint0
and wayPoint3
that reference to the two waypoint GameObjects in our scene. The NextWaypoint(…)
method takes a single parameter named current
, which is a reference to the current waypoint that the object was moving towards (or null). This method's task is to return a reference to the next waypoint that the NPC should travel towards. The logic for this method is simple—if current
refers to waypoint0
, then we'll return waypoint3
, otherwise we'll return waypoint0
. Note that if we pass this null
method, then we'll get waypoint0
back (so, it is our default first waypoint).
The C# script class called ArrowNPCMovement
has three variables: one is a reference to the destination GameObject named targetGO
. The second is a reference to the NavMeshAgent
component of the GameObject in which our instance of the class called ArrowNPCMovement
is also a component. The third variable called WaypointManager
is a reference to the sibling scripted component, an instance of our WaypointManager
script class.
When the scene starts, via the Start()
method, the NavMeshAgent and WaypointManager
sibling components are found, and the HeadForDestination()
method is called.
The HeadForDestination()
method first sets the variable called targetGO
to refer to the GameObject that is returned by a call to NextWaypoint(…)
of the scripted component called WaypointManager
(that is, targetGO
is set to refer to either Capsule-waypoint-0 or Capsule-waypoint-3). Next, it instructs the NavMeshAgent
to make its destination the position of the targetGO
GameObject.
Each frame method called Update()
is called. A test is made to see if the distance from the NPC arrow object is close to the destination waypoint. If the distance is smaller than twice the stopping distance, set in our NavMeshAgent
, then a call is made to WaypointManager
.NextWaypoint(…)
to update our target destination to be the next waypoint in the sequence.
There are some details that you don't want to miss.
NavMeshes are far superior to waypoints, since a location in a general area (not a specific point) can be used, and the path finding the algorithm will automatically find the shortest route. For a succinct recipe (such as the above), we can simplify the implementation of waypoints using NavMeshes for calculating movements for us. However, for optimized, real-world games the most common way to move from one waypoint to the next is via linear interpolation, or by implementing Craig Reynold's Seek algorithm (for details follow the link listed in the Conclusion section, at the end of this chapter).
Having a separate C# script class called WaypointManager
to simply swap between Capsule-waypoint-0 and Capsule-waypoint-3 may have seemed to be a heavy duty and over-engineering task, but this was actually a very good move. An object of the script class called WaypointManager
has the job of returning the next waypoint. It is now very straightforward to add a more sophisticated approach of having an array of waypoints, without us having to change any code in the script class called ArrowNPCMovement
. We can choose a random waypoint to be the next destination (see the Choosing destinations – find nearest (or a random) spawnpoint recipe). Or, we can have an array of waypoints, and choose the next one in the sequence.
To improve our game to work with an array of waypoints in the sequence to be followed, we need to do the following:
WaypointManager
with the following code:using UnityEngine; using System.Collections; using System; public class WaypointManager : MonoBehaviour { public GameObject[] waypoints; public GameObject NextWaypoint (GameObject current) { if( waypoints.Length < 1) Debug.LogError ("WaypointManager:: ERROR - no waypoints have been added to array!"); int currentIndex = Array.IndexOf(waypoints, current); int nextIndex = ((currentIndex + 1) % waypoints.Length); return waypoints[nextIndex]; } }
WaypointManager
scripted component set the size of the Waypoints
array to 6
. Now, drag in all the six capsule waypoint objects called as Capsule-waypoint
-0
/1
/2
/3
/4
/5
.In the NextWaypoint(…)
method, first we check in case the array is empty, in which case an error is logged. Next, the array index for the current waypoint GameObject is found (if present in the array). Finally, the array index for the next waypoint is calculated using a modulus operator to support a cyclic sequence, returning to the beginning of the array after the last element has been visited.
Rather than forcing a GameObject to follow a single rigid sequence of locations, we can make things more flexible by defining a WayPoint
class, whereby each waypoint GameObject has an array of possible destinations, and each of these has its own array and so on. In this way a di-graph (directed graph) can be implemented, of which a linear sequence is just one possible instance.
To improve our game to work with a di-graph of waypoints, do the following:
WayPointManager
component from the Sphere-arrow GameObject.ArrowNPCMovement
with the following code:using UnityEngine; using System.Collections; public class ArrowNPCMovement : MonoBehaviour { public Waypoint waypoint; private bool firstWayPoint = true; private NavMeshAgent navMeshAgent; void Start (){ navMeshAgent = GetComponent<NavMeshAgent>(); HeadForNextWayPoint(); } void Update () { float closeToDestinaton = navMeshAgent.stoppingDistance * 2; if (navMeshAgent.remainingDistance < closeToDestinaton){ HeadForNextWayPoint (); } } private void HeadForNextWayPoint (){ if(firstWayPoint) firstWayPoint = false; else waypoint = waypoint.GetNextWaypoint(); Vector3 target = waypoint.transform.position; navMeshAgent.SetDestination (target); } }
WayPoint
with the following code:using UnityEngine; using System.Collections; public class Waypoint: MonoBehaviour { public Waypoint[] waypoints; public Waypoint GetNextWaypoint () { return waypoints[ Random.Range(0, waypoints.Length) ]; } }
WayPoint
.WayPoint
.ArrowNPCMovement
scripted component drag Capsule-waypoint-0 into the Waypoint public variable slot.Waypoints
array size to 1
, and drag in Capsule-waypoint-1. Next, select Capsule-waypoint-1, set its Waypoints array size to 1, and drag in Capsule-waypoint-2. Do the following until you finally link Capsule-waypoint-5 back to Capsule-waypoint-0.You now have a much more flexible game architecture, allowing GameObjects to randomly select one of several different paths at each waypoint reached. In this final recipe variation, we have implemented a waypoint sequence, since each waypoint has an array of just one linked waypoint. However, if you change the array size to 2 or more, you will then be creating a graph of linked waypoints, adding random variations in the sequence of waypoints that a computer controlled character follows for any given run of your game.
3.141.30.210