Last active
July 5, 2022 16:05
-
-
Save FleshMobProductions/05cdf911e1dcec42dd31e07113c58108 to your computer and use it in GitHub Desktop.
Weighted Randomness class for Unity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
namespace FMPUtils.Randomness | |
{ | |
[System.Serializable] | |
public class WeightedElement<T> | |
{ | |
public T value; | |
[Range(0, 100)] | |
public int weight = 1; | |
} | |
// ToDo: implement weightedValues as list? | |
// The way the generics are done is to have a serialization support in unity later one for weightedValues | |
/// <summary> | |
/// Class for grabbing a random value from a weightened value array, | |
/// Either use GetRandomValue() to get a random value without weighting, or use | |
/// GetRandomValueWeighted() to get a random value where the randomness weight is considered, | |
/// whereas values with higher weight are more likely to be selected | |
/// </summary> | |
[System.Serializable] | |
public class WeightedRandomizer<T, WeightedT> where WeightedT : WeightedElement<T>, new() | |
{ | |
[SerializeField] protected WeightedT[] weightedValues; | |
public WeightedT[] WeightedValues { get { return weightedValues; } } | |
public void AddValue(T newValue, int weight, bool preventAddIfSameElementExists = false) | |
{ | |
if (preventAddIfSameElementExists) | |
{ | |
for (int i = 0; i < weightedValues.Length; i++) | |
{ | |
if (weightedValues[i].value.Equals(newValue)) | |
{ | |
weightedValues[i].weight = weight; | |
return; | |
} | |
} | |
} | |
WeightedT[] valuesNew = new WeightedT[weightedValues.Length + 1]; | |
for (int i = 0; i < weightedValues.Length; i++) | |
{ | |
valuesNew[i] = weightedValues[i]; | |
} | |
var newWeightedEntry = new WeightedT(); | |
newWeightedEntry.value = newValue; | |
newWeightedEntry.weight = weight; | |
valuesNew[valuesNew.Length - 1] = newWeightedEntry; | |
weightedValues = valuesNew; | |
} | |
/// <summary> | |
/// If value already exists, its weight will be updated, otherwise it will be added | |
/// </summary> | |
/// <param name="newValue"></param> | |
/// <param name="weight"></param> | |
public void AddOrUpdateValue(T value, int weight) | |
{ | |
AddValue(value, weight, true); | |
} | |
public void RemoveValue(T valueToRemove) | |
{ | |
int indexOf = -1; | |
for (int i = 0; i < weightedValues.Length; i++) | |
{ | |
if (weightedValues[i].value.Equals(valueToRemove)) | |
{ | |
indexOf = i; | |
break; | |
} | |
} | |
if (indexOf >= 0) | |
{ | |
WeightedT[] clipsNew = new WeightedT[weightedValues.Length - 1]; | |
int newIndex = 0; | |
for (int i = 0; i < weightedValues.Length; i++) | |
{ | |
if (i != indexOf) | |
{ | |
clipsNew[newIndex] = weightedValues[i]; | |
} | |
newIndex++; | |
} | |
weightedValues = clipsNew; | |
} | |
} | |
public T GetRandomValue() | |
{ | |
return weightedValues[UnityEngine.Random.Range(0, weightedValues.Length)].value; | |
} | |
/// <summary> | |
/// Selects a random value while considering the assigned weights | |
/// </summary> | |
/// <returns></returns> | |
public T GetRandomValueWeighted() | |
{ | |
int weightSum = 0; | |
for (int i = 0; i < weightedValues.Length; i++) | |
{ | |
weightSum += weightedValues[i].weight; | |
} | |
int randomVal = UnityEngine.Random.Range(0, weightSum); | |
int weightSumTemp = 0; | |
for (int i = 0; i < weightedValues.Length; i++) | |
{ | |
weightSumTemp += weightedValues[i].weight; | |
if (randomVal < weightSumTemp) | |
{ | |
return weightedValues[i].value; | |
} | |
} | |
return weightedValues[weightedValues.Length - 1].value; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
namespace FMPUtils.Randomness | |
{ | |
public class WeightedRandomPrefabSupply : MonoBehaviour | |
{ | |
[System.Serializable] | |
public class WeightedGameObjectElement : WeightedElement<GameObject> | |
{ | |
} | |
[System.Serializable] | |
public class WeightedGameObjectRandomizer : WeightedRandomizer<GameObject, WeightedGameObjectElement> | |
{ | |
} | |
[SerializeField] private WeightedGameObjectRandomizer elements; | |
public GameObject GetRandomPrefab() | |
{ | |
return elements.GetRandomValue(); | |
} | |
public GameObject GetRandomPrefabWeighted() | |
{ | |
return elements.GetRandomValueWeighted(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
namespace FMPUtils.Randomness | |
{ | |
public class WeightedRandomUtility | |
{ | |
/// <summary> | |
/// Takes an AnimationCurve that stores the weight distribution which will be applied as weight values to elmeents in the passed WeightenedElement array | |
/// </summary>, | |
/// <typeparam name="T">WeightedElement type</typeparam> | |
/// <param name="elements">The elements to apply the weight to</param> | |
/// <param name="weightCurve">Weight value curve, the time of the curve should go from 0 (first elment) to 1 (last element)</param> | |
/// <param name="weightMultiplier">multiplier applied to the weight curve values (because WeightedElement uses Int32 as weight, so we should stay outside of fraction ranges)</param> | |
public static void AssignWeightsFromNormalizedCurve<T>(WeightedElement<T>[] elements, AnimationCurve weightCurve, float weightMultiplier = 1f) | |
{ | |
for (int i = 0; i < elements.Length; i++) | |
{ | |
float sampleTime = ((float)i) / (elements.Length - 1); | |
// Simple round | |
int weight = (int)(weightCurve.Evaluate(sampleTime) * weightMultiplier + 0.5f); | |
elements[i].weight = weight; | |
} | |
} | |
/// <summary> | |
/// Takes an AnimationCurve that stores the weight distribution which will be applied as weight values to elmeents in the passed WeightenedElement array | |
/// </summary>, | |
/// <typeparam name="T">WeightedElement type</typeparam> | |
/// <param name="elements">The elements to apply the weight to</param> | |
/// <param name="weightCurve">Weight value curve. Needs at least 2 keyframes. takes the first keyframe time for the first element time and the last keyframe for the last element time</param> | |
/// <param name="weightMultiplier">multiplier applied to the weight curve values (because WeightedElement uses Int32 as weight, so we should stay outside of fraction ranges)</param> | |
public static void AssignWeightsFromCurve<T>(WeightedElement<T>[] elements, AnimationCurve weightCurve, float weightMultiplier = 1f) | |
{ | |
var keys = weightCurve.keys; | |
if (keys == null) | |
{ | |
Debug.LogError($"AssignWeightsFromCurve: AnimationCurve.keys are null"); | |
return; | |
} | |
else if (keys.Length < 2) | |
{ | |
Debug.LogError($"AssignWeightsFromCurve: AnimationCurve.keys need to have at least 2 keys"); | |
return; | |
} | |
float startTime = keys[0].time; | |
float endTime = keys[keys.Length - 1].time; | |
float startToEnd = endTime - startTime; | |
float elementTimeDelta = startToEnd / (elements.Length - 1); | |
for (int i = 0; i < elements.Length; i++) | |
{ | |
float sampleTime = startTime + i * elementTimeDelta; | |
// Simple round | |
int weight = (int)(weightCurve.Evaluate(sampleTime) * weightMultiplier + 0.5f); | |
elements[i].weight = weight; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey nice job!
One recommendation to this would be to remove the range limit on the weight and set the min/max on the array instead.
Better value validation this way