Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

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

Discussion in 'Scripting' started by CodeMateo, Jul 18, 2020.

  1. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Diagnostics;
    4. using System.Security.Cryptography;
    5. using UnityEngine;
    6.  
    7. public class RoomSpawner : MonoBehaviour
    8. {
    9.     public int openingDirection;
    10.     // 1 --> need bottom door
    11.     // 2 --> need top door
    12.     // 3 --> need left door
    13.     // 4 --> need right door
    14.  
    15.     private RoomTemplates templates;
    16.     private AddRoom add;
    17.     private int rand;
    18.     private bool spawned = false;
    19.  
    20.     private float waitTime = 8f;
    21.     private RoomMax nr;
    22.     private SetSeed sSeed;
    23.  
    24.     void Awake()
    25.     {
    26.         sSeed = GameObject.FindGameObjectWithTag("MainRooms").GetComponent<SetSeed>();
    27.         Random.InitState(sSeed.seed);
    28.     }
    29.  
    30.     void Start()
    31.     {
    32.         Destroy(gameObject, waitTime);
    33.         templates = GameObject.FindGameObjectWithTag("MainRooms").GetComponent<RoomTemplates>();
    34.         add = gameObject.GetComponentInParent<AddRoom>();
    35.         Invoke("Spawn", 1f);
    36.         nr = GameObject.FindWithTag("MainRooms").GetComponent<RoomMax>();
    37.     }
    38.  
    39.     void Spawn()
    40.     {
    41.         if (sSeed == null)
    42.         {
    43.             UnityEngine.Debug.Log("sSeed not been set");
    44.         }
    45.         if (spawned == false)
    46.         {
    47.             if (nr.numberRoom <= nr.maxRoom)
    48.             {
    49.                 if (openingDirection == 0)
    50.                 {
    51.                 }
    52.                 if (openingDirection == 1)
    53.                 {
    54.                     rand = Random.Range(0, templates.bottomRooms.Length);
    55.                     Instantiate(templates.bottomRooms[rand], transform.position, templates.bottomRooms[rand].transform.rotation);
    56.                     nr.numberRoom += 1f;
    57.                 }
    58.                 else if (openingDirection == 2)
    59.                 {
    60.                     rand = Random.Range(0, templates.topRooms.Length);
    61.                     Instantiate(templates.topRooms[rand], transform.position, templates.topRooms[rand].transform.rotation);
    62.                     nr.numberRoom += 1f;
    63.                 }
    64.                 else if (openingDirection == 3)
    65.                 {
    66.                     rand = Random.Range(0, templates.leftRooms.Length);
    67.                     Instantiate(templates.leftRooms[rand], transform.position, templates.leftRooms[rand].transform.rotation);
    68.                     nr.numberRoom += 1f;
    69.                 }
    70.                 else if (openingDirection == 4)
    71.                 {
    72.                     rand = Random.Range(0, templates.rightRooms.Length);
    73.                     Instantiate(templates.rightRooms[rand], transform.position, templates.rightRooms[rand].transform.rotation);
    74.                     nr.numberRoom += 1f;
    75.                 }
    76.                 spawned = true;
    77.                 //UnityEngine.Debug.Log(rand + " " + transform.position);
    78.             }
    79.             if (nr.numberRoom > nr.maxRoom)
    80.             {
    81.                 Invoke("SpawnClose", 0.75f);
    82.             }
    83.         }
    84.     }
    85.  
    86.     void SpawnClose()
    87.     {
    88.         if (spawned == false)
    89.         {
    90.             if (nr.numberRoom > nr.maxRoom)
    91.             {
    92.                 if (openingDirection == 1)
    93.                 {
    94.                     Instantiate(templates.ClosedbottomRooms, transform.position, templates.ClosedbottomRooms.transform.rotation);
    95.                 }
    96.                 else if (openingDirection == 2)
    97.                 {
    98.                     Instantiate(templates.ClosedtopRooms, transform.position, templates.ClosedtopRooms.transform.rotation);
    99.                 }
    100.                 else if (openingDirection == 3)
    101.                 {
    102.                     Instantiate(templates.ClosedleftRooms, transform.position, templates.ClosedleftRooms.transform.rotation);
    103.                 }
    104.                 else if (openingDirection == 4)
    105.                 {
    106.                     Instantiate(templates.ClosedrightRooms, transform.position, templates.ClosedrightRooms.transform.rotation);
    107.                 }
    108.                 spawned = true;
    109.             }
    110.         }
    111.     }
    112.  
    113.     void OnTriggerEnter2D(Collider2D other)
    114.     {
    115.         if (other.CompareTag("SpawnPoint"))
    116.         {
    117.             if (other.GetComponent<RoomSpawner>().spawned == false && spawned == false)
    118.             {
    119.                 Instantiate(templates.closedRooms, transform.position, templates.closedRooms.transform.rotation);
    120.                 Destroy(gameObject);
    121.             }
    122.             if (other.GetComponent<RoomSpawner>().spawned == true && spawned == true)
    123.             {
    124.                 //add.Remove();
    125.             }
    126.             spawned = true;
    127.         }
    128.     }
    129. }
    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.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class SetSeed : MonoBehaviour
    6. {
    7.     public string stringSeed = "seed string";
    8.     public int seed;
    9.     public bool randomizeSeed;
    10.  
    11.     void Awake()
    12.     {
    13.         if (randomizeSeed == false)
    14.         {
    15.             seed = stringSeed.GetHashCode();
    16.         }
    17.  
    18.         if (randomizeSeed == true)
    19.         {
    20.             seed = Random.Range(0, 99999);
    21.         }
    22.  
    23.         Random.InitState(seed);
    24.         UnityEngine.Debug.Log(Random.Range(0, 99999));
    25.         UnityEngine.Debug.Log(Random.Range(0, 99999));
    26.         UnityEngine.Debug.Log(Random.Range(0, 99999));
    27.     }
    28. }
    Here's sSeed. Thank you
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    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:

    https://docs.unity3d.com/Manual/ExecutionOrder.html

    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.
     
    Dextozz and CodeMateo like this.
  3. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    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?
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    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.
     
  5. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    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
     
  6. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Absolutely never do
    Random.InitState(seed);
    on it's own.
    Only ever do this:
    Code (CSharp):
    1. UnityEngine.Random.State oldstate = UnityEngine.Random.state;
    2. UnityEngine.Random.InitState(your seed);
    3. use your seed random
    4. 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.
     
    ironugget likes this.
  7. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    I'm having a hard time understanding this. Do you have many objects with your RoomSpawner script on it?
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    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.
     
  9. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Yes I do
     
  10. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Ok I’ll try that next time I’m on the computer thank you
     
  11. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    How would I go doing it and making it work?
     
  12. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    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.
     
    Last edited: Jul 20, 2020
  13. Andrew-Carvalho

    Andrew-Carvalho

    Joined:
    Apr 22, 2013
    Posts:
    42
    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.
     
  14. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    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
     
  15. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Ok I can try that, how would I write that?
     
  16. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    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.
     
  17. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    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/space...ob/master/SpacepuppyUnityFramework/IRandom.cs

    Then implemented various Random classes:
    https://github.com/lordofduct/space...epuppyUnityFramework/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:
    Code (csharp):
    1.  
    2. IRandom rng = RandomUtil.CreateDeterministicRNG(someSeed);
    3. //use rng to do all my generation
    4.  
    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/space...cepuppyUnityFramework/Utils/ArrayUtil.cs#L278
     
    Kurt-Dekker likes this.
  18. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    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.
     
  19. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    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?
     
  20. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    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
     
  21. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    You wouldn't be able to just copy paste it without some minor tweaks...

    This one you could though. Just create a file called like "IRandom.cs" or something, and paste this into it:

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. /// <summary>
    7. /// Interface/contract for a random number generator.
    8. /// </summary>
    9. public interface IRandom
    10. {
    11.  
    12.     float Next();
    13.     double NextDouble();
    14.     int Next(int size);
    15.     int Next(int low, int high);
    16.  
    17. }
    18.  
    19. public static class RandomUtil
    20. {
    21.  
    22.     #region Static Fields
    23.  
    24.     private static UnityRNG _unityRNG = new UnityRNG();
    25.  
    26.     public static IRandom Standard { get { return _unityRNG; } }
    27.  
    28.     public static IRandom CreateRNG(int seed)
    29.     {
    30.         return new MicrosoftRNG(seed);
    31.     }
    32.  
    33.     public static IRandom CreateRNG()
    34.     {
    35.         return new MicrosoftRNG();
    36.     }
    37.  
    38.     /// <summary>
    39.     /// Create an rng that is deterministic to that 'seed' across all platforms.
    40.     /// </summary>
    41.     /// <param name="seed"></param>
    42.     /// <returns></returns>
    43.     public static IRandom CreateDeterministicRNG(int seed)
    44.     {
    45.         return new SimplePCG(seed);
    46.     }
    47.  
    48.     #endregion
    49.  
    50.     #region Static Properties
    51.  
    52.     public static float Angle(this IRandom rng)
    53.     {
    54.         return rng.Next() * 360f;
    55.     }
    56.  
    57.     public static float Radian(this IRandom rng)
    58.     {
    59.         return rng.Next() * Mathf.PI * 2f;
    60.     }
    61.  
    62.     /// <summary>
    63.     /// Return 0 or 1. Numeric version of Bool.
    64.     /// </summary>
    65.     /// <returns></returns>
    66.     public static int Pop(this IRandom rng)
    67.     {
    68.         return rng.Next(1000) % 2;
    69.     }
    70.  
    71.     public static int Sign(this IRandom rng)
    72.     {
    73.         int n = rng.Next(1000) % 2;
    74.         return n + n - 1;
    75.     }
    76.  
    77.     /// <summary>
    78.     /// Return a true randomly.
    79.     /// </summary>
    80.     /// <returns></returns>
    81.     public static bool Bool(this IRandom rng)
    82.     {
    83.         return (rng.Next(1000) % 2 != 0);
    84.     }
    85.  
    86.     public static bool Bool(this IRandom rng, float oddsOfTrue)
    87.     {
    88.         int i = rng.Next(100000);
    89.         int m = (int)(oddsOfTrue * 100000);
    90.         return i < m;
    91.     }
    92.  
    93.     /// <summary>
    94.     /// Return -1, 0, 1 randomly. This can be used for bizarre things like randomizing an array.
    95.     /// </summary>
    96.     /// <returns></returns>
    97.     public static int Shift(this IRandom rng)
    98.     {
    99.         return (rng.Next(999) % 3) - 1;
    100.     }
    101.  
    102.     public static UnityEngine.Vector3 OnUnitSphere(this IRandom rng)
    103.     {
    104.         //uniform, using angles
    105.         var a = rng.Next() * Mathf.PI * 2f;
    106.         var b = rng.Next() * Mathf.PI * 2f;
    107.         var sa = Mathf.Sin(a);
    108.         return new Vector3(sa * Mathf.Cos(b), sa * Mathf.Sin(b), Mathf.Cos(a));
    109.  
    110.         //non-uniform, needs to test for 0 vector
    111.         /*
    112.         var v = new UnityEngine.Vector3(Value, Value, Value);
    113.         return (v == UnityEngine.Vector3.zero) ? UnityEngine.Vector3.right : v.normalized;
    114.             */
    115.     }
    116.  
    117.     public static UnityEngine.Vector2 OnUnitCircle(this IRandom rng)
    118.     {
    119.         //uniform, using angles
    120.         var a = rng.Next() * Mathf.PI * 2f;
    121.         return new Vector2(Mathf.Sin(a), Mathf.Cos(a));
    122.     }
    123.  
    124.     public static UnityEngine.Vector3 InsideUnitSphere(this IRandom rng)
    125.     {
    126.         return rng.OnUnitSphere() * rng.Next();
    127.     }
    128.  
    129.     public static UnityEngine.Vector2 InsideUnitCircle(this IRandom rng)
    130.     {
    131.         return rng.OnUnitCircle() * rng.Next();
    132.     }
    133.  
    134.     //public static UnityEngine.Vector3 AroundAxis(this IRandom rng, Vector3 axis)
    135.     //{
    136.     //    var a = rng.Angle();
    137.     //    if (VectorUtil.NearSameAxis(axis, Vector3.forward))
    138.     //    {
    139.     //        return Quaternion.AngleAxis(a, axis) * VectorUtil.GetForwardTangent(Vector3.up, axis);
    140.     //    }
    141.     //    else
    142.     //    {
    143.     //        return Quaternion.AngleAxis(a, axis) * VectorUtil.GetForwardTangent(Vector3.forward, axis);
    144.     //    }
    145.     //}
    146.  
    147.     public static UnityEngine.Quaternion Rotation(this IRandom rng)
    148.     {
    149.         return UnityEngine.Quaternion.AngleAxis(rng.Angle(), rng.OnUnitSphere());
    150.     }
    151.  
    152.     #endregion
    153.  
    154.     #region Methods
    155.  
    156.     /// <summary>
    157.     /// Select between min and max, exclussive of max.
    158.     /// </summary>
    159.     /// <param name="rng"></param>
    160.     /// <param name="max"></param>
    161.     /// <param name="min"></param>
    162.     /// <returns></returns>
    163.     public static float Range(this IRandom rng, float max, float min = 0.0f)
    164.     {
    165.         return (float)(rng.NextDouble() * (max - min)) + min;
    166.     }
    167.  
    168.     /// <summary>
    169.     /// Select between min and max, exclussive of max.
    170.     /// </summary>
    171.     /// <param name="rng"></param>
    172.     /// <param name="max"></param>
    173.     /// <param name="min"></param>
    174.     /// <returns></returns>
    175.     public static int Range(this IRandom rng, int max, int min = 0)
    176.     {
    177.         return rng.Next(min, max);
    178.     }
    179.  
    180.     /// <summary>
    181.     /// Select an weighted index from 0 to length of weights.
    182.     /// </summary>
    183.     /// <param name="rng"></param>
    184.     /// <param name="weights"></param>
    185.     /// <returns></returns>
    186.     public static int Range(this IRandom rng, params float[] weights)
    187.     {
    188.         int i;
    189.         float w;
    190.         float total = 0f;
    191.         for (i = 0; i < weights.Length; i++)
    192.         {
    193.             w = weights[i];
    194.             if (float.IsPositiveInfinity(w)) return i;
    195.             else if (w >= 0f && !float.IsNaN(w)) total += w;
    196.         }
    197.  
    198.         if (rng == null) rng = RandomUtil.Standard;
    199.         if (total == 0f) return rng.Next(weights.Length);
    200.  
    201.         float r = rng.Next();
    202.         float s = 0f;
    203.  
    204.         for (i = 0; i < weights.Length; i++)
    205.         {
    206.             w = weights[i];
    207.             if (float.IsNaN(w) || w <= 0f) continue;
    208.  
    209.             s += w / total;
    210.             if (s > r)
    211.             {
    212.                 return i;
    213.             }
    214.         }
    215.  
    216.         //should only get here if last element had a zero weight, and the r was large
    217.         i = weights.Length - 1;
    218.         while (i > 0 && weights[i] <= 0f) i--;
    219.         return i;
    220.     }
    221.  
    222.     /// <summary>
    223.     /// Select an weighted index from 0 to length of weights.
    224.     /// </summary>
    225.     /// <param name="rng"></param>
    226.     /// <param name="weights"></param>
    227.     /// <returns></returns>
    228.     public static int Range(this IRandom rng, float[] weights, int startIndex, int count = -1)
    229.     {
    230.         int i;
    231.         float w;
    232.         float total = 0f;
    233.         int last = count < 0 ? weights.Length : System.Math.Min(startIndex + count, weights.Length);
    234.         for (i = startIndex; i < last; i++)
    235.         {
    236.             w = weights[i];
    237.             if (float.IsPositiveInfinity(w)) return i;
    238.             else if (w >= 0f && !float.IsNaN(w)) total += w;
    239.         }
    240.  
    241.         if (rng == null) rng = RandomUtil.Standard;
    242.         if (total == 0f) return rng.Next(weights.Length);
    243.  
    244.         float r = rng.Next();
    245.         float s = 0f;
    246.  
    247.         for (i = startIndex; i < last; i++)
    248.         {
    249.             w = weights[i];
    250.             if (float.IsNaN(w) || w <= 0f) continue;
    251.  
    252.             s += w / total;
    253.             if (s > r)
    254.             {
    255.                 return i;
    256.             }
    257.         }
    258.  
    259.         //should only get here if last element had a zero weight, and the r was large
    260.         i = last - 1;
    261.         while (i > 0 && weights[i] <= 0f) i--;
    262.         return i;
    263.     }
    264.  
    265.     #endregion
    266.  
    267.  
    268.  
    269.  
    270.         #region Special Types
    271.  
    272.         private class UnityRNG : IRandom
    273.         {
    274.  
    275.             public float Next()
    276.             {
    277.                 //return Random.value;
    278.                 //because unity's Random returns in range 0->1, which is dumb
    279.                 //why you might say? Well it means that the 1 is the least likely value to generate, so for generating indices you get uneven results
    280.                 return Random.value * 0.9999f;
    281.             }
    282.  
    283.             public double NextDouble()
    284.             {
    285.                 //return (double)Random.value;
    286.                 //because unity's Random returns in range 0->1, which is dumb
    287.                 //why you might say? Well it means that the 1 is the least likely value to generate, so for generating indices you get uneven results
    288.                 return (double)Random.value * 0.99999999d;
    289.             }
    290.  
    291.             public int Next(int size)
    292.             {
    293.                 return (int)((double)size * NextDouble());
    294.             }
    295.  
    296.  
    297.             public int Next(int low, int high)
    298.             {
    299.                 return (int)(NextDouble() * (high - low)) + low;
    300.             }
    301.         }
    302.  
    303.         private class MicrosoftRNG : System.Random, IRandom
    304.         {
    305.  
    306.             public MicrosoftRNG() : base()
    307.             {
    308.  
    309.             }
    310.  
    311.             public MicrosoftRNG(int seed) : base(seed)
    312.             {
    313.  
    314.             }
    315.  
    316.  
    317.             float IRandom.Next()
    318.             {
    319.                 return (float)this.NextDouble();
    320.             }
    321.  
    322.             double IRandom.NextDouble()
    323.             {
    324.                 return this.NextDouble();
    325.             }
    326.  
    327.             int IRandom.Next(int size)
    328.             {
    329.                 return this.Next(size);
    330.             }
    331.  
    332.             int IRandom.Next(int low, int high)
    333.             {
    334.                 return this.Next(low, high);
    335.             }
    336.         }
    337.      
    338.         /// <summary>
    339.         /// A simple deterministic rng using a linear congruential algorithm.
    340.         /// Not the best, but fast and effective for deterministic rng for games.
    341.         ///
    342.         /// Various known parameter configurations are included as static factory methods for ease of creating known long-period generators.
    343.         /// See the wiki article for a list of more known long period parameters: https://en.wikipedia.org/wiki/Linear_congruential_generator
    344.         /// </summary>
    345.         public class LinearCongruentialRNG : IRandom
    346.         {
    347.  
    348.             #region Fields
    349.  
    350.             private ulong _mode;
    351.             private ulong _mult;
    352.             private ulong _incr;
    353.             private ulong _seed;
    354.  
    355.             private System.Func<double> _getNext;
    356.  
    357.             #endregion
    358.  
    359.             #region CONSTRUCTOR
    360.  
    361.             public LinearCongruentialRNG(long seed, ulong increment, ulong mult, ulong mode)
    362.             {
    363.                 _mode = mode;
    364.                 _mult = System.Math.Max(1, System.Math.Min(mode - 1, mult));
    365.                 _incr = System.Math.Max(0, System.Math.Min(mode - 1, increment));
    366.                 if (seed < 0)
    367.                 {
    368.                     seed = System.DateTime.Now.Millisecond;
    369.                 }
    370.                 _seed = (ulong)seed % _mode;
    371.  
    372.                 if (_mode == 0)
    373.                 {
    374.                     //this counts as using 2^64 as the mode
    375.                     _getNext = () =>
    376.                     {
    377.                         _seed = _mult * _seed + _incr;
    378.                         return (double)((decimal)_seed / 18446744073709551616m); //use decimal for larger sig range
    379.                     };
    380.                 }
    381.                 else if (_mode > 0x10000000000000)
    382.                 {
    383.                     //double doesn't have the sig range to handle these, so we'll use decimal
    384.                     _getNext = () =>
    385.                     {
    386.                         _seed = (_mult * _seed + _incr) % _mode;
    387.                         return (double)((decimal)_seed / 18446744073709551616m); //use decimal for larger sig range
    388.                     };
    389.                 }
    390.                 else
    391.                 {
    392.                     //just do the maths
    393.                     _getNext = () => (double)(_seed = (_mult * _seed + _incr) % _mode) / (double)(_mode);
    394.                 }
    395.             }
    396.  
    397.             #endregion
    398.  
    399.             #region IRandom Interface
    400.  
    401.             public double NextDouble()
    402.             {
    403.                 return _getNext();
    404.             }
    405.  
    406.             public float Next()
    407.             {
    408.                 return (float)_getNext();
    409.             }
    410.  
    411.             public int Next(int size)
    412.             {
    413.                 return (int)(size * _getNext());
    414.             }
    415.  
    416.             public int Next(int low, int high)
    417.             {
    418.                 return (int)((high - low) * _getNext() + low);
    419.             }
    420.  
    421.             #endregion
    422.  
    423.             #region Static Factory
    424.  
    425.             public static LinearCongruentialRNG CreateMMIXKnuth(long seed = -1)
    426.             {
    427.                 return new LinearCongruentialRNG(seed, 1442695040888963407, 6364136223846793005, 0);
    428.             }
    429.  
    430.             public static LinearCongruentialRNG CreateAppleCarbonLib(int seed = -1)
    431.             {
    432.                 return new LinearCongruentialRNG(seed, 0, 16807, 16807);
    433.             }
    434.  
    435.             public static LinearCongruentialRNG CreateGLibc(int seed = -1)
    436.             {
    437.                 return new LinearCongruentialRNG(seed, 12345, 1103515245, 2147483648);
    438.             }
    439.  
    440.             public static LinearCongruentialRNG CreateVB6(int seed = -1)
    441.             {
    442.                 return new LinearCongruentialRNG(seed, 12820163, 1140671485, 16777216);
    443.             }
    444.  
    445.             #endregion
    446.  
    447.         }
    448.  
    449.         public class SimplePCG : IRandom
    450.         {
    451.  
    452.             #region Fields
    453.  
    454.             private ulong _seed;
    455.             private ulong _inc;
    456.  
    457.             #endregion
    458.  
    459.             #region CONSTRUCTOR
    460.  
    461.             public SimplePCG(long seed = -1, ulong inc = 1)
    462.             {
    463.                 if (seed < 0)
    464.                 {
    465.                     seed = System.DateTime.Now.Ticks;
    466.                 }
    467.                 _seed = 0;
    468.                 _inc = (inc << 1) | 1;
    469.                 this.GetNext();
    470.                 _seed += (ulong)seed;
    471.                 this.GetNext();
    472.             }
    473.  
    474.             #endregion
    475.  
    476.             #region Methods
    477.  
    478.             private uint GetNext()
    479.             {
    480.                 ulong old = _seed;
    481.                 _seed = old * 6364136223846793005 + _inc;
    482.                 uint xor = (uint)(((old >> 18) ^ old) >> 27);
    483.                 int rot = (int)(old >> 59);
    484.                 return (xor >> rot) | (xor << (64 - rot));
    485.             }
    486.  
    487.             #endregion
    488.  
    489.             #region IRandom Interface
    490.  
    491.             public double NextDouble()
    492.             {
    493.                 return (double)this.GetNext() / (double)(0x100000000u);
    494.             }
    495.  
    496.             public float Next()
    497.             {
    498.                 return (float)((double)this.GetNext() / (double)(0x100000000u));
    499.             }
    500.  
    501.             public int Next(int size)
    502.             {
    503.                 return (int)(size * this.NextDouble());
    504.             }
    505.  
    506.             public int Next(int low, int high)
    507.             {
    508.                 return (int)((high - low) * this.NextDouble()) + low;
    509.             }
    510.  
    511.             #endregion
    512.  
    513.         }
    514.  
    515.         #endregion
    516.  
    517. }
    518.  
    Your 'SetSeed' would no longer be useful for anything.

    As for seeding generators I do something like this:

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Diagnostics;
    5. using System.Security.Cryptography;
    6. using UnityEngine;
    7.  
    8. public class RoomSpawner : MonoBehaviour
    9. {
    10.     [Tooltip("The seed that Room will be generated with. Set to a value %lt 0 to have the seed generated randomly on build/generation.")]
    11.     public seed = -1;
    12.     public int openingDirection;
    13.     // 1 --> need bottom door
    14.     // 2 --> need top door
    15.     // 3 --> need left door
    16.     // 4 --> need right door
    17.  
    18.     private RoomTemplates templates;
    19.     private AddRoom add;
    20.     private int rand;
    21.     private bool spawned = false;
    22.  
    23.     private float waitTime = 8f;
    24.     private RoomMax nr;
    25.  
    26.     void Start()
    27.     {
    28.         Destroy(gameObject, waitTime);
    29.         templates = GameObject.FindGameObjectWithTag("MainRooms").GetComponent<RoomTemplates>();
    30.         add = gameObject.GetComponentInParent<AddRoom>();
    31.         Invoke("Spawn", 1f);
    32.         nr = GameObject.FindWithTag("MainRooms").GetComponent<RoomMax>();
    33.     }
    34.  
    35.     void Spawn()
    36.     {
    37.         var rng = RandomUtil.CreateDeterministicRNG(seed);
    38.  
    39.         if (spawned == false)
    40.         {
    41.             if (nr.numberRoom <= nr.maxRoom)
    42.             {
    43.                 if (openingDirection == 0)
    44.                 {
    45.                 }
    46.                 if (openingDirection == 1)
    47.                 {
    48.                     rand = rng.Range(templates.bottomRooms.Length);
    49.                     Instantiate(templates.bottomRooms[rand], transform.position, templates.bottomRooms[rand].transform.rotation);
    50.                     nr.numberRoom += 1f;
    51.                 }
    52.                 else if (openingDirection == 2)
    53.                 {
    54.                     rand = rng.Range(templates.topRooms.Length);
    55.                     Instantiate(templates.topRooms[rand], transform.position, templates.topRooms[rand].transform.rotation);
    56.                     nr.numberRoom += 1f;
    57.                 }
    58.                 else if (openingDirection == 3)
    59.                 {
    60.                     rand = rng.Range(templates.leftRooms.Length);
    61.                     Instantiate(templates.leftRooms[rand], transform.position, templates.leftRooms[rand].transform.rotation);
    62.                     nr.numberRoom += 1f;
    63.                 }
    64.                 else if (openingDirection == 4)
    65.                 {
    66.                     rand = rng.Range(templates.rightRooms.Length);
    67.                     Instantiate(templates.rightRooms[rand], transform.position, templates.rightRooms[rand].transform.rotation);
    68.                     nr.numberRoom += 1f;
    69.                 }
    70.                 spawned = true;
    71.                 //UnityEngine.Debug.Log(rand + " " + transform.position);
    72.             }
    73.             if (nr.numberRoom > nr.maxRoom)
    74.             {
    75.                 Invoke("SpawnClose", 0.75f);
    76.             }
    77.         }
    78.     }
    79.  
    80.     void SpawnClose()
    81.     {
    82.         if (spawned == false)
    83.         {
    84.             if (nr.numberRoom > nr.maxRoom)
    85.             {
    86.                 if (openingDirection == 1)
    87.                 {
    88.                     Instantiate(templates.ClosedbottomRooms, transform.position, templates.ClosedbottomRooms.transform.rotation);
    89.                 }
    90.                 else if (openingDirection == 2)
    91.                 {
    92.                     Instantiate(templates.ClosedtopRooms, transform.position, templates.ClosedtopRooms.transform.rotation);
    93.                 }
    94.                 else if (openingDirection == 3)
    95.                 {
    96.                     Instantiate(templates.ClosedleftRooms, transform.position, templates.ClosedleftRooms.transform.rotation);
    97.                 }
    98.                 else if (openingDirection == 4)
    99.                 {
    100.                     Instantiate(templates.ClosedrightRooms, transform.position, templates.ClosedrightRooms.transform.rotation);
    101.                 }
    102.                 spawned = true;
    103.             }
    104.         }
    105.     }
    106.  
    107.     void OnTriggerEnter2D(Collider2D other)
    108.     {
    109.         if (other.CompareTag("SpawnPoint"))
    110.         {
    111.             if (other.GetComponent<RoomSpawner>().spawned == false && spawned == false)
    112.             {
    113.                 Instantiate(templates.closedRooms, transform.position, templates.closedRooms.transform.rotation);
    114.                 Destroy(gameObject);
    115.             }
    116.             if (other.GetComponent<RoomSpawner>().spawned == true && spawned == true)
    117.             {
    118.                 //add.Remove();
    119.             }
    120.             spawned = true;
    121.         }
    122.     }
    123. }
    124.  
    Note that default value of -1 will create a RNG with a random seed (based on the current 'ticks' value of DateTime.Now). Otherwise, it'll use the seed you put in there.

    This is literally how I do it in my own random scene generator:
    upload_2020-7-20_19-57-22.png

    upload_2020-7-20_19-57-49.png

    I would definitely suggest exploring said code though, get familiar with it, rather than blindly trusting it.

    You can go to that 'CreateDeterministicRNG' method and return one of the other classes in there instead. Play with it. See which algorithm you prefer.

    Code (csharp):
    1.  
    2.         /// <summary>
    3.         /// Create an rng that is deterministic to that 'seed' across all platforms.
    4.         /// </summary>
    5.         /// <param name="seed"></param>
    6.         /// <returns></returns>
    7.         public static IRandom CreateDeterministicRNG(int seed)
    8.         {
    9.             //return new SimplePCG(seed);
    10.             return LinearCongruentialRNG.CreateGLibc(seed);
    11.         }
    12.  
     
    Last edited: Jul 21, 2020
  22. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Ok I'm getting a good grip of how it works, would I put this code on a C# file? and then on a game object or just leave it be?
     
  23. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    Just leave it be, it's not a monobehaviour, it's just regular code.
     
    CodeMateo likes this.
  24. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    ok cool, only problem I've got at the moment is that it's saying (11): error CS1519: Invalid token '=' in class, struct, or interface member declaration with public seed = -1;

    edit: just added int after public it fixed it lol
     
  25. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    ok I implemented the code, and uh it's kind of weird. If I put -1 into the seed it's completely random with no consistency. But if I do 1 it's just the same number again and again. If I'm trying to put it on several objects, I need it to be consistent so I can bring up the same scene when I go back.
     
  26. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    Yeah, that's how a seeded random number generator works.

    Like I said in my description above:
    I have -1 mean "create a random sequence based on the DateTime.Now.Ticks". So this will be a new sequence every time you use it.

    Any value 0 or greater with be the same sequence every time.

    That's how a deterministic random number generator works. Any given seed will generate the same sequence. If you want a random sequence, seed it randomly.

    Think of it like minecraft... if you start a random world, you get a random world. BUT if you type something in as the seed, you get the same world for that same seed. Which is why people share minecraft seeds:
    https://www.pcgamesn.com/minecraft/30-best-minecraft-seeds

    That "1" you put in is the seed "1". That's your algorithms result when passed an rng seeded with "1".

    Otherwise... what's the point of a seed?
     
  27. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Ok understood, it’s just that random set of numbers that are memorized and kept (like how the seed is). And you’re code would work perfectly for it except I have several different game objects calling this script, only grabbing one of the random.ranges, so I’m just having a large map that is a straight hallway. Is there anyway I can implement it with several objects (ex. 1, 3, 2, 1, etc) but not have the same number (ex. 3, 3, 3, etc)
     
  28. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    I'm not exactly following you.

    If you have a need for several different 'RoomSpawner's, then create multiple gameobjects with their own 'RoomSpawner' attached to it.

    Each one can then have a different seed value if you want a repeated/remember generation sequence. Just make sure each of them aren't > 0 and not equal.

    If you want it completely random leave them at -1.

    If for whatever reason your generation is happening so fast that the 'ticks' value is the same for each (not likely... but could happen). Then seed with a random int from UnityEngine.Random.

    ...

    If I'm not understanding what you're shooting for... try reexplaining it. Maybe from the context of what your end goals are, rather than in the form of code.
     
  29. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Ok so I have prefabs that spawn these game objects with this script. Based on the random number, a new prefab is spawned in (a left room, hallway, etc) I’m trying to get a way that there’s a stairway you can take at the end and go the next scene and use to come back to the original scene. Everything works mostly except that I can’t get the seed to work so I can save the random map to come back in
     
  30. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    On exit of the scene you're going to have to save that seed somewhere. That's up to you since that's not really part of the rng stuff. You could just have like a 'globalgamestate' script or something that stores the "last scene seed" or even associate a seed with each seed.

    You could even have a "scene seed" sequence. So like you create an RNG on start of the game that creates a sequence of integers that get associated with each scene (by name in a dict per haps?), then on the load of a given scene it looks up its seed value from that dict and creates and rng using that as a seed.

    We may want to add a 'seed' property to the 'IRandom' interface to read back seeds if you needed that for whatever reason.
     
  31. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    So cross scene stuff can get fairly complicated as Unity generaly destroys everything and loads the next scene when you change scenes.

    There is 'DontDestroyOnLoad' of course to maintain objects across scenes. But when does that get created? How do you cover launching your game from any given random scene?

    The solution I use is a ScriptableObject instead that is placed in my Resources folder. This SO will be a singleton, and there's an 'Init' method on it that can be called from anywhere that will load itself out of the Resources folder.

    Note that what I'm going to show you in the following is not the "only" way to do it... nor is it necessarily the "best" way as that is subjective. It's the way I do it. So I'm sharing that.

    So it goes something like so:

    Code (csharp):
    1.  
    2. [CreateAssetMenu(fileName = "GameSettings", menuName = "My Game/GameSettings")]
    3. public class GameSettings : ScriptableObject
    4. {
    5.  
    6.     #region Singleton
    7.  
    8.     private static GameSettings _instance;
    9.  
    10.     public static GameSettings Instance { get { return _instance; } }
    11.  
    12.     public static bool Initialized { get { return _instance != null; } }
    13.  
    14.     public static void Init()
    15.     {
    16.         _instance = Resources.Load("GameSettings") as GameSettings; //note - this line assumes that you created an instance called 'GameSettings' in the 'Resources' folder
    17.     }
    18.  
    19.     #endregion
    20.  
    21. }
    22.  
    Usually then I have a "bootscene" (the first scene my game loads) that has a script in it that calls "Init". I also tend to use this bootscene as a chance to show my logo and stuff.
    Code (csharp):
    1.  
    2. public class BootScript : MonoBehaviour
    3. {
    4.  
    5.     void Awake()
    6.     {
    7.         if(!GameSettings.Initialized)
    8.         {
    9.             GameSettings.Init();
    10.         }
    11.     }
    12.  
    13. }
    14.  
    (Note - you likely want to set this guys execution order to super duper low so that it runs before everything else)

    Nice thing is you can also add this script to every scene and it'll initialize your scene even in debug mode... or you can have a special debug startup script (which is what I do). The nice thing about a special debug startup is that you can handle special debug load steps. Like in my game I have state objects that need to be loaded when you start a game (in my game they're called "episode settings"), but if you are in the editor in a random scene, we need to load up some generalized stuff otherwise the scene won't behave correctly.

    So I have my scripts like this (under spoiler):

    BootScript:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3.  
    4. using com.spacepuppy;
    5. using com.spacepuppy.Scenario;
    6. using com.spacepuppy.Utils;
    7.  
    8. namespace com.mansion.Scenes
    9. {
    10.  
    11.     public class GameStartup : SPComponent
    12.     {
    13.  
    14.         [SerializeField]
    15.         private Trigger _onReady;
    16.  
    17.         protected override void Start()
    18.         {
    19.             base.Start();
    20.  
    21.             this.StartCoroutine(this.DoStartup());
    22.         }
    23.  
    24.         System.Collections.IEnumerator DoStartup()
    25.         {
    26.             //we have to wait because some platforms take a frame or more for Input to fully validate
    27.             yield return null;
    28.             float t = 1f;
    29.             while(t > 0f && Input.GetJoystickNames().Length == 0)
    30.             {
    31.                 yield return t;
    32.                 t -= Time.unscaledDeltaTime;
    33.             }
    34.  
    35.             if(!Game.Initialized)
    36.             {
    37.                 Game.Init();
    38.                 Game.EpisodeSettings.Difficulty = Difficulty.Normal;
    39.                 Cursor.visible = false;
    40.  
    41.                 string sqlty = PlayerPrefs.GetString(Constants.PREF_QUALITY, Constants.VALUE_QUALITYBEST);
    42.                 int i = System.Array.IndexOf(QualitySettings.names, sqlty);
    43.                 if (i >= 0)
    44.                 {
    45.                     QualitySettings.SetQualityLevel(i, false);
    46.                 }
    47.             }
    48.  
    49.             _onReady.ActivateTrigger(this, null);
    50.         }
    51.  
    52.     }
    53.  
    54. }
    55.  
    DebugStartup:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Linq;
    5.  
    6. using com.spacepuppy;
    7. using com.spacepuppy.Collections;
    8. using com.spacepuppy.Utils;
    9.  
    10. using com.mansion.Entities.Weapons;
    11. using com.mansion.Messages;
    12.  
    13. namespace com.mansion.DebugScripts
    14. {
    15.  
    16. #if (UNITY_EDITOR || DEVELOPMENT_BUILD)
    17.     public class DebugStartup : SPComponent, IGameStateChangedGlobalHandler
    18. #else
    19.     public class DebugStartup : SPComponent
    20. #endif
    21.     {
    22.  
    23. #if UNITY_EDITOR
    24.  
    25.         [SerializeField]
    26.         private EpisodeSettings _episodeSettings;
    27.  
    28.         [SerializeField]
    29.         [Tooltip("Set with scene name to load if death occurs before any checkpoint occurs. Otherwise no reload occurs.")]
    30.         private string _debugCheckpointReloadScene;
    31.  
    32.         protected override void Awake()
    33.         {
    34.             base.Awake();
    35.    
    36.             if (!Game.Initialized)
    37.             {
    38.                 Debug.Log("DEBUG INITIALIZING GAME");
    39.                 //Cursor.visible = false;
    40.                 Game.Init((g) =>
    41.                 {
    42.                     if (_episodeSettings != null) g.SetEpisodeSettings(_episodeSettings);
    43.                 });
    44.                 _dispatchStartMessage = true;
    45.  
    46.                 if (_episodeSettings != null && _episodeSettings.ScenarioType != null)
    47.                     Game.StartScenario(_episodeSettings.ScenarioType).StartGame(new SaveGameToken()
    48.                     {
    49.                         Data = null,
    50.                         Checkpoint = new CheckpointState(string.IsNullOrEmpty(_debugCheckpointReloadScene) ? Constants.DEBUG_STARTUP_CHECKPOINT_SCENE : _debugCheckpointReloadScene)
    51.                     });
    52.             }
    53.         }
    54.  
    55. #endif
    56.  
    57. #if (UNITY_EDITOR || DEVELOPMENT_BUILD)
    58.  
    59.         private bool _dispatchStartMessage;
    60.         private bool _showDebugMenu;
    61.         private Texture2D _background;
    62.  
    63.         protected override void Start()
    64.         {
    65.             base.Start();
    66.  
    67.             _background = new Texture2D(1, 1);
    68.             _background.SetPixels(new Color[] { Color.gray });
    69.  
    70.             Messaging.RegisterGlobal<IGameStateChangedGlobalHandler>(this);
    71.  
    72.             if(_dispatchStartMessage)
    73.             {
    74.                 Messaging.FindAndBroadcast<IDebugStartMessageHandler>((o) => o.OnDebugStart());
    75.             }
    76.         }
    77.  
    78.         protected override void OnDestroy()
    79.         {
    80.             base.OnDestroy();
    81.  
    82.             Messaging.UnregisterGlobal<IGameStateChangedGlobalHandler>(this);
    83.         }
    84.  
    85.         private void Update()
    86.         {
    87.             if(Input.GetKeyDown(KeyCode.BackQuote))
    88.             {
    89.                 const string TOKEN_DEEBUGPAUSE = "*DebugMenuPause*";
    90.  
    91.                 _showDebugMenu = !_showDebugMenu;
    92.                 if (_showDebugMenu)
    93.                     Game.Scenario.GameStateStack.Push(GameState.Paused, TOKEN_DEEBUGPAUSE);
    94.                 else
    95.                     Game.Scenario.GameStateStack.Pop(TOKEN_DEEBUGPAUSE);
    96.             }
    97.         }
    98.  
    99.         private void OnGUI()
    100.         {
    101.             if (!_showDebugMenu) return;
    102.  
    103.             var style = new GUIStyle(GUI.skin.box);
    104.             style.normal.background = _background;
    105.             GUI.Box(new Rect(10f, 10f, 500f, 300f), "DEBUG MENU", style);
    106.  
    107.             GUILayout.BeginArea(new Rect(15f, 60f, 490f, 245f));
    108.    
    109.             //validate player exists
    110.             var playerEntity = IEntity.Pool.Find<IEntity>((e) => e.Type == IEntity.EntityType.Player);
    111.             if (playerEntity == null)
    112.             {
    113.                 GUILayout.BeginHorizontal();
    114.                 GUILayout.FlexibleSpace();
    115.                 GUILayout.Label("No player was located.");
    116.                 GUILayout.FlexibleSpace();
    117.                 GUILayout.EndHorizontal();
    118.  
    119.                 goto Finish;
    120.             }
    121.  
    122.             //infinite ammo
    123.             var ammo = playerEntity.FindComponent<AmmoPouch>();
    124.             if(ammo != null)
    125.             {
    126.                 foreach(var e in System.Enum.GetValues(typeof(AmmoType)).Cast<AmmoType>())
    127.                 {
    128.                     GUILayout.BeginHorizontal();
    129.  
    130.                     var oldcnt = ammo.GetAmmoCount(e);
    131.                     var cnt = DiscreteFloatField(e.ToString() + " Ammo Count: ", oldcnt);
    132.                     if(oldcnt != cnt)
    133.                         ammo.SetAmmoCount(e, cnt);
    134.  
    135.                     bool oldInfAmmo = float.IsPositiveInfinity(cnt);
    136.                     bool infAmmo = GUILayout.Toggle(oldInfAmmo, " Infinite Ammo");
    137.                     if (oldInfAmmo != infAmmo)
    138.                     {
    139.                         ammo.SetAmmoCount(e, infAmmo ? float.PositiveInfinity : 10f);
    140.                     }
    141.  
    142.                     GUILayout.EndHorizontal();
    143.                 }
    144.             }
    145.  
    146.             //Health - God Mode
    147.             if(playerEntity.HealthMeter != null)
    148.             {
    149.                 playerEntity.HealthMeter.MaxHealth = FloatField("Max Health", playerEntity.HealthMeter.MaxHealth);
    150.                 playerEntity.HealthMeter.Health = FloatField("Health", playerEntity.HealthMeter.Health);
    151.  
    152.                 bool oldGodMode = float.IsPositiveInfinity(playerEntity.HealthMeter.Health);
    153.                 bool godMode = GUILayout.Toggle(oldGodMode, " God Mode");
    154.                 if(godMode != oldGodMode)
    155.                 {
    156.                     if(godMode)
    157.                     {
    158.                         playerEntity.HealthMeter.MaxHealth = float.PositiveInfinity;
    159.                         playerEntity.HealthMeter.Health = float.PositiveInfinity;
    160.                     }
    161.                     else
    162.                     {
    163.                         playerEntity.HealthMeter.MaxHealth = 100f;
    164.                         playerEntity.HealthMeter.Health = 100f;
    165.                     }
    166.                 }
    167.             }
    168.  
    169.             //1-btn grapple release
    170.             /*
    171.             var grapple = playerEntity.FindComponent<com.mansion.Entities.Actors.Player.PlayerGrappledState>();
    172.             if(grapple != null)
    173.             {
    174.                 GUILayout.BeginHorizontal();
    175.                 GUILayout.Label("Release Odds: " + grapple.GrappleReleaseOdds.ToString("P"));
    176.                 grapple.GrappleReleaseOdds = Mathf.Clamp01(GUILayout.HorizontalSlider(grapple.GrappleReleaseOdds, 0f, 1f));
    177.                 GUILayout.EndHorizontal();
    178.             }
    179.             */
    180.             var actionMotor = playerEntity.FindComponent<com.mansion.Entities.Actors.Player.PlayerActionMotor>();
    181.             if(actionMotor != null && actionMotor.DefaultMovementSettings != null)
    182.             {
    183.                 GUILayout.BeginHorizontal();
    184.                 GUILayout.Label("Release Odds: " + actionMotor.DefaultMovementSettings.GrappleReleaseOdds.ToString("P"));
    185.                 actionMotor.DefaultMovementSettings.GrappleReleaseOdds = Mathf.Clamp01(GUILayout.HorizontalSlider(actionMotor.DefaultMovementSettings.GrappleReleaseOdds, 0f, 1f));
    186.                 GUILayout.EndHorizontal();
    187.             }
    188.  
    189. Finish:
    190.             GUILayout.EndArea();
    191.  
    192.         }
    193.  
    194.  
    195.         private static float DiscreteFloatField(string label, float value)
    196.         {
    197.             GUILayout.BeginHorizontal();
    198.  
    199.             GUILayout.Label(label);
    200.  
    201.             string val = GUILayout.TextField(value.ToString());
    202.             //val = System.Text.RegularExpressions.Regex.Replace(val, @"^[0-9]", "");
    203.             if (!float.TryParse(val, out value))
    204.                 value = 0;
    205.  
    206.             GUILayout.EndHorizontal();
    207.  
    208.             return value;
    209.         }
    210.  
    211.         private static float FloatField(string label, float value)
    212.         {
    213.             GUILayout.BeginHorizontal();
    214.  
    215.             GUILayout.Label(label);
    216.  
    217.             string val = GUILayout.TextField(value.ToString());
    218.             //val = System.Text.RegularExpressions.Regex.Replace(val, @"^[0-9\.\+\-]", "");
    219.             if (!float.TryParse(val, out value))
    220.                 value = 0;
    221.  
    222.             GUILayout.EndHorizontal();
    223.  
    224.             return value;
    225.         }
    226.  
    227.  
    228.         void IGameStateChangedGlobalHandler.OnGameStateChanged(GameState last, GameState current)
    229.         {
    230.             if ((current & GameState.Paused) == 0)
    231.             {
    232.                 _showDebugMenu = false;
    233.             }
    234.         }
    235.  
    236. #endif
    237.  
    238.     }
    239.  
    240. }
    241.  
    As you can see in my debugscript I wrapped with compiler tags so it doesn't end up in my final build. Also it contains tons of debug display stuff to help me while testing the game.

    Again, set the execution order of debug super duper low too.

    OK... so now that we have a global game settings that can persist between scenes. And it is initialized both in our boot scene, as well in debug mode when we launch through Unity.

    Lets get our 'special scene rng'.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4. using UnityEngine.SceneManagement;
    5.  
    6. [CreateAssetMenu(fileName = "GameSettings", menuName = "My Game/GameSettings")]
    7. public class GameSettings : ScriptableObject
    8. {
    9.  
    10.     #region Singleton
    11.  
    12.     private static GameSettings _instance;
    13.  
    14.     public static GameSettings Instance { get { return _instance; } }
    15.  
    16.     public static bool Initialized { get { return _instance != null; } }
    17.  
    18.     public static void Init()
    19.     {
    20.         _instance = Resources.Load("GameSettings") as GameSettings;//note - this line assumes that you created an instance called 'GameSettings' in the 'Resources' folder
    21.         _instance.OnInitialized();
    22.     }
    23.  
    24.     #endregion
    25.  
    26.     #region Fields
    27.  
    28.     public int GlobalSeed = -1;
    29.  
    30.     private Dictionary<string, int> _sceneSeeds = new Dictionary<string, int>();
    31.  
    32.     #endregion
    33.  
    34.     #region Methods
    35.  
    36.     private void OnInitialized()
    37.     {
    38.         _sceneSeeds.Clear();
    39.         var rng = RandomUtil.CreateDeterministicRNG(GlobalSeed);
    40.         for(int i = 0; i < SceneManager.sceneCount; i++)
    41.         {
    42.             var sc = SceneManager.GetSceneAt(i);
    43.             _sceneSeeds[sc.name] = rng.Next(int.MaxValue);
    44.         }
    45.     }
    46.  
    47.     public int GetSceneSeed(string sceneName)
    48.     {
    49.         int result;
    50.         if(_sceneSeeds.TryGetValue(sceneName ?? string.Empty, out result))
    51.         {
    52.             return result;
    53.         }
    54.         else
    55.         {
    56.             return -1;
    57.         }
    58.     }
    59.  
    60.     public int GetActiveSceneSeed()
    61.     {
    62.         return GetSceneSeed(SceneManager.GetActiveScene().name);
    63.     }
    64.  
    65.     #endregion
    66.  
    67. }
    68.  
    Then in our RoomSpawner we can do something like this:

    Code (csharp):
    1. var rng = RandomUtil.CreateDeterministicRNG(GameSettings.Instance.GetActiveSceneSeed());
    Note that you will need to go into your build settings and make sure all your scenes are added appropriately.

    (last note - please excuse any typos, I typed most of this in the browser and it's not tested... it's purposes is for demonstration)
     
    Last edited: Jul 21, 2020
  32. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Ok that sounds all good, but would this work with my several game object problem? Like can I have a main place where I write one seed and it spawns the identical prefabs I need
     
  33. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    I don't know because I don't really understand what you're going for.

    You've talked about multiple scenes and multiple gameobjects and... I honestly don't know what you're going for.

    If you could describe your goal maybe? Like draw a picture or something? Give me the big picture of your game.

    "My game starts, I go through a start menu, Scene A is loaded. In Scene A I have N RoomSpawners attached to N gameobjects that I expect to spawn uniquely for this game. Each RoomSpawner creates a door that leads to another unique scene that follow the same rules as Scene A. When I enter that other scene I need to be able to return to the previous scene and the N RoomSpawners should be respawn with the same pattern. I also need to return the specific door I had entered through."

    Without this big picture, this all just feels like a moving goal post.

    Furthermore this big picture probably spreads across more than just RNG. We're getting into saving the state of the scene as we transition between scenes. This can get very complicated as we need to save those states... juggling a simple solution that works generically is going to be hard. But I can't give a simpler adhoc solution since I don't know your adhoc situation.
     
    Last edited: Jul 22, 2020
  34. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Seed Problem 1.png In the beginning of the game we have a main room, with a small game object (orange icon). The game object, named RoomSpawner, has the script called RoomSpawner. Depending on which way it opens up, it picks randomly (here is where I need a seed) which prefabs it can use from a array.

    Seed Problem 2.png
    Then it spawns it in like this with new RoomSpawners. I just need a way to hold several RoomSpawners Random.Ranges so I can use the same seed to debug and come back to the same scene.
     
  35. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    So this is all happening in a single scene?

    Or in multiple scenes?

    Cause this post seems to suggest this is all in a single scene... where as a previous post of your suggested you had to travel between scenes.
     
  36. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    This is all in one scene, but then after a certain amount of time it spawns a stairs prefab that takes you to the next layer (or scene). Where it's the same entry room and it spawns a whole new dungeon, but the difference is that in the 2nd scene there's a stairs in the entry room that'll take you to the 1st scene. It all works but the saving the scene, so when you go back, it just makes a completely random and new scene.
     
  37. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    OK, so basically it goes:

    The game starts on Scene A where N RoomSpawners exist in sequence. When you reach the end of that sequence you can go through a stairway to Scene B where another N RoomSpawners exist in sequence. We need it so that when you travel back and forth between scenes that the same sequence is generated.

    I'm going to guess you're recycling scenes right? So you just have a template for how the scene generates and it randomized each time. So when you load the next scene... who knows what it is.

    So to give this some scalability I'd go and create a "SceneInitialization" script. It will reference all of your RoomSpawners, as well as some id information about itself so we can save its state for returning. So lets do something like this.

    First lets change our 'GameSettings' to something like this. This will give us the ability to have a 'SceneState' for the current scene, and a list that stores pre-existing ones as we traverse scene to scene:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4. using UnityEngine.SceneManagement;
    5.  
    6. [CreateAssetMenu(fileName = "GameSettings", menuName = "My Game/GameSettings")]
    7. public class GameSettings : ScriptableObject
    8. {
    9.  
    10.     #region Singleton
    11.  
    12.     private static GameSettings _instance;
    13.  
    14.     public static GameSettings Instance { get { return _instance; } }
    15.  
    16.     public static bool Initialized { get { return _instance != null; } }
    17.  
    18.     public static void Init()
    19.     {
    20.         _instance = Resources.Load("GameSettings") as GameSettings;
    21.         _instance.OnInitialized();
    22.     }
    23.  
    24.     #endregion
    25.  
    26.     #region Fields
    27.  
    28.     public int GlobalSeed = -1;
    29.     public IRandom GlobalRNG { get; private set; }
    30.     public SceneState CurrentSceneState { get; private set; }
    31.     public int SceneCount { get { return _previousScenes.Count; } }
    32.  
    33.     private List<SceneState> _previousScenes = new List<SceneState>();
    34.  
    35.     #endregion
    36.  
    37.  
    38.  
    39.     #region Methods
    40.  
    41.     private void OnInitialized()
    42.     {
    43.         _previousScenes.Clear();
    44.         GlobalRNG = RandomUtil.CreateDeterministicRNG(GlobalSeed);
    45.     }
    46.  
    47.     public void LoadNextScene()
    48.     {
    49.         //see if the 'next' scene is an existing scene
    50.         if(_previousScenes.Count > 0 && (this.CurrentSceneState.Id + 1) < _previousScenes.Count)
    51.         {
    52.             this.LoadExistingScene(this.CurrentSceneState.Id + 1);
    53.             return;
    54.         }
    55.    
    56.         //if 'next' scene doesn't exist... create a new scene
    57.         CurrentSceneState = new SceneState() {
    58.             Id = _previousScenes.Count,
    59.             Seed = GlobalRNG.Next(int.MaxValue)
    60.         };
    61.         _previousScenes.Add(CurrentSceneState);
    62.         SceneManager.LoadScene("TemplateScene");
    63.     }
    64.  
    65.     public void LoadExistingScene(int id)
    66.     {
    67.         if (id < 0 || id >= _previousScenes.Count) throw new System.ArgumentException("Unknown Scene Id");
    68.  
    69.         this.CurrentSceneState = _previousScenes[id];
    70.         SceneManager.LoadScene("TemplateScene");
    71.     }
    72.  
    73.     #endregion
    74.  
    75.  
    76.  
    77.     public struct SceneState
    78.     {
    79.         public int Id;
    80.         public int Seed;
    81.         //other stuff to save between scenes
    82.     }
    83. }
    84.  
    Note - this code assumes we're reusing the same scene each time as a template. If this isn't how your logic works, you'll have to modify that.

    Next the scene initialization script which I'm going to call "SceneStateManager":

    Code (csharp):
    1.  
    2. public class SceneStateManager : MonoBehaviour
    3. {
    4.  
    5.     public int SceneId;
    6.     public List<RoomSpawner> Rooms;
    7.  
    8.     void Awake()
    9.     {
    10.         var state = GameSettings.Instance.CurrentSceneState;
    11.         SceneId = state.Id;
    12.    
    13.         var rng = RandomUtil.CreateDeterministicRNG(state.Seed);
    14.         //this gives each room a seed based on the state's seed.
    15.         foreach(var room in Rooms)
    16.         {
    17.             room.Seed = rng.Next(int.MaxValue);
    18.         }
    19.     }
    20.  
    21. }
    22.  
    Note - you'll have to give it references to each of the 'RoomSpawners' in the scene via the inspector.

    Then when you go to load your next scene you call this special "GameSettinngs.Instance.LoadNextScene" or the "LoadPreviousScene" (depending the stairs you go through).

    So like if you go through the "back" door/stairs. You can have a script like so:
    Code (csharp):
    1.  
    2. public class BackDoorScript : MonoBehaviour
    3. {
    4.  
    5.     void OnTriggerEnter(Collider c)
    6.     {
    7.         if(c.CompareTag("Player"))
    8.         {
    9.             var game = GameSettings.Instance;
    10.             game.LoadExistingScene(game.CurrentSceneState.Id - 1);
    11.         }
    12.     }
    13.  
    14. }
    15.  
    And your "next" door/stairs. You can have a script like so:
    Code (csharp):
    1.  
    2. public class NextDoorScript : MonoBehaviour
    3. {
    4.  
    5.     void OnTriggerEnter(Collider c)
    6.     {
    7.         if(c.CompareTag("Player"))
    8.         {
    9.             var game = GameSettings.Instance;
    10.             game.LoadNextScene();
    11.         }
    12.     }
    13.  
    14. }
    15.  
    Final note - this is just a general idea. As I don't know your specific needs it's serving as a template for you to expand on.

    Furthermore this logic doesn't take in account spawning the "first" scene. In "build mode" you'd be calling "LoadNextScene" to load that first scene for a game... but when launching from editor you're going to need something to account for starting in the editor. You'll likely want to do something in your 'DebugStartupScript' that adds a "dummy first scene". Something like:

    Code (csharp):
    1.  
    2. //in GameSettings:
    3. public void ForceAddFirstScene()
    4. {
    5.     _previousScenes.Add(new SceneState() {
    6.         Id = 0,
    7.         Seed = GlobalRNG.Next(int.MaxValue);
    8.     });
    9. }
    10.  
    11. //in your DebugStartupScript:
    12. void Awake()
    13. {
    14.     if(!GameSettings.Instance.Initialized)
    15.     {
    16.         GameSettings.Instance.Init();
    17.         GameSettings.Instance.ForceAddFirstScene();
    18.     }
    19. }
    20.  
    And of course that DebugStartupScript is going to have to have a SUPER early execution order.
     
    Last edited: Jul 23, 2020
  38. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    ok so in the first script I had to put static in line 12 to get rid of some errors.
    Would I put this 'SceneStateManager' script on my main entry room.

    And also, do I edit the changes I did before to RoomSpawner?
     
  39. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    Sorry, that's a typo on my part. LIke I said before, I've just been typing this in the browser. Not actually running the code.

    It would have to exist in any scene that has RoomSpawners.

    I don't know what it looks like now... if it still relies on the 'seed' field/variable from before. No... it can stay the same.

    Like I've said though... you should be reading this code. Familiarizing yourself with it. Not just copy pasting and crossing your fingers.

    If you're having any hard times understanding the logic. Ask.
     
    CodeMateo likes this.
  40. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Ok it's just that in RoomSpawner I put 'var rng = RandomUtil.CreateDeterministicRNG(seed);' in the script like how you put it before a few messages before, and I have no idea if it would hurt this script
     
  41. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    Nope, that's what you want it to do.

    'seed', which is passed into the CreateDeterministicRNG method, is getting set by the SceneStateManager in SceneStateManager.Awake.

    So then when RoomSpawner does its thing in Start (happens after all Awakes), it'll be based off of the 'seed' that it was set to.

    Mind you, SceneStateManager needs to be referencing every RoomSpawner, so don't forget to drop those references on it in the inspector.
     
  42. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    ok so I put in all the stuff, aaannd it stills spawns everything in randomly with no set seed keeping consistent. This is insanely confusing to me
     
  43. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    did you set a GlobalSeed?

    If you expect consistency, there needs to be a single non-negative seed somewhere that feeds the rest of the sequence.
     
  44. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Would that be the scene Id because I did set that?

    "NullReferenceException: Object reference not set to an instance of an object
    SceneStateManager.Awake () (at Assets/Code/SceneStateManager.cs:13)"

    Also just saw this error it was buried under debug.logs
     
  45. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    No GlobalSeed, in the 'GameSettings'.

    Which if you're getting that error, you never created an instance of GameSettings.

    As I stated previously... this specific design uses a ScriptableObject, so you need to create an instance. Create a 'Resources' folder, right click it, add a 'GameSettings' (it'll be in My Game/GameSettings... unless you changed the code for the 'CreateAssetMenu').

    Like I said, read the code... try to follow its logic. If anything doesn't make sense... bring it up. Like "what is SceneID for?" or "What is GameSettings and why is there a CreateAssetMenu attribute on it?". If you don't understand any of the code... well, you're not going to understand it. Don't expect it to "just work", that's not how this works (and is also why there's typos... everything I posted is just pseudo-code). I'm not creating you a system that just works... that's not how forums work. I'm giving suggestions in the direction you can go. It's just that the direction you're going is none-trivial, it takes some heavy lifting since you have inter-scene communication, saving states, a lot of stuff going on. So the code I've demonstrated has several moving parts. You need to read said code, understand said code, and if you don't... ask and we can explain it to you.

    If this is all too much.

    Then maybe you should scale your project back some. Maybe no inter-scene save state.

    Start smaller, and build up once you've mastered that smaller stuff.

    If you'd like to do that... we can start from a single scene setup.
     
    Last edited: Jul 23, 2020
  46. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Ooooh ok ok, that’s makes a bit more sense, I’ve never done scriptable objects. I’ll tinker with that a bit.


    No it’s fine, I just need to learn all this stuff at once, it’s a lot to handle lol, I just got to keep going
     
  47. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
  48. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    Ok so I put in the Game settings, in a resources folder, and it's still saying it's null. So then I went to the next scene and it also said null, so it's just not written correctly I guess, but GameSettings.Instance should be right, so I'm not sure
     
  49. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    Did you create the 'startup' script that calls 'Init'???

    I talk about one that would be in your boot scene (BootScript) that would work for your compiled builds. But that you would also need one in your individual scenes to work during debug mode when launching from the editor. And I suggested how you should have a specific 'DebugStartupScript' for debug mode. I even shared various examples, including my own debug script that I use in one of my own games. Don't forget, I also talk about the importance of its execution order timing. If Init isn't called before everything else, it will still be null until Init is called. There are alternatives though... for instance you could put logic in the 'Instance' property that checks if it's null and calls Init for you (this is called lazy initialization)... the only reason I don't do that is in case the first time 'Instance' is called isn't on the main thread... if it's not you'd get an error since you can't load resources from other threads.

    Basically... you need something that loads the GameSettings out of the Resources folder. Thusly instantiating the singleton, and setting the _instance field/variable for use, so that it's not null.
     
  50. CodeMateo

    CodeMateo

    Joined:
    May 21, 2020
    Posts:
    77
    uhh I don't remember if I did or didn't, which one is the startup again?