Search Unity

Preload Scene - Always, or it depends...

Discussion in 'Scripting' started by eco_bach, Jan 19, 2018.

  1. eco_bach

    eco_bach

    Joined:
    Jul 8, 2013
    Posts:
    1,601
    Came across this SO post when researching proper DontDestroyOnLoad and Singleton use. The OP suggest you MUST ALWAYS use a preload scene, but this seems to be a quite inflexible approach to game and application architecture. So what is the consensus, should a preload scene be part of every project and if not, when SHOULD you use one?

    https://stackoverflow.com/questions...-script-works-only-one-time/35891919#35891919
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    Ummm... I don't know about "must always", but I do always use a preload scene.

    It just makes organizing that much easier.

    I use the preload scene to initialize anything that is must have to the game. Such as singletons/services that I use through out the game.

    In theory I could just also use 'execution order' to make sure that my 'startup' script that initializes this stuff runs first on the first scene. I just don't like that way... I prefer the preload scene. The preload scene is more extensible in that I can also stick other stuff in there that I deem necessary as the project grows with out needing to muck about in execution order for it either.

    Especially if I decide to change out the first scene... what if I've been developing the game to start on the title screen... but then my artist creates an animated splash screen and we go to insert that. Well now I have to go in and remove the startup stuff from the title screen and move it into the splash screen. Where as if it was in a preload screen, the artist just orders the scenes so that the splash occurs in between preload and title screen. Done.

    There does come the issue of testing the game though... making sure that what the 'preload' scene does gets done when I play the game in the editor directly from any given level. And really this is very specific to each game. But usually I have a 'DebugLoad' script that has a very early execution order that effectively does the preload screen. If and only if it hasn't already happened... and uses the compiler directives to only work if the game hasn't already been initialized. So it's like I've done the job twice you can say........ but like my examples above show... in practice the preload scene just has some good bennies that make it useful regardless of the existence of both.

    Case in point, here is my GameStartup script that occurs in my preload scene:
    Code (csharp):
    1.  
    2.     public class GameStartup : SPComponent
    3.     {
    4.  
    5.         protected override void Awake()
    6.         {
    7.             base.Awake();
    8.  
    9.             Cursor.visible = false;
    10.             Cursor.lockState = CursorLockMode.Confined;
    11.  
    12.             if (!Game.Initialized)
    13.             {
    14.                 Game.Init();
    15.                 Game.Settings.Difficulty = Difficulty.Normal;
    16.             }
    17.         }
    18.  
    19.     }
    20.  
    Here is my DebugStartup that occurs in any level if started from the editor:
    Code (csharp):
    1.  
    2.     public class DebugStartup : SPComponent
    3.     {
    4.  
    5. #if UNITY_EDITOR
    6.  
    7.         [SerializeField]
    8.         [TypeReference.Config(typeof(IScenarioController), allowAbstractClasses = false, allowInterfaces = false)]
    9.         private TypeReference _scenario;
    10.  
    11.         protected override void Awake()
    12.         {
    13.             base.Awake();
    14.          
    15.             //foreach(var obj in com.spacepuppy.Render.MaterialUtil.GetAllMaterialSources())
    16.             //{
    17.             //    var mat = com.spacepuppy.Render.MaterialUtil.GetUniqueMaterial(obj);
    18.             //}
    19.  
    20.             if (!Game.Initialized)
    21.             {
    22.                 Debug.Log("DEBUG INITIALIZING GAME");
    23.                 Cursor.visible = false;
    24.                 Game.Init();
    25.  
    26.                 if (_scenario.Type != null)
    27.                     Game.StartScenario(_scenario.Type);
    28.  
    29.                 if (Game.Scenario.Statistics != null)
    30.                 {
    31.                     Game.Scenario.Statistics.StartGameTimer();
    32.                 }
    33.             }
    34.         }
    35.      
    36. #endif
    37.  
    38. #if (UNITY_EDITOR || DEVELOPMENT_BUILD)
    39.  
    40.         private bool _showDebugMenu;
    41.         private Texture2D _background;
    42.  
    43.         protected override void Start()
    44.         {
    45.             base.Start();
    46.  
    47.             _background = new Texture2D(1, 1);
    48.             _background.SetPixels(new Color[] { Color.gray });
    49.         }
    50.  
    51.         private void Update()
    52.         {
    53.             if(Input.GetKeyDown(KeyCode.BackQuote))
    54.             {
    55.                 _showDebugMenu = !_showDebugMenu;
    56.                 Game.Scenario.SetPause(_showDebugMenu);
    57.             }
    58.         }
    59.  
    60.         private void OnGUI()
    61.         {
    62.             if (!_showDebugMenu) return;
    63.  
    64.             var style = new GUIStyle(GUI.skin.box);
    65.             style.normal.background = _background;
    66.             GUI.Box(new Rect(10f, 10f, 500f, 300f), "DEBUG MENU", style);
    67.  
    68.             GUILayout.BeginArea(new Rect(15f, 60f, 490f, 245f));
    69.          
    70.             //validate player exists
    71.             var playerEntity = IEntity.Pool.Find<IEntity>((e) => e.Type == IEntity.EntityType.Player);
    72.             if (playerEntity == null)
    73.             {
    74.                 GUILayout.BeginHorizontal();
    75.                 GUILayout.FlexibleSpace();
    76.                 GUILayout.Label("No player was located.");
    77.                 GUILayout.FlexibleSpace();
    78.                 GUILayout.EndHorizontal();
    79.  
    80.                 goto Finish;
    81.             }
    82.  
    83.             //infinite ammo
    84.             var ammo = playerEntity.FindComponent<AmmoPouch>();
    85.             if(ammo != null)
    86.             {
    87.                 foreach(var e in System.Enum.GetValues(typeof(AmmoType)).Cast<AmmoType>())
    88.                 {
    89.                     GUILayout.BeginHorizontal();
    90.  
    91.                     var oldcnt = ammo.GetAmmoCount(e);
    92.                     var cnt = DiscreteFloatField(e.ToString() + " Ammo Count: ", oldcnt);
    93.                     if(oldcnt != cnt)
    94.                         ammo.SetAmmoCount(e, cnt);
    95.  
    96.                     bool oldInfAmmo = float.IsPositiveInfinity(cnt);
    97.                     bool infAmmo = GUILayout.Toggle(oldInfAmmo, " Infinite Ammo");
    98.                     if (oldInfAmmo != infAmmo)
    99.                     {
    100.                         ammo.SetAmmoCount(e, infAmmo ? float.PositiveInfinity : 10f);
    101.                     }
    102.  
    103.                     GUILayout.EndHorizontal();
    104.                 }
    105.             }
    106.  
    107.             //Health - God Mode
    108.             if(playerEntity.HealthMeter != null)
    109.             {
    110.                 playerEntity.HealthMeter.MaxHealth = FloatField("Max Health", playerEntity.HealthMeter.MaxHealth);
    111.                 playerEntity.HealthMeter.Health = FloatField("Health", playerEntity.HealthMeter.Health);
    112.  
    113.                 bool oldGodMode = float.IsPositiveInfinity(playerEntity.HealthMeter.Health);
    114.                 bool godMode = GUILayout.Toggle(oldGodMode, " God Mode");
    115.                 if(godMode != oldGodMode)
    116.                 {
    117.                     if(godMode)
    118.                     {
    119.                         playerEntity.HealthMeter.MaxHealth = float.PositiveInfinity;
    120.                         playerEntity.HealthMeter.Health = float.PositiveInfinity;
    121.                     }
    122.                     else
    123.                     {
    124.                         playerEntity.HealthMeter.MaxHealth = 100f;
    125.                         playerEntity.HealthMeter.Health = 100f;
    126.                     }
    127.                 }
    128.             }
    129.  
    130.             //1-btn grapple release
    131.             var grapple = playerEntity.FindComponent<com.mansion.Entities.Actors.Player.PlayerGrappledState>();
    132.             if(grapple != null)
    133.             {
    134.                 GUILayout.BeginHorizontal();
    135.                 GUILayout.Label("Release Odds: " + grapple.GrappleReleaseOdds.ToString("P"));
    136.                 grapple.GrappleReleaseOdds = Mathf.Clamp01(GUILayout.HorizontalSlider(grapple.GrappleReleaseOdds, 0f, 1f));
    137.                 GUILayout.EndHorizontal();
    138.             }
    139.  
    140. Finish:
    141.             GUILayout.EndArea();
    142.  
    143.         }
    144.  
    145.  
    146.         private static float DiscreteFloatField(string label, float value)
    147.         {
    148.             GUILayout.BeginHorizontal();
    149.  
    150.             GUILayout.Label(label);
    151.  
    152.             string val = GUILayout.TextField(value.ToString());
    153.             //val = System.Text.RegularExpressions.Regex.Replace(val, @"^[0-9]", "");
    154.             if (!float.TryParse(val, out value))
    155.                 value = 0;
    156.  
    157.             GUILayout.EndHorizontal();
    158.  
    159.             return value;
    160.         }
    161.  
    162.         private static float FloatField(string label, float value)
    163.         {
    164.             GUILayout.BeginHorizontal();
    165.  
    166.             GUILayout.Label(label);
    167.  
    168.             string val = GUILayout.TextField(value.ToString());
    169.             //val = System.Text.RegularExpressions.Regex.Replace(val, @"^[0-9\.\+\-]", "");
    170.             if (!float.TryParse(val, out value))
    171.                 value = 0;
    172.  
    173.             GUILayout.EndHorizontal();
    174.  
    175.             return value;
    176.         }
    177.  
    178.  
    179. #endif
    180.  
    181.     }
    182.  
    Big difference there if I might say....




    Note, the code that loads the 'first scene' in my preload scene doesn't occur in the GameStartup script, but instead is a separate 'i_LoadScene' script which my artist/designer is familiar with. That way if he changes the first scene, he knows how to update it quickly.

     
  3. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    I use an initialization scene of sorts, but it isn't really focused around MonoBehaviours the way most are, rather just waiting for the various static data handlers to ready themselves. Once everything gives the "all okay" sign, the program knows it can just continue to the first scene in the game without waiting. For most of my smaller projects, everything is ready by the time the splash screen is over- it only switches over to say "Loading..." for however many additional seconds it takes for everything to finish and give the thumbs up. In that way, it's less a preload scene and more just an additional function of the splash screen scene.

    RuntimeInitializeOnLoad is good attribute to look at, as it allows you to initialize your static handlers without having to make a MonoBehaviour in your initialization scene poke them. If the assets are in a Resources folder, or asset bundles (as almost all of my assets are), it doesn't require any sort of GameObject/MonoBehaviour interface in order to get everything loaded up and ready to go.
     
    jmcgraw961 and diliupg like this.
  4. Fido789

    Fido789

    Joined:
    Feb 26, 2013
    Posts:
    343
    That post at SO is stupid. People upvote it just because it is long and nicely formatted. You don't need a preload scene, to initialize and access your dependencies, you can use dependency injection or service locator or singleton managers or static classes or anything else.

    To answer your question, you should use a preload scene when you need one.

    And no, I don't use a preload scene.
     
    Last edited: Jan 19, 2018
  5. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    I honestly hadn't even looked at it, but yeah, the tone of the "accepted answer" rather annoys me. Preload scenes aren't required, lazy-loading services and singletons is perfectly viable in many scenarios, and as I pointed out, even though I do use something akin to a preload scene, most of the time the loading is done before the splash screen is finished anyways. The tiny hiccups that would occur from those operations during the start menu or even gameplay would be negligible even if I allowed them to lazy-load.

    There are definitely projects that need a preload scene- if you're experiencing hiccups as a result of lazy-loading or background-loading, then use one. If you're not experiencing any real performance loss without it, then you don't need it. Simple as that. I would even go so far as to (broadly) place this into the "premature optimization" category if you're going out of your way to do it without any actual reason.
     
    bowserscastle likes this.
  6. diliupg

    diliupg

    Joined:
    Jan 23, 2018
    Posts:
    45
    A different type of "Preload" but yet one. :)
     
  7. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    You can avoid a preload scene by instantiating DontDestroyOnLoad objects in really any scene, by just first checking that they don't exist before instantiating them

    A preload scene is nice because you can just have these objects as part of the scene itself if you want, and don't need to add any code to check if these objects already exist. Doing it that way makes it easy to have all this stuff all in one place.

    I use both methods, but since I always create a splash screen scene with my company name and some animation, I find that is a convenient scene to double as the preload scene for these objects.
     
    sean244 and diliupg like this.
  8. diliupg

    diliupg

    Joined:
    Jan 23, 2018
    Posts:
    45
    That is another good start! :)
     
    Joe-Censored likes this.
  9. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    The real problem lies when testing the game in the editor. The fact that the scene loaded in the hierarchy is loaded before the Preload scene creates errors because the Preload scene must be loaded first.
    This problem is resolved if you always make sure in that the hierarchy always has the Preload scene loaded but it's not ideal in terms of workflow.

    In my tests, the Awake or OnEnable methods of the gameobjects in the hierarchy scene is called before the Preload scene is loaded.

    I tried it your way but again the Awake or OnEnable methods got called before the Preload scene got loaded.

    Can someone explain to me how to make the Preload scene load before the Awake or OnEnable methods of the hierarchy scene(or any loaded scene in the hierarchy)?
     
    Last edited: Feb 13, 2020
    sean244 likes this.
  10. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    Ok this is a great solution actually: http://wiki.unity3d.com/index.php/SceneAutoLoader
    The scene needs to be loaded before playmode is all.
     
  11. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    It raises concerns about a project's setup and architecture if a pre-loading scene forces you to take these steps.

    And thanks for bumping this thread, that exaggerated StackOverflow post (the one linked by the OP) was a fun read.