How to make AI that runs fast?

I’ve recently been working on AI for my game but regardless of my efforts I can’t make it perform at the level I need it to, I need as many zombies on the screen at once as I can, but i cant get even 50 or so at once or else it lags to much to be playable :frowning: I’ve tried numerous options these being:

  1. Character controller and raycast.
  2. Character controller and trigger.
  3. rigid body and trigger.
  4. rigid body and trigger.

The character controllers are moved with simple move and the rigid bodies are moved with transform.translate, but none of these run fast enough.

This is the basic code, all of the above deviations have been slightly changed versions of this:

#pragma strict

var speed : float;
var call : boolean;

public var spawnpoint : GameObject;

var head : Renderer;
var head2 : Renderer;
var blastplane : Renderer;


var health : int = 4;

private var thisTransform : Transform;
private var playerTransform : Transform;
private var player : GameObject;
private var fwd : Vector3;
private var playerfwd : Vector3;
private var thisTransformPos : Vector3;
private var controller : CharacterController;
private var ggameController : GameController;

function Start()
{
player = GameObject.FindWithTag ("Player");
thisTransform = transform;
thisTransformPos = thisTransform.position;
controller = GetComponent(CharacterController);
playerTransform = player.transform;
playerfwd = playerTransform.forward;
fwd = thisTransform.TransformDirection(Vector3.forward);
ggameController = player.GetComponent(GameController);

InvokeRepeating("main", 0, 0.1);
InvokeRepeating("move", 0, 0.01);
} 

function main () 
{
var hit : RaycastHit;
    if (ggameController.Chase == true)//cache this!
     {
      thisTransform.LookAt(playerTransform);
      fwd = thisTransform.TransformDirection(Vector3.forward);
      Debug.Log("SPEEEEED");
     }

    else if (Physics.Raycast (thisTransform.position, fwd, hit)) 
    {
     if (hit.distance < 10.1)
     {
      if (hit.transform == playerTransform)
       {
         Debug.Log ("Grrr Brains!");
         //shreak here
         player.GetComponent(GameController).Chase = true;;//cache this!
       }
      else if (hit.distance < 1.1)
       {
         transform.Rotate (0,transform.rotation.y + Random.Range(0,360),0);
         fwd = thisTransform.TransformDirection(Vector3.forward);
       }
      }
     }
  
}

function move ()
{
controller.SimpleMove(fwd * speed);
}
function Damage(dmg : int)
    {
        health -= dmg;
     
        switch (health)
        {
             case (3):
             head.material.SetTextureOffset ("_MainTex", Vector2(0.25,0));
             head2.material.SetTextureOffset ("_MainTex", Vector2(0.25,0));
             break;
             case (2):
              head.material.SetTextureOffset ("_MainTex", Vector2(0.5,0));
              head2.material.SetTextureOffset ("_MainTex", Vector2(0.5,0));
             break;
             case (1):
              head.material.SetTextureOffset ("_MainTex", Vector2(0.75,0));
              head2.material.SetTextureOffset ("_MainTex", Vector2(0.75,0));
             break;
             case (0):
              //Destroy (gameObject);
              //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
              //!!! move to start position !!!!!!!!!!!!!!!!!
              //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
              transform.position = spawnpoint.transform.position;
              health = 3;
             head.material.SetTextureOffset ("_MainTex", Vector2(0,0));
             head2.material.SetTextureOffset ("_MainTex", Vector2(0,0));
             break;
        }
    }
    
    
function Blast ()
 {
   blastplane.enabled = true;
   yield WaitForSeconds (0.25);
   blastplane.enabled = false;
 }

So, I was wondering how people get there AI to work so fast :face_with_spiral_eyes: mine is not even complex all it does is wonder around until the player gets seen or the player shoots… Could someone give some tips and tricks to how to make nice AI or a link to a good tutorial(I’ve looked at some but they are all just like mine except don’t cache anything) So any tips or help would be great :smile:

Just one thing i notice: player = GameObject.FindWithTag (“Player”);

Finding anything is resource hungry. Avoid it when you can. Make a variable at the top, and connect it to the player. By GameObject or Transform, depends of the needs.

var player: GameObject;
or
var player: Transform;

You will have a slot in the inspector then where you can drag the Player in. And then you dont need the "find" anymore. Even when it doesnt really eat lots of resources in your case because you use it in the start loop.

Mh, raycasts are also resource eaters.

Another thought: the graphics can be the trouble makers too. Shaders, animation, lots of polys …

Remove all of your Debug statements before doing any real benchmarking.

The problem with doing it manualy is having to apply the variables it would take ages to do them all at the start, and it only happens once any way. My graphics are very simple the map runs at 2000fps when optimized but drops quickly with the addition of any zombies.

Thanks :slight_smile: I will try that tomorrow.

Something that gets called once per zombie won’t make much of a difference. But because there’s really not much inefficiency I could find at a glance, I’ll go ahead and give a tip.

Another way instead of GameObject.Find is to keep a script with static variables (in this example I’ll call it Variables.js) attached to any object.

static var player : GameObject;

