Home > database >  How to Spawn Objects in Unity3D with a Minimum Distance between
How to Spawn Objects in Unity3D with a Minimum Distance between

Time:12-17

I am Programming a random "Stone" Spawner and have a big problem at the moment. I have some ideas how to fix it, but want to know a performance friendly way to do it.

So my way to Spawn the Objects on the Sphere Surface is this one:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpawnObjects : MonoBehaviour
{
    public Vector3 centerOfSphere = new Vector3(0, 5000, 0);
    public float radiusOfSphere = 5000.0f;

    public List<GameObject> stones;
    public int stonesToSpawn = 1;

    void Start()
    {
        Vector3 center = transform.position   centerOfSphere;       //Setting the center position of the Sphere

        //This for loop is spawning the Stones on a random position on Sphere Surface
        for (int i = 0; i < stonesToSpawn; i  )
        {
            Vector3 pos = RandomCircle(center, radiusOfSphere);
            Quaternion rot = Quaternion.FromToRotation(Vector3.forward, center - pos);
            Instantiate(stones[Random.Range(0, stones.Count)], pos, rot);
        }
    }

    //Method returns a Random Position on a Sphere
    Vector3 RandomCircle(Vector3 center, float radius) 
    {
        float alpha = UnityEngine.Random.value * 360;
        float beta = UnityEngine.Random.value * 360;
        Vector3 pos;
        pos.x = radius * Mathf.Cos(beta) * Mathf.Cos(alpha);
        pos.y = radius * Mathf.Cos(beta) * Mathf.Sin(alpha);
        pos.z = radius * Mathf.Sin(beta);

        return pos;
    }
}

So thank you for your following explanations! :)

CodePudding user response:

As said to make your live one step easier simply use Random.onUnitSphere so your entire method RandomCircle (change that name btw!) can be shrinked to

private Vector3 RandomOnSphere(Vector3 center, float radius)
{
    return center   Random.onUnitSphere * radius;
}

And then in order to have a minimum distance between them there are probably multiple ways but I guess the simplest - brute force - way would be:

  • store already used positions
  • when you get a new random position check the distance to already existing ones
  • keep get new random positions until you have found one, that is not too close to an already existing one

This depends of course a lot on your use case and the amount of objects and the minimum distance etc - in other words I leave it up to you to assure that the requested amount and minimum distance is doable at all with the given sphere radius.

You could always leave an "emergency exit" and give up after e.g. 100 attempts.

Something like e.g.

// Linq offers some handy query shorthands that allow to shorten
// long foreach loops into single calls
using System.Linq;

...

private const int MAX_ATTEMPTS = 100;

public float minimumDistance = 1f;

void Start()
{
    var center = transform.position   centerOfSphere;

    // It is cheaper to use with square magnitudes
    var minDistanceSqr = minimumDistance * minimumDistance;

    // For storing the already used positions
    // Already initialize with the correct capacity, this saves resources
    var usedPositions = new List<Vector3>(stonesToSpawn);

    for (int i = 0; i < stonesToSpawn; i  )
    {
        // Keep track of the attempts (for the emergency break)
        var attempts = 0;
        Vector3 pos = Vector3.zero;
        do
        {
            // Get a new random position
            pos = RandomOnSphere(center, radiusOfSphere);
            // increase the attempts
            attempts  ;

            // We couldn't find a "free" position within the 100 attempts :(
            if(attempts >= MAX_ATTEMPTS)
            {
                throw new Exception ("Unable to find a free spot! :'(");
            }
        }
        // As the name suggests this checks if any "p" in "usedPositions" is too close to the given "pos"
        while(usedPositions.Any(p => (p - pos).sqrMagnitude <= minDistanceSqr)));
        
        var rot = Quaternion.FromToRotation(Vector3.forward, center - pos);
        Instantiate(stones[Random.Range(0, stones.Count)], pos, rot);

        // Finally add this position to the used ones so the next iteration
        // also checks against this position
        usedPositions.Add(pos);
    }
}

Where

usedPositions.Any(p => (p - pos).sqrMagnitude <= minDistanceSqr))

basically equals doing something like

private bool AnyPointTooClose(Vector3 pos, List<Vector3> usedPositions, float minDistanceSqr)
{
    foreach(var p in usedPositions)
    {
        if((p - pos).sqrMagnitude <= minDistanceSqr)
        {
            return true;
        }
    }

    return false;
}

if that's better to understand for you

  • Related