Load StartUp-Scene VS Isolated-Scenes

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?

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://discussions.unity.com/t/820920/2
https://discussions.unity.com/t/820920/4

https://discussions.unity.com/t/824447/2

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://discussions.unity.com/t/805654/2

Timing of scene loading:

https://discussions.unity.com/t/813922/2

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://discussions.unity.com/t/840487/10

https://discussions.unity.com/t/851480/4


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:

public void DestroyThyself()
{
   Destroy(gameObject);
   Instance = null;    // because destroy doesn't happen until end of frame
}

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.

public static MyClass Instance { get; private set; }

void OnEnable()
{
  Instance = this;
}
void OnDisable()
{
  Instance = null;     // keep everybody honest when we're not around
}

Anyone can get at it via MyClass.Instance., but only while it exists.

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):

[InGame] //scene-name
  >InGame // GameObject
      -GameManager-Script // Script-Component
[GamePlay] //scene-name (additively loaded through "GameManager"-Script)
  >GamePlay //GameObject: loads Players and co
  >UI //GameObject
[Level01] //scene-name (additively loaded through "GameManager"-Script)
  > ... // 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?

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.

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.

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)?

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.

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?

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:

public sealed class LevelCompletionComponent : MonoBehaviour
{
    #region Inspector Fields

    [BoxGroup("L")]
    [SerializeField]
    [Tooltip("The time before the Level Completion menu is displayed.")]
    private float _completionDelay = 2f;

    [BoxGroup("L")]
    [SerializeField]
    [Tooltip("The virtual camera used to give an overview of the level when the " +
        "Level Completion Menu is displayed.")]
    private CinemachineVirtualCameraBase _overviewVirtualCamera;       

    #endregion

    #region Unity Callbacks

    private IEnumerator OnTriggerEnter(Collider other)
    {
        if (other.TryGetComponent(out IPlayableSphereController playableSphere))
        {
            playableSphere.SphereBehaviour.DisableSphereBehaviour();
            LevelManager.CompleteActiveLevel();

            yield return new WaitForSeconds(_completionDelay);
           
            if (_overviewVirtualCamera)
            {
                CameraManager.AddCameraOverride(_overviewVirtualCamera);
            }
           
            UserInterfaceProvider.OpenOverlayMenu<LevelCompletionMenu>();
        }
    }

    #endregion
}

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.

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.

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

1 Like