Game lagging while looking for closest Enemy

Code of handling input making look for closest enemy:

if (Input.GetMouseButtonDown(0) && !hitDelay)
{
	AttackAnim();

	if (closestEnemy())
	{
		if (Vector3.Distance(transform.position, closestEnemy().transform.position) <= 3)
		{
			if (closestEnemy().tag == "Enemy")
			{
				Debug.Log("Hitting enemy");
				enemy = closestEnemy().gameObject.GetComponent<EnemyScript>();
				enemy.Hit();
				if (enemy.life <= 0)
					DestroyClosestEnemy();
			}
			else if (closestEnemy().tag != "Player" && closestEnemy().tag != "Untagged")
			{
				Debug.Log("Destroying " + closestEnemy().tag);
				DestroyClosestEnemy();
				wood++;
				Debug.Log("You have " + wood + " woods");
			}
			else
			{
				;
			}
		}
	}
	StartCoroutine(HitCoroutine());
}

code of function

private GameObject closestEnemy()
{
    GameObject[] Objects = Object.FindObjectsOfType<GameObject>().Where(f => (f.tag == "Enemy" || f.tag == "Tree") && Vector3.Distance(f.transform.position, transform.position) <= 3).ToArray();
    
    GameObject closestEnemy = null;
    bool first = true;
    foreach(var obj in Objects)
    {
        float Distance = Vector3.Distance(transform.position, obj.transform.position);
        if (first)
        {
            closestEnemy = obj;
            first = false;
        }
        if (Distance < Vector3.Distance(transform.position, closestEnemy.transform.position))
        {
            closestEnemy = obj;
        }
    }
    return closestEnemy;
}

Oooh this is a good one. First of all welcome to optimisation!

The main process of optimisation goes like this:

*Profile

*Find choke points

*Fix those

*Repeat

You’ll find the Unity profiler in Windows->Analysis. Run this while the game is playing and then pause the editor during or after the area you want to improve. You should be seeing a load of unsightly spikes which show your frame time. Click on a point at which the frame time is high and then have a look at the hierarchy in the lower half of your screen (you might have to switch to it from timeline).

I expect you’ll see this script and function occupying a fair bit of the frame time. If you can see the function taking up a lot of time but want to know more info about where exactly within that function you can add profiler samples. These are pairs of calls to Profiler.BeginSample(“sample name”) and Profiler.EndSample wrapped around the code you want details for. These will then show up within the function showing you how the profile for that region.

Please do do this as this is a great opportunity to figure out how it works so you can solve your issues quickly next time!

BUT I expect the root of your problem is Object.FindObjectsOfType.Where.... If you have a scene that’s anything more than trivially simple, doing this every frame is going to be expensive as hell. The good news is that there is a load of extra work here that can be pretty easily cut down on! To start with, you could maintain a collection of enemies that are alive and ready to be attacked. Some kind of enemy manager could add them when they spawn and remove them when they die. This would prevent you from A) getting every object in the world every frame and B) from using expensive LINQ code with that Where call. I’m sure there are more optimisations you can make that are specific to your setup, maybe it doesn’t need the closest but just ‘near enough’ if they’re likely to be clustered? Up to you!

60FPS 101, Rule #1:

Never, ever, never use FindObjectsOfType and FindGameObjectsWithTag (tags is general) outside Awake or Start. Period.

How to replace them? Use arrays and System.Collection.Generic.List lists instead.

Code below shows how you can do that and without physics being involved.

Player.cs

using System.Collections.Generic;
using UnityEngine;
[DisallowMultipleComponent]
public class Player : MonoBehaviour
{
    public static List<Player> Instances = new List<Player>();
    public float _reach = 3f;
    public int wood = 0;
    void OnEnable () => Player.Instances.Add( this );
    void OnDisable () => Player.Instances.Remove( this );
    void Update ()
    {
        if( Input.GetMouseButtonDown(0) )
        {
            if( this.FindNearest( Enemy.Visible , _reach , out Enemy enemy , out float enemyDist ) )
            {
                enemy.Hit();
            }
            else if( this.FindNearest( Destructible.Visible , _reach , out Destructible destructible , out float destructibleDist ) )
            {
                if( destructible.type==DestructibleType.Wood )
                {
                    wood++;
                    Debug.Log($"You have {wood} wood");
                }
                destructible.Die();
            }
        }
    }
    #if UNITY_EDITOR
    void OnDrawGizmosSelected ()
    {
        Gizmos.color = Color.cyan;
        Gizmos.DrawWireSphere( transform.position , _reach );
    }
    #endif
}

