I’m working on a way to improve the workflow of inter-scene dependencies and additive scene loading. Following git-amend tutorials on Unity about the Additive Scene Loading with groups of scene to load together and Dependency injection, i have hooked a Bootstrapper scene where the injector and scene loader are located. What I want is for all the scenes to load and then I allow there activation and I would like then the injector to fetch all the service providers from every monobehaviours in every loaded scenes and then provide these services to the monobehaviours that require them.
The injection works properly on a single scene and the scene loader as well. The problem is that I want to trigger the Injection as soon as the scenes are loaded / awaken but BEFORE the scripts’ Start being called for me to use the injected references there.
The problem is that the script’s start is called before the injection so I don’t know when exactly I can run code before the actual start of the objects.
I hope you will be able to help me, I can provide code snippets if needed and don’t hesitate to ask questions if it can help you to understand better the problem. Any track to solve this problem is welcomed , thanks in advance.
Padrox
Hey, first thank you for your answer.
I already know of RuntimeInitializeOnLoadAttribute and this is a good track although what happens is actually the following:
The Bootstrapper class has a method called Initialize which is marked with the RuntimeInitializeOnLoad and loads the scene Bootstrapper. The bootstrapper contains the injector and the scene loader. The scene loader then loads the first group of scenes (for the sake of example Level #1).
So I have a list of AsyncOperations that I await until all scenes are loaded. Here is the catch, the scenes are loaded in Unity at 0.9/1. Then I allow the scene a tivation and now all the scenes are ready they need to be accessible with FindObjectByType but before they actually are started they need to be injected by the injector.
you can subscribe to SceneManager.sceneLoaded event.
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
public interface IInjectable
{
void Inject();
}
public class InjectableFinder : MonoBehaviour
{
private void OnEnable()
{
// Subscribe to the sceneLoaded event to get notified when a scene is loaded
SceneManager.sceneLoaded += OnSceneLoaded;
}
private void OnDisable()
{
// Unsubscribe from the sceneLoaded event
SceneManager.sceneLoaded -= OnSceneLoaded;
}
// This is called when a new scene has been loaded
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// Get all root game objects in the newly loaded scene
GameObject[] rootObjects = scene.GetRootGameObjects();
List<IInjectable> injectables = new List<IInjectable>();
// Search through root objects and their children for components implementing IInjectable
foreach (GameObject rootObject in rootObjects)
{
// Get all components that implement IInjectable in this root object and its children
IInjectable[] injectableComponents = rootObject.GetComponentsInChildren<IInjectable>(true);
injectables.AddRange(injectableComponents);
}
// Example: Process the found IInjectable objects
foreach (IInjectable injectable in injectables)
{
Debug.Log("Found IInjectable: " + injectable.GetType().Name);
injectable.Inject(); // Optionally call the Inject method if needed
}
}
}
This is a great idea, although, i don’t want to develop my game on a scene with all its objects disabled. How can I load the scene and disable everything before the scene instantiation ?
And would it decreases the performance significantly?
That is a smart approach, from what I saw in the Unity documentation, the Scene loaded event is called after Awake and OnEnable but before Start. The problem that I see is the following: my current system loads a group of n scenes so the event would trigger n times.
Alternatively, i could handle the injection of every single scene on their own, like when a scene is complete loop through every gameobjects of this specific scene in argument and inject them. The issue is that first of all the system registers the services and after that only it injects the required services. Because the main purpose is to inject services from external scenes.
I also look forward about your solution, hope we can dig it up.
You cannot disable programmatically everything before the scene instantiation as they don’t exist yet, but you can disable each game object in Awake, do things in Awake → disable the object in Awake → inject what you want → enable the objects ->then start runs.
The code example only iterates over the newly loaded scene objects, notice that code is not using FindObjectsByType. So the event triggering multiple times shouldn’t be much of a problem in this case.
Ideally your services would be initialized before the scenes are loaded. I do this with RuntimeInitializeOnLoadMethod with BeforeSceneLoad load type and mark them as DontDestroyOnLoad. And if some services are a part of the scene, you’d register them in Awake as services, then request those services only in Start to make sure the service is present before asking for it.
Alternatively, you can have IInjectable objects register themselves with the central system, which then injects the dependencies to these objects. This way the central system doesn’t have to process all objects in all scenes. The relevant objects request their dependencies when necessary instead.
If they are tagged as RuntimeInitializationBeforeSceneLoad, can you setup some monobehaviour without the object being loaded in the scene where it should exist ?
I feel like going with disabling all objects and re-enabling them is a lot of setup and it looses the point of fast and lightweight injection.
The approach suggesting the attribute RuntimeInitialization sounds better to me but the problem remains the same if I’m not mistaking. If i load in my group both the Level 1 scene and then the Systems scene that contains the AudioService, I can’t rely on the Awake method of the AudioService because it runs whenever it is loaded, thus if the level scene requires the audioservice but loads before the system, the service wouldn’t be registered.
I was wondering if you guys know if the event sceneLoaded of the SceneManager triggers even if I deactivate “allowSceneActivation” when loading the scene. Or alternatively, If I am right, in Unity, a loaded scene has its progress at 0.9. Maybe i can access the GameObjects of this scene before the scene getting “instantiated”. Those two suggestions are ideas I got. What do you guys think ?
Not quite catching your meaning. Are you asking about the service holding scene references from scenes that are not loaded yet? A Monobehaviour object can be loaded before any scene, it just can’t reference objects from the scene directly until the scene is loaded and you hand the service the said object. You also can’t disrupt or change the execution order of Awake or Start. If the object is enabled when the scene loads, those lifecycle events will trigger no matter what. There is no way around it.
So you have a few options.
The code I’ve provided that loops over all scene objects of a newly loaded scene.
IInjectable objects register themselves with the central system that then injects the dependencies they request, which is what I’ve done in my project. I mark fields that need injection with [Inject] attribute, the central system uses that attribute to determine which fields and with what types to inject the field. I have an abstract InjectableMonoBehaviour that implements IInjectable as a shortcut that registers the object in Start() and unregisters it OnDestroy() with the system. Since services are registered in Awake() and objects request dependencies in Start(), I’ve had no issues so far.
Mess with script execution order settings so that specific scripts execute before the rest but this is a slippery slope.
Re-architect the project so that global services don’t rely on specific scene objects and instead instantiate them, or perhaps there’s a way of not depending on them, hard to say without knowing the specifics.
You cannot access the objects of a scene before a scene gets instantiated because those objects don’t exist yet for the game engine. First the scene gets instantiated then the objects in it.
The three solutions you can have are the solutions already mentioned, either you start with all your game objects disabled so the Awakes won’t run, or you disable them in awake and enable them again, or you use the event.
There is no reason to worry about the performance of disable/enable considering that you are loading a new scene. The performance hit in comparison to everything the engine has to do for the scene load: Read assets from disk, load meshes, load audio, register all the game objects to the native part of the engine, and so on, is negligible.
Then I’m down to try your approach because I feel like there are a lot of resources about how to load multiple scenes at the same time and also for handling dependencies. But not for actually sharing the references properly between scenes. Thus my question, I hope to find a flexible way of handling my references and my scenes.
Although, my injection right now keeps track of a dictionary of type keys and object values. And the service provides itself with the attribute [Provide] on a method that returns itself. Using RuntimeInitialization like you mentioned:
How does the service reference itself to the Central Injection system ?
I have a Dictionary<Type, Component> registry that registers services by their component type and any interfaces they implement. Might change that in the future to just register interfaces, will see.
public static void Add(Component component)
{
if (component == null) throw new ArgumentNullException(nameof(component), "Cannot add a null component.");
RegisterComponentTypes(component);
NotifyInjectables();
}
Then any services call this in Awake. ComponentLocator is my central system, which is a static class so it’s always in memory and accessible. Services are registered in Awake and dependencies are requested in Start, which is a hard limit of this approach but handles interscene dependencies just fine assuming Scene B needs something from Scene A and not the other way around. Additive scenes assume a specific load order in general.
Personally, not a big fan of additive scene workflows due to how it enforces a specific load order. Something like a self contained Settings screen - sure, be a scene. But why have gameplay UI in a separate scene if prefabs are perfectly serviceable? And you don’t have to deal with having scenes loaded in proper order anymore. You can still develop UI logic in a separate assembly module with the prefab workflow.
If none of this helps, you can look into getting Init(args) DI framework, which allows inspector drag and drop between scenes as well as automatic instantiation/lazy loading of services when they are requested and marking any object as a Service - scene objects, scriptable objects, even plain C# classes: Init(args) | Utilities Tools | Unity Asset Store
Theoretically you could register instances before their scenes have become active using SerializationCallbackReceiver.OnAfterDeserialize - however, then clients could receive references to uninitialized objects, and if they tried to access any Unity-specific members (such their gameObject or name) on them before their scene becomes active, it would cause an exception.
So don’t do this.
What you can do instead is have clients disable themselves temporarily during their Awake event until all their services have become available. Note that you don’t need to set the entire GameObject inactive, nor disable any clients that already have access to all their services when their Awake event gets executed, so the overhead should be very minimal.
As was already pointed out by @meredoth , this would delay the execution of their Start event methods (as well as OnEnable and Update etc.) until all services have been injected to them.
Example:
public interface IClient
{
Type[] GetDependencyTypes();
void Init(IService[] dependencies);
}
public interface IService
{
Type ServiceType => GetType();
}
public abstract class BaseBehaviour : MonoBehaviour
{
private static readonly Dictionary<Type, IService> services = new();
private async void Awake()
{
if(this is IService service)
{
services[service.ServiceType] = service;
}
if(this is IClient client)
{
var dependencyTypes = client.GetDependencyTypes();
var dependencyCount = dependencyTypes.Length;
var dependencies = new IService[dependencyCount];
for(int i = 0; i < dependencyCount; i++)
{
var dependencyType = dependencyTypes[i];
while(!services.TryGetValue(dependencyType, out dependencies[i]))
{
// only disable temporarily if any service is missing during Awake
enabled = false;
await Awaitable.NextFrameAsync();
}
}
client.Init(dependencies);
// Now ready to execute OnEnable etc.
enabled = true;
}
}
private void OnDestroy()
{
if(this is IService service)
{
services.Remove(service.ServiceType);
}
}
}
Another approach would of course be to just to make sure to organize objects into scenes in such a way, and load the scene in such an order, that no objects in any of the loaded scenes depend on objects in the scenes that are loaded after them.
A third approach would be to use soft references, such as Func<T> or Task<T>. These could be safely injected before the objects they refer to even exist.
Watched the tutorial, it appears he builds the entire system within a single scene with monobehaviours and only registers providers and injects dependencies in Awake() once.
Simply extract the logic from Awake() into a method and call it on SceneManager.onSceneLoaded. Then only access dependencies in Start() since SceneManager.onSceneLoaded should execute before Start(). This does enforce a specific scene load order where the scenes loaded first can’t depend on scenes loaded later but perhaps architecting in a way that doesn’t introduce cyclic scene dependencies is healthier for the project.
If this does not work for you, what others suggested about disabling objects in Awake() to delay Start() applies or the Func<T>, Task<T> injection.