Yesterday I was trying to place objects picked from a list but failed halfway.
The objects are non-exhaustive.
Perhaps someone has suggestions beyond what I can produce.
The grid is reaching from X=-100 to X=+100.
Thus 200x200 (resulting in 40.000 squares).
And from Z=-100 and Z=+100.
Each square is 1.0x1.0 (and an object is placed in the middle, for instance at X=0,5 and Y=0,5.
My first thought was to create a list of 40000 unique locations (in term of X and Y position).
And select unique positions from this list.
If it has an object, it is removed from the list.
Unfortunately this failed halfway around 26000 (Unity or C# seems to get stuck somehow).
An alternative approach would be to start with randomly create position.
And proceed from this point.
And add this to a list of non-selectable positions.
If the position is already taken, rinse and repeat for another position (up to a certain number of times).
Both of your approaches are correct. The second is simpler to implement, but it only works well when the number of placements is small relative to the available positions.
The first approach works for any number of placements. Maintaining a list from which you remove available positions can be done in several ways:
Keep a list, select a random element, remove it from the list, and optionally add it to another list if you need to track filled positions.
Keep a list, shuffle it, and remove the first element each time.
Shuffle the list (or use an array, since you won’t be removing elements), and instead of removing items, maintain an index that represents how many elements have been used. Since the list is already randomized, you can simply access elements in order.
The most important thing to understand is the following:
That “somehow” is the real issue. The approach should work, and if it doesn’t, you need to understand why instead of looking for a different solution. Your method is valid and if it fails, the problem likely lies in your implementation. Identify the cause in your code and fix it.
Possible reasons I can think of, for this failure may include:
Off-by-one errors
Incorrect calculation when mapping the grid or 3D grid (based on your description) to a single dimension
Failing to remove elements properly or removing them more than once
You should also note that removals do not need to happen at the same time as selecting random positions. You can precompute the random positions first and then remove objects based on that result.
Much appreciated for your answer.
As a first step I wrote some (simple I guess) code to produce all potential positions.
Those are 200*200 = 40000 in total. Ranging from X=-100 to X=+100 and Y=-100 to Y=+100.
I’d expect it to be handled with ease, but I guess not?
It resulted into something it calls stack overflow.
This after it got pretty far (but not complete).
Didn’t run into this error before.
using UnityEngine;
public class BoardManager : MonoBehaviour
{
public GameObject healthPotion;
public int maxX;
public int minX;
public int minZ;
public int maxZ;
public int x1 = -10;
public int y1 = -10;
private int positivex1;
private int positivey1;
void Start()
{
positivex1 = System.Math.Abs(x1);
positivey1 = System.Math.Abs(y1);
Debug.Log("positivex1: " + positivex1);
Debug.Log("positivey1: " + positivey1);
Counter();
}
void Counter()
{
if (x1 < positivex1)
{
x1++;
Debug.Log("x1: " + x1 + " y1: " + y1);
Counter();
}
else if (x1 == positivex1 && y1 < positivey1)
{
y1++;
x1 = 0;
Counter();
}
else if (y1==positivey1 && x1==positivex1)
{
Debug.Log("Made it!");
return;
}
}
}
And this returns the following error when: x=-100 and y=-100:
StackOverflowException: The requested operation caused a stack overflow.
System.String.CreateStringFromEncoding (System.Byte* bytes, System.Int32 byteLength, System.Text.Encoding encoding) (at <1eb9db207454431c84a47bcd81e79c37>:0)
It works fine for x=-10 and y=-10. Also x=-50 and y=-50 results normally.
I thought it a good idea to show and test all coordinates in debug mode, before I’d make a list…
But that first step got me stuck.
A stack overflow occurs because the operation is recursive (the method calls itself). Since stack memory is limited, using recursion for a large number of iterations can exhaust it. In such cases, it is better to implement the solution using a loop instead of recursion.
EDIT: Here’s the method without the recursion:
void Counter()
{
while (true)
{
if (x1 < positivex1)
{
x1++;
Debug.Log("x1: " + x1 + " y1: " + y1);
}
else if (x1 == positivex1 && y1 < positivey1)
{
y1++;
x1 = 0;
}
else if (y1 == positivey1 && x1 == positivex1)
{
Debug.Log("Made it!");
return;
}
else
{
// sanity check
Debug.LogError("Error in Counter method, this branch should never have been called, error in my logic.")
return;
}
}
}
But there are much simpler ways to map grid positions to a list. You can find tutorials that explain how to do this, and it’s very useful to create your own collection that encapsulates that list and retrieves elements using a custom indexer: Indexers - C# | Microsoft Learn. This will allow you to write expressions like myGrid[-5,2]
Your tips are much appreciated.
I went back to the drawing board and made certain different decisions.
While I do understand certain principles and concepts when isolated, it is when they are combined when errors are made. Simply said, I think I was trying to do to much in too big steps.
I wrote down a concept which was:
A) List of game objects that can be selected random
B) List of random positions (it is indeed not needed to list them all
C) A way to remove double random positions
D) Remove any positions that are unwanted to place anything (such as the player start)
E) Place the list of A) at the positions that are still left as a product at the end of D)
I went back to the drawing board and ended up with the following code:
using NUnit.Framework;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
public class BoardManager : MonoBehaviour
{
public GameObject healthPotion;
public int minX;
public int maxX;
public int minZ;
public int maxZ;
public GameObject Object1;
public GameObject Object2;
public GameObject Object3;
public GameObject Object4;
public GameObject Object5;
public GameObject Object6;
public GameObject Object7;
public GameObject Object8;
public GameObject Object9;
public GameObject Object10;
private int t;
public int amountOfObjectsCreated;
public List<Vector3> points { get; set; }
public List<GameObject> objects { get; set; }
void Start()
{
Spawner();
}
void Spawner()
{
points = new List<Vector3>(); //starts the list for the random Vector3 creation
objects = new List<GameObject>(); //starts the GameObject list
//List of game Objects manually added
objects.Add(Object1);
objects.Add(Object2);
objects.Add(Object3);
objects.Add(Object4);
objects.Add(Object5);
objects.Add(Object6);
objects.Add(Object7);
objects.Add(Object8);
objects.Add(Object9);
Debug.Log(objects);
while (t < amountOfObjectsCreated)
{
var position = new Vector3(
x: Random.Range(minX, maxX)+0.5f,
y: 0.3f,
z: Random.Range(minZ, maxZ)+0.5f);
t++;
points.Add(position);
points = points.Distinct().ToList();
Debug.Log("GameObject placed with counter t: " + t);
}
foreach (Vector3 v in points)
{
// this is where positions are created that are not meant to be used by the Spawner
var removeposition = new Vector3(-0.5f,0.3f,.5f); //This is the player start, a few others may follow
points.Remove(removeposition);
Debug.Log(v); //End of the line
Instantiate(objects[Random.Range(0, objects.Count)]
, v, Quaternion.identity);
}
}
}
With this, Unity seems to be able to produce the following result with 10000 random objects produced less than a minute):
The selection of the random objects is far from perfect (I have to add extra objects with the same name to increase their chance of occurence), but it is something I can work with and understand.
If you don’t really care too much about the exact number of items and you just want to spawn lots of them, then the code below will try to spawn 10000 items in unique positions in a fraction of a second.
using UnityEngine;
public class SpawnItems : MonoBehaviour
{
public GameObject[] items; // use the inspector to drag items that you want to spawn onto this public array
void Start()
{
for (int i = 0; i < 10000; i++)
{
Vector3 p = new Vector3(Random.Range(-100f, 100f), 0.3f, Random.Range(-100f, 100f));
if (!Physics.CheckSphere(p, 0.5f))
Instantiate(items[Random.Range(0,items.Length)], p, Quaternion.identity);
}
}
}
But more importantly it shows you how you can create a public array of game objects which you can then fill in using the Inspector. So then you don’t need to have Object1, Object2, Object3 etc…
Yes, that is very handy. My whole game making good progress.
Getting most items to be produced by procedure is fine for this creation.
I was curious if there is any better approach than my current object selection.
Sometimes I would like an object B to spawn at a 3 % chance.
In my current setup, for instance, I would create a list of 97 objects type A.
And 3 of type B.
Or as an alternative approach I would create a random number.
For instance 1-100.
And if it is 1, 2 or 3: Spawn object B.
And if something else: Spawn Object A.
I suppose the last procedure is the best for most things?
I guess creating 100 numbers is easier on the computer than a list of 100 objects?
Ofcourse approach 1 has the ability to change things manually.
I guess I’m drifting a bit off topic from my main question.
int index = Random.Range(0, Random.Range(0, 100));
The above line will produce a number in the range of 0 to 100 but with the lower values occurring more frequently. So you can place your objects into an array with the objects you want to appear more frequently at the start of the array and then use the above line to access the objects.
Step 1) Random number between 0 and 100 generated.
Step 2) Create a number between 0 and the number from step 1.
I guess for it to become 99 it would be relatively 1/100 * 1/99 ~1/9900
And for it to become 50 it would be relatively 1/100 * 1/50 ~ 1/5000
And for it to become 1 it would be relatively 1/100 * 1/1 ~ 1/100