Lags because of GameObjectsFindWithTag();

HI!I have script, which works so good without FindClosestEnemies(); function.
Here is part of code:

GameObject FindClosestEnemy()
{			 
    gos = GameObject.FindGameObjectsWithTag("Player");
    GameObject closest=null;
    
    for(int i=0;i<100;i++){}	
					
    float distance = Mathf.Infinity;
    Vector3 position = transform.position;
		
    foreach(var go in gos){		
        var diff = (go.transform.position-position);
        var curDistance=diff.sqrMagnitude;
        if(curDistance<distance)
        {
            closest=go;
            distance=curDistance;				
        }
    }
}

With this function, i have <15fps, without >200;
What Should I do? I think i shoud change this: gos = GameObject.FindGameObjectsWithTag(“Player”);, but i have not any ideas how to do it.
(I make RTS game^ and this cannon’s(Turrel) script(part of one)

[Edit by Berenger, code formatting]

I agree your code looks odd.

But why don’t you cache the list of players rather then fetching them each time you enter this routine?

The best way depends on how many enemies / objects will be there, what’s the max-distance and how are they distributed across the map.

  1. Use Physics.OverlapSphere with your max distance to get a list of all objects within the max distance. Make sure you put all units that should be selectable on a seperate unit-layer. Every enemy need to have a collider attached. This function is way faster then any of Unitys search-functions, especially when you have many objects and a max detection distance.
  2. Another way is that each enemy register / unrigisters itself by some kind of static Unit-Manager. This manager always holds an updated list of all units. If the map is really big and you have a lot of enemies (200+), it’s better to use the first version.

I know in a lot questions people suggest to use FindGameObjectsWithTag, but keep in mind it has to iterate through all objects so it’s quite slow. OverlapSphere uses the physics system which probably uses some kind of space partitioning which minimize the overhead.

Your sorting loop is already in it’s optimal form :wink:

It certainly better to get the reference to things like player once at the start. Whether its going to make a lot of difference to your routine I don’t know.

So do something like:

 private var playerObject: GameOject;
 
 function Startup()
 {
     playerObject= GameObject.FindGameObjectsWithTag("Player");
 }
  
 function DoWhatever()
 {
 
 // You can use "playerObject" instead of doing a find on it

 }

Thats good practice regardless.

Call it once every second or two. It will even make the enemies look cooler (when a better target comes closer, they’ll sometimes take a few shots at the old target before noticing.)

“Easy” way is a counter: if(AIcheck>0) AIcheck--; else { AIcheck=100; // do long loop.

Slightly CPU faster way is a coroutine, called at start, like:

IEnumerator getClosestRunner() {
  // each enemy waits a random amount, so they don't all check same frame:
  yield return new WaitForSeconds(Random.Range(0.0f, 1.5f);
  while(true) {
    getClosest();
    yield return new WaitForSeconds(1.5f);
  }
}

getClosest is what you already have. Maybe it runs GameObject.Find, or uses a predone list. It’s split out of the “every 1.5 secs” loop so you can call it when your current target dies, or when you’re first spawned.

You should not call any Find function in Update (or in any periodic function). Find functions are too slow, and will kill your framerate.

Getting all enemies at Start could be a good idea, but enemies have the bad habit of be spawned during the game, what invalidates this approach.

But there’s a faster alternative that requires some changes in your game:

1- Create an empty game object, reset its position/location and call it “Enemies”;

2- Attach your enemy spawning script to Enemies;

3- Each time you instantiate a new enemy, child it to Enemies - like this:

  GameObject enemy = Instantiate(...) as GameObject;
  enemy.transform.parent = transform; // make this object the enemy's parent

Since Unity keeps the children of any object in a list, you don’t need to use Find anymore. Your code above would be simpler and faster:

Transform enemies; // drag the Enemies object here

GameObject FindClosestEnemy()
{       
    GameObject closest=null;
    float distance = Mathf.Infinity;
    Vector3 position = transform.position;
    // iterate through the enemies children:
    foreach (Transform enemy in enemies){  
        float curDistance = (enemy.position-position).sqrMagnitude;
        if( curDistance < distance ){
            closest = enemy.gameObject;
            distance = curDistance;          
        }
    }
    return closest;
}

I solved the problem!!!
i just put body of function in update - all works fine!!!

Use
Vector3.Distance(firstVector3, secondVector3);
to get distances in one line. Its much cheaper this way.