petitviolet blog

    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.