Seeded Random in Unity C#
2023-07-30
UnityC#In Unity, UnityEngine.Random
is to deal with random capabilities that can be used as below:
using System;
UnityEngine.Random.value
It looks simple, however, as UnityEngine.Random.value
is a shared value in the global scope, it's nearly impossible to test codes that are using UnityEngine.Random.value
inside them.
To be capable of testing that, UnityEngine.Random.InitState
could be used to set state of the random instance.
In addition, there could be things that want to use UnityEngine.Random
directly without considering its seed values and state.
So, it should be keep its state before and after use of InitState
.
See the below snippet to achieve it:
using System;
public class SeedRandom
{
private UnityEngine.Random.State _randomState;
public static int DefaultSeed()
{
return (int)System.DateTime.Now.Ticks;
}
public SeedRandom() : this(SeedRandom.DefaultSeed()) { }
public SeedRandom(int seed)
{
var originalState = UnityEngine.Random.state;
UnityEngine.Random.InitState(seed);
_randomState = UnityEngine.Random.state;
UnityEngine.Random.state = originalState;
}
private T WithRandom<T>(Func<T> f)
{
var originalState = UnityEngine.Random.state; // state before use
UnityEngine.Random.state = _randomState; // set state of the last use
var result = f(); // invoke `f` that might use `UnityEngine.Random` within it
_randomState = UnityEngine.Random.state; // store the state after use
UnityEngine.Random.state = originalState; // reset state
return result;
}
public int Range(int minInclusive, int maxExclusive)
{
return WithRandom(() => UnityEngine.Random.Range(minInclusive, maxExclusive));
}
public float Range(float minInclusive, float maxInclusive)
{
return WithRandom(() => UnityEngine.Random.Range(minInclusive, maxInclusive));
}
public float Value()
{
return WithRandom(() => UnityEngine.Random.value);
}
}
Thanks to SeedRandom
, it's possible to provide random instance from outside when calling any functions that need random capabilities.