need help to spawn objects not too close to eachother

hi guys,

I currently use the following snippet to spawn my mushrooms (level-ups) for a 2D sidescroller game.

  void Update()
  {
    if (stopping == false)
    {
      transform.Translate(-Vector3.right * speed * Time.deltaTime);
      mushroom = GameObject.FindGameObjectWithTag("Mushroom");
    }

    if (mushroom != null)
    {
      while (stopping == false && count < 6)
      {
        var position = new Vector2(Random.Range(-25f, 120f), Random.Range(-20f, 30f));
        Instantiate(mushroom, position, Quaternion.identity);
        count++;
      }
    }
  }

The problem I have is the x position being way too close to eachother. sometimes they even overlap.
I wasn’t able to find a solution so far. really interesting random.range has similar values pretty often !!

my first idea was calculation last_position minus x and if that’s within a certain range I random x again.
this didn’t work out since I only compare the last_position of the last gameobject I created.
but it wouldn’t compare for example loop1 vs loop4

hope someone could help me ! thanks in advance !

You can create a list of previously instanced positions.
Then check against it to see if your position is too close to any of them. If it isn’t , instantiate your object and add its position to the list.

thanks for the fast response.
could you maybe provide any script? I seem clueless at the moment.

I started something like:

  void Update()
  {
    List<float> spawnspots = new List<float>();
    if (stopping == false)
    {
      transform.Translate(-Vector3.right * speed * Time.deltaTime);
      mushroom = GameObject.FindGameObjectWithTag("Mushroom");
    }

    if (mushroom != null)
    {
      float x = 0;
      while (stopping == false && count < 6)
      {
        x = Random.Range(-25f, 120f);
        var position = new Vector2(x, Random.Range(-20f, 30f));
        spawnspots.Add(x);
        foreach (var f in spawnspots)
        {
          ???
        }
        Instantiate(mushroom, position, Quaternion.identity);
        count++;
      }
    }
  }

Well, one way to do it would be to write a simple method like this one (untested and off the top of my head):

bool IsTooClose(float x, float minimumDistance, List<float> list)
{
    if(list.Count == 0)
    {
        return false;
    }

    bool tooClose = false;

    foreach(var f in list)
    {
        if(Mathf.Abs(x) <= Mathf.Abs(f) + minimumDistance)
        {
            tooClose = true;
            break;
        }
    }

    return tooClose;
}

If it returns false, add your x to the list, instantiate your object and increase the count.

thanks for your answer again.
I’ve been playing around with different settings and different code additions in the last hour but it seems that didn’t work out. the mushrooms are still too close to eachother.

current code produces an infinite loop too but didn’t know how to implement it the other way round …

  void Update()
  {
    List<float> spawnspots = new List<float>();
    if (stopping == false)
    {
      transform.Translate(-Vector3.right * speed * Time.deltaTime);
      mushroom = GameObject.FindGameObjectWithTag("Mushroom");
    }

    if (mushroom != null)
    {
      float x, y = 0;
      while (stopping == false && count < 6)
      {
        x = Random.Range(-25f, 100f);
        y = Random.Range(-20f, 30f);
        var position = new Vector2(x, y);
        if (IsTooClose(x, 15, spawnspots) == false)
        {
          spawnspots.Add(x);
          Instantiate(mushroom, position, Quaternion.identity);
          count++;
        }
        else
        {
          x = Random.Range(-25f, 100f);
        }
      }
    }
  }

  bool IsTooClose(float x, float minimumDistance, List<float> list)
  {
    if (list == null)
    {
      return false;
    }

    bool tooClose = true;

    foreach (var f in list)
    {
      if (Mathf.Abs(x) > Mathf.Abs(f) + minimumDistance)
      {
        tooClose = false;
        break;
      }
    }

    return tooClose;
  }

Your IsTooClose method is wrong.

That’s actually my mistake as well, I submitted the wrong code, then quickly updated it.
The code was wrong because it checked to see if x was far enough from ANY list element (where it should have been checking if it was far enough from ALL of them).
Also, the way to get the number of elements in a list would be “list.Count”, not “list.Length” (as i said, it was untested).

On top of this, you replaced this with (list == null) which is always false, since you create it every update “List spawnspots =new List();”.
But when you’re trying to add the first element, it won’t even go once through the foreach (because the count of the list is 0), so your method will ALWAYS return true. That’s why you were stuck in an infinite loop :slight_smile:

Check my previous answer for the updated logic. You also don’t need the else statement (if the IsTooClose returns true).

thanks again for that lecture.
I’m still not used to C# enough.

wasn’t able to test if it’s actually working now since I’m still in an infinite loop due to my count variable not being incremented outside the if statement. but if I do so there are obviously not enough mushrooms :wink:

see the part I’m referring to here:

        if (IsTooClose(x, 15, spawnspots) == false)
        {
          spawnspots.Add(x);
          Instantiate(star, position, Quaternion.identity);
          count++;
        }

Is creating all of them at the beginning of the level an option, or do you need to create them dynamically?

creating all 6 mushrooms at level start would be possible.
if the player picks up one mushroom the object gets destroyed and he gains a level-up.

therefore the count decrements by one and another mushroom is spawned in a random location

Then generate them “from left to right”.
You need to know the boundaries of your level along the x axis, to make sure they don’t spawn outside.

An example:
Say you want to spawn 4 mushrooms between x[10, 110].

