Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

need help to spawn objects not too close to eachother

Discussion in 'Scripting' started by 12dollar, Jan 11, 2015.

  1. 12dollar

    12dollar

    Joined:
    Jan 3, 2015
    Posts:
    18
    hi guys,

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

    Code (CSharp):
    1.   void Update()
    2.   {
    3.     if (stopping == false)
    4.     {
    5.       transform.Translate(-Vector3.right * speed * Time.deltaTime);
    6.       mushroom = GameObject.FindGameObjectWithTag("Mushroom");
    7.     }
    8.  
    9.     if (mushroom != null)
    10.     {
    11.       while (stopping == false && count < 6)
    12.       {
    13.         var position = new Vector2(Random.Range(-25f, 120f), Random.Range(-20f, 30f));
    14.         Instantiate(mushroom, position, Quaternion.identity);
    15.         count++;
    16.       }
    17.     }
    18.   }
    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 !
     
  2. alegoris

    alegoris

    Joined:
    Dec 16, 2014
    Posts:
    6
    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.
     
  3. 12dollar

    12dollar

    Joined:
    Jan 3, 2015
    Posts:
    18
    thanks for the fast response.
    could you maybe provide any script? I seem clueless at the moment.

    I started something like:

    Code (CSharp):
    1.   void Update()
    2.   {
    3.     List<float> spawnspots = new List<float>();
    4.     if (stopping == false)
    5.     {
    6.       transform.Translate(-Vector3.right * speed * Time.deltaTime);
    7.       mushroom = GameObject.FindGameObjectWithTag("Mushroom");
    8.     }
    9.  
    10.     if (mushroom != null)
    11.     {
    12.       float x = 0;
    13.       while (stopping == false && count < 6)
    14.       {
    15.         x = Random.Range(-25f, 120f);
    16.         var position = new Vector2(x, Random.Range(-20f, 30f));
    17.         spawnspots.Add(x);
    18.         foreach (var f in spawnspots)
    19.         {
    20.           ???
    21.         }
    22.         Instantiate(mushroom, position, Quaternion.identity);
    23.         count++;
    24.       }
    25.     }
    26.   }
     
  4. alegoris

    alegoris

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

    Code (CSharp):
    1. bool IsTooClose(float x, float minimumDistance, List<float> list)
    2. {
    3.     if(list.Count == 0)
    4.     {
    5.         return false;
    6.     }
    7.  
    8.     bool tooClose = false;
    9.  
    10.     foreach(var f in list)
    11.     {
    12.         if(Mathf.Abs(x) <= Mathf.Abs(f) + minimumDistance)
    13.         {
    14.             tooClose = true;
    15.             break;
    16.         }
    17.     }
    18.  
    19.     return tooClose;
    20. }
    If it returns false, add your x to the list, instantiate your object and increase the count.
     
    Last edited: Jan 11, 2015
  5. 12dollar

    12dollar

    Joined:
    Jan 3, 2015
    Posts:
    18
    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 .....


    Code (CSharp):
    1.   void Update()
    2.   {
    3.     List<float> spawnspots = new List<float>();
    4.     if (stopping == false)
    5.     {
    6.       transform.Translate(-Vector3.right * speed * Time.deltaTime);
    7.       mushroom = GameObject.FindGameObjectWithTag("Mushroom");
    8.     }
    9.  
    10.     if (mushroom != null)
    11.     {
    12.       float x, y = 0;
    13.       while (stopping == false && count < 6)
    14.       {
    15.         x = Random.Range(-25f, 100f);
    16.         y = Random.Range(-20f, 30f);
    17.         var position = new Vector2(x, y);
    18.         if (IsTooClose(x, 15, spawnspots) == false)
    19.         {
    20.           spawnspots.Add(x);
    21.           Instantiate(mushroom, position, Quaternion.identity);
    22.           count++;
    23.         }
    24.         else
    25.         {
    26.           x = Random.Range(-25f, 100f);
    27.         }
    28.       }
    29.     }
    30.   }
    31.  
    32.   bool IsTooClose(float x, float minimumDistance, List<float> list)
    33.   {
    34.     if (list == null)
    35.     {
    36.       return false;
    37.     }
    38.  
    39.     bool tooClose = true;
    40.  
    41.     foreach (var f in list)
    42.     {
    43.       if (Mathf.Abs(x) > Mathf.Abs(f) + minimumDistance)
    44.       {
    45.         tooClose = false;
    46.         break;
    47.       }
    48.     }
    49.  
    50.     return tooClose;
    51.   }
     
    Last edited: Jan 11, 2015
  6. alegoris

    alegoris

    Joined:
    Dec 16, 2014
    Posts:
    6
    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<float> spawnspots =new List<float>();".
    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 :)

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

    12dollar

    Joined:
    Jan 3, 2015
    Posts:
    18
    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 ;)

    see the part I'm referring to here:

    Code (CSharp):
    1.         if (IsTooClose(x, 15, spawnspots) == false)
    2.         {
    3.           spawnspots.Add(x);
    4.           Instantiate(star, position, Quaternion.identity);
    5.           count++;
    6.         }
     
  8. TheSniperFan

    TheSniperFan

    Joined:
    Jul 18, 2013
    Posts:
    712
    Is creating all of them at the beginning of the level an option, or do you need to create them dynamically?
     
  9. 12dollar

    12dollar

    Joined:
    Jan 3, 2015
    Posts:
    18
    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
     
  10. TheSniperFan

    TheSniperFan

    Joined:
    Jul 18, 2013
    Posts:
    712
    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.
     
    Last edited: Jan 12, 2015
  11. 12dollar

    12dollar

    Joined:
    Jan 3, 2015
    Posts:
    18
    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 :)
     
  12. TheSniperFan

    TheSniperFan

    Joined:
    Jul 18, 2013
    Posts:
    712
    Be sure to hit F5. I simplified the algorithm. (Made a small mistake that lead to unnecessarily complicated fixes)
     
  13. 12dollar

    12dollar

    Joined:
    Jan 3, 2015
    Posts:
    18
    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 :mad:

    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
     
    Last edited: Jan 12, 2015
  14. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    8,937
    check this video for ideas also,
    Procedural Generation (White and Blue Noise)
     
  15. alegoris

    alegoris

    Joined:
    Dec 16, 2014
    Posts:
    6
    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
    Code (CSharp):
    1. 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.
     
  16. 12dollar

    12dollar

    Joined:
    Jan 3, 2015
    Posts:
    18
    I already adapted the code as mentioned but I still have/had the infinite loop.
    see the whole code again:


    Code (CSharp):
    1.   void Update()
    2.   {
    3.     List<float> spawnspots = new List<float>();
    4.     if (stopping == false)
    5.     {
    6.       transform.Translate(-Vector3.right * speed * Time.deltaTime);
    7.       mushroom = GameObject.FindGameObjectWithTag("Mushroom");
    8.     }
    9.  
    10.     if (mushroom != null)
    11.     {
    12.       float x, y = 0;
    13.       while (stopping == false && count < 6)
    14.       {
    15.         x = Random.Range(-25f, 100f);
    16.         y = Random.Range(-20f, 30f);
    17.         var position = new Vector2(x, y);
    18.         if (IsTooClose(x, 15, spawnspots) == false)
    19.         {
    20.           spawnspots.Add(x);
    21.           Instantiate(mushroom, position, Quaternion.identity);
    22.           count++;
    23.         }
    24.       }
    25.     }
    26.   }
    27.  
    28.   bool IsTooClose(float x, float minimumDistance, List<float> list)
    29.   {
    30.     if (list.Count == 0)
    31.     {
    32.       return false;
    33.     }
    34.  
    35.     bool tooClose = false;
    36.  
    37.     foreach (var f in list)
    38.     {
    39.       if (Mathf.Abs(x) <= Mathf.Abs(f) + minimumDistance)
    40.       {
    41.         tooClose = true;
    42.         break;
    43.       }
    44.     }
    45.     return tooClose;
    46.   }
     
  17. TheSniperFan

    TheSniperFan

    Joined:
    Jul 18, 2013
    Posts:
    712
    Code (csharp):
    1. // Generates the desired amount of mushrooms within set boundaries
    2. // x_min is the position from where mushrooms should start spawning (somewhere after the start of the level)
    3. // x_max is the position after which no more mushromms should spawn (somewhere before the end of the level)
    4. // amount is the desired amount of mushrooms you want to spawn
    5. private void GenerateMushrooms(float x_min, float x_max, int amount) {
    6.     if(x_min >= x_max)
    7.         throw new AgumentException("x_min must be smaller than x_max");
    8.  
    9.     float rangeDelta = x_max - x_min;
    10.     float avg_dist = rangeDelta / amount;
    11.     float rnd = UnityEngine.Random.Range(0.0f, avg_dist * 0.5f);
    12.  
    13. for(int i=0; i<amount; i++) {
    14.         float x_pos = (avg_dist * i) + rnd;
    15.         // Now create a mushroom using x_pos as position along the x axis here
    16.     }
    17. }
    You call this function once at the beginning of your level.
     
  18. 12dollar

    12dollar

    Joined:
    Jan 3, 2015
    Posts:
    18
    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?


    Code (CSharp):
    1.  
    2.   void Start()
    3.   {
    4.     mushroom = GameObject.FindGameObjectWithTag("Mushroom");
    5.     if (mushroom != null)
    6.     {
    7.       GenerateMushrooms(-25f, 100f, 6);
    8.     }
    9.   }
    10.  
    11.   void Update()
    12.   {
    13.     if (stopping == false)
    14.     {
    15.       transform.Translate(-Vector3.right * speed * Time.deltaTime);
    16.     }
    17.   }
    18.  
    19.   private void GenerateMushrooms(float x_min, float x_max, int amount)
    20.   {
    21.     float rangeDelta = x_max - x_min;
    22.     float avg_dist = rangeDelta / amount;
    23.     float rnd = UnityEngine.Random.Range(0.0f, avg_dist * 0.5f);
    24.  
    25.     for (int i = 0; i < amount; i++)
    26.     {
    27.       float y = Random.Range(-20f, 30f);
    28.       float x = (avg_dist * i) + rnd;
    29.       var position = new Vector2(x, y);
    30.       Instantiate(mushroom, position, Quaternion.identity);
    31.     }
    32.   }
     
  19. TheSniperFan

    TheSniperFan

    Joined:
    Jul 18, 2013
    Posts:
    712
    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
    Code (csharp):
    1. if(mushroom)
    2.     GenerateMushrooms;
    Instead of
    Code (csharp):
    1. if(mushroom != null)
    2.     GenerateMushrooms;
    It's shorter.
     
  20. 12dollar

    12dollar

    Joined:
    Jan 3, 2015
    Posts:
    18
    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
     
  21. 12dollar

    12dollar

    Joined:
    Jan 3, 2015
    Posts:
    18
    sorry for double posting but I managed to get a working solution. thanks a lot to everyone ! virtual beer provided :)

    the solution that worked:

    Code (CSharp):
    1.   void Update()
    2.   {
    3.     if (stopping == false)
    4.     {
    5.       star = GameObject.FindGameObjectWithTag("Mushroom");
    6.       transform.Translate(-Vector3.right * speed * Time.deltaTime);
    7.       if (mushroom)
    8.       {
    9.         float rangeDelta = x_max - x_min;
    10.         float avg_dist = rangeDelta / max_stars;
    11.         float rnd = Random.Range(0.0f, avg_dist * 0.5f);
    12.  
    13.         while (stopping == false && count < max_stars)
    14.         {
    15.           float y = Random.Range(-20f, 30f);
    16.           float x = (avg_dist * count) + rnd;
    17.           var position = new Vector2(x, y);
    18.           Instantiate(mushroom, position, Quaternion.identity);
    19.           count++;
    20.         }
    21.       }
    22.     }
    23.   }
     
  22. kdubnz

    kdubnz

    Joined:
    Apr 19, 2014
    Posts:
    177
    Here's another way to approach the task.
    The script is attached to a placeholder object.

    This method has the possible advantage that there is no necessity for a clash test.
    A similar methodology can be used for locations in a flat plane.

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. // Proof of Concept for non-clashing Random locations in the X Axis
    7. // Draw markers in the Scene View only using Debug.DrawLine().
    8. // NOTE: This won't appear in the actual game view.
    9.  
    10. public class RandomSpawn : MonoBehaviour
    11. {
    12.     [SerializeField]
    13.     private int cellCount = 6;
    14.     [SerializeField]
    15.     private float minSpacing = 1.3f;
    16.     [SerializeField]
    17.     private float maxSpacing = 10f;
    18.  
    19.     private Vector3 originOfLine;
    20.     private Vector3 endOfLine;
    21.  
    22.     [SerializeField]
    23.     private float cellRange;
    24.     [SerializeField]
    25.     private float cellLength;
    26.     [SerializeField]
    27.     private List<float> offsetList = new List<float>();
    28.  
    29.     // Use this for initialization
    30.     private void Start()
    31.     {
    32.         cellRange = (maxSpacing - minSpacing) * 0.5f;
    33.         cellLength = cellRange + minSpacing;
    34.  
    35.         offsetList.Add(
    36.                        minSpacing * 0.5f +
    37.                        Random.Range(0.0f, cellRange));
    38.  
    39.         originOfLine = transform.position;
    40.         endOfLine = new Vector3(
    41.             transform.position.x + (cellLength * cellCount),
    42.             transform.position.y);
    43.  
    44.         for (var i = 1; i < cellCount; i++)
    45.         {
    46.             offsetList.Add(
    47.                            cellLength * i + minSpacing * 0.5f +
    48.                            Random.Range(0.0f, cellRange));
    49.         }
    50.     }
    51.  
    52.     // Update is called once per frame
    53.     private void Update()
    54.     {
    55.         Debug.DrawLine(originOfLine, endOfLine, Color.green);
    56.  
    57.         foreach (var nodeOffset in offsetList)
    58.         {
    59.             Debug.DrawLine(
    60.                            new Vector3(
    61.                                originOfLine.x + nodeOffset,
    62.                                originOfLine.y),
    63.                            new Vector3(
    64.                                originOfLine.x + nodeOffset,
    65.                                originOfLine.y + 2),
    66.                            Color.red);
    67.         }
    68.     }
    69. }
     
    Last edited: Jan 13, 2015
  23. TimLoo6

    TimLoo6

    Joined:
    Dec 29, 2019
    Posts:
    6
    TRY