Jump to content
Unity Insider Forum

Spawnen von GameObjects im definierten Bereich


Chronicle

Recommended Posts

Moin,

ich bins mal wieder und versuch gerade mir wieder etwas C# beizubringen. Derzeit möchte ich in einem definierten Bereich (grün) gelbe Quadrate spawnen lassen. Dabei soll der rote Bereich ausgelassen werden und sich keine der gelben Quadrate berühren. Wenn Sie sich berühren, soll eine neue Position gesucht werden. Hierfür mal ein kleines Bild zum Verständnis:

grafik.png.a32ad7d1e094a6bda70da37ae7a6254a.png

Das die gelbvben Quadrate manchmal etwas außerhalb der grünen Fläche landen liegt am "Centerpoint" des Objektes vermute ich mal.

Der Code des Programms sieht bisher so aus:

public class ObjectSpawnInArea : MonoBehaviour
{
    public GameObject GameObjectprefab;
    public Vector3 centerPositionSpawnArea;
    public Vector3 sizeSpawnArea;
    public Vector3 sizeNonSpawnArea;
    public Vector3 centerPositionNonSpawnArea;
    private Vector3 PositionForSpawn;
    private Vector3 PositionForNonSpawn;
    private Vector3 FinalSpawnPosition;



    // Start is called before the first frame update
    void Start()
    {
        for (int i = 0; i <= 10; i++)
            SpawnGameObject();
         //Funktion zum spawnen wird aufgerufen. Bei Start wird GameObject 10-Mal gespawnt.
    }    
    


    // Update is called once per frame
    void Update()
    {
        
    }

    // Diese Funktion färbt die Fläche ein, in der das GameObject Spawnen soll ... 
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = new Color(0, 1, 0, 0.5f); //Farbeinstellungen
        Gizmos.DrawCube(centerPositionSpawnArea, sizeSpawnArea); //Position der Fläche 2D bzw. des Würfels 3D
        Gizmos.color = new Color(1, 0, 0, 0.5f);
        Gizmos.DrawCube(centerPositionNonSpawnArea, sizeNonSpawnArea);
    }

    //Diese Funktion ermittelt die Position des Spawnpunktes für ein GameObject in der eingefärbten Fläche über die Variable sizeSpawnArea
    private void SpawnGameObject()
    {
        PositionForSpawn = centerPositionSpawnArea + new Vector3(Random.Range(-sizeSpawnArea.x / 2, sizeSpawnArea.x / 2), Random.Range(-sizeSpawnArea.y / 2, sizeSpawnArea.y / 2), 0); //z=0 wegen 2D
        Instantiate(GameObjectprefab, PositionForSpawn, Quaternion.identity); //Sorgt für die Auswahl des GameObjectes Prefab, also welches Object (Puzzleteil, Gegner, Spieler). Quaternion ist die Rotation des Objects. Quaternion.identity = keine Rotation.
    }
}

 

Wo brauche ich nun eure Hilfe. Ich verstehe nicht wie ich die Collision detekten kann und dann dem Programm sag, such nach detektierter Collision eine neue Position. Ich hab da was mit dieser funktion OnCollisionEnter2D() gearbeitet, aber irgendwie komme ich da nicht weiter.

Der zweite Punkt ist, wie kann ich bei der Ermittlung der "Random Position" sagen, lass die rote Fläche aus?

 

Bin wirklich für jede Hilfe dankbar.

 

Grüße

 

 

 

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Kleines Update. Das Problem mit der Area in der nicht gespawnt werden soll, konnte ich lösen ...

grafik.png.d37e7ec27deccae205f253794e9f243c.png

der Code dazu sieht nun so aus:

public class ObjectSpawnInArea : MonoBehaviour
{
    public GameObject GameObjectprefab;
    public Vector3 centerPositionSpawnArea;
    public Vector3 sizeSpawnArea;
    public Vector3 sizeNonSpawnArea;
    public Vector3 centerPositionNonSpawnArea;
    private Vector3 PositionForSpawn;

