Hi all,
I am trying to create an AI system for a topdown shooter and I’d like some of the enemies to wander randomly instead of following the player. I was wandering if I could somehow use Random.insideUnitSphere to get a point relatively close to the enemy every so often and have him travel to that point using the NavMeshAgent.SetDestination?
If this is the incorrect way of doing it, does anyone have any suggestions on how else I can get a wandering AI?
You’ll need an origin (your AI agent), a maximum distance, and a layer mask (I’d recommend -1 meaning all layers) and the method is going to return a random point on the NavMesh within a given distance to the origin.
I thought it would as it gives that semi-random look to the wandering and the code you shared helped a lot
With a few additions to the code to make it travel for a few seconds and then select a new target, it’s wandering around awesomely!
Below will be the code so if anyone needs help with a similar topic, then they can see how I’ve done it!
[code=CSharp]using UnityEngine;
using System.Collections;
public class WanderingAI : MonoBehaviour {
public float wanderRadius;
public float wanderTimer;
private Transform target;
private NavMeshAgent agent;
private float timer;
// Use this for initialization
void OnEnable () {
agent = GetComponent<NavMeshAgent> ();
timer = wanderTimer;
}
// Update is called once per frame
void Update () {
timer += Time.deltaTime;
if (timer >= wanderTimer) {
Vector3 newPos = RandomNavSphere(transform.position, wanderRadius, -1);
agent.SetDestination(newPos);
timer = 0;
}
}
public static Vector3 RandomNavSphere(Vector3 origin, float dist, int layermask) {
Vector3 randDirection = Random.insideUnitSphere * dist;
randDirection += origin;
NavMeshHit navHit;
NavMesh.SamplePosition (randDirection, out navHit, dist, layermask);
return navHit.position;
}
}
The random point generated will be a random point in 3d space. So it might not be accessible via the navmesh, which is why we need to get the closest point on the navmesh to the random point in 3d space.
I know your problem is solved. But I’ve noticed you are using a MonoBehaviour to define the wandering behaviour of your AI (WanderingAI). I guess that your are writing a new class for each type of behaviour.
Writing AI exclusively in C# can quickly become harder as your AI grows in complexity. Particularly when dealing with time dependent logic for implement long running actions, and it becomes even harder when your actions are triggered or interrupted by some conditions. Behaviour Tree is the ideal tool for defining such long running actions and their logic in clear and intuitive way and keeps your code organized and maintainable, therefore less likely to contain bugs.
You might be interested by Panda BT to implement your AI, it’s a script based Behaviour Tree engine:
@ericbegue I just tried out PandaBT and really like it but notice a lot of GC spikes in HasLOS, didn’t dig further but one easy way to prevent hammering expensive routines is to stagger the updates of some of the trees, or part of the tree.
One thing I am curious about is AI pooling, your shooter example runs each AI on each agent.
PS: hijacking thread ftw!
@laurentlavigne Thank you for trying Panda BT. Great that you like it!
The examples are not GC optimized, they are intended to illustrate usages of Panda BT with straigth forward code with focus on readability. However, since version 1.2.3 (published today) the core engine is GC optimized and allocates 0B after initialization. But it’s always possible to write tasks that are not GC optimized. If you use the tasks from the examples, you have to take care of the GC optimization if you are concerned.
The cpu time spent in executing the tree itself is negligeable, the real work is done by the tasks. So they are where the focus should be put on.
In the Shooter example, each AI agent executes is own BT. It has to be that way since each agent has its own data/memory that can’t be shared (expl: each agent has its own position), so each agent has its own set of variables, which also mean that each agent must have its own copy of the BT in a specific state.
I would provide some stats, if there are particular performance issues you are concerned about?
Hi!
Thanks @Cnc96 for sharing your code. It’s very helpful for the beginner.
I have tried to develop more features for Wander such as using Coroutine to check whether object reached to a destination. But if RandomNavSphere creates a random position that cannot reach and can only get close to the destination (or an internal position), then object can only stand and wait.
How to make this wait a little time and randomly move to the next position?
I’ve tried NavMeshPathStatus.PathInvalid and NavMeshPathStatus.PathPartial but it still not work.
Sorry, I’m a beginner and I lost 3 day(even today) for this problem.
Thanks!.
IEnumerator GoToDestination()
{
nav.SetDestination(destination);
while (nav.pathPending)
yield return null;
float remain = nav.remainingDistance;
while (remain == Mathf.Infinity || remain - nav.stoppingDistance > float.Epsilon || nav.pathStatus != NavMeshPathStatus.PathComplete)
{
if(nav.pathStatus != NavMeshPathStatus.PathInvalid)
{
// it don't send event after nav agent can only get close
}
if(nav.pathStatus != NavMeshPathStatus.PathInvalid)
{
// not work
}
remain = nav.remainingDistance;
yield return null;
}
isMoving = false;
}
For mine I do a velocity check to see if the player’s velocity has stopped. You can use agent.velocity.magniute to get the velocity of a nav mesh agent.
I have one issue with the Unit Sphere solution though. If your radius is much larger than the area you are trying to wander in (i.e. if you want multiple floors), the probability that your agent moves to the sides is higher because the sides of the room are more likely than the rest of the room. This is because if the sphere is larger than the room, the closest point for anything outside of the room is the sides/corners of the room.
@massey_digital
Let’s say you have 3 floors and want him to wander randomly between them and inside of them. Here’s how I’d do it:
The “wander” script has a public list of objects for wander spots. Create a empty game object inside each room and asign it to the script. Then inside the script you chose one of the spots randomly and then do the whole unit sphere solution around this point.
I expanded the code of the previous guys:
public class WanderSpot {
public GameObject Center;
public float WanderRadius;
}
public class WanderScript {
public List<WanderSpot> Spots;
private NavMeshAgent agent;
private void Update() {
if (!hasReachedDestination()) return;
var newSpot = Spots[Random.Range(0, Spots.Count)];
agent.SetDestination(RandomPosition(newSpot));
}
private bool hasReachedDestination() {
return agent.remainingDistance <= agent.stoppingDistance;
}
private Vector3 RandomPosition(WanderSpot spot) {
var randDirection = Random.insideUnitSphere * spot.WanderRadius;
randDirection += spot.Center.transform.position;
NavMeshHit navHit;
NavMesh.SamplePosition(randDirection, out navHit, spot.WanderRadius, -1);
return navHit.position;
}
}
I did something a little bit different. I simply attached 3 empty game objects to the base object with the navmesh. I made these 3 game objects children and just had the script attached to the base object switch randomly between these and at random delays.
Then, when the player gets within a certain distance of this object, it switches state to thinking about how to respond to the player’s actions.
I am doing something very similar to this but I have one concern. Lets say that your agent is wondering with this script and he is very close to a wall. On the other side of the wall is another navigable area that he can actually get to but you don’t want him wondering all the way around the map to get to that spot. Whats the best way to limit this? Can you check the distance that he would have to travel? Should you just do a raycast from origin to destination to make sure its within line of sight?