Laden...

Unity Spawnalgorithmus auf einem Planeten mit Kollisionsabfrage

Erstellt von Garzec vor 7 Jahren Letzter Beitrag vor 7 Jahren 3.529 Views
G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 7 Jahren
Unity Spawnalgorithmus auf einem Planeten mit Kollisionsabfrage

Hallo,
ich programmiere für ein Schulprojekt eine Simulation. Dabei hat man eine Kugel (Planet) und Objekte(Haus, Baum, ...), die zufällig platziert werden und dann per Gravitation angezogen werden.

Ich habe schon die Routine, die das Spawnen übernimmt. Fehlen tut mir eine Kollisionsprüfung, ob das neu zu spawnende Objekt am Punkt x,y,z mit der Größe "a,b,c" platziert werden darf, OHNE mit einem bereits vorhandenen Objekt zu kollidieren.

Weil ein Haus auf einem Baum wäre ja komisch.

Aus diesem Grund soll vor dem Spawnen eine Prüfung stattfinden, die Schwierigkeit liegt da einfach an der Kugel 😠

Ich habe den Mittelpunkt (setzen wir mal 0,0,0), den Radius, die Position des Objektes und die Größe des Objektes.

Ich habe bereits angefangen, komme allerdings bei der Kollisionsprüfung nicht mehr weiter:


public class WorldGenerator : MonoBehaviour
{
    CalcCoordinates calculator; // Hilfsklasse

    GameObject planet; // Der Planet
    public GameObject[] planetPrefabs; // Objekte auf dem Planeten
    public GameObject[] skyPrefabs; // Objekte in der Luft
    public GameObject[] spacePrefabs; // Objekte im All

    void Start()
    {
        calculator = GameObject.FindGameObjectWithTag("Calculator").GetComponent<CalcCoordinates>(); 
        planet = GameObject.FindGameObjectWithTag("Planet");

        SpawnObjects(planet, planetPrefabs, PlayerPrefs.GetInt("planetObjectsCount"), 50); // SpawnRoutine
        SpawnObjects(planet, skyPrefabs, PlayerPrefs.GetInt("skyObjectsCount"), 150);
        SpawnObjects(planet, spacePrefabs, PlayerPrefs.GetInt("spaceObjectsCount"), 200);
    }

    private void SpawnObjects(GameObject planet, GameObject[] prefabs, float objectCount, float distanceToPlanet)
    {
        List<gameObject> spawnObjectsList = new List<gameObject>(); // speichere alle bereits erstellten Objekte
        for (int i = 0; i < objectCount; i++)
        {
            gameObject spawnObject = prefabs[Random.Range(0, prefabs.Length)]; // Welches Objekt soll gespawnt werden?
            Vector3 spawnPos = calculator.CalcPointOnSphere(distanceToPlanet); // Wo soll das Objekt gespawnt werden?
            if(CheckSpawnPoint(spawnObjectsList, spawnObject, spawnPos)) // Kollisionsprüfung
            {
                    gameObject objectToSpawn = (gameObject)Instantiate(spawnObject, spawnPos, Quaternion.identity); // neues Objekt
                    spawnObjectsList.Add(objectToSpawn); // das neue Objekt der Liste hinzufügen
            }
        }
    }
    
    private bool CheckSpawnPoint(List<gameObject> spawnObjectsList, GameObject objectToSpawn, Vector3 spawnPos)
    {
        foreach(gameObject listObject in spawnObjectsList) // alle bereits gespawnten Objekte
        {
            if(..) // wenn das zu spawnende objekt mit einem bereits vorhandenen Objekt kollidieren würde ...
            {
                return false;
            }
        }
        return true;
    }
}

Ich hoffe die Kommentare helfen ein wenig, manche Unity-Begriffe besser zu verstehen.

Sonst einfach melden 🙂

771 Beiträge seit 2009
vor 7 Jahren

Bin zwar kein Unity-Experte, aber prüfe mal ob dir Physics.OverlapSphere dabei hilft?

G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 7 Jahren

Das weiß ich auch nicht, dadurch würde ich ja erstmal nur die Objekte geliefert bekommen.. Die habe ich ja aber bereits eigentlich

771 Beiträge seit 2009
vor 7 Jahren

Was ich meine in Pseudocode:


