Jump to content
Unity Insider Forum

Mehrere GameObjects in der Nähe von existierenden GameObjects spawnen


Recommended Posts

Moin,

ich habe eine kleine Wiese erstellt, in dem Rehe Gräser fressen können. Wird das Spiel gestartet FirstSpawn(), wird eine bestimmte Anzahl von Gräsern (prefab) auf der Wiese generiert. Wenn die maximale Anzahl an Gräsern durch fressen unterschritten wird, werden mittels IEnumerator GrassSpawner() weitere Gräser auf der Wiese bis zur maximal erlaubten Anzahl generiert. Das klappt alles wunderbar.

Jetzt möchte ich für die neuen Gräser aber eine Änderung. Sie sollen nur noch in einem nahen Umkreis existierender Gräser "wachsen"/spawnen UND sich nach Möglichkeit nicht überlappen. Wie bekomme ich das am besten hin?

Hier die relevanten Code-Ausschnitte:

private float grenze = 4.5f; // Ein Faktor für den Spawnbereich Wiese

----------------------------------------------------   

void FirstSpawn() //Spielstart
{
	for (int i = 0; i < AnzahlRehFutter; i++)
	{
		GameObject instanceGrass = (GameObject)Instantiate(prefabGrass);
		instanceGrass.transform.position = new Vector3(Random.Range(-size.x * grenze, size.x * grenze), 0, Random.Range(-size.z * grenze, size.z * grenze)); //Zufällig spwanen
		Destroy(instanceGrass, GlobalCounter.GrassLebensdauer); //Lebendsdauer der Gräser, falls nicht gefressen
	}
}

----------------------------------------------------       
  
void Update()
{
		if (schalter2 == true && GlobalCounter.grassC < AnzahlRehFutter) //Beginnt, wenn Max Anzahl Gräser unterschritten wird
		{
			StartCoroutine(GrassSpawner());
        }  
}

----------------------------------------------------  
  
IEnumerator GrassSpawner()
{
	Vector3 size = transform.localScale;
	GameObject instanceGrass = (GameObject)Instantiate(prefabGrass);
	instanceGrass.transform.position = new Vector3(Random.Range(-size.x * grenze, size.x * grenze), 0, Random.Range(-size.z * grenze, size.z * grenze));
	Destroy(instanceGrass, GlobalCounter.GrassLebensdauer); //Lebendsdauer der Gräser, falls nicht gefressen
	yield return new WaitForSeconds(RehFutterSpawnZeit); // Zeitversetztes spawnen
}

 

Link to post
Share on other sites

Wenn du dir ein Grid machst, wird's relativ einfach. Wenn nicht, wird's ziemlich schwer. Da könnte man evtl mal schauen, ob man da Poisson-Disc draufschmeißen kann. Aber überlege lieber erstmal, ob ein Grid vielleicht okay wäre :)

Link to post
Share on other sites
vor 28 Minuten schrieb Sascha:

Wenn du dir ein Grid machst, wird's relativ einfach. Wenn nicht, wird's ziemlich schwer. Da könnte man evtl mal schauen, ob man da Poisson-Disc draufschmeißen kann. Aber überlege lieber erstmal, ob ein Grid vielleicht okay wäre :)

Danke für die schnelle Rückmeldung. Ein Grid käme eher weniger in Frage. Das wird am Ende eine Predator-Prey Simulation in 3D und ein Grid würde zu sehr an die 2D Simulationen erinnern.

Poisson Disc kannte ich noch gar nicht. Sieht aber genau nach dem aus, was ich mir vorgestellt habe. Und komplex sieht es aus :D. Kennst du zufällig eine gute Vorlage, die ich nutzen kann? Eine, die ich trotz Google nicht so schnell finden würde. ;)

Link to post
Share on other sites

Ein Grid muss ja nicht mit Linien dem Spieler ins Gesicht geschmissen werden. Das kann man auch recht unauffällig machen, und evtl. noch innerhalb des Feldes eine zufällige Position wählen. Da geht ne Menge, ohne dass es komplizierter wird als sowas :)

Nach Tutorials und Vorlagen müsste ich selber suchen, hab das nie selber implementiert.

