The idea would be to practically get something like the image below, at least virtually.
So each node would have a certain chance to happen, but as soon as you adjust the point in the odds, the other ones automatically adjust to keep it so it adds up to 100%
If you have a list of probabilities, you can calculate the sum of all of them and then divide each one by this sum. This will normalize them so that they add up to 1 (100%).
It works regardless of what they added up to before normalization. For instance if you have 0.8 (80%) and 0.4 (40%), their sum is 120%, but after normalization you get 66.6% and 33.3%, which add up to 100% (0.8/1.2=0.66 and 0.4/1.2=0.33).
Also note their relative chance is not affected, 0.8 is 0.4 * 2, just like 0.66 is 0.33 * 2.
Oh that is very smart!! My only worry is how I will keep track of what % chance it actually is…
Yep, that’s how I always do it.
This sounds like a good place to use a custom property drawer, but it actually sounds non-trivial…you’d need to draw a list of values and their effective % chances.
Shouldn’t be too bad… Maybe some kind of pie chart? Not sure if there’s a handy arc / pie wedge drawer in the GUI tools but it wouldn’t be too hard to simulate it with a bunch of radial lines, or even with many thin graphic slices rotated by setting the GUI.matrix
Or just a list of percentage numbers live displayed below in a custom inspector?
I would just use as many Range (0,1) type float sliders as I needed, then as listed above, sum them and divide by them and show the percentage value next to each one.
Don’t forget to handle all zeros or you’ll be NaN
My helper method for picking weighted random:
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];
}
}
I have a propertydrawer for it which results in something like this:
It’s technically here:
BUT, as was previously mentioned, the drawer is non-trivial. My specific implementation relies on my extensions library around the unity editor. Not only that, said system is built around an older version of Unity that didn’t support propertydrawer’s in arrays the way that I liked like it does today in newer versions and I’ve had to further modify it to offer both backwards/forwards compatibility so that I didn’t have to rewrite large portions of it. Not only that it’s generalized so I can adapt the drawer to any script.
But yeah… basically not exactly plug and play.
If you only needed it one off. I’d personally just write a adhoc script in a custom editor for that specific component. That would be roughly trivial. Just get a ReorderableListDrawer from UnityEditorInternal, hook the draw element call back. Then in your component have a list of some serializable struct/class that has the weight and result. Then in your editor read that property and loop over all of them and sum the weights, and divide each by that total sum. And display as necessary.