do
{
  Platziere GameObject an zufälliger Position
} while(es gibt überlappende Objekte)

G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 7 Jahren

Hey,

durch die Schleife hängt sich das Projekt auf.

Also im Code

CalcCoordinates calculator;

    GameObject planet;
    public GameObject[] planetPrefabs;
    public GameObject[] skyPrefabs;
    public GameObject[] spacePrefabs;

    void Start()
    {
        calculator = GameObject.FindGameObjectWithTag("Calculator").GetComponent<CalcCoordinates>();

        planet = GameObject.FindGameObjectWithTag("Planet");

        SpawnObjects(planet, planetPrefabs, PlayerPrefs.GetInt("planetObjectsCount"), 50);
        SpawnObjects(planet, skyPrefabs, PlayerPrefs.GetInt("skyObjectsCount"), 150);
        SpawnObjects(planet, spacePrefabs, PlayerPrefs.GetInt("spaceObjectsCount"), 200);
    }

    private void SpawnObjects(GameObject planet, GameObject[] prefabs, float objectCount, float distanceToPlanet)
    {
        for (int i = 0; i < objectCount; i++)
        {
            Instantiate(prefabs[Random.Range(0, prefabs.Length)], calculator.CalcPointOnSphere(distanceToPlanet), Quaternion.identity);
        }
    }

hatte ich innerhalb der for-Schleife eingebaut:

Collider[] colliders = ...
do
{
Instantiate...
}
while
{
colliders != null;
}
5.658 Beiträge seit 2006
vor 7 Jahren

Hi Garzec,

du hast dir eine Endlosschleife gebaut, da sich die Bedingung colliders != null nicht ändert.

Außerdem ist die Verwendung von Collidern evtl. nicht das richtige für dein Vorhaben. Collider erkennen ja Kollisionen während des Spiels, also zwischen sich bewegendenden, dynamischen Objekten. Du willst ja, wenn ich das richtig verstehe, nur statische Objekte erzeugen. Da würde es reichen, bei der Erstellung zu überprüfen, ob ein Objekt mit einem anderen kollidiert.

Evtl. würde es den Vorgang erleichtern, wenn du statt 3D-Kollisionstests die Tests für die Objekte auf der Planetenoberfläche in 2D berechnen kannst, beispielsweise indem du die Koordinaten auf der Kugel mit einer Merkator- o.ä. Projektion in eine 2D-Fläche transformierst.

Weeks of programming can save you hours of planning

G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 7 Jahren

Ich hatte gehofft das Ganze mathematisch zu lösen. Alle vorhandenen Objekte werden in einer Liste gespeichert, sodass nur noch prüfen muss, ob das Objekt an dem Platz keinen anderen "Würfel" schneidet, der dort schon platziert ist.

5.658 Beiträge seit 2006
vor 7 Jahren

Natürlich löst man soetwas mathematisch. Aber du kannst auch die eingebauten Features benutzen, z.B. indem du prüfst, ob die BoundingBoxes der Objekte in der Liste mit der BoundingBox eines neuen Objekts kollidieren. Beispielsweise mit der Bounds.Intersects-Methode.

In 2D wäre diese Überprüfung performanter als in 3D, was sich bei sehr vielen Objekten bemerkbar machen würde. Aber dazu müßtest du deine 3D-Koordinaten erstmal in 2D transformieren.

Weeks of programming can save you hours of planning

G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 7 Jahren

Also da das Ganze "nur" ein Schulprojekt ist und ich das Ganze eher verstärkt dokumentieren muss, beschränke ich mich auf 100 Objekte. Einfach, damit mein alter Laptop das beim Vorstellen noch schafft.

Deine genannte Methode kannte ich noch gar nicht. Das klingt so, als wäre das Thema gar nicht so gigantisch wie gedacht 😄

Oder doch?

Bis jetzt hab ich erstmal meine alten Abi Bücher rauskramen müssen und viel Mathe auffrischen müssen 😛

G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 7 Jahren

Habe grade leider noch keinen Zugriff aufs Projekt, habe aber schonmal eine Routine versucht:


