Address
304 North Cardinal St.
Dorchester Center, MA 02124

Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM

Unity random seed

Unity Random Seed

Today, we delve into Unity’s Random class, focusing specifically on the use of a seed.
Presenting a feature is more engaging with a concrete example.

That’s why we will implement a rogue-like room type system (like Hades) with a random reward system based on a seed.
Finally, we’ll explore the randomness in one of the most beloved roguelikes.

Table of contents

But before we go any further, let’s revisit the basics and explore how to implement randomness.

Note: My examples are fairly simple, so I’m going to use Unity’s Random class.
If you need to achieve more complex tasks with multiple seeds, I recommend using “System.Random” directly or even using another library that suits your needs.

Unity: How to Use Random?

Random is a static class that provides several functions to obtain random numbers.

Get a Random Number Between 0 and 1

To get a random Number Between 0 and 1, use the static property “value” of “Random“.

using UnityEngine; 
    
var value = Random.value;
    

Get a Random Number with Range

To obtain a random number between two values, use the static “Range” function of “Random”.

using UnityEngine;
    
var value = Random.Range(min, max);
    

When using integers, the start value is inclusive, and the end value is exclusive. When using floats, both values are inclusive.

Modify the Seed

To modify the seed, use the “InitState” function of “Random”.
Be careful not to use the seed property again, as it is deprecated.

using UnityEngine;
    
Random.InitState(seed);
    

What Are We Trying to Achieve?

Our goal is to implement randomness behind a room system.
The objective of our game is to traverse different rooms, and at the end of each room, the player receives a random reward (power, gold, events, etc.).

Moreover, the player has control over their fate, choosing which room to enter, making the game like a maze.

Hades choice roomHades – Choice between 2 doors – Image from IGN

Regardless of the user’s navigation choices, we want fairness in the game.
Two players with the same seed in the same room should receive the same rewards.

How do “Random” and “seed” work?

Randomness, at least in Unity, is managed using pseudorandom numbers.

This process begins with an initial seed, which is employed to generate the first number which generates another seed used to generate the next number, and so on.

Random seed explain
Illustration random seed with fake values 

Therefore, using the same seed will always yield the same numbers with your random calls.

In our scenario, this poses a problem as the user determines the path:

Random seed wrong reward

Using Random directly, we observe that rewards are no longer tied to rooms but to the number of rooms crossed.
Moreover, in a game, other randomness can occur between different rooms due to user or monster actions, providing a different experience with the same seed.

There’s nothing wrong with this functionality, but in our case, we want identical rewards.

Why Do This?

You might wonder why bother with this—what’s the point of having a seed that perfectly reproduces the level?

  • Level Sharing: To share the experienced gameplay with others.
  • Equity in a Competitive Game: Randomness can disrupt equity between two players. Providing them with the same seed in a confrontation helps settle the score.
  • Aiding Debugging: During testing phases, if a user encounters a bug, sharing the seed makes it easy for developers to reproduce it.
  • Assisting Speedrun Community: For speedrunning games, this system could aid in training by repeating segments with a specific seed.

Attention: The goal is not to entirely remove randomness but to control it to maintain a form of equity.

The Plan

The plan to address this issue is quite simple.
During the game’s launch, we will pre-calculate rewards for each room using an ordered list.
Then, we will regenerate a seed to handle actions independent of our seed in a game.

Plan random

We’ll stick to a relatively simple architecture.

A GameRoomManager will store a list of RoomConfigurations (a ScriptableObject storing the configuration of each room).
The GameRoomManager will be responsible for assigning each random reward to the correct RoomConfiguration.

Let’s code, Random and seed in Unity

For each room, we will start with four possible rewards, each with the same probability.
An enum Reward will represent these different options:

    public enum Reward
    {
        POWER,
        LEVELUP,
        GOLD,
        EVENT
    }
    

Next, the RoomConfiguration ScriptableObject:

    using UnityEngine;

    [CreateAssetMenu()]
    public class RoomConfiguration : ScriptableObject
    {
        public int id;

        // Sample Fields configurations
        public AudioClip backgroundMusic;
        public Sprite sprite;
        public Vector2 minMaxNumberEnemy;

        // Other configuration fields here...

        // This value will be set by our GameRoomManager
        public Reward? Reward { get; set; }
    }
    