    // Start is called before the first frame update
    void Start()
    {
        for (int i = 0; i < 10; i++) 
        {
            SpawnGameObject();          //Funktion zum spawnen wird aufgerufen. Bei Start wird GameObject 10-Mal gespawnt

        }
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    // Diese Funktion färbt die Fläche ein, in der das GameObject Spawnen soll ... 
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = new Color(0, 1, 0, 0.5f); //Farbeinstellungen
        Gizmos.DrawCube(centerPositionSpawnArea, sizeSpawnArea); //Position der Fläche 2D bzw. des Würfels 3D
        Gizmos.color = new Color(1, 0, 0, 0.5f);
        Gizmos.DrawCube(centerPositionNonSpawnArea, sizeNonSpawnArea);
    }

    //Diese Funktion ermittelt die Position des Spawnpunktes für ein GameObject in der eingefärbten Fläche über die Variable sizeSpawnArea
    private void SpawnGameObject()
    {
        PositionForSpawn = centerPositionSpawnArea + new Vector3(Random_Value(sizeNonSpawnArea.x, sizeSpawnArea.x)/2, Random_Value(sizeNonSpawnArea.y, sizeSpawnArea.y)/2, 0); //z=0 wegen 2D
        Instantiate(GameObjectprefab, PositionForSpawn, Quaternion.identity); //Sorgt für die Auswahl des GameObjectes Prefab, also welches Object (Puzzleteil, Gegner, Spieler). Quaternion ist die Rotation des Objects. Quaternion.identity = keine Rotation.
    }

    //Diese Funktion generiert positive oder negative zufallszahlen mit wechselndem Vorzeichen
    private float Random_Value(float Minimum, float Maximum)
    {
        float value = Random.Range(Minimum, Maximum); // Zahl zwischen Minimum und Maximum
        float sign = Random.value < 0.5f ? -1f : 1f; // Generiert + oder -

        return value * sign;
    }
    
}

Nun fehlt nur noch das nicht kollidieren ...

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 4 Stunden schrieb Chroncile:

such nach detektierter Collision eine neue Position.

Moin!

Naja, die Grundidee ist einfach:

do
{
  position = FindRandomPosition();
}
while(IsBlocked(position));

SpawnAt(position);

Bin kein großer Fan davon, weil ich es nicht mag, dass hier die Laufzeit theoretisch unbestimmt lang werden kann, aber solange du nur ein paar Dinger baust und nicht 90% der Fläche voll machst, sollte es zu verkraften sein. Vor allem, weil Alternativen schwierig zu implementieren sind.

Ich kann mir vorstellen, dass die Frage auch nach IsBlocked ist; wie man das implementieren würde. Wenn du auf deinen Objekten Collider2D drauf hast, dann kannst du mit Physics2D.OverlapBox schauen, ob ein Collider mit der angegebenen Box überschneidet.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Moin,

danke für die Antwort! Ich bin auch schon über diese Overlapdinger gestolpert, komme aber nicht mit der offiziellen Doku klar. Bin da noch irgendwie zu blöd für um das ganze kauderwelsch zu verstehen, bzw finde es schade das dort selten beispiele sind die man verfolgen kann 😕 ...

ich möchte ja einfach nur abfragen mit if("Overlap detected") dann suche neue Position ...

Ich weiß auch nicht wieso da aufeinmal Vektoren für die Box angegeben werden müssen .... ich kenne die Vektorposition der Box doch gar nicht, weil ich die Position des Colliders nicht kenne ... oder ist das die Box in der ein Overlap detektiert werden soll? ich heul fast ... den ganzen Herrentag verschwendet und kein Stück weiter 😕 ... ^^

Grüße

Link zu diesem Kommentar
Auf anderen Seiten teilen

Moin,

