Game AI Basics - Patrolling
Patrolling is the most commonly used movement type while designing enemy AI, it is simple and makes your enemies looks "smarter" than just standing still, also it is easy to implement.
A simple solution - static waypoints
Waypoints seems to be the most straight forward solution to come up with, the basic idea is to setup an array of waypoint game objects, and let the enemy character move from one to another.
The approach is simple (Use a 2D space in Unity as an example):
public class EnemyMovement : MonoBehaviour {
public GameObject[] waypoints;
public float moveSpeed = 5f;
private GameObject currentWaypoint;
private int wpIndex = 0; // index of the current waypoint in array
private void Start() {
wpIndex = 0;
currentWaypoint = waypoints[wpIndex]; // initialize current waypoint
}
private void Update() {
Patrol();
}
private void Patrol() {
if (WpReached()) {
UpdateWp(); // move to next waypoint if reached the current one
}
gameObject.transform.position = Vector2.MoveTowards(
gameObject.transform.position,
currentWaypoint.transform.position,
moveSpeed * Time.deltaTime
); // move towards the current waypoint
}
private bool WpReached(Vector2 position, Vector2 target, float allowance) {
return Vector2.Distance(position, target) <= allowance
}
private void UpdateWp(){
wpIndex++; // next waypoint
wpIndex = wpIndex % waypoints.Length; // cycling from 0 to waypoint length
currentWaypoint = waypoints[wpIndex];
}
}
Looks good, but seems like the enemy will always move between the given waypoints, so the player can predict the enemy's path after few plays. What if I want my enemy patrols randomly?
Improvement - Random waypoints
To make the enemy move randomly, we have to let enemy chose the waypoint by itself, the idea is to randomly choose a new x location when the enemy reaches its destination, and set the next waypoint location to (newX, currentY)
.
Here's the approach (again, a 2D space in Unity):
public class EnemyMovement : MonoBehaviour {
public float moveSpeed = 5f;
private GameObject currentWaypoint;
private void Start() {
currentWaypoint = SelectRandomPoint(); // initialize current waypoint
}
private void Update() {
Patrol();
}
private void Patrol() {
if (WpReached()) {
currentWaypoint = SelectRandomPoint(); // select a new waypoint
}
gameObject.transform.position = Vector2.MoveTowards(
gameObject.transform.position,
currentWaypoint.transform.position,
moveSpeed * Time.deltaTime
); // move towards the current waypoint
}
private bool WpReached(Vector2 position, Vector2 target, float allowance) {
return Vector2.Distance(position, target) <= allowance
}
private Vector2 SelectRandomPoint() {
// select a random point inside a circle
var point = Random.insideUnitCircle * wanderingRadius;
// eliminate the change of Y
point.y = 0;
// Add the difference to the current locaton of enemy
point += (Vector2)gameObject.transform.position;
return point;
}
}
This will make the enemy patrol to random locations, but what about those location which could be inside of a obstacle? We need to take care of that.
Add this member variable to the EnemyMovement
class:
public LayerMask layer; // layer of all obstacles
And this to the beginning of your Patrol()
function:
if (Physics2D.OverlapCircleAll(wanderWaypoint, 1, layer).Length != 0) {
currentWaypoint = SelectRandomPoint();
}
This will detect if the waypoint overlaps with any game objects in obstacle layer, if yes then select a new point.
Of course, you need to set all of your obstacle game objects (such as walls, ground blocks, platforms, etc.) to a new layer, and then attach this code to your enemy game object then select the obstacle layer(s) in the inspector.