Range delta = max - min = 100
Average distance = range delta / # mushrooms = 25

This would mean we spawn the mushrooms at (10, 35, 60, 85). We don’t want them to be distributed this evenly though.

We could add a random value to them. Now, this random value must not exceed the average distance. Otherwise the first one may spawn behind the second.

rnd = +/- average distance / 2 = +/- 12.5

The second problem would be that they might spawn too close to each other. If the fist random value would be almost the maximum and the next one would be almost the minimum. (35 + 12 and 60 - 12)
To fix this, we make sure that the values are always positive.

rnd = average distance / 2 = 12.5
position = (average distance * mushroom-index) + rnd

In this example the following values would be calculated:
#1 x[10-22.5]
#2 x[35-47.5]
#3 x[60-72.5]
#4 x[85-97.5]

This should be good enough.

thanks for that huge amount of information.
too be honest I’m sitting here with my eyes wide open and try to adapt my code I already posted in my previous comments but math plus me leads to a deadly dosis of frustration :slight_smile:

Be sure to hit F5. I simplified the algorithm. (Made a small mistake that lead to unnecessarily complicated fixes)

tried to implement it but was stuck again translating algorithms to actual code.
I think I better stick to alegoris answer and try to find a solution for my infinite loop.

edit: okay I at least got rid of the infinite loop switching from a while to an if statement
but the mushrooms are sadly enough still too close to eachother. this really drives me up the wall :rage:

edit2: okay I’m too stupid switching from while to if only executes it once (if at all) everytime update is called and I’m creating a new List deleting the old one too. No light at the end of the tunnel

check this video for ideas also,
Procedural Generation (White and Blue Noise)

You can fix the infinite loop by just using the method i posted. (IsTooClose)

As I said in my previous post, it has been fixed and should work.
See if it’s different from your method and in what way. You CAN’T have if(list == null) as your initial check, it will not work.

Try and copy the entire method again, see if that fixes it for you.

I already adapted the code as mentioned but I still have/had the infinite loop.
see the whole code again:

  void Update()
  {
    List<float> spawnspots = new List<float>();
    if (stopping == false)
    {
      transform.Translate(-Vector3.right * speed * Time.deltaTime);
      mushroom = GameObject.FindGameObjectWithTag("Mushroom");
    }

    if (mushroom != null)
    {
      float x, y = 0;
      while (stopping == false && count < 6)
      {
        x = Random.Range(-25f, 100f);
        y = Random.Range(-20f, 30f);
        var position = new Vector2(x, y);
        if (IsTooClose(x, 15, spawnspots) == false)
        {
          spawnspots.Add(x);
          Instantiate(mushroom, position, Quaternion.identity);
          count++;
        }
      }
    }
  }

  bool IsTooClose(float x, float minimumDistance, List<float> list)
  {
    if (list.Count == 0)
    {
      return false;
    }

    bool tooClose = false;

    foreach (var f in list)
    {
      if (Mathf.Abs(x) <= Mathf.Abs(f) + minimumDistance)
      {
        tooClose = true;
        break;
      }
    }
    return tooClose;
  }
// Generates the desired amount of mushrooms within set boundaries
// x_min is the position from where mushrooms should start spawning (somewhere after the start of the level)
// x_max is the position after which no more mushromms should spawn (somewhere before the end of the level)
// amount is the desired amount of mushrooms you want to spawn
private void GenerateMushrooms(float x_min, float x_max, int amount) {
    if(x_min >= x_max)
        throw new AgumentException("x_min must be smaller than x_max");

    float rangeDelta = x_max - x_min;
    float avg_dist = rangeDelta / amount;
    float rnd = UnityEngine.Random.Range(0.0f, avg_dist * 0.5f);

for(int i=0; i<amount; i++) {
        float x_pos = (avg_dist * i) + rnd;
        // Now create a mushroom using x_pos as position along the x axis here
    }
}

You call this function once at the beginning of your level.

thanks again for helping me out. I really appreciate all your effort.
I don’t really know why my program always crashes with infinite loops.

did I oversee something?

  void Start()
  {
    mushroom = GameObject.FindGameObjectWithTag("Mushroom");
    if (mushroom != null)
    {
      GenerateMushrooms(-25f, 100f, 6);
    }
  }

  void Update()
  {
    if (stopping == false)
    {
      transform.Translate(-Vector3.right * speed * Time.deltaTime);
    }
  }

  private void GenerateMushrooms(float x_min, float x_max, int amount)
  {
    float rangeDelta = x_max - x_min;
    float avg_dist = rangeDelta / amount;
    float rnd = UnityEngine.Random.Range(0.0f, avg_dist * 0.5f);

    for (int i = 0; i < amount; i++)
    {
      float y = Random.Range(-20f, 30f);
      float x = (avg_dist * i) + rnd;
      var position = new Vector2(x, y);
      Instantiate(mushroom, position, Quaternion.identity);
    }
  }

I couldn’t see anything wrong, so I just copy/pasted the script here and can confirm that it works.
It must be something else.

EDIT:
Oh, and you can write

if(mushroom)
    GenerateMushrooms;

Instead of

if(mushroom != null)
    GenerateMushrooms;

It’s shorter.

thanks for the information.
mhmm well that’s akward. it doesn’t crash at all if I create the mushrooms with the script I posted in the starting thread.

#confused