meine Funktion für das Spawnen sieht derzeit so aus:

    private void SpawnGameObject(int amountOfSpwans)
    {
        for (int i = 0; i < amountOfSpwans; i++)
        {
            PositionForSpawn = centerPositionSpawnArea + new Vector3(Random_Value(sizeNonSpawnArea.x, sizeSpawnArea.x)/2, Random_Value(sizeNonSpawnArea.y, sizeSpawnArea.y)/2, 0); //z=0 wegen 2D       
            CollisionDetected = Physics2D.OverlapCircle(PositionForSpawn, radius); //Prüft ob das aktuelle GameObject mit einem anderen kollidiert. Wichtig. Das Prefab braucht einen Collider. 
            if (CollisionDetected == false)
                Instantiate(GameObjectprefab, PositionForSpawn, Quaternion.identity); //Sorgt für die Platzierung des GameObjectes Prefab, also welches Object (Puzzleteil, Gegner, Spieler). Quaternion ist die Rotation des Objects. Quaternion.identity = keine Rotation
            else if (CollisionDetected == true)
                i--;
        }
    }

Das Problem ist, das mein programm nun abstürzt, da ich vermutlich eine Endlosschleife gebaut habe. Wie kann ich ihm denn nun sagen, hey, wenn du eine Collision Detected hast, dann such weiter nach einem Platz!

Mein problem ist, ohne das else if, hält das Programm die Anzahl der Spawns nicht ein. Geht das vielleicht mit While? Keine Ahnung 😕 ...

 

Grüße

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Meiner meinung nach müsste das so aussehen .... funktioniert leider nicht ...

    private void SpawnGameObject(int amountOfSpwans)
    {
        for (int i = 0; i < amountOfSpwans; i++)
        {
            PositionForSpawn = centerPositionSpawnArea + new Vector3(Random_Value(sizeNonSpawnArea.x, sizeSpawnArea.x)/2, Random_Value(sizeNonSpawnArea.y, sizeSpawnArea.y)/2, 0); //z=0 wegen 2D       
            CollisionDetected = Physics2D.OverlapCircle(PositionForSpawn, radius); //Prüft ob das aktuelle GameObject mit einem anderen kollidiert. Wichtig. Das Prefab braucht einen Collider. 
            if (CollisionDetected == false)
                Instantiate(GameObjectprefab, PositionForSpawn, Quaternion.identity); //Sorgt für die Platzierung des GameObjectes Prefab, also welches Object (Puzzleteil, Gegner, Spieler). Quaternion ist die Rotation des Objects. Quaternion.identity = keine Rotation
            else
                amountOfSpwans++;
        }
    }

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du hast ja schon eine for-Schleife, und dein Ansatz würde für sich genommen funktionieren. Auch wenn du das if hinter dem else weglassen kannst, weil das ja schon der "sonst"-Fall ist. CollisionDetected kann in dem Moment sowieso nur true sein, brauchst du also nicht noch einmal zu überprüfen.

Über die Lesbarkeit des Codes kann man streiten (und das meine ich ganz wörtlich - ist echt Gechmackssache). Ansonsten sieht das schon ganz gut aus. Ein Problem kann ich im Code selbst nicht erkennen. Daher wäre meine Vermutung, dass da doch zu viele Collider in der Szene sind und er einfach keinen freien Platz mehr findet. Genau diese Problematik, die ich zuvor geschildert habe. Wenn du amountOfSpawns auf 1 setzt, geht es vermutlich, oder?

Edit: Hehe, da haste direkt noch ein Update gepostet während ich geschrieben habe. Nur so als Nebeninfo: Es ist unüblich, die Zählervariable oder das Limit einer for-Schleife in dessen Rumpf zu ändern. Leute erwarten, dass die Schleife "amountOfSpawns-oft" durchläuft, stimmt dann am Ende aber gar nicht. Ansonsten die neue Variante genauso brauchbar wie die davor.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Wenn du die Durchläufe der for-Schleife nicht anfassen willst, muss in ihrem Rumpf eine zweite Schleife her:

