Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

How to make AI that runs fast?

Discussion in 'Scripting' started by TylerPerry, Sep 24, 2012.

  1. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    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 :( 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:

    Code (csharp):
    1.  
    2. #pragma strict
    3.  
    4. var speed : float;
    5. var call : boolean;
    6.  
    7. public var spawnpoint : GameObject;
    8.  
    9. var head : Renderer;
    10. var head2 : Renderer;
    11. var blastplane : Renderer;
    12.  
    13.  
    14. var health : int = 4;
    15.  
    16. private var thisTransform : Transform;
    17. private var playerTransform : Transform;
    18. private var player : GameObject;
    19. private var fwd : Vector3;
    20. private var playerfwd : Vector3;
    21. private var thisTransformPos : Vector3;
    22. private var controller : CharacterController;
    23. private var ggameController : GameController;
    24.  
    25. function Start()
    26. {
    27. player = GameObject.FindWithTag ("Player");
    28. thisTransform = transform;
    29. thisTransformPos = thisTransform.position;
    30. controller = GetComponent(CharacterController);
    31. playerTransform = player.transform;
    32. playerfwd = playerTransform.forward;
    33. fwd = thisTransform.TransformDirection(Vector3.forward);
    34. ggameController = player.GetComponent(GameController);
    35.  
    36. InvokeRepeating("main", 0, 0.1);
    37. InvokeRepeating("move", 0, 0.01);
    38. }
    39.  
    40. function main ()
    41. {
    42. var hit : RaycastHit;
    43.     if (ggameController.Chase == true)//cache this!
    44.      {
    45.       thisTransform.LookAt(playerTransform);
    46.       fwd = thisTransform.TransformDirection(Vector3.forward);
    47.       Debug.Log("SPEEEEED");
    48.      }
    49.  
    50.     else if (Physics.Raycast (thisTransform.position, fwd, hit))
    51.     {
    52.      if (hit.distance < 10.1)
    53.      {
    54.       if (hit.transform == playerTransform)
    55.        {
    56.          Debug.Log ("Grrr Brains!");
    57.          //shreak here
    58.          player.GetComponent(GameController).Chase = true;;//cache this!
    59.        }
    60.       else if (hit.distance < 1.1)
    61.        {
    62.          transform.Rotate (0,transform.rotation.y + Random.Range(0,360),0);
    63.          fwd = thisTransform.TransformDirection(Vector3.forward);
    64.        }
    65.       }
    66.      }
    67.  
    68. }
    69.  
    70. function move ()
    71. {
    72. controller.SimpleMove(fwd * speed);
    73. }
    74. function Damage(dmg : int)
    75.     {
    76.         health -= dmg;
    77.      
    78.         switch (health)
    79.         {
    80.              case (3):
    81.              head.material.SetTextureOffset ("_MainTex", Vector2(0.25,0));
    82.              head2.material.SetTextureOffset ("_MainTex", Vector2(0.25,0));
    83.              break;
    84.              case (2):
    85.               head.material.SetTextureOffset ("_MainTex", Vector2(0.5,0));
    86.               head2.material.SetTextureOffset ("_MainTex", Vector2(0.5,0));
    87.              break;
    88.              case (1):
    89.               head.material.SetTextureOffset ("_MainTex", Vector2(0.75,0));
    90.               head2.material.SetTextureOffset ("_MainTex", Vector2(0.75,0));
    91.              break;
    92.              case (0):
    93.               //Destroy (gameObject);
    94.               //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    95.               //!!! move to start position !!!!!!!!!!!!!!!!!
    96.               //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    97.               transform.position = spawnpoint.transform.position;
    98.               health = 3;
    99.              head.material.SetTextureOffset ("_MainTex", Vector2(0,0));
    100.              head2.material.SetTextureOffset ("_MainTex", Vector2(0,0));
    101.              break;
    102.         }
    103.     }
    104.    
    105.    
    106. function Blast ()
    107.  {
    108.    blastplane.enabled = true;
    109.    yield WaitForSeconds (0.25);
    110.    blastplane.enabled = false;
    111.  }
    112.  
    So, I was wondering how people get there AI to work so fast :confused: 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 :D
     
  2. Tiles

    Tiles

    Joined:
    Feb 5, 2010
    Posts:
    2,481
    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 don`t need the "find" anymore. Even when it doesn`t 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 ...
     
    Last edited: Sep 24, 2012
  3. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Remove all of your Debug statements before doing any real benchmarking.
     
  4. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    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.
     
  5. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    Thanks :) I will try that tomorrow.
     
  6. DanielQuick

    DanielQuick

    Joined:
    Dec 31, 2010
    Posts:
    3,137
    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.
    Code (csharp):
    1.  
    2. static var player : GameObject;
    3.  
    4. function Start () {
    5.     player = GameObject.FindWithTag ("Player");
    6. }
    7.  
    8. // In your zombie code...
    9.  
    10. function Start () {
    11.     player = Variables.player;
    12. }
    13.  
    The piece I noticed is this:
    Code (csharp):
    1.  
    2. player.GetComponent(GameController).Chase = true;;//cache this!
    3.  
    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.
     
    Last edited: Sep 24, 2012
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    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/
     
    Last edited: Sep 24, 2012
  8. Tiles

    Tiles

    Joined:
    Feb 5, 2010
    Posts:
    2,481
    Yes. But i don`t know your other code. And when you make heavy use of find ... i thought i mention it :)

    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.
     
  9. Kinos141

    Kinos141

    Joined:
    Jun 22, 2011
    Posts:
    969
    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.
     
    Last edited: Sep 24, 2012
  10. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    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:

    Code (csharp):
    1. #pragma strict
    2.  
    3. var speed : float;
    4. var call : boolean;
    5.  
    6. public var spawnpoint : GameObject;
    7.  
    8. var head : Renderer;
    9. var head2 : Renderer;
    10. var blastplane : Renderer;
    11.  
    12.  
    13. var health : int = 4;
    14.  
    15. private var thisTransform : Transform;
    16. private var playerTransform : Transform;
    17. private var player : GameObject;
    18. private var fwd : Vector3;
    19. private var playerfwd : Vector3;
    20. private var thisTransformPos : Vector3;
    21. private var controller : CharacterController;
    22. private var ggameController : GameController;
    23.  
    24. function Start()
    25. {
    26. player = GameObject.FindWithTag ("Player");
    27. thisTransform = transform;
    28. thisTransformPos = thisTransform.position;
    29. controller = GetComponent(CharacterController);
    30. playerTransform = player.transform;
    31. fwd = thisTransform.TransformDirection(Vector3.forward);
    32. ggameController = player.GetComponent(GameController);
    33.  
    34. InvokeRepeating("main", 0, 0.1);
    35. InvokeRepeating("move", 0, 0.01);
    36. }
    37.  
    38. function main ()
    39. {
    40. var hit : RaycastHit;
    41.     if (ggameController.Chase == true)
    42.      {
    43.       thisTransform.forward = -playerTransform.forward;
    44.       //thisTransform.LookAt(playerTransform);
    45.       fwd = thisTransform.TransformDirection(Vector3.forward);
    46.      }
    47.  
    48.     else if (Physics.Raycast (thisTransform.position, fwd, hit))
    49.     {
    50.      if (hit.distance < 10.1)
    51.      {
    52.       if (hit.transform == playerTransform)
    53.        {
    54.          ggameController.Chase = true;
    55.          return;
    56.        }
    57.       else if (hit.distance < 1.1)
    58.        {
    59.          thisTransform.Rotate (0,transform.rotation.y + Random.Range(0,360),0);
    60.          fwd = thisTransform.TransformDirection(Vector3.forward);
    61.        }
    62.       }
    63.      }
    64.  
    65. }
    66.  
    67. function move ()
    68. {
    69. controller.SimpleMove(fwd * speed);
    70. }
    71. function Damage(dmg : int)
    72.     {
    73.         health -= dmg;
    74.      
    75.         switch (health)
    76.         {
    77.              case (3):
    78.              head.material.SetTextureOffset ("_MainTex", Vector2(0.25,0));
    79.              head2.material.SetTextureOffset ("_MainTex", Vector2(0.25,0));
    80.              break;
    81.              case (2):
    82.               head.material.SetTextureOffset ("_MainTex", Vector2(0.5,0));
    83.               head2.material.SetTextureOffset ("_MainTex", Vector2(0.5,0));
    84.              break;
    85.              case (1):
    86.               head.material.SetTextureOffset ("_MainTex", Vector2(0.75,0));
    87.               head2.material.SetTextureOffset ("_MainTex", Vector2(0.75,0));
    88.              break;
    89.              case (0):
    90.               //Destroy (gameObject);
    91.               //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    92.               //!!! move to start position !!!!!!!!!!!!!!!!!
    93.               //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    94.               transform.position = spawnpoint.transform.position;
    95.               health = 3;
    96.              head.material.SetTextureOffset ("_MainTex", Vector2(0,0));
    97.              head2.material.SetTextureOffset ("_MainTex", Vector2(0,0));
    98.              break;
    99.         }
    100.     }
    101.    
    102.    
    103. function Blast ()
    104.  {
    105.    blastplane.enabled = true;
    106.    yield WaitForSeconds (0.25);
    107.    blastplane.enabled = false;
    108.  }
     
  11. Tiles

    Tiles

    Joined:
    Feb 5, 2010
    Posts:
    2,481
    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.
     
  12. CrazySi

    CrazySi

    Joined:
    Jun 23, 2011
    Posts:
    538
    Yep I would comment out everything but the movement part and see what happens.

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

    tomvds

    Joined:
    Oct 10, 2008
    Posts:
    1,028
    Try moving the zombies without a character controller. Character controllers are quite expensive.
     
  14. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    I'll do that tomorrow, to tired now :D

    Because it doesn't need to happen every frame.

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

    CrazySi

    Joined:
    Jun 23, 2011
    Posts:
    538
    What spec computer are you using?
     
  16. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    The specs have really nothing to do with the problem as i get it in his game too :p
     
  17. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Code (csharp):
    1. InvokeRepeating("main", 0, 0.1);
    2.  
    3. 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.

    Code (csharp):
    1. InvokeRepeating("main", Random.value, 0.3);
    2.  
    3. InvokeRepeating("move", Random.value, 0.1);
     
  18. CrazySi

    CrazySi

    Joined:
    Jun 23, 2011
    Posts:
    538
    Maybe you both have crap computers? JK.
     
  19. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    Thanks :) these have helped lots with performance :D

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

    Performance is much better now, works well on my computer :D 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:

    Code (csharp):
    1.  
    2. #pragma strict
    3.  
    4. var speed : float;
    5. var call : boolean;
    6.  
    7. public var spawnpoint : GameObject;
    8.  
    9. var head : Renderer;
    10. var head2 : Renderer;
    11. var blastplane : Renderer;
    12.  
    13.  
    14. var health : int = 4;
    15.  
    16. private var thisTransform : Transform;
    17. private var playerTransform : Transform;
    18. private var player : GameObject;
    19. private var fwd : Vector3;
    20. private var playerfwd : Vector3;
    21. private var thisTransformPos : Vector3;
    22. private var controller : CharacterController;
    23. private var ggameController : GameController;
    24.  
    25. function Start()
    26. {
    27. player = GameObject.FindWithTag ("Player");
    28. thisTransform = transform;
    29. thisTransformPos = thisTransform.position;
    30. controller = GetComponent(CharacterController);
    31. playerTransform = player.transform;
    32. fwd = thisTransform.TransformDirection(Vector3.forward);
    33. ggameController = player.GetComponent(GameController);
    34.  
    35. InvokeRepeating("main", Random.value, 1);
    36. InvokeRepeating("move", Random.value, 0.2);
    37. }
    38.  
    39. function main ()
    40. {
    41. var hit : RaycastHit;
    42.     if (ggameController.Chase == true)
    43.      {
    44.       return;
    45.      }
    46.  
    47.     else if (Physics.Raycast (thisTransform.position, fwd, hit))
    48.     {
    49.      if (hit.distance < 10.1)
    50.      {
    51.       if (hit.transform == playerTransform)
    52.        {
    53.          ggameController.Chase = true;
    54.          return;
    55.        }
    56.       else if (hit.distance < 1.1)
    57.        {
    58.          thisTransform.Rotate (0,transform.rotation.y + Random.Range(0,360),0);
    59.          fwd = thisTransform.TransformDirection(Vector3.forward);
    60.        }
    61.       }
    62.      }
    63.  
    64. }
    65.  
    66. function move ()
    67. {
    68.     if (ggameController.Chase == true)
    69.      {
    70.       thisTransform.forward = -playerTransform.forward;
    71.      }
    72. controller.SimpleMove(fwd * speed);
    73. }
    74. function Damage(dmg : int)
    75.     {
    76.         health -= dmg;
    77.      
    78.         switch (health)
    79.         {
    80.              case (3):
    81.              head.material.SetTextureOffset ("_MainTex", Vector2(0.25,0));
    82.              head2.material.SetTextureOffset ("_MainTex", Vector2(0.25,0));
    83.              break;
    84.              case (2):
    85.               head.material.SetTextureOffset ("_MainTex", Vector2(0.5,0));
    86.               head2.material.SetTextureOffset ("_MainTex", Vector2(0.5,0));
    87.              break;
    88.              case (1):
    89.               head.material.SetTextureOffset ("_MainTex", Vector2(0.75,0));
    90.               head2.material.SetTextureOffset ("_MainTex", Vector2(0.75,0));
    91.              break;
    92.              case (0):
    93.               //Destroy (gameObject);
    94.               //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    95.               //!!! move to start position !!!!!!!!!!!!!!!!!
    96.               //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    97.               transform.position = spawnpoint.transform.position;
    98.               health = 3;
    99.              head.material.SetTextureOffset ("_MainTex", Vector2(0,0));
    100.              head2.material.SetTextureOffset ("_MainTex", Vector2(0,0));
    101.              break;
    102.         }
    103.     }
    104.    
    105.    
    106. function Blast ()
    107.  {
    108.    blastplane.enabled = true;
    109.    yield WaitForSeconds (0.25);
    110.    blastplane.enabled = false;
    111.  }
    112.  
    And this is a webplayer of the script running.
     
    Last edited: Sep 26, 2012
  20. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    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.
     
  21. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    Is this better?

    It should have been moving around when it is not chasing the character just very slowly, now it goes quicker.
     
  22. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Yep, they are moving now, have you checked out the Unite Performance Tips and Tricks Video http://unity3d.com/unite/

    There is also an older video from 2007 that covers optimisation and 'boids' a bit older but might could come in handy.
     
    Last edited: Sep 26, 2012
  23. blaze

    blaze

    Joined:
    Dec 21, 2011
    Posts:
    211