Seed is confusing and placing the same number, it's driving me insane

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Cryptography;
using UnityEngine;

public class RoomSpawner : MonoBehaviour
{
    public int openingDirection;
    // 1 --> need bottom door
    // 2 --> need top door
    // 3 --> need left door
    // 4 --> need right door

    private RoomTemplates templates;
    private AddRoom add;
    private int rand;
    private bool spawned = false;

    private float waitTime = 8f;
    private RoomMax nr;
    private SetSeed sSeed;

    void Awake()
    {
        sSeed = GameObject.FindGameObjectWithTag("MainRooms").GetComponent<SetSeed>();
        Random.InitState(sSeed.seed);
    }

    void Start()
    {
        Destroy(gameObject, waitTime);
        templates = GameObject.FindGameObjectWithTag("MainRooms").GetComponent<RoomTemplates>();
        add = gameObject.GetComponentInParent<AddRoom>();
        Invoke("Spawn", 1f);
        nr = GameObject.FindWithTag("MainRooms").GetComponent<RoomMax>();
    }

    void Spawn()
    {
        if (sSeed == null)
        {
            UnityEngine.Debug.Log("sSeed not been set");
        }
        if (spawned == false)
        {
            if (nr.numberRoom <= nr.maxRoom)
            {
                if (openingDirection == 0)
                {
                }
                if (openingDirection == 1)
                {
                    rand = Random.Range(0, templates.bottomRooms.Length);
                    Instantiate(templates.bottomRooms[rand], transform.position, templates.bottomRooms[rand].transform.rotation);
                    nr.numberRoom += 1f;
                }
                else if (openingDirection == 2)
                {
                    rand = Random.Range(0, templates.topRooms.Length);
                    Instantiate(templates.topRooms[rand], transform.position, templates.topRooms[rand].transform.rotation);
                    nr.numberRoom += 1f;
                }
                else if (openingDirection == 3)
                {
                    rand = Random.Range(0, templates.leftRooms.Length);
                    Instantiate(templates.leftRooms[rand], transform.position, templates.leftRooms[rand].transform.rotation);
                    nr.numberRoom += 1f;
                }
                else if (openingDirection == 4)
                {
                    rand = Random.Range(0, templates.rightRooms.Length);
                    Instantiate(templates.rightRooms[rand], transform.position, templates.rightRooms[rand].transform.rotation);
                    nr.numberRoom += 1f;
                }
                spawned = true;
                //UnityEngine.Debug.Log(rand + " " + transform.position);
            }
            if (nr.numberRoom > nr.maxRoom)
            {
                Invoke("SpawnClose", 0.75f);
            }
        }
    }

    void SpawnClose()
    {
        if (spawned == false)
        {
            if (nr.numberRoom > nr.maxRoom)
            {
                if (openingDirection == 1)
                {
                    Instantiate(templates.ClosedbottomRooms, transform.position, templates.ClosedbottomRooms.transform.rotation);
                }
                else if (openingDirection == 2)
                {
                    Instantiate(templates.ClosedtopRooms, transform.position, templates.ClosedtopRooms.transform.rotation);
                }
                else if (openingDirection == 3)
                {
                    Instantiate(templates.ClosedleftRooms, transform.position, templates.ClosedleftRooms.transform.rotation);
                }
                else if (openingDirection == 4)
                {
                    Instantiate(templates.ClosedrightRooms, transform.position, templates.ClosedrightRooms.transform.rotation);
                }
                spawned = true;
            }
        }
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("SpawnPoint"))
        {
            if (other.GetComponent<RoomSpawner>().spawned == false && spawned == false)
            {
                Instantiate(templates.closedRooms, transform.position, templates.closedRooms.transform.rotation);
                Destroy(gameObject);
            }
            if (other.GetComponent<RoomSpawner>().spawned == true && spawned == true)
            {
                //add.Remove();
            }
            spawned = true;
        }
    }
}

In the above code, I have separate empty game objects that pick a random number and place a prefab from an array and then move on to another empty game object. sSeed sits in a main room, that hosts the manual or random seed. The problem is that they’re mostly choosing the number 0, and they’re not spawning the same prefabs in the game. It’s driving me insane.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SetSeed : MonoBehaviour
{
    public string stringSeed = "seed string";
    public int seed;
    public bool randomizeSeed;

    void Awake()
    {
        if (randomizeSeed == false)
        {
            seed = stringSeed.GetHashCode();
        }

        if (randomizeSeed == true)
        {
            seed = Random.Range(0, 99999);
        }

        Random.InitState(seed);
        UnityEngine.Debug.Log(Random.Range(0, 99999));
        UnityEngine.Debug.Log(Random.Range(0, 99999));
        UnityEngine.Debug.Log(Random.Range(0, 99999));
    }
}

Here’s sSeed. Thank you

Your code looks dependent entirely on SetSeed’s Awake() function occurring BEFORE you run RoomSpawner’s Awake(). Unity makes no such promises about any order of calls.

If you’re not doing this deconfliction yourself by actually instantiating the RoomSpawner script later than the SetSeed script, the order is effectively random.

One approach might be to make the seed-maker an actual lazy getter inside of SetSeed, or else in the RoomSpawner, don’t call what’s in SetSeed until the Start() method.

As you can see, Start() would be after Awake(), assuming both script instances existed at the same time:

I recommend you do NOT use the script execution order settings because those are a great way to introduce baffling bugs long in the future when you copy this code to another project without those execution order settings and it starts failing. Instead, make your code execution explicit, as I suggest above.

2 Likes