And finally, the heart of the program, the GameRoomManager:

    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using System;
    using Random = UnityEngine.Random;

    public class GameRoomManager : MonoBehaviour
    {
        public List<RoomConfiguration> roomConfigurations = new List<RoomConfiguration>();

        public void InitRooms(int seed)
        {
            Random.InitState(seed);

            // Optionally, sort by id
            roomConfigurations.Sort((a, b) => a.id.CompareTo(b.id));

            foreach (var room in roomConfigurations)
            {
                room.Reward = GetRandomReward();
            }

            EndInit();
        }

        private Reward GetRandomReward()
        {
            // Cache it; it will be better!
            var rewards = Enum.GetValues(typeof(Reward)).Cast().ToList();

            // Base random between our rewards
            return rewards[Random.Range(0, rewards.Count)];
        }

        public void EndInit()
        {
            // Get a new seed for all other random
            Random.InitState((int)DateTime.Now.Ticks);
        }
    }
    

To initialize our rooms, we just need to call the “InitRooms” method, passing a seed as a parameter.
Each of our RoomConfigurations will be traversed to associate a reward with it.

We can then retrieve a room by its identifier using the following function:

    public RoomConfiguration GetRoomConfigurationById(int id)
    {
        return roomConfigurations.Find(x => x.id == id);
    }
    

Now, this is what we have:

Random seed correct rewardNote: I used a different code to generate this image.

Perfect now our rewards remain the same per room.

Perhaps you’re wondering whether this code is still viable with thousands of rooms?
The “Random” class is so fast that it can generate a million random numbers in a hundred milliseconds (figures to be taken with a pinch of salt).

Note: In this code, ScriptableObjects are stored directly in memory. Depending on the number of stored rooms and their data, using Addressable Assets may be more efficient.

Case Study: How Hades Manages its Randomness

Now it’s time to see how one of the market leaders in roguelites implemented its randomness system.
Just a reminder: Hades is a roguelite where you play as the son of the god of death trying to escape from the underworld.

To gather this information I took an interest in speedrunning.
Honestly, speedrunners are the people who know the most about a game after the developers.

On speedrun.com, I found this information:

“A new seed is generated based on a combination of your current seed and how many times RNG has been used, and that new seed replaces your current one.”

Here we can see the basic workings of a Random.
Initially, there’s a seed that creates our first state, evolving with each used Random.

However, the only changing point is that a new seed is generated after leaving a room.

“However, when the doors unlock, your position is reset to the beginning.”

In Hades, the doors open upon completing the room (getting the reward…).
When they mention resetting the position, it means starting with a seed without any random applied to it.

In reality, the seed is reset once when the reward appears and once when the doors open.

Hades random room resetScreen capture from youtube video: Hades – Routed Speedrunning Basics – How to Run a Route

For a better visualization, you can watch this video:


Hades – Routed Speedrunning Basics – How to Run a Route | cgull

If I were to translate this functionality into Unity, it would look something like this:


    using UnityEngine;

    public class RoomHades : MonoBehaviour
    {
        private Random.State _roomState;

        // This function is called when a player enters the room.
        public void AfterChangeRoom()
        {
            GenerateNewSeed();

            // Save the current state
            _roomState = Random.state;
        }

        private void GenerateNewSeed()
        {
            // Some math to generate a new seed
        }


        // Player has cleared the room & gets the reward.
        public void UnlockDoor()
        {
            // Reset seed
            Random.state = _roomState;
        }
    }

Explanations, Theories

This is where it gets interesting—why use this process and not just keep the base seed?

I don’t have the exact answer, but I’ll propose two theories.
Feel free to correct me in the comments =)

1. Reinforcement of Gameplay Variations

Generating a new seed after each room contributes to diversifying gameplay.

With a frequently changing seed, players can experience a greater variety of room configurations, encounters, and rewards, making each playthrough more unique.

2. Improvement of Anti-Cheating Measures and Complexity of Routing

Changing the seed at each door helps make routing (strategic path choices) more complex.
As seen with our speedrunning friends, manipulating the RNG remains challenging.

Additionally, if the seed remained constant, players could potentially memorize room configurations and optimize their progression, introducing a potential cheating problem.

I hope this article has been enjoyable and has helped you understand a bit more about how Random and their seeds work.

Bud illustration by OpenClipart-Vectors
Background illustration by Peace,love,happiness

Find out more

Check our other articles about Unity:

Leave a Reply

Your email address will not be published. Required fields are marked *