private void SpawnObjects(GameObject planet, GameObject[] prefabs, float objectCount, float distanceToPlanet)
    {
            List<Bounds> objectsBoundsList = new List<Bounds>();
            for (int i = 0; i < objectCount; i++)
            {
                    GameObject spawnObject = prefabs[Random.Range(0, prefabs.Length)];
                    Vector3 spawnPos = calculator.CalcPointOnSphere(distanceToPlanet);
                    Bounds tempSpawnBounds = new Bounds(spawnPos, spawnObject.GetComponent<Renderer>().bounds.size);
                    foreach (Bounds objectBounds in objectsBoundsList)
                    {
                            if (objectBounds.Intersects(tempSpawnBounds))
                            {
                                    continue;
                            }
                    }
                    GameObject objectToSpawn = (GameObject)Instantiate(spawnObject, spawnPos, Quaternion.identity);
                    objectsBoundsList.Add(new Bounds(objectToSpawn.transform.position, objectToSpawn.GetComponent<Renderer>().bounds.size));
            }
    }

Testen kann ichs leider noch nicht...

G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 7 Jahren

Nachtrag:

Konnte den Code jetzt mal testen. Der Debugger kommt bei 100 Objekten ca. 1-2 mal an die Stelle, die den Code abbricht, weil sich Objekte schneiden.

Größere Häuser können dennoch gelegentlich ineinander spawnen :S

T
314 Beiträge seit 2013
vor 7 Jahren

Dein Code bricht nie ab. Die foreach bewirkt genau nichts.
Das continue wirkt sich lediglich auf deine foreach aus.


private void SpawnObjects(GameObject planet, GameObject[] prefabs, float objectCount, float distanceToPlanet)
    {
            List<Bounds> objectsBoundsList = new List<Bounds>();
            for (int i = 0; i < objectCount; i++)
            {
                    GameObject spawnObject = prefabs[Random.Range(0, prefabs.Length)];
                    Vector3 spawnPos = calculator.CalcPointOnSphere(distanceToPlanet);
                    Bounds tempSpawnBounds = new Bounds(spawnPos, spawnObject.GetComponent<Renderer>().bounds.size);
                    bool isColliding = false;
                    foreach (Bounds objectBounds in objectsBoundsList)
                    {
                            if (objectBounds.Intersects(tempSpawnBounds))
                            {
                                    isColliding  =true;
                                    break;
                            }
                    }
                    if(isColliding)
                         continue;

                    GameObject objectToSpawn = (GameObject)Instantiate(spawnObject, spawnPos, Quaternion.identity);
                    objectsBoundsList.Add(new Bounds(objectToSpawn.transform.position, objectToSpawn.GetComponent<Renderer>().bounds.size));
            }
    }

Alternativ die ganze Foreach durch ein Any(...) ersetzen.

5.658 Beiträge seit 2006
vor 7 Jahren

Mit dem Debugger kann man solche Probleme relativ einfach aufspüren, indem man an den entsprechenden Stellen Haltepunkte setzt und den Code Schritt für Schritt durchläuft: [Artikel] Debugger: Wie verwende ich den von Visual Studio?

Weeks of programming can save you hours of planning

G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 7 Jahren

Ich weiß ja wie man den Debugger nutzt, ich weiß auch wie man ihn mit Unity nutzt.

Dennoch grübel ich über dem Thema, wieso manche Häuser noch ineinander hängen 😃

Selbst die genannte Codeänderung von t0ms3n lässt den Debugger nur 1x abbrechen, ich denke das sollte häufiger passieren.

5.658 Beiträge seit 2006
vor 7 Jahren

Naja, ohne Debugger kann man viel vermuten, aber nur schlecht herausfinden, woran es wirklich liegt.

Es gibt Möglichkeiten Unity-Games bzw. -Skripte zu debuggen, such einfach mal bei der Suchmaschine deiner Wahl danach.

Ich kenne mich damit nicht besonders gut aus, aber mir erscheint die Verwendung der BoundingBoxen der Objekte etwas merkwürdig: new Bounds(objectToSpawn.transform.position, objectToSpawn.GetComponent<Renderer>().bounds.size).

Eigentlich würde man erwarten, daß ein Objekt eine Eigenschaft mit der bereits transfomierten BoundingBox hat.

Weeks of programming can save you hours of planning

G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 7 Jahren

Die 2. Möglichkeit anstatt des Renderers wäre der Collider.

Aber da die Modelle ja aus Einzelteilen bestehen ist es schwierig dem leeren Gameobject als Container einen Collider zu geben