Link to post
Share on other sites

Ah ok. Ich hatte schon Sorge, dass die Gräser alle im rechten Winkel zueinander angeordnet sind, in Spalten und Zeilen. :D

Theoretisch würden dann nach Spielstart erstmal die Gräser zufällig verteilt werden und landen dabei innerhalb bestimmter Zellen im Grid. Die betroffene Zelle muss erstmal mit meinetwegen 3 Gräsern befüllt werden (Zufallsverteilt) bevor die Nachbarszelle den Spawn-Rythmus übernimmt und ihrerseits ihre Fläche befüllt, usw. 

Meintest du das so?

Ich muss mich mal da reinfuchsen. 

Ansonsten, handelt es sich ja um eine kleine Spielfläche. Wenn ich den Gräsern ein Skript anhänge, können die doch in Bezug zu ihrer Position versetzt ein Gras-Objekt spawnen. Gibt es denn ein Befehl, womit ich vor dem spawnen prüfen kann, ob da schon ein Gras GameObjekt ist und wenn ja soll er den nächsten freien Punkt suchen?

Meine Rehe laufen zufällig durch die Gegend und laufen nur zum nächsten Zufallsziel hin, wenn sich das Ziel innerhalb der Wiesenfläche befindet. Ansonsten wird die nächste Zufallskoordinate genommen. Das wäre doch sowas ähnliches.

Link to post
Share on other sites
vor 24 Minuten schrieb imexx89:

in Spalten und Zeilen.

Gibt halt auch sechseckige Grids ;)

vor 25 Minuten schrieb imexx89:

Meintest du das so?

Joa, klingt doch gut.

vor 25 Minuten schrieb imexx89:

Gibt es denn ein Befehl, womit ich vor dem spawnen prüfen kann, ob da schon ein Gras GameObjekt ist und wenn ja soll er den nächsten freien Punkt suchen?

Du kannst den Dingern Collider geben und mit Physics-Funktionen (z.B. OverlapSphere) schauen, wo sich Collider befinden. Sonst selber ne Spatial Hashmap schreiben, aber das ist vielleicht ein bisschen viel für diese Situation :)

Ich bin ehrlich gesagt kein Fan von "Probiere solange, bis du was findest". Da kann man halt immer nicht sagen, wie lange das dauert. Nachher hast du viel Gras und das dauert 10 Sekunden, bis du zufällig was findest. In der Zeit bleibt das Spiel halt stehen. Unschön, und man kann nicht mit Sicherheit sagen, dass das nicht passiert.

  • Thanks 1
Link to post
Share on other sites

Moin 🙂,

ich habe bezüglich Poisson Disc etwas gefunden was in meinem Rahmen soweit funktioniert (Bild unity01). Es gibt aber noch ein paar Fragenzeichen/Probleme, die ich nicht lösen bzw. erkennen kann.

1. Die Objekte werden in der falschen Szene Menu untergebracht (Bild unity02), statt in MainScene. Das Skript tPoissonDiscSampler.cs bezieht sich auf eine Rect Transform Komponente und das Canvas Element in der erste Szene, ist das erste Element mit so einer Komponente. Das wäre ok, wenn ich die Gras-Objekt-Platzierung auf die gesamte Fläche ausweiten könnte, kriege ich aber nicht hin :/. Über GrassLifecycle.cs kann ich mittels "... new tPoissonDiscSampler(19, 18, 2.5f)" nur die rechte Ecke füllen (Bild unity01). Die Werte beziehen sich auf das Zentrum und gehen in X- und Z-Richtung. Negative Werte sind möglich, aber platziert wird nur weiterhin in der rechten Ecke. Unten links befindet sich ein manuell platziertes Gras-Objekt mit dem angehängten Skript. Es wird also nur gerechnet und definiert platziert, unabhängig vom manuell platzierten Gras-Standort.

Meine Fragen also:
- Wie beziehe ich mich mit dem Grid auf die komplette Bodenfläche Plane?
- Wie kann ich das Abtasten und Befüllen vom lokalen Standort des manuell platzierten Gras-Objekts starten lassen.

