Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Load StartUp-Scene VS Isolated-Scenes

Discussion in 'Editor & General Support' started by Mauschelbaer, Aug 30, 2023.

  1. Mauschelbaer

    Mauschelbaer

    Joined:
    Aug 4, 2019
    Posts:
    17
    Hi, I was wondering if there is any best practice on how to organise your game in terms of screen flow.

    Specifically, I'm thinking about which path is better, or if there are any pitfalls I haven't seen before:

    1. design the scenes loosely coupled, i.e. in such a way that virtually every scene can be started directly (i.e. via editor play or e.g. via command line argument).
    Advantage: Can be tested directly "in-game" in an isolated and uncomplicated way. 2.
    Disadvantage: Boilerplate for case distinctions if, for example, a distinction has to be made in the in-game manager as to which map is to be loaded (default map, from a ComandLine argument or a profile manager that may exist).

    2. a startup scene that instantiates a number of general GameObjects and ensures with "dontDestroyOnLoad" that the GameObjects also remain available in the subsequent scenes.
    Advantage: More hierarchy and possibly cleaner. Less boilerplate.
    Disadvantage: Quickly pressing "Play" or automated tests thus become more difficult to act.

    Can you share your experiences with me here?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,951
    NEVER drop managers and other crap into any scene.

    That's just a complete disaster, something that so many tutorials teach you to do, unfortunately.

    Instead, move up the value chain and make all your long-lived managers demand-loaded only, and then you can explicitly control their lifecycles.

    Ideally you want your game start-able from any "reasonable" scene.

    -----------------


    Additive scene loading is one possible solution:

    https://forum.unity.com/threads/right-way-for-performance-divide-scene.1023673/#post-6630961
    https://forum.unity.com/threads/right-way-for-performance-divide-scene.1023673/#post-6754330

    https://forum.unity.com/threads/problem-with-canvas-ui-prefabs.1039075/#post-6726169

    A multi-scene loader thingy:

    https://pastebin.com/Vecczt5Q

    My typical Scene Loader:

    https://gist.github.com/kurtdekker/862da3bc22ee13aff61a7606ece6fdd3

    Other notes on additive scene loading:

    https://forum.unity.com/threads/removing-duplicates-on-load-scene.956568/#post-6233406

    Timing of scene loading:

    https://forum.unity.com/threads/fun...ject-in-the-second-scene.993141/#post-6449718

    Also, if something exists only in one scene, DO NOT MAKE A PREFAB out of it. It's a waste of time and needlessly splits your work between two files, the prefab and the scene, leading to many possible errors and edge cases.

    Two similar examples of checking if everything is ready to go:

    https://forum.unity.com/threads/daily-events-and-content-changes.1108202/#post-7143713

    https://forum.unity.com/threads/uni...on-before-other-scripts.1153739/#post-7401794



    ---------------------

    Simple Singleton (UnitySingleton):

    Some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

    Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

    https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

    These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance

    Alternately you could start one up with a
    RuntimeInitializeOnLoad
    attribute.

    The above solutions can be modified to additively load a scene instead, BUT scenes do not load until end of frame, which means your static factory cannot return the instance that will be in the to-be-loaded scene. This is a minor limitation that is simple to work around.

    If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

    Code (csharp):
    1. public void DestroyThyself()
    2. {
    3.    Destroy(gameObject);
    4.    Instance = null;    // because destroy doesn't happen until end of frame
    5. }
    There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

    OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

    And finally there's always just a simple "static locator" pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle.

    WARNING: this does NOT control their uniqueness.

    WARNING: this does NOT control their lifecycle.

    Code (csharp):
    1. public static MyClass Instance { get; private set; }
    2.  
    3. void OnEnable()
    4. {
    5.   Instance = this;
    6. }
    7. void OnDisable()
    8. {
    9.   Instance = null;     // keep everybody honest when we're not around
    10. }
    Anyone can get at it via
    MyClass.Instance.
    , but only while it exists.
     
  3. Mauschelbaer

    Mauschelbaer

    Joined:
    Aug 4, 2019
    Posts:
    17
    Ok, first of all thanks for the detailed answer.

    The "pure-code" approach suits my style perfectly; as well as additive loading.

    However, here's what I would do, and I'm wondering what you think of it:
    My in-game scene (i.e. not "Title" or "Menu") would look like this (I'm trying to reproduce the Unity Hierarchy window in ascii):

    Code (CSharp):
    1. [InGame] //scene-name
    2.   >InGame // GameObject
    3.       -GameManager-Script // Script-Component
    4. [GamePlay] //scene-name (additively loaded through "GameManager"-Script)
    5.   >GamePlay //GameObject: loads Players and co
    6.   >UI //GameObject
    7. [Level01] //scene-name (additively loaded through "GameManager"-Script)
    8.   > ... // all the level specific stuff

    The order and subdivision is of course debatable, but I would like to understand from where in your opinion
    the singleton managers are used.
    The GameManager that exists in the in-game scene would now also be in the scene itself and can't just be somewhere else in my understanding, can it?
     
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    6,008
    I find it best when managers just lazily initialise themselves. Such as an Audio Manager component that instances itself, DDOL's itself, and sits there on the sidelines for as long as it needs to.

    Also consider whether some managers are just purely code, and might be betting off being a static class/plain C# class with singleton. Not everything needs to live in a scene.

    Though my usual pattern is scriptable object singleton + static class as its API interface. Managers shouldn't live in scenes, IMO.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,951
    If you follow my pattern above they will always be with you once you access them.

    They will also be in the semi-hidden DontDestroyOnLoad scene, nicely out of your sight.
     
  6. Mauschelbaer

    Mauschelbaer

    Joined:
    Aug 4, 2019
    Posts:
    17
    I don't think I've been able to get my point across. What I mean is, if no script object exists in the scene, who is supposed to access the scripts that have been implemented as singeltons?
    Even if the entire business logic has been implemented in singeltons, at what point are they used? The highest element must be a GameObject in the scene.

    Also, because of @spiney199 comment, perhaps I should clarify what a manager or a controller is. I'm talking about objects that, for example, let the players spawn, jump back to the menu scene when the goal is reached or load the level additively.

    So in your opinion, it should not be in the game scene, but work as a singleton? And again: Where should it start and since it only has tasks within the running game, how should it know when it is operating on what (if it also exists during other scenes)?
     
  7. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    6,008
    None of these are anywhere close to what I could call managers or controllers. They can just be regular components that themselves might call methods from more centralised APIs.
     
  8. Mauschelbaer

    Mauschelbaer

    Joined:
    Aug 4, 2019
    Posts:
    17
    Okay, I understand, instead of having a central object that listens for certain things (e.g. "are all players dead?"), each player in its
    PlayerHealthScript
    (once dead) would call the higher API, e.g. "
    GameManager.Instance.CheckIfAllPlayersDead()
    ".
    This in turn would cause the scene to switch to the main menu, correct?

    But if that is the case, then I wonder where the script is that loads the "in-game" scenes additively when loading the "under" scenes (i.e. Level01 scene, UI scene, ...).

    Can you explain this to me?
     
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    6,008
    Basically. Your systems can be thought of as your game's internal API that other parts of the code can touch to make things happen.

    For example, here's the 'level completion' component in my puzzle platformer project:
    Code (CSharp):
    1. public sealed class LevelCompletionComponent : MonoBehaviour
    2. {
    3.     #region Inspector Fields
    4.  
    5.     [BoxGroup("L")]
    6.     [SerializeField]
    7.     [Tooltip("The time before the Level Completion menu is displayed.")]
    8.     private float _completionDelay = 2f;
    9.  
    10.     [BoxGroup("L")]
    11.     [SerializeField]
    12.     [Tooltip("The virtual camera used to give an overview of the level when the " +
    13.         "Level Completion Menu is displayed.")]
    14.     private CinemachineVirtualCameraBase _overviewVirtualCamera;      
    15.  
    16.     #endregion
    17.  
    18.     #region Unity Callbacks
    19.  
    20.     private IEnumerator OnTriggerEnter(Collider other)
    21.     {
    22.         if (other.TryGetComponent(out IPlayableSphereController playableSphere))
    23.         {
    24.             playableSphere.SphereBehaviour.DisableSphereBehaviour();
    25.             LevelManager.CompleteActiveLevel();
    26.  
    27.             yield return new WaitForSeconds(_completionDelay);
    28.            
    29.             if (_overviewVirtualCamera)
    30.             {
    31.                 CameraManager.AddCameraOverride(_overviewVirtualCamera);
    32.             }
    33.            
    34.             UserInterfaceProvider.OpenOverlayMenu<LevelCompletionMenu>();
    35.         }
    36.     }
    37.  
    38.     #endregion
    39. }
    You can see it's primarily just sequence call to other parts of the project's API.

    If you mean if your project should initialise bunch of stuff when entering the play mode in the editor, then that kind of stuff can just be handled by editor code if required. It's very common to use stuff like this to do extra work when entering play mode: https://docs.unity3d.com/ScriptReference/EditorApplication-playModeStateChanged.html

    You can also use attributes like RuntimeInitializeOnLoadMethod and similar to do extra work at various stages of initialisation too: https://docs.unity3d.com/ScriptReference/RuntimeInitializeOnLoadMethodAttribute.html

    In any case there's never really one 'a script', there will be many handling various different things.
     
  10. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,951
    Good point... let me clarify: don't drop manager and other long-lived game infrastructure into scenes.

    A given scene has a given purpose. For instance if you have a "start playing this game mode" scene, it should have a script to do that. One common pattern is to have a tiny script to additively load other scenes that also contain purpose-built scripts, such as a UI scene, or Camera scene, etc., if you're using additive scenes.

    The point is, if you have a manager that does centralized logic, DON'T put that in a scene because now you become beholden to getting that scene into place. Just let it spin itself up the way I listed above.
     
  11. Mauschelbaer

    Mauschelbaer

    Joined:
    Aug 4, 2019
    Posts:
    17
    Thank you both for all the explanations! I now understand what you mean. This has made a lot of things clear to me! Thanks for the support
     
    Kurt-Dekker likes this.