Enemy.cs

using System.Collections.Generic;
using UnityEngine;
[DisallowMultipleComponent]
[RequireComponent( typeof(SkinnedMeshRenderer) )]
public class Enemy : MonoBehaviour
{
    public static List<Enemy> Instances = new List<Enemy>();
    public static List<Enemy> Visible = new List<Enemy>();
    [SerializeField] float _life = 3f;
    void OnEnable () => Enemy.Instances.Add( this );
    void OnDisable () => Enemy.Instances.Remove( this );
    void OnBecameVisible () => Enemy.Visible.Add( this );
    void OnBecameInvisible () => Enemy.Visible.Remove( this );
    public void Hit ()
    {
        Debug.Log($"{this.name} hit");

        /* hit code */
        
        if( _life>0f )
        {
            _life -= 1.6f;
            if( _life<=0f ) Die();
        }
    }
    public void Die ()
    {
        _life = 0f;

        /* death code */

        Debug.Log($"{this.name} died");
        this.gameObject.SetActive( false );
    }
}

Destructible.cs

using System.Collections.Generic;
using UnityEngine;
[DisallowMultipleComponent]
[RequireComponent( typeof(MeshFilter) )]
[RequireComponent( typeof(MeshRenderer) )]
public class Destructible : MonoBehaviour
{
    public static List<Destructible> Visible = new List<Destructible>();
    public DestructibleType type = DestructibleType.Undefined;
    void OnBecameVisible () => Destructible.Visible.Add( this );
    void OnBecameInvisible () => Destructible.Visible.Remove( this );
    public void Die ()
    {
        /* death code */
		
        Debug.Log($"{this.name} destroyed");
		this.gameObject.SetActive( false );
    }
}

public enum DestructibleType
{
    Undefined   = 0 ,
    Wood        = 1 ,
    Stone       = 2
}

ComponentExtentionMethods.cs

using System.Collections.Generic;
using UnityEngine;
public static class ComponentExtentionMethods
{
    public static bool FindNearest <T,K>
    (
        this T me ,
        List<K> others ,
        float maxSearchDistance ,
        out K nearest ,
        out float nearestDist
    )
        where T : Component
        where K : Component
    {
        Vector3 myPosition = me.transform.position;
        float maxSearchDistanceSq = maxSearchDistance * maxSearchDistance;
        float nearestDistSq = float.MaxValue;
        bool hit = false;
        nearest = null;
        int len = others.Count;
        for( int i=0 ; i<len ; i++ )
        {
            K next = others*;*

float nextDistSq = Vector3.SqrMagnitude( next.transform.position - myPosition );
// if( closer && below search threshold && not me )
if( nextDistSq<nearestDistSq && nextDistSq<maxSearchDistanceSq && nextDistSq>0f )
{
nearest = next;
nearestDistSq = nextDistSq;
hit = true;
}
}
nearestDist = hit ? Mathf.Sqrt(nearestDistSq) : float.MaxValue;
return hit;
}
public static bool FindNearest <T,K>
(
this T me ,
List others ,
float maxSearchDistance ,
out K nearest
)
where T : Component
where K : Component
{
return FindNearest( me:me , others:others , maxSearchDistance:maxSearchDistance , out nearest , out float nearestDist );
}
public static bool FindNearest <T,K>
(
this T me ,
K[] others ,
float maxSearchDistance ,
out K nearest ,
out float nearestDist
)
where T : Component
where K : Component
{
Vector3 myPosition = me.transform.position;
float maxSearchDistanceSq = maxSearchDistance * maxSearchDistance;
float nearestDistSq = float.MaxValue;
bool hit = false;
nearest = null;
int len = others.Length;
for( int i=0 ; i<len ; i++ )
{
K next = others*;*
float nextDistSq = Vector3.SqrMagnitude( next.transform.position - myPosition );
// if( closer && below search threshold && not me )
if( nextDistSq<nearestDistSq && nextDistSq<maxSearchDistanceSq && nextDistSq>0f )
{
nearest = next;
nearestDistSq = nextDistSq;
hit = true;
}
}
nearestDist = hit ? Mathf.Sqrt(nearestDistSq) : float.MaxValue;
return hit;
}
public static bool FindNearest <T,K>
(
this T me ,
K[] others ,
float maxSearchDistance ,
out K nearest
)
where T : Component
where K : Component
{
return FindNearest( me:me , others:others , maxSearchDistance:maxSearchDistance , out nearest , out float nearestDist );
}
}