ok I fixed that, but why does the script not produce the same series of prefabs, even though I put in the exact same seed?

Time to start putting in Debug.Log() statements to see what the values are coming out of the random generator. Perhaps the seed didn’t get set as you think it did. Debug.Log() statements will also tell you what order things are happening in.

I have debug.logs that’s how I know it’s not putting in the right prefab. It’ll pick a number through 1-4, and it seems pretty random and with no complete pattern

Absolutely never do Random.InitState(seed); on it’s own.
Only ever do this:

UnityEngine.Random.State oldstate = UnityEngine.Random.state;
UnityEngine.Random.InitState(your seed);
use your seed random
UnityEngine.Random.state = oldstate;

I once lost 3 days because some random inspector code was setting the seed every frame. Never ever set the seed without first taking the state and reverting it back after usage. Hopefully that’s your issue.

1 Like

I’m having a hard time understanding this. Do you have many objects with your RoomSpawner script on it?

Oh yeah, what @eisenpony asks… if you have more than one script, there’s no order guarantee for them. Just two separate random number consumers would be undefined in what order they consume the stream of numbers.

Yes I do

Ok I’ll try that next time I’m on the computer thank you

How would I go doing it and making it work?

What you need to understand is that the Random class provided by Unity uses a global object.

Each of your instances of that script are using the same object. It’s not possible for the second instance to get the same numbers as the first instance since the first instance has already consumed them.

In cases where I need a random sequence to stay consistent for a single object, I lean toward using System.Random as it creates an instance of a random number generator instead of a global generator like the one provided in UnityEngine.Random. This sounds like your use case as well, so it may be an alternate approach that will avoid needing to save states or manage the global object.

4 Likes

Ok I think that’s what I want it to do, I just want the numbers to be in a sequence that produces the same generated dungeon based on the number of prefab

Ok I can try that, how would I write that?

Ok new thing, apparently me setting the Random.InitState(sSeed.seed), at line 26-27, causes my enemySpawner to produce the same number, each time.

This is the documentatin of System.Random:
https://docs.microsoft.com/en-us/dotnet/api/system.random?view=netcore-3.1

Only problem with this solution is that .net makes no guarantee that the underlying sequence is the same from version to version of .net. Hence this note in that documentation:

Unity throws a bit of confusion into this as it used mono for years, but now with the newer .net 4.x support it gets even more confusing. I honestly don’t know what exact version of .net is used, or if it varies from build to build.

Some testing could be done to confirm the consistency of System.Random from build target to build target, and version of .net it targets, etc.

But I never bothered to sit down and do that.

Also the fact I now have 2 distinct interfaces between Unity Random, and Microsoft Random annoyed me. And well of course the aforementioned lack of object identity for Unity’s Random.

So what I did was define my own IRandom interface:
https://github.com/lordofduct/spacepuppy-unity-framework-3.0/blob/master/SpacepuppyUnityFramework/IRandom.cs

Then implemented various Random classes:
https://github.com/lordofduct/spacepuppy-unity-framework-3.0/blob/master/SpacepuppyUnityFramework/Utils/RandomUtil.cs#L259

Here I have:
UnityRNG - a wrapper around the Unity Random class that gives it object identity
MicrosoftRNG - just System.Random with the implementation of IRandom
LinearCongruentialRNG - a simple deterministic rng, its distribution isn’t all that smooth, but it’s fast and effective for games
SimplePCG - a simple implementation of PCG algorithm, though I can’t remember if this is the latest version… my first implementation had a bug in it…

And of course I include several extension methods for IRandom to get things like random bools, vectors, ranges, etc.

But yeah, when I need to say generate a map that is reproduceable. I do something like:

IRandom rng = RandomUtil.CreateDeterministicRNG(someSeed);
//use rng to do all my generation

But this way, through out my code, if I write any code that allows random. I consume an ‘IRandom’ so I can choose in the moment which one I use (always defaulting to unity if null is passed in):
https://github.com/lordofduct/spacepuppy-unity-framework-3.0/blob/master/SpacepuppyUnityFramework/Utils/ArrayUtil.cs#L278

1 Like

I believe a big issue you’re having is not related to random at all, and as was stated, has mostly everything to do with script execution order.

Note that 2 scripts in a scene have no guarantee of what order their ‘Awake/Start’ methods are called in.

Awake always happens before Start, but ScriptA’s Awake may happen before ScriptB’s Awake… or vice versa. More so if more than 1 ScriptA is in the scene, which ScriptA’s Awake is called first isn’t guranteed.

This is why Awake/Start exist. Unity suggests you use Awake to initialize the state of the self, and Start to communicate/alter/retrieve states of other objects.

And if you need even more granular control you can use the script execution order settings that forces certain scripts of a specific type to be ordered a specific way:
https://docs.unity3d.com/Manual/class-MonoManager.html

In your code you’re relying on ‘SetSeed’ script to come up with a seed. Then ‘RoomSpawner’ to rely on that state.

More so you’re doing things like “FindGameObjectWithTag” which is just grabbing any first object out there with a specific tag. This method is generally frowned upon in the community.

Question…

Why is SetSeed and RoomSpawner 2 distinct scripts?

Is SetSeed a script that more scripts rely on? Not just RoomSpawner?

Why the division of this behaviour? I can think of multiple reasons to divide this behaviour, but I’m wondering what your specific need is.

Ok

ok so I’m still a super big noob when it comes to C# and unity lol. So how is the best way to implement this?, putting down that exact code?

I was just trying to use this script as a main base for my seed, as I use this seed to also choose the amount of rooms that are placed