Mam problem z prawidłowym wykryciem bliskość obiektów przy użyciu obliczeń odległości i mieć nadzieję, że jeden z was może mi pomóc.

Co chcę: Istnieje kilka obiektów rozgrywanych z tą samą tagiem w mojej scenie i chcę zmienić kolor materiału, jeśli ich odległość na osi X i Z znajduje się poniżej "1". Aby to iterować na liście wszystkich obiektów i porównaj ich pozycję z pozycją bieżącego obiektu gry.

Problem: Materiał kolory zmieniają się losowo na zderzeniowych obiektach, a czasami nie zmieniają się, gdy kolizja się skończyła.

mój kod do tej pory:

     public class DistanceTester : MonoBehaviour
 {
     void Start()
     {
 
     }
 
     void Update()
     {
         var menschen = GameObject.FindGameObjectsWithTag("Mensch");
         float positionX = transform.position.x;
         float positionZ = transform.position.z;
 
         foreach (GameObject mensch in menschen)
         {
             float distanceX = Mathf.Abs(mensch.transform.position.x - positionX);
             float distanceZ = Mathf.Abs(mensch.transform.position.z - positionZ);
 
             if (gameObject != mensch) //Making sure the object is not the same
             {
                 if (distanceX <= 1 && distanceZ <= 1)
                 {
                     GetComponent<Renderer>().material.color = Color.red;
                     mensch.GetComponent<Renderer>().material.color = Color.red;
                 }
                 else
                 {
                     GetComponent<Renderer>().material.color = Color.green;
                     mensch.GetComponent<Renderer>().material.color = Color.green;
                 }
             }
         }
     }
 }

Próbowałem już użyć wyzwalaczy do wykrywania kolizji, ale chciałby korzystać bardziej efektywny sposób w moim przykładzie powyżej.

1
jim_mccann 28 lipiec 2020, 14:19

1 odpowiedź

Najlepsza odpowiedź

Głównym problemem jest prawdopodobnie

GetComponent<Renderer>().material.color = ...;

Więc co jeśli jesteś blisko menschen[0], ale daleko od menschen[1]?

→ Zresetuj swój kolor zawsze z wynikiem ostatniej pozycji w menschen!


Brzmi to tak, jak powinieneś raczej obsługiwać własny obiekt, ponieważ wszystkie inne obiekty robią to samo dobrze?

using Sytsem.Linq;

public class DistanceTester : MonoBehaviour
{
     // reference this via the Inspector already
     [SerializeField] private Renderer _renderer;

     private void Awake()
     {
         // As fallback get it ONCE
         if(!_renderer) _renderer = GetComponent<Renderer>();
     }
 
     private void Update()
     {
         // If possible you should also store this ONCE
         var menschen = GameObject.FindGameObjectsWithTag("Mensch");

         // This checks if ANY of the objects that is not this object is clsoe enough
         if(menschen.Where(m => m != gameObject).Any(m => (transform.position - m.transform.position).sqrMagnitude < 1))
         {
             _renderer.material.color = Color.red;
         }
         else
         {
             _renderer.material.color = Color.green;
         } 
     }
 }

Gdzie ten ekspresja LINQ za pomocą Where i < href = "https://docs.microsoft.com/dotnet/api/system.linq.Enumerable.any" Rel = "NOFollow NefErr"> Any

menschen.Where(m => m!= gameObject).Any(m => (transform.position - m.transform.position).sqrMagnitude < 1)

Zasadniczo równa się robi coś takiego

var isClose = false;
foreach(var m in menschen)
{
    if(m == gameObject) continue;

    if((transform.position - m.transform.position).sqrMagnitude < 1)
    {
        isClose = true;
        break;
    }
}

if(isClose)
{
    ...

Zauważ, że nadal będzie bardziej wydajny, jeśli możesz przechowywać wynik FindGameObjectsWithTag raz zamiast uzyskać każdą ramkę.

Zakładając, że którekolwiek z obiektów Mensch będzie miał komponent DistanceTester mógłbyś nawet wdrożyć jakiś rodzaj "auto-detekcji", używając wzorca

public class DistanceTester : MonoBehaviour
{
    private static HashSet<DistanceTester> _instances = new HashSet<DistanceTester>();

    private void Awake()
    {
        _instances.Add(this);

        ...
    }

    private void OnDestroy()
    {
        _instances.Remove(this);
    }

    ...
}

Wtedy możesz bardzo sprawić, że zamiast tego możesz zachować iterować _instances.

Jeszcze bardziej wydajne chociaż rzeczywiście będzie iterować tylko raz z globalnego kontrolera zamiast robienia tego w każdej instancji DistanceTester!

2
derHugo 28 lipiec 2020, 11:45