function Start () {
    player = GameObject.FindWithTag ("Player");
}

// In your zombie code...

function Start () {
    player = Variables.player;
}

The piece I noticed is this:

player.GetComponent(GameController).Chase = true;;//cache this!

You commented to cache it (and you have in ggameController) but you’re not using the cached variable!

And why do you have a reference to both the GameObject of player as well as its Transform? You never use the GameObject version.

If I could suggest something to you.

Zombies are a flocking creature. They tend to amass into large groups and follow each other around. One zombie in the group might see human flesh and follow it, all others are just following that one zombie.

There is a super speedy flocking algorithm out there called ‘Boids’ (yes it’s a pun on birds). You could easily have all of your zombies attempt to flock with each other… when a member of the flock sees a human it wants to eat… it and only it bee-lines for the human, and the boid flocking will pull the rest in tow, and they in turn will sight the human and bee-line as well.

This sighting can be easily done as a spherecast then, instead of a raycast. This sphercast ‘radar’ could occur occasionally instead of 30 or 40 times a second. The zombie doesn’t have to update it’s target very frequently… they’re zombies and slow thinkers.

Then you only use simple raycasting occasionally to make sure the zombie isn’t running into a wall. But again, use it sparingly because it’s a zombie. Zombies are stupid so they will trip over stuff and walk into walls and fences.

Then if you want rage zombies (fast speedy ones) integrate some pathfinding and when you ‘see’ (spherecast) a human and do that bee-line to it. You calculate a path to the human with your pathfinding, and just have the zombie run down the points of the path.

UnitySteer has a Boid system already written for you:
http://arges-systems.com/blog/2009/07/08/unitysteer-steering-components-for-unity/

And there are numerous Pathfinding systems out there. Including the unity built-in one. Though I find the built-in one lacking…

AronGranberg A* Pathfinding:
http://www.arongranberg.com/unity/a-pathfinding/

Angry Ant:
http://angryant.com/path/

Yes. But i don`t know your other code. And when you make heavy use of find … i thought i mention it :slight_smile:

Which can still be a thing of the graphics. Do some tests. Remove all AI and let your zombies just move into one direction, playing just a walk animation. Then you`ll see how performant eating your graphics really are.

Have you tried having 50 non-scripted zombies just stand there and test the game’s preformance, maybe it’s not the script, it’s amount of polys on screen.

Remove debug statements, that kills speed.

I’ve tried removing the script and just having the zombies and it works well but with the script enabled it is slow.

Here is my updated code:

#pragma strict

var speed : float;
var call : boolean;

public var spawnpoint : GameObject;

var head : Renderer;
var head2 : Renderer;
var blastplane : Renderer;


var health : int = 4;

private var thisTransform : Transform;
private var playerTransform : Transform;
private var player : GameObject;
private var fwd : Vector3;
private var playerfwd : Vector3;
private var thisTransformPos : Vector3;
private var controller : CharacterController;
private var ggameController : GameController;

function Start()
{
player = GameObject.FindWithTag ("Player");
thisTransform = transform;
thisTransformPos = thisTransform.position;
controller = GetComponent(CharacterController);
playerTransform = player.transform;
fwd = thisTransform.TransformDirection(Vector3.forward);
ggameController = player.GetComponent(GameController);

InvokeRepeating("main", 0, 0.1);
InvokeRepeating("move", 0, 0.01);
} 

function main () 
{
var hit : RaycastHit;
    if (ggameController.Chase == true)
     {
      thisTransform.forward = -playerTransform.forward;
      //thisTransform.LookAt(playerTransform);
      fwd = thisTransform.TransformDirection(Vector3.forward);
     }

    else if (Physics.Raycast (thisTransform.position, fwd, hit)) 
    {
     if (hit.distance < 10.1)
     {
      if (hit.transform == playerTransform)
       {
         ggameController.Chase = true;
         return;
       }
      else if (hit.distance < 1.1)
       {
         thisTransform.Rotate (0,transform.rotation.y + Random.Range(0,360),0);
         fwd = thisTransform.TransformDirection(Vector3.forward);
       }
      }
     }
  
}

function move ()
{
controller.SimpleMove(fwd * speed);
}
function Damage(dmg : int)
    {
        health -= dmg;
     
        switch (health)
        {
             case (3):
             head.material.SetTextureOffset ("_MainTex", Vector2(0.25,0));
             head2.material.SetTextureOffset ("_MainTex", Vector2(0.25,0));
             break;
             case (2):
              head.material.SetTextureOffset ("_MainTex", Vector2(0.5,0));
              head2.material.SetTextureOffset ("_MainTex", Vector2(0.5,0));
             break;
             case (1):
              head.material.SetTextureOffset ("_MainTex", Vector2(0.75,0));
              head2.material.SetTextureOffset ("_MainTex", Vector2(0.75,0));
             break;
             case (0):
              //Destroy (gameObject);
              //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
              //!!! move to start position !!!!!!!!!!!!!!!!!
              //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
              transform.position = spawnpoint.transform.position;
              health = 3;
             head.material.SetTextureOffset ("_MainTex", Vector2(0,0));
             head2.material.SetTextureOffset ("_MainTex", Vector2(0,0));
             break;
        }
    }
    
    
