- Home /
Random AI patrol movement
Hey everybody! I have been trying to implement some patrolling features in an enemy for a top down 2D game. To begin with I just wanted that the enemy would randomly decides whether to stay in his/her current location or decide to pick a random direction and move towards it at a certain speed for a certain amount of time. Here is the script that I made in C#:
public class Enemy_Patrol : MonoBehaviour
{
Rigidbody2D rb;
Collider2D coll;
[SerializeField] Vector2 movement;
[SerializeField] float normalSpeed;
[SerializeField] float alarmSpeed;
[SerializeField] float restTime;
[SerializeField] float initialrestTime;
[SerializeField] float moveTime;
[SerializeField] float initialmoveTime;
[SerializeField] float lookTime;
[SerializeField] float initiallookTime;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
coll = GetComponent<Collider2D>();
restTime = initialrestTime;
moveTime = initialmoveTime;
lookTime = initiallookTime;
}
// Update is called once per frame
void Update()
{
Patrol();
}
void Patrol() {
int patrolChoice = Random.Range(0, 2);
switch (patrolChoice) {
case 0:
if (restTime > 0)
{
Debug.Log("The Uncle is resting!");
restTime -= Time.deltaTime; // each frame it will reduce the rest time
print(restTime);
return; // it does nothing, just staying there
}else if(restTime <= 0){
restTime = initialrestTime;
}
break;
case 1:
if (moveTime > 0)
{
Debug.Log("The Uncle is moving!");
moveTime -= Time.deltaTime;
print(moveTime);
movement = new Vector2 (x: Random.Range(-1, 2)*normalSpeed, y: Random.Range(-1, 2) * normalSpeed);
rb.velocity = movement;
}else if (moveTime <= 0)
{
moveTime = initialmoveTime;
}
break;
case 2:
//I want him to stay still but rotate his field of view
break;
default:
if (restTime > 0)
{
Debug.Log("The Uncle is resting!");
restTime -= Time.deltaTime; // each frame it will reduce the rest time
print(restTime);
return; // it does nothing, just staying there
}else if(restTime <= 0){
restTime = initialrestTime;
}
break;
}
}
}
Basically I want that, once the enemy will begin an action, whether it is moving or staying still, it will perform it until the end of the assigned time, whereas now I think that for each frame it is deciding again an action, making it move slightly aroung the starting point.
So the main question would be how to make it perform the action until the end of the defined time?.
The bonus question is: is this the most suitable way to make an enemy patrol? I read a lot about setting several nodes that the AI will randomly pick every time so that the enemy will move among these nodes. I thought that this method would be more random, making the AI "wander" rather than "patrol", but at the same time I should bias the random walk a bit in order not to let the AI stay around the starting point.
Any suggestions?
Answer by DCordoba · Jan 27, 2019 at 07:42 AM
well, I refactorized it a bit, splitting (maybe exessive?) the main code on minor functions, to make it more readable, Idk if I reach this goal...
well, I add a simple chooser to update the function once the previous time is over, so, the uncle will be more consistent doing tasks...
and sorry but this, but please, give feedback, so comment if this didn work/corrections you did, or accept if this was what you needed, so many questions seem just remains there...
public class Enemy_Patrol : MonoBehaviour
{
Rigidbody2D rb;
Collider2D coll;
[SerializeField] Vector2 movement;
[SerializeField] float rotationAngle;
[SerializeField] float normalSpeed;
[SerializeField] float alarmSpeed;
[SerializeField] float restTime;
[SerializeField] float initialrestTime;
[SerializeField] float moveTime;
[SerializeField] float initialmoveTime;
[SerializeField] float lookTime;
[SerializeField] float initiallookTime;
int currenPatrolChoice = 0;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
coll = GetComponent<Collider2D>();
restTime = initialrestTime;
moveTime = initialmoveTime;
lookTime = initiallookTime;
}
// Update is called once per frame
void Update()
{
Patrol();
}
void Patrol() {
if (checkBusy()) {
DoTask ();
UpdateTask ();
} else {
StarTask ();
}
}
/// <summary>
/// Checks if the uncle is busy performing something
/// </summary>
/// <returns><c>true</c>, if the time isn't over, <c>false</c> otherwise.</returns>
bool checkBusy(){
switch (currenPatrolChoice) {
case 0:
if (restTime > 0)
{
return true;
}else if(restTime <= 0){
return false;
}
break;
case 1:
if (moveTime > 0)
{
return true;
}else if (moveTime <= 0)
{
return false;
}
break;
case 2:
//I want him to stay still but rotate his field of view
if (lookTime > 0)
{
return true;
}else if (lookTime <= 0)
{
return false;
}
break;
default:
// return false too, the system is going wrong, and put unlce to sleep by default (lets make it robust)
Debug.Log("this is a error, task beyong range: "+currenPatrolChoice);
currenPatrolChoice = 0;
return false;
break;
}
return false;//just to keep happy the compiler
}
/// <summary>
/// perform the task assigned... or just stay there
/// </summary>
void DoTask(){
switch (currenPatrolChoice) {
case 0:
break;// it does nothing, just staying there added to the future?
case 1:
Move ();
break;
case 2:
//chek if this bbehavior is what you need here
LookAround();
break;
default:
Debug.Log("this is a error, task beyong range: "+currenPatrolChoice);//this is worst than the previous, the system jumps the if!
break;
}
}
/// <summary>
/// Updates the Time, of the task assigned ofc.
/// </summary>
void UpdateTask(){
switch (currenPatrolChoice) {
case 0:
Debug.Log("The Uncle is resting!");
restTime -= Time.deltaTime; // each frame it will reduce the rest time
print(restTime);
break;
case 1:
Debug.Log("The Uncle is moving!");
moveTime -= Time.deltaTime;
print(moveTime);
break;
case 2:
//I want him to stay still but rotate his field of view
Debug.Log("The Uncle is Searching!");
lookTime -= Time.deltaTime;
print (lookTime);
break;
default:
//we handled before the error inside the if
break;
}
}
/// <summary>
/// initialize variables to start task... on the next frame :P
/// </summary>
void StarTask(){
int currenPatrolChoice = Random.Range(0, 3);//las is exclusive, so dont count...
switch (currenPatrolChoice) {
case 0:
restTime = initialrestTime;
break;
case 1:
moveTime = initialmoveTime;
movement = new Vector2 (x: Random.Range(-1, 2)*normalSpeed, y: Random.Range(-1, 2) * normalSpeed);
break;
case 2:
//lets give this life
lookTime = initiallookTime;
rotationAngle = Random.Range (-1, 1);//choose direction and speed?
break;
default:
Debug.Log("task beyong range: "+currenPatrolChoice + "restarting");//the error was previously detected so, restart the sleep time
restTime = initialrestTime;
break;
}
}
void Move(){
rb.velocity = movement;
}
//check if this is the "looking behaviour" that you wanted
void LookAround(){
rb.MoveRotation (rotationAngle);
}
}
Answer by Sici9 · Jan 29, 2019 at 11:59 AM
@DCordoba I implemented your script, the good thing is that the Enemy now moves randomly, the problem I think is here:
void Patrol() {
if (checkBusy()) {
DoTask ();
UpdateTask ();
} else {
StarTask ();
}
}
Basically, when setting the variable patrolChoice at the beginning either to 0 or 1, it will do the same task all the time because it never reaches the StartTask. I solved it by calling StartTask at the end of UpdateTask once the time was approaching zero.
The problem, has now become almost the opposite, i.e. once the Enemy starts moving, it won't stop moving. It seems that what is not working is here:
void StartTask()
{
int currenPatrolChoice = Random.Range(0, 2);//last is exclusive, so dont count...
Debug.Log("The current choice is: " + currenPatrolChoice);
switch (currenPatrolChoice)
{
case 0:
restTime = initialrestTime;
Debug.Log("Time to rest");
break;
case 1:
moveTime = initialmoveTime;
Debug.Log("Time to move");
Move();
movement = new Vector2(x: Random.Range(-1, 2) * normalSpeed, y: Random.Range(-1, 2) * normalSpeed);
break;
case 2:
//lets give this life
lookTime = initiallookTime;
rotationAngle = Random.Range(-1, 1);//choose direction and speed?
break;
default:
Debug.Log("task beyong range: " + currenPatrolChoice + "restarting");//the error was previously detected so, restart the sleep time
restTime = initialrestTime;
break;
}
}
I have put some prints to see if it manages to reach each chunk of code (for now I neglect the case 2, but still thanks for the tip ;). I have noticed that if the Enemy begins in its resting position it is alright, but as soon as the script make it switch to the move case:
The moveTime is the same as the resTtime;
If it goes again to the resting situation, it keeps moving;
Once it reaches case 1 of StartTask, it goes immediately to case 0 of UpdateTask
I have tried to set the velocity to zero with Vector2.zero when the case 0 of StartTask, but in that case the enemy stood still for all the time. Any suggestions? Thanks a lot for the help so far anyway! Here is the script I am using (yours, plus some minor things):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Patrol_Alternative : MonoBehaviour
{
Rigidbody2D rb;
Collider2D coll;
[SerializeField] Vector2 movement;
[SerializeField] float rotationAngle;
[SerializeField] float normalSpeed;
[SerializeField] float alarmSpeed;
[SerializeField] float restTime;
[SerializeField] float initialrestTime;
[SerializeField] float moveTime;
[SerializeField] float initialmoveTime;
[SerializeField] float lookTime;
[SerializeField] float initiallookTime;
int currenPatrolChoice ;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
coll = GetComponent<Collider2D>();
restTime = initialrestTime;
moveTime = initialmoveTime;
lookTime = initiallookTime;
}
// Update is called once per frame
void Update()
{
Patrol();
}
void Patrol()
{
if (checkBusy())
{
DoTask();
UpdateTask();
}
else
{
StartTask();
}
}
/// <summary>
/// Checks if the uncle is busy performing something
/// </summary>
/// <returns><c>true</c>, if the time isn't over, <c>false</c> otherwise.</returns>
bool checkBusy()
{
switch (currenPatrolChoice)
{
case 0:
if (restTime > 0)
{
return true;
}
else if (restTime <= 0)
{
return false;
}
break;
case 1:
if (moveTime > 0)
{
return true;
}
else if (moveTime <= 0)
{
return false;
}
break;
case 2:
//I want him to stay still but rotate his field of view
if (lookTime > 0)
{
return true;
}
else if (lookTime <= 0)
{
return false;
}
break;
default:
// return false too, the system is going wrong, and put unlce to sleep by default (lets make it robust)
Debug.Log("this is a error, task beyong range: " + currenPatrolChoice);
currenPatrolChoice = 0;
return false;
}
return false;//just to keep happy the compiler
}
/// <summary>
/// perform the task assigned... or just stay there
/// </summary>
void DoTask()
{
switch (currenPatrolChoice)
{
case 0:
break;// it does nothing, just staying there added to the future?
case 1:
Move();
break;
case 2:
//chek if this bbehavior is what you need here
LookAround();
break;
default:
Debug.Log("this is a error, task beyong range: " + currenPatrolChoice);//this is worst than the previous, the system jumps the if!
break;
}
}
/// <summary>
/// Updates the Time, of the task assigned ofc.
/// </summary>
void UpdateTask()
{
switch (currenPatrolChoice)
{
case 0:
Debug.Log("The Uncle is resting!");
restTime -= Time.deltaTime; // each frame it will reduce the rest time
print("The resttime is: " + restTime);
if (restTime < 0.2) {//I just put some margin to make it work better
Debug.Log("Do something else!");
StartTask(); // so that it will move to a new task at the end of the current
}
break;
case 1:
Debug.Log("The Uncle is moving!");
moveTime -= Time.deltaTime;
print("The movetime is: " + moveTime);
if (moveTime < 0.2)
{
Debug.Log("Do something else!");
StartTask();
}
break;
case 2:
//I want him to stay still but rotate his field of view
Debug.Log("The Uncle is Searching!");
lookTime -= Time.deltaTime;
print(lookTime);
if (lookTime < 0.2)
{
Debug.Log("Do something else!");
StartTask();
}
break;
default:
//we handled before the error inside the if
break;
}
}
/// <summary>
/// initialize variables to start task... on the next frame :P
/// </summary>
void StartTask()
{
int currenPatrolChoice = Random.Range(0, 2);//last is exclusive, so dont count...
Debug.Log("The current choice is: " + currenPatrolChoice);
switch (currenPatrolChoice)
{
case 0:
restTime = initialrestTime;
Debug.Log("Time to rest");
break;
case 1:
moveTime = initialmoveTime;
Debug.Log("Time to move");
Move();
movement = new Vector2(x: Random.Range(-1, 2) * normalSpeed, y: Random.Range(-1, 2) * normalSpeed);
break;
case 2:
//lets give this life
lookTime = initiallookTime;
rotationAngle = Random.Range(-1, 1);//choose direction and speed?
break;
default:
Debug.Log("task beyong range: " + currenPatrolChoice + "restarting");//the error was previously detected so, restart the sleep time
restTime = initialrestTime;
break;
}
}
void Move()
{
rb.velocity = movement;
}
//check if this is the "looking behaviour" that you wanted
void LookAround()
{
rb.MoveRotation(rotationAngle);
}
}
if you want to modify the bounds of the $$anonymous$$imal time, please modyfy CheckBusy(),
have it own separate function to do each task on his own function. on the deafault script, the point to call StartTask()
is reached, add a breakpoint on monodevelop to check if you want :) the problem is Random Range
always choose 1... and when you set a movement to zero to resting, the random always choose 0, I still checking this strange behaviour, and what caused this...
I found te error, on line 154 of the first script, jut at the beginning of StarTask()
when I try to set the var currenPatrolChoice
I mistake and create a local var with the same name
void StarTask(){
int currenPatrolChoice = Random.Range(0, 3);//las is exclusive, so dont count...
switch (currenPatrolChoice) {
just dont create a local variable, delete that int
of there
void StarTask(){
currenPatrolChoice = Random.Range(0, 3);//las is exclusive, so dont count...
switch (currenPatrolChoice) {
sorry to give you a bugged script, the other error was, that what you already discovered, I never set velocity to zero to stack up while resting
void DoTask(){
switch (currenPatrolChoice) {
case 0:
StackUp ();
break;// it does nothing, just staying there added to the future?
case 1:
$$anonymous$$ove ();
break;
case 2:
//chek if this bbehavior is what you need here
LookAround();
break;
default:
Debug.Log("this is a error, task beyong range: "+currenPatrolChoice);//this is worst than the previous, the system jumps the if!
break;
}
}
void StackUp(){
rb.velocity = Vector2.zero;
}
Answer by sundhar_unity · Oct 27, 2020 at 04:51 AM
Check this video for Random Patrol based on area and position : https://youtu.be/BIyYldTyyR8