So mind you… you’re going to want to separate your concepts of instantiate and concepts of picking random. These are 2 different operations. Instantiating a prefab is fairly trivial so I’m not even going to cover that here.
As for picking a random entry you’ll want to calculate a random value over some range and depending on where in that range that value is is associated with one of the values. So like in your image above you could theoretically generate a value between 0 and 1, if it’s less than or equal to 0.8 it goes to plant01 and if it’s between 0.8 and 1 it’s plant02.
BUT, I would like to mention this is really annoying to do because you’re now having to adjust your odds to make sure that they all sum up to 1 (for 100%) because you’re relying on this hard coded percentage.
Have you ever noticed though that when you watch sports or hear horse race numbers things are not percentages? It’s not like “Daddy’s Lucky Horse” has a 50% chance of winning does it? Or some sports player is the 90% ball kicker. Really there are stats that “rank” them and those rankings can then be compared to one another to calculate the chance on the fly no matter who they’re pitted against.
So that’s how I do my PickRandom (and so do a lot of people):
public static T PickRandom<T>(this IEnumerable<T> lst, System.Func<T, float> weightPredicate, IRandom rng = null)
{
var arr = (lst is IList<T>) ? lst as IList<T> : lst.ToList();
if (arr.Count == 0) return default(T);
using (var weights = com.spacepuppy.Collections.TempCollection.GetList<float>())
{
int i;
float w;
double total = 0f;
for (i = 0; i < arr.Count; i++)
{
w = weightPredicate(arr[i]);
if (float.IsPositiveInfinity(w)) return arr[i];
else if (w >= 0f && !float.IsNaN(w)) total += w;
weights.Add(w);
}
if (rng == null) rng = RandomUtil.Standard;
double r = rng.NextDouble();
double s = 0f;
for (i = 0; i < weights.Count; i++)
{
w = weights[i];
if (float.IsNaN(w) || w <= 0f) continue;
s += w / total;
if (s > r)
{
return arr[i];
}
}
//should only get here if last element had a zero weight, and the r was large
i = arr.Count - 1;
while (i > 0 && weights[i] <= 0f) i--;
return arr[i];
}
}
github source
So this method accepts a collection of options, and a delegate to retrieve the “weight” (rank) of that element, as well as an optional IRandom (this is specific to my code… you can just replace this IRandom with unity ‘Random’ logic). It sums up those weights and calculates each elements odds based on it and then picks one.
It would get used something like this for your code:
var choice = obstaclePrefabs.PickRandom(o => o.spawnChance);
var prefab = choice?.obstaclePrefab;
The nice thing here though is that ‘spawnChance’ doesn’t have to be 0.8 and 0.2. They could be 4 and 1 for example (which is the same). You can just put “weight” values that are saying how more likely one is than the other like ranks. The plant01 is 4 times as likely than the standard rank of 1. Where as plant02 has the standard rank of 1.
…
And here I’ve removed the logic that has dependencies on my personal code:
public static T PickRandom<T>(this IEnumerable<T> lst, System.Func<T, float> weightPredicate)
{
var arr = (lst is IList<T>) ? lst as IList<T> : lst.ToList();
if (arr.Count == 0) return default(T);
var weights = new List<float>(); //may want to come up with a way to recycle this
{
int i;
float w;
double total = 0f;
for (i = 0; i < arr.Count; i++)
{
w = weightPredicate(arr[i]);
if (float.IsPositiveInfinity(w)) return arr[i];
else if (w >= 0f && !float.IsNaN(w)) total += w;
weights.Add(w);
}
float r = Random.value;
double s = 0f;
for (i = 0; i < weights.Count; i++)
{
w = weights[i];
if (float.IsNaN(w) || w <= 0f) continue;
s += w / total;
if (s > r)
{
return arr[i];
}
}
//should only get here if last element had a zero weight, and the r was large
i = arr.Count - 1;
while (i > 0 && weights[i] <= 0f) i--;
return arr[i];
}
}