private void SpawnGameObject(int amountOfSpwans)
{
  for (int i = 0; i < amountOfSpwans; i++)
  {
    do
    {
      PositionForSpawn = ...
    }
    while(IsBlocked(PositionForSpawn));
    SpawnAt(PositionForSpawn);
  }
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Danke, wie genau funktioniert so ene while Schleife eigentlich?

 

und was meinst du mit folgender Aussage?

Zitat

Leute erwarten, dass die Schleife "amountOfSpawns-oft" durchläuft, stimmt dann am Ende aber gar nicht.

Also ich dachte immer das eine for-Schleife so lange durchläuft, bis amountOfSpawns erreicht ist ...

Grüße

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ja, technisch sind deine beiden Codes halt unbedenklich. Aber guten Code zu schreiben heißt nicht, dass er läuft - es heißt, dass man Code schreibt, der gewisse Qualitätskriterien erfüllt. So sollte dein Code z.B. "wartbar" sein. Das heißt z.B., dass wenn du eine einzelne Sache ändern willst, du am besten nicht mehrere Codestellen anfassen musst, von denen du dann vielleicht eine vergisst. So als Beispiel:

if (something)
{
  ...
}
else if (!something)
{
  ...
}

Funktioniert 1a, aber wenn die "..."-Blöcke etwas länger sind, und du möchtest "something" ändern (kann ja auch etwas komplexeres sein, so wie "a + b > c + d"), dann besteht halt die Gefahr, dass du das oben änderst und unten vergisst. Der Code ist also nicht sehr wartbar, und das zweite if wegzulassen ist sinnvoll.

Eine weitere Sache ist Lesbarkeit. Wenn jemand anders deinen Code liest, oder du ihn selber nach einem Monat das erste Mal wieder anschaust, dann sollte er schnell zu verstehen sein und möglichst schwer zu missverstehen. for-Schleifen sind dabei, wenn sie diesem Standardmuster mit (i = 0; i < max; i++) entsprechen, ein sehr weit verbreitetes Ding, das quasi jeder sofort erkennt. Und es wird eben erwartet, dass der Rumpf der Schleife "max"-Mal durchläuft - nicht öfter oder seltener. Wenn sich im Rumpf aber eine kleine Zeile versteckt, die das ändert, dann kann man da schonmal beim Debuggen von der Spur des Bugs abgelenkt werden, den man gerade zu beheben versucht. Ist also kein Weltuntergang, aber halt der Unterschied zwischen funktionierendem Code und qualitativem Code.

vor 3 Stunden schrieb Chroncile:

Danke, wie genau funktioniert so ene while Schleife eigentlich?

C# kennt vier Arten von Schleifen: while, do-while, for und foreach. (if ist übrigend keine Schleife, weil ein if-Statement keine Wiederholung verursacht).

while:

while (bedingung)
{
  Zeug
}

Checkt die Bedingung, wenn sie zutrifft, wird Zeug ausgeführt und dann das ganze von vorne.

do-while:

do
{
  Zeug
}
while (bedingung);

Ist quasi dasselbe, aber hier wird zuerst einmal Zeug gemacht und dann gecheckt, ob die Bedingung zutrifft und Zeug entsprechend wiederholt werden soll. Das benutze ich hier, weil ich natürlich erstmal eine Zufallsposition würfeln will und dann schauen will, ob sie geblockt ist.

for... naja, die kennst du ja :)

for (Initialisierung; bedingung; Update)
{
  Zeug
}

Initialisierung, dann Bedingung checken, wenn ja, dann Zeug, dann Update, und dann zurück zum Bedingungs-Check. Ist eine hübschere Schreibweise, aber ansonsten äquivalent, für

Initialisierung
while (bedingung)
{
  Zeug
  update
}

Und dann gibt's noch foreach, womit du über alle Elemente einer Aufzählung iterieren kannst:

