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

[SOLVED] c# Find nearest object (via tag) and update constantly?

Discussion in 'Scripting' started by tarikkoenig, Oct 22, 2018.

  1. tarikkoenig

    tarikkoenig

    Joined:
    Sep 5, 2018
    Posts:
    6
    Hey Unity Community,

    yes, i am aware that this Forum already has several threads with a similar topic, but none of it could help me since i am a complete newbie in programming.

    Step1: Core Issue: I want to find the nearest Gameobject to my 'sensor' of a total of 73 every FixedTimestep (0.02sec).
    Step2: Then i want to extract the objects position Coordinates, and fill an Array with X,Y Coordinates, and name of the GO.

    The objects are moving. I know that FindbyTag operators on many objects at this rate is very CPU consuming, but it's a very simple 2D- game so i shouldn't have any problems with it.


    i already found several solutions to the core issue here like:
    https://docs.unity3d.com/ScriptReference/GameObject.FindGameObjectsWithTag.html

    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. }
    The issue here is, that this is done within the declaration/initialization process. i can't simply put that in the FixedUpdate() void. but can't i first just initialize it, and define it later on in the void Fixedupdate() void?

    another solution i found at this forum:
    https://forum.unity.com/threads/clean-est-way-to-find-nearest-object-of-many-c.44315/
    Code (CSharp):
    1.     Transform GetClosestEnemy (Transform[] enemies)
    2.     {
    3.         Transform bestTarget = null;
    4.         float closestDistanceSqr = Mathf.Infinity;
    5.         Vector3 currentPosition = transform.position;
    6.         foreach(Transform potentialTarget in enemies)
    7.         {
    8.             Vector3 directionToTarget = potentialTarget.position - currentPosition;
    9.             float dSqrToTarget = directionToTarget.sqrMagnitude;
    10.             if(dSqrToTarget < closestDistanceSqr)
    11.             {
    12.                 closestDistanceSqr = dSqrToTarget;
    13.                 bestTarget = potentialTarget;
    14.             }
    15.         }
    16.    
    17.         return bestTarget;
    18.     }
    i don't know how to apply this to my problem since i am new to C# and especially Unity. i don't even understand the commands.
    how would i adress my Gameobjects with this? Somebody there said "Simple pass just the gameobjects with that tag into the array parameter.". How though? and how would the full code look like if i wanted it to update every FixedTimestep?


    Would be so glad for help, i have to solve this within 3 days and i am desperate.

    Greetings
     
  2. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    Are you sure you need to find the closest enemy every single frame? You almost always can get similar results by getting the closest enemy every 0.5 seconds and caching it, and that would reduce your CPU cost for this by over 90%.

    No matter which way you go, don't use any of the "Find" methods by tag, name, etc. When an enemy runs it's Start method have it add itself to a central list of enemies, and in OnDestroy have it remove itself. If you made the list a static member of a class, then you could reference it without having to find the object hosting it either. So you won't have to Find by tag at all.

    Then at whatever interval you choose, you just iterate through the list instead of first populating the list and then iterating through it on every use.
     
    Last edited: Oct 22, 2018
    egoquat and tarikkoenig like this.
  3. tarikkoenig

    tarikkoenig

    Joined:
    Sep 5, 2018
    Posts:
    6
    I think 0.1 seconds would be fine, they move very quickly.

    The enemies don‘t get destroyed. So i could create an array with all enemies inside and then reference it? Sounds plausible.
    How would i initialize the array filled with gameobjects, though? I only know int, double, string etc.

    And which part of the 2nd code above should be in the Update void?

    If you don‘t mind, would you upload an example of the corrected code from above (2nd one) with an array of e.g. Two enemies? Would help a lot!
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,384
    FindGameObjectsWithTag is both slow and creates an array every time it's called. This can be an issue.

    What I'd do instead is create an 'Enemy' script that had a collection for accessing any that currently exist. I do this frequently with what I call a 'MultitonPool'. I wrote a custom one which can be seen here with some querying checks and the sort:
    https://github.com/lordofduct/space...ppyUnityFramework/Collections/MultitonPool.cs

    And here is an example of using it. I'm also implementing an Octree for fast spacial tracking, but I haven't finished it, and I don't need it currently:
    https://github.com/lordofduct/space...ster/SPSensors/Sensors/Visual/VisualAspect.cs

    This all my be overkill for you though...

    So a trimmed down version can be accomplished with a simple 'HashSet' like so:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. public class Enemy : MonoBehaviour
    6. {
    7.  
    8.     public readonly static HashSet<Enemy> Pool = new HashSet<Enemy>();
    9.  
    10.     private void OnEnable()
    11.     {
    12.         Enemy.Pool.Add(this);
    13.     }
    14.  
    15.     private void OnDisable()
    16.     {
    17.         Enemy.Pool.Remove(this);
    18.     }
    19.  
    20.  
    21.  
    22.     public static Enemy FindClosestEnemy(Vector3 pos)
    23.     {
    24.         Enemy result = null;
    25.         float dist = float.PositiveInfinity;
    26.         var e = Enemy.Pool.GetEnumerator();
    27.         while(e.MoveNext())
    28.         {
    29.             float d = (e.Current.transform.position - pos).sqrMagnitude;
    30.             if(d < dist)
    31.             {
    32.                 result = e.Current;
    33.                 dist = d;
    34.             }
    35.         }
    36.         return result;
    37.     }
    38.  
    39. }
    40.  
    Then you can just call the static method 'FindClosestEnemy' from anywhere passing along the position you want to find near to.

    You just slap this 'Enemy' script on any GameObject that is an enemy... rather than tagging it.

    Note I use a 'HashSet' because as a collection it forces uniqueness (so no repeat entries), and it has a fast insert for large collections. And if you have a lot of enemies, you're going to have a large collection.
     
  5. tarikkoenig

    tarikkoenig

    Joined:
    Sep 5, 2018
    Posts:
    6
    Wow this is so smooth !! I will try this right this morning!! I did understand how this works, but not every detail.

    As i understood, only this part has to be put on every Enemy GO? And then i use this Method on my GameObject i want to know the distance from:

    Would i therefore simply put this method code in the FixedUpdate void ?

    I am sorry to ask i am unexperienced with all this, i slowly feel like a dummy.
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,384
    You'd call that method from FixedUpdate... something like this:

    Code (csharp):
    1.  
    2. void FixedUpdate()
    3. {
    4.      var nearestEnemy = Enemy.FindClosestEnemy(transform.position);
    5.      //do stuff with that enemy
    6. }
    7.  
     
  7. tarikkoenig

    tarikkoenig

    Joined:
    Sep 5, 2018
    Posts:
    6
    Oh so it‘s even simpler thank you and have a great day you definetly made mine !! I hope this helps a few other newbies hahaha

    I‘ll mark this as solved
     
  8. McMurderousStudios

    McMurderousStudios

    Joined:
    May 12, 2019
    Posts:
    16
    I know this post is old. Anyone doing a google search can apply the same concepts above to multiple teams in their game (Good vs Bad), simply copy the script twice. For simplicity reasons, we will name a script "Ally" while the other is "Enemy"

    Then you can find the closest enemy to the ally by accessing the pool. I'll explain in comments in the below script.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3. public class Ally: MonoBehaviour //<-- Attach to your ally obviously
    4. {
    5.     public readonly static HashSet<Ally> Pool = new HashSet<Ally>(); //You're going to want to add your ally to your enemy's list too. So that both can find each other.
    6.     private void OnEnable()
    7.     {
    8.         Ally.Pool.Add(this); //Keep it consistent, obviously again.  :)
    9.     }
    10.     private void OnDisable()
    11.     {
    12.         Ally.Pool.Remove(this);
    13.     }
    14.     public static Ally FindClosestAlly(Vector3 pos) //This public static will be read by your enemy script, it makes sense to name it this, to find the closest ally
    15.     {
    16.         Ally result = null;
    17.         float dist = float.PositiveInfinity;
    18.         var e = Ally.Pool.GetEnumerator();
    19.         while(e.MoveNext())
    20.         {
    21.             float d = (e.Current.transform.position - pos).sqrMagnitude;
    22.             if(d < dist)
    23.             {
    24.                 result = e.Current;
    25.                 dist = d;
    26.             }
    27.         }
    28.         return result;
    29.     }
    30. void FixedUpdate() //This is the key part of the code, do not change anything here to "Ally" (in this case, you could have named it something else than ally). If this script is attached to your ally, this part of the code is responsible for finding the closest enemy. We can leave this part alone
    31. {
    32.      var nearestEnemy = Enemy.FindClosestEnemy(transform.position); //On your enemy script, change this from "Enemy" to "Ally" for the expected result.
    33.      //do stuff with that enemy
    34. }
    35. }
    I have explained how the code can be applied to team-based games in // comments.
    Hopefully this helps a google user :)
     
    InsaneDuane and mpbMKE like this.
  9. mpbMKE

    mpbMKE

    Joined:
    Aug 13, 2019
    Posts:
    3
    This is all great stuff and a perfect intro into practical object pooling, thanks!
     
  10. InsaneDuane

    InsaneDuane

    Joined:
    Nov 1, 2019
    Posts:
    9

    This is working great except when a team gets annihilated (i.e Enemy) the other team (i.e. Ally) keep shooting in the direction of the last opponent. I tried adding "nearestEnemy=null" at the bottom of void FixedUpdate() and added

    if (nearestEnemy == null)
    { _animator.SetBool("isAttacking", false);
    agent.SetDestination(gameObject.transform.localPosition); }

    but no change. any ideas? Except for this one issue I am loving this script snippet. :)
     
  11. InsaneDuane

    InsaneDuane

    Joined:
    Nov 1, 2019
    Posts:
    9
    I noticed if there is at least one enemy my team acts like they should (wont continue to shoot and advance when all the enemies are gone) so my "fix/band-aid" was I created an empty object with enemy script attached and put it well beyond their "do something" range. I would still like to know the "correct way" to solve this but I have MANY more problems to solve. I'm working on a Unity VR shooter type of game and I am obsessed. 8o
     
  12. bwwebs

    bwwebs

    Joined:
    Apr 19, 2020
    Posts:
    1

    Awesome solution, exactly what I was looking for, thank you so much!