Search Unity

Find nearest object with tag

Discussion in 'Scripting' started by RandomSpell, Jul 16, 2019.

  1. RandomSpell

    RandomSpell

    Joined:
    Nov 30, 2018
    Posts:
    12
    I have a script that moves an object towards objects with a certain tag and unity has an example script finding the nearest object with a certain tag. The issue is I'm not sure how to combine the two scripts.

    Unity's example script:
    Code (CSharp):
    1. // Find the name of the closest enemy
    2.  
    3. using UnityEngine;
    4. using System.Collections;
    5.  
    6. public class ExampleClass : MonoBehaviour
    7. {
    8.     public GameObject FindClosestEnemy()
    9.     {
    10.         GameObject[] gos;
    11.         gos = GameObject.FindGameObjectsWithTag("Enemy");
    12.         GameObject closest = null;
    13.         float distance = Mathf.Infinity;
    14.         Vector3 position = transform.position;
    15.         foreach (GameObject go in gos)
    16.         {
    17.             Vector3 diff = go.transform.position - position;
    18.             float curDistance = diff.sqrMagnitude;
    19.             if (curDistance < distance)
    20.             {
    21.                 closest = go;
    22.                 distance = curDistance;
    23.             }
    24.         }
    25.         return closest;
    26.     }
    27. }
    My script:
    Code (CSharp):
    1. {
    2.     private Transform target;
    3.     public float speed = 5f;
    4.  
    5.     void Update()
    6.     {
    7.          target = GameObject.FindGameObjectWithTag("Resource").transform;
    8.             transform.position = Vector2.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
    9.     }
    10.  
    11. }
    It seems like it should be super simple but I'm just not seeing it...

    Any help would be greatly appreciated!
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,534
    I would advise against using tags in this situation, especially for how much garbage you going to generate calling 'FindGameObjectsWithTag' every Update (which allocates an array of everything).

    Instead create a script like:
    Code (csharp):
    1.  
    2. public class ChaseableEntity : MonoBehaviour
    3. {
    4.  
    5.     public static readonly HashSet<ChaseableEntity> Entities = new HashSet<ChaseableEntity>();
    6.  
    7.     void Awake()
    8.     {
    9.         Entities.Add(this);
    10.     }
    11.  
    12.     void OnDestroy()
    13.     {
    14.         Entities.Remove(this);
    15.     }
    16.  
    17. }
    18.  
    (you could use Enable/Disable if you wanted as well for a slightly different behaviour)

    Then just loop those entities and do your distance comparison.
    Code (csharp):
    1.  
    2. Vector3 pos = this.transform.position;
    3. float dist = float.PositiveInfinity;
    4. ChaseableEntity targ = null;
    5. foreach(var obj in ChaseableEntity.Entities)
    6. {
    7.     var d = (pos - obj.transform.position).sqrMagnitude;
    8.     if(d < dist)
    9.     {
    10.         targ = obj;
    11.         dist = d;
    12.     }
    13. }
    14. //do whatever with targ
    15.  
     
    Vacerdin, AndersMalmgren and GeorgeCH like this.
  3. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    You would just call FindClosestEnemy, but look for the proper tag you're using, and assign the result to your target transform. But as already mentioned this isn't something you should be doing every update. FindGameObjectsWithTag is an expensive call, and not something you should be running every frame.

    Whenever I'm in a position where FindGameObjectsWithTag would be the easy way to go, I put it on a timer. So I'd call FindClosestEnemy either if target is null, or if 10 seconds have passed since the last time I called it. So instead of calling FindGameObjectsWithTag ~60 times every second or ~3600 times per minute, you call it about 6 times per minute. Much less costly, and brings FindGameObjectsWithTag into the realm of actual usability during gameplay.

    Of course you should profile the results. Typically though, maintaining a collection of whatever you want to find, and just iterating through that collection as needed, is a better approach.
     
    Waffle247 and RandomSpell like this.
  4. GeorgeCH

    GeorgeCH

    Joined:
    Oct 5, 2016
    Posts:
    222
    As others posters have said, calling FindObjectsWithTag every frame is expensive, not to mention everything that comes with it (creating a new array every frame - ouch!) This is the sort of thing that you can probably get away with when developing relatively simple stuff for desktops but would absolutely kill performance on a mobile platform.

    For a more performance solution, you have roughly two options:
    • A Circle/Sphere overlap (depending on whether you're doing it in 2D or 3D), which basically raycasts over a circle/area every frame (emanating from the player's current location), grabs every object that was hit and stored it in a pre-existing array (hence, no allocations). Then, loop through the results of the raycast, see whether any of the objects has the tag you need (hint: use gameObject.CompareTag(), not gameObject.tag == "tag"), gets their distance from the player, and handles the subsequent movement.
    • Even better, what you really need to know is where each enemy is and where the player is. So, instead of looking for this information every frame, consider creating a singleton (e.g., EnemyManager) that is responsible for keeping track of each enemy's location (a simple List<Transform> would do it). Then, every frame, have your player script loop through the singleton's list of enemies, find the closest enemy based on the distance, and set that enemy's position as the player's destination. Using this approach, you avoid all raycasting / finding, and this is probably going to be the most performant solution. (EDIT: Upon re-reading, this is the solution that @lordofduct suggested).
    Good luck!
     
  5. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,748
    Copy the example to your class, change tag, and in your code replace
    Code (CSharp):
    1. FindGameObjectWithTag("Resource")
    with
    Code (CSharp):
    1. FindClosestEnemy()
     
  6. RandomSpell

    RandomSpell

    Joined:
    Nov 30, 2018
    Posts:
    12
    This looks like a simple solution but I don't understand it very well... It's hard for me to decipher what words are being used as placeholders (if any).

    Is the first script supposed to be on the gameobjects that are supposed to be sought out by the gameobject that is connected to the second script?

    Sorry, I'm dense