2. Die neuen Gras-Objekte werden alle nach einem kurzen Laden instant platziert. Daher möchte ich, ähnlich wie in einem anderen IEnumerable ein WaitForSeconds einbauen, für eine langsamere Platzierung. Ich denke das müsste in tPoissonDiscSampler.cs > public IEnumerable<Vector2> Samples() gemacht werden. Leider lässt sich an den für mich logischen Stellen kein WaitForSeconds unterbringen. Es ist bestimmt nur was rein formales, was ist nicht sehe, aber wie und wo platziere ich am gescheitesten die Wartezeit?

tPoissonDiscSampler.cs:

public class tPoissonDiscSampler
{
    private const int k = 30; // Anzahl der Versuche

    private readonly Rect rect; // Problematisch?
    private readonly float radius2;
    private readonly float cellSize;
    private Vector2[,] grid;
    private List<Vector2> activeSamples = new List<Vector2>();


    public tPoissonDiscSampler(float width, float height, float radius) // Die Werte werden GrassLifecycle.cs entnommen
    {
        rect = new Rect(0, 0, width, height); //Problematisch?
        radius2 = radius * radius;
        cellSize = radius / Mathf.Sqrt(2);
        grid = new Vector2[Mathf.CeilToInt(width / cellSize),
                           Mathf.CeilToInt(height / cellSize)];
    }

    public IEnumerable<Vector2> Samples() // Hier soll ein WaitForSeconds eingebaut werden, aber wo?
    {
        yield return AddSample(new Vector2(Random.value * rect.width, Random.value * rect.height));
        
        while (activeSamples.Count > 0)
        {
            int i = (int)Random.value * activeSamples.Count;
            Vector2 sample = activeSamples[i];

            bool found = false;
            for (int j = 0; j < k; ++j)
            {
                float angle = 2 * Mathf.PI * Random.value;
                float r = Mathf.Sqrt(Random.value * 3 * radius2 + radius2); 
                Vector2 candidate = sample + r * new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));

                if (rect.Contains(candidate) && IsFarEnough(candidate))
                {
                    found = true;
                    yield return AddSample(candidate);
                    break;
                }
            }

            if (!found)
            {
                activeSamples[i] = activeSamples[activeSamples.Count - 1];
                activeSamples.RemoveAt(activeSamples.Count - 1);
            }            
        }        
    }

    private bool IsFarEnough(Vector2 sample)
    {
        GridPos pos = new GridPos(sample, cellSize);

        int xmin = Mathf.Max(pos.x - 2, 0);
        int ymin = Mathf.Max(pos.y - 2, 0);
        int xmax = Mathf.Min(pos.x + 2, grid.GetLength(0) - 1);
        int ymax = Mathf.Min(pos.y + 2, grid.GetLength(1) - 1);

        for (int y = ymin; y <= ymax; y++)
        {
            for (int x = xmin; x <= xmax; x++)
            {
                Vector2 s = grid[x, y];
                if (s != Vector2.zero)
                {
                    Vector2 d = s - sample;
                    if (d.x * d.x + d.y * d.y < radius2) return false;
                }
            }
        }
        return true;
    }

    private Vector2 AddSample(Vector2 sample)
    {
        activeSamples.Add(sample);
        GridPos pos = new GridPos(sample, cellSize);
        grid[pos.x, pos.y] = sample;
        return sample;
    }

    private struct GridPos
    {
        public int x;
        public int y;
        public GridPos(Vector2 sample, float cellSize)
        {
            x = (int)(sample.x / cellSize);
            y = (int)(sample.y / cellSize);
        }
    }
}

GrassLifecycle.cs:

public class GrassLifecycle : MonoBehaviour
{
    public GameObject prefabGrass;

    void OnValidate()
    {
        tPoissonDiscSampler sampler = new tPoissonDiscSampler(19, 18, 2.5f); // Werte für tPoissonDiscSampler.cs (width, height, radius)
        foreach (Vector2 sample in sampler.Samples())
        {
            Instantiate(prefabGrass, new Vector3(sample.x, 0, sample.y), Quaternion.Euler(0, Random.Range(0.0f, 360.0f), 0));
        }

    }
}

 

unity01.PNG

unity02.PNG

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...