How to create a loading system with refrences to other instances?

I want to create a loading system that will know the priority of some systems and will wait for them to instantiate first. For example, I need it to wait for my input manager to load on game boot and then load other stuff like Scene, and UI and PlayerController that rely on the input manager instance. I would also like to put a loading % if it’s possible e.g. for loading the scene

I’m implementing InputManager and UIManager as singletones. I’ll put the code for those below

InputManager:

using System;
using UnityEngine;
using UnityEngine.InputSystem;

public class InputReader : MonoBehaviour, InputSystemActions.IPlayerActions, InputSystemActions.IDialogueActions
{
  [SerializeField] DialogueChannel dialogueChannel;
  private InputSystemActions inputActions;
  public event Action<Vector2> MoveEvent;
  public event Action<Vector2> LookEvent;
  public event Action JumpEvent;
  public event Action InteractEvent;
  public event Action SprintStartEvent;
  public event Action SprintEndEvent;
  public event Action PauseEvent;
  public event Action ContinueEvent;
  public event Action CallInventoryEvent;
  private InputActionMap currentInputActionMap;

  public static InputReader Instance { get; private set; }
  private void Awake()
  {
    if (Instance == null)
    {
      Instance = this;
      DontDestroyOnLoad(gameObject);
      Debug.Log("Input Reader Created");
    }
    else
    {
      Destroy(gameObject);
    }
  }

  private void OnEnable()
  {
    if (inputActions == null)
    {
      inputActions = new InputSystemActions();
      inputActions.Player.SetCallbacks(this);
      inputActions.Dialogue.SetCallbacks(this);
      inputActions.Player.Enable();
    }
    dialogueChannel.OnDialogueStart += EnableDialogueScheme;
    dialogueChannel.OnDialogueEnd += DisableDialogueScheme;
  }

  private void OnDisable()
  {
    inputActions.Disable();

    dialogueChannel.OnDialogueStart -= EnableDialogueScheme;
    dialogueChannel.OnDialogueEnd -= DisableDialogueScheme;
  }

  public void OnMove(InputAction.CallbackContext context)
  {
    MoveEvent?.Invoke(context.ReadValue<Vector2>());
  }

  public void OnLook(InputAction.CallbackContext context)
  {
    LookEvent?.Invoke(context.ReadValue<Vector2>());
  }

  public void OnSprint(InputAction.CallbackContext context)
  {
    if (context.phase == InputActionPhase.Performed)
    {
      SprintStartEvent?.Invoke();
    }
    else if (context.phase == InputActionPhase.Canceled)
    {
      SprintEndEvent?.Invoke();
    }
  }

  public void OnJump(InputAction.CallbackContext context)
  {
    if (context.phase == InputActionPhase.Performed)
    {
      JumpEvent?.Invoke();
    }
  }

  public void OnInteract(InputAction.CallbackContext context)
  {
    if (context.phase == InputActionPhase.Performed)
    {
      InteractEvent?.Invoke();
    }
  }

  public void OnPause(InputAction.CallbackContext context)
  {
    if (context.phase == InputActionPhase.Performed)
    {
      PauseEvent?.Invoke();
    }
  }

  public void OnInventory(InputAction.CallbackContext context)
  {
    if (context.phase == InputActionPhase.Performed)
    {
      CallInventoryEvent?.Invoke();
      Debug.Log("Call Inventory Event");
    }
  }

  public void OnContinue(InputAction.CallbackContext context)
  {
    if (context.phase == InputActionPhase.Performed)
    {
      ContinueEvent?.Invoke();
      Debug.Log("Continue to the next node");
    }
  }

  private void EnableDialogueScheme(DialogueSO dialogue)
  {
    inputActions.Disable();
    inputActions.Dialogue.Enable();
  }

  private void DisableDialogueScheme(DialogueSO dialogue)
  {
    inputActions.Enable();
    inputActions.Dialogue.Disable();
  }
}

UIManager:

using System;
using UnityEngine;

public class UIManager : MonoBehaviour
{
  [Header("Dialogue")]
  [SerializeField] DialogueChannel dialogueChannel;
  [SerializeField] GameObject dialogueUI;
  [Header("Inventory")]
  [SerializeField] GameObject inventoryUI;

  private bool showInventoryUI = false;
  public static UIManager Instance { get; private set; }
  private void Awake()
  {
    if (Instance == null)
    {
      Instance = this;
      DontDestroyOnLoad(gameObject);
    }
    else
    {
      Destroy(gameObject);
    }
  }

  private void OnEnable()
  {
    dialogueChannel.OnDialogueStart += ShowDialogueUI;
    dialogueChannel.OnDialogueEnd += HideDialogueUI;

    InputReader.Instance.CallInventoryEvent += ToggleInventoryUI;
  }

  private void OnDisable()
  {
    dialogueChannel.OnDialogueStart -= ShowDialogueUI;
    dialogueChannel.OnDialogueEnd -= HideDialogueUI;

    InputReader.Instance.CallInventoryEvent -= ToggleInventoryUI;
  }

  private void ShowDialogueUI(DialogueSO dialogue)
  {
    dialogueUI.SetActive(true);
  }

  private void HideDialogueUI(DialogueSO dialogue)
  {
    dialogueUI.SetActive(false);
  }

  private void ToggleInventoryUI()
  {
    showInventoryUI = !showInventoryUI;
    inventoryUI.SetActive(showInventoryUI);
  }
}

First, fix those defective singletons above. Those are going to cause you ceaseless problems, especially with all that public stuff, which you’re likely going to be tempted to drag stuff into.

Some super-simple Singleton examples to take and modify:

Simple Unity3D Singleton (no predefined data):

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

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.

There is never a reason to drag a GameObject into a scene if it will be DontDestroyOnLoad.

If you do:

  • you may drag it into something else that isn’t DDOL. FAIL
  • you may drag something else into it that isn’t DDOL. FAIL

Just DO NOT drag anything into a scene that will be marked DontDestroyOnLoad. Just Don’t Do It!

As for your original question about loading, personally I use additive scenes to simplify control of what gets loaded and unloaded when. Here’s some reading:

Additive scene loading is one possible solution:

A multi-scene loader thingy:

My typical Scene Loader:

Other notes on additive scene loading:

Timing of scene loading:

Also, if something exists only once 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:

1 Like

Good stuff as always, thanks!

It’s simpler than most think …

LaunchScene

  • add all your singleton instances here
  • ensure they are in DontDestroyOnLoad
  • load the “first” scene

FirstScene

  • everything already initialized

To further avoid complications, make sure that all systems are setting up their internal references etc in Awake but external references are acquired in Start. This ensures that you do not get a system that hasn’t fully awoken yet.

If you have two systems which depend on each other’s Start having already run, you have a circular dependency and need to solve that, not work around the order of execution issue. Dependency inversion is the solution.

Internal references

  • every object that you “new”
  • GetComponent(InChildren)

External references

  • GetComponentInParent
  • “Find” anything … avoid those at all costs!
1 Like

I’ve actually decided to stick with this solution, as it seems to give me more control over loading stuff.