function Blast ()
 {
   blastplane.enabled = true;
   yield WaitForSeconds (0.25);
   blastplane.enabled = false;
 }

Then the next step is to find out which part of the script causes the slowdown. Try to remove it line by line and feature by feature, and have a look at the performance while that.

Yep I would comment out everything but the movement part and see what happens.

And also, why are you using InvokeRepeating and not Update?

Try moving the zombies without a character controller. Character controllers are quite expensive.

I’ll do that tomorrow, to tired now :smile:

Because it doesn’t need to happen every frame.

I have, just using transform.translate with a rigid body gives the same speed issues :frowning:

What spec computer are you using?

The specs have really nothing to do with the problem as i get it in his game too :stuck_out_tongue:

InvokeRepeating("main", 0, 0.1);

InvokeRepeating("move", 0, 0.01);

Your calling move 100 times a second, and main 10 times a second if that’s for 50 zombies 5,000 times and 500 times.

Unless you have zombies that are lightning fast you should be able to drop this to every second or so for the main and a few times a second for move.

You can add initial offsets to the invoke repeating timer this should help prevent your calls from all happening at the same time.

e.g.

InvokeRepeating("main", Random.value, 0.3);

InvokeRepeating("move", Random.value, 0.1);

Maybe you both have crap computers? JK.

Thanks :slight_smile: these have helped lots with performance :smile:

My computer is crap, but i plan on targeting mobile so it is not that slow :smile:

Performance is much better now, works well on my computer :smile: this is the code, the only problem is that because it only raycasts once a second you can avoid the enemy’s because they don’t come at you so IDK i’ll try and figure that out later, it also works well at 0.5 seconds but i just relised i have the same problem.

Here is the code:

#pragma strict

var speed : float;
var call : boolean;

public var spawnpoint : GameObject;

var head : Renderer;
var head2 : Renderer;
var blastplane : Renderer;


var health : int = 4;

private var thisTransform : Transform;
private var playerTransform : Transform;
private var player : GameObject;
private var fwd : Vector3;
private var playerfwd : Vector3;
private var thisTransformPos : Vector3;
private var controller : CharacterController;
private var ggameController : GameController;

function Start()
{
player = GameObject.FindWithTag ("Player");
thisTransform = transform;
thisTransformPos = thisTransform.position;
controller = GetComponent(CharacterController);
playerTransform = player.transform;
fwd = thisTransform.TransformDirection(Vector3.forward);
ggameController = player.GetComponent(GameController);

InvokeRepeating("main", Random.value, 1);
InvokeRepeating("move", Random.value, 0.2);
} 

function main () 
{
var hit : RaycastHit;
    if (ggameController.Chase == true)
     {
      return;
     }

    else if (Physics.Raycast (thisTransform.position, fwd, hit)) 
    {
     if (hit.distance < 10.1)
     {
      if (hit.transform == playerTransform)
       {
         ggameController.Chase = true;
         return;
       }
      else if (hit.distance < 1.1)
       {
         thisTransform.Rotate (0,transform.rotation.y + Random.Range(0,360),0);
         fwd = thisTransform.TransformDirection(Vector3.forward);
       }
      }
     }
  
}

function move ()
{
    if (ggameController.Chase == true)
     {
      thisTransform.forward = -playerTransform.forward;
     }
controller.SimpleMove(fwd * speed);
}
function Damage(dmg : int)
    {
        health -= dmg;
     
        switch (health)
        {
             case (3):
             head.material.SetTextureOffset ("_MainTex", Vector2(0.25,0));
             head2.material.SetTextureOffset ("_MainTex", Vector2(0.25,0));
             break;
             case (2):
              head.material.SetTextureOffset ("_MainTex", Vector2(0.5,0));
              head2.material.SetTextureOffset ("_MainTex", Vector2(0.5,0));
             break;
             case (1):
              head.material.SetTextureOffset ("_MainTex", Vector2(0.75,0));
              head2.material.SetTextureOffset ("_MainTex", Vector2(0.75,0));
             break;
             case (0):
              //Destroy (gameObject);
              //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
              //!!! move to start position !!!!!!!!!!!!!!!!!
              //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
              transform.position = spawnpoint.transform.position;
              health = 3;
             head.material.SetTextureOffset ("_MainTex", Vector2(0,0));
             head2.material.SetTextureOffset ("_MainTex", Vector2(0,0));
             break;
        }
    }
    
    
function Blast ()
 {
   blastplane.enabled = true;
   yield WaitForSeconds (0.25);
   blastplane.enabled = false;
 }

And this is a webplayer of the script running.

Your link is not accessible unless you change the https to http

It looks like main is just checking if the zombie sees the player by casting 1 ray directly forwards, it’s bound to miss the player if it is only called every second.

Why not just do a Line Of sight check against the player:

Fire the ray at the player
Check that it can hit the player
If it’s angle is within the zombies sight arc.

Also your zombies are static when not chasing the player, you should have them do a random walk.