foreach (var auto in meineGarage)
{
  Debug.Log(auto);
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 6 Stunden schrieb Sascha:

Nein, bei foreach gibt es kein "wenn". Da wird über eine Sammlung von Elementen (z.B. eine Liste) drübergegangen, für jedes Element ein Durchlauf.

ich hätte da noch eine Frage. Transform.Position ... kann es sein das hier keine Collider betrachtet werden wenn man darüber Gegenstände verschiebt? Muss ich das quasi wie beamen betrachten? dann muss ich sicherlich mit .MoveTowarrds arbeiten oder?

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 1 Stunde schrieb Chroncile:

ich hätte da noch eine Frage. Transform.Position ... kann es sein das hier keine Collider betrachtet werden wenn man darüber Gegenstände verschiebt? Muss ich das quasi wie beamen betrachten? dann muss ich sicherlich mit .MoveTowarrds arbeiten oder?

Ja, das ist korrekt. Du setzt da direkt die Position eines Objekts, und bist damit sozusagen ganz unten in der Hierarchie. Physik und Kollisionen sind darauf aufbauen implementiert. MoveTowards hilft da nicht, da das nur eine vektormathematische Funktion ist. Wenn du Kollisionen haben willst, brauchst du entweder eine eigene Kollisionsabfrage mit den Methoden der Physics-Klasse oder, was erstmal einfacher wäre, Collider und einen Rigidbody. Rigidbody ist die Komponente, die ein Objekt bewegt und dabei eben auf Kollisionen achtet.

Link zu diesem Kommentar
Auf anderen Seiten teilen

vergiss bitte meine beiden Posts zuvor ... ich konnte fast alles lösen ... nur eines nicht. Beim Moven ignorieren die Gameobjekte ihre eigenen Collider, nicht aber von Gegenständen die von Anfang an in der Szene sind. Der Hund auf folgendem Bild hat einen Box Collider und die Kisten spawnen niemals auf ihm. Alle Kisten haben ebenfalls BoxCollider und sollen im Radis (rot) eigentlich nach anderen Collidern scannen ...

Hier ein Beispiel:

grafik.png.d61a1256a1f8f258213279f6cbdb239f.png

dann habe ich mir gedacht, das liegt bestimmt da dran, das der Code die Dinger gleichzeitg plaziert. Also habe ich eine for-Schleife gebaut und lege dort die GameObjecte in einem array an sodass die nach und nach gespawnt werden, aber auch so werden die collider ignoriert. Wieso?

Hier mein Code:

    [SerializeField] private Vector3 centerPositionMoveArea;
    [SerializeField] private Vector3 sizeMoveArea;
    [SerializeField] private float radius;
    private Vector3 PositionForMove;
    private Collider2D CollisionDetected;
    [SerializeField] private GameObject[] prefabs;
    private int tries;
    [SerializeField] private int maxMoveTries;



	void Update()
    {
        if (Input.GetButtonDown("Fire1"))
            for (int i = 0; i < prefabs.Length; i++)
                prefabs[i].transform.position = MoveGameObject(maxMoveTries);
    }


private Vector3 MoveGameObject(int maxMoveTries)
    {
        int MoveTries = 0;
        while (MoveTries < maxMoveTries)
        { 
        PositionForMove = centerPositionMoveArea + new Vector3(Random_Value(0, sizeMoveArea.x) / 2, Random_Value(0, sizeMoveArea.y) / 2, 0); //z=0 da 2D 
            
            if(!IsBlocked(PositionForMove, radius))
            {
                
                Debug.Log("Gebiet frei");
                Debug.Log("I tried to place " + MoveTries);
                transform.position = PositionForMove;
                break;
            }

            if (IsBlocked(PositionForMove, radius))
            {
                Debug.Log("BLOCKIERT!!!");
                MoveTries++;
            }

            if (MoveTries==maxMoveTries)
            {
                Debug.Log("ABGEBROCHEN, da blockiert!!!");
                Debug.Log("I tried to place " + MoveTries);
                break;
            }
        }
        return transform.position;
    }

 

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 21 Stunden schrieb Chroncile:

dann habe ich mir gedacht, das liegt bestimmt da dran, das der Code die Dinger gleichzeitg plaziert.

Das könnte übrigens stimmen. Also... die werden schon nacheinander Positioniert, aber es kann sein, dass die Physik-Engine zwischendurch nicht automatisch mitkriegt, dass da jetzt neue Objekte sind. Ruf mal nach jedem Spawnen Physics2D.SyncTransforms auf.

vor 21 Stunden schrieb Chroncile:

Also habe ich eine for-Schleife gebaut und lege dort die GameObjecte in einem array an sodass die nach und nach gespawnt werden

So ein Array brauchst du nicht :)

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...