Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs-unity.molca.id/llms.txt

Use this file to discover all available pages before exploring further.

Applies to: Molca Core

Overview

This recipe shows you how to use the [Inject] attribute to access RuntimeManager services (like EventDispatcher, DataManager, ReferenceManager) in your custom MonoBehaviour components. Dependency injection eliminates the need for singleton patterns or manual service lookups, making your code cleaner and more testable. You’ll learn how to inject services into scene objects and runtime-created objects.

Prerequisites

Step-by-step

Step 1: Add [Inject] attribute to service fields

Mark service fields with the [Inject] attribute. The RuntimeManager will automatically populate these fields after initialization.
using UnityEngine;
using Molca;
using Molca.Events;
using Molca.ReferenceSystem;

public class MyFeatureController : MonoBehaviour
{
    // These fields will be injected automatically
    [Inject] private EventDispatcher _eventDispatcher;
    [Inject] private ReferenceManager _referenceManager;
    
    // Optional: allow missing service without logging an error
    [Inject(Required = false)] private DataManager _dataManager;
}
Why this works: The RuntimeManager scans all scene MonoBehaviour components after initialization and fills fields marked with [Inject]. The Required parameter controls whether a missing service logs an error (default: true).

Step 2: Wait for RuntimeManager initialization

Always call await RuntimeManager.WaitForInitialization() in Start before using injected services. Injected fields are null in Awake and early Start.
using UnityEngine;
using Molca;
using Molca.Events;

public class MyFeatureController : MonoBehaviour
{
    [Inject] private EventDispatcher _eventDispatcher;

    private async void Start()
    {
        // Wait for RuntimeManager to complete initialization and injection
        await RuntimeManager.WaitForInitialization();
        
        // Now it's safe to use injected services
        if (_eventDispatcher == null)
        {
            Debug.LogError("EventDispatcher injection failed");
            return;
        }
        
        // Use the service
        _eventDispatcher.RegisterEvent("MyEvent", OnMyEvent);
    }

    private void OnMyEvent()
    {
        Debug.Log("Event received");
    }
}
Why this works: RuntimeManager.WaitForInitialization() blocks until RuntimeManager.IsReady is true, which happens after all subsystems are initialized and scene injection is complete.

Step 3: Inject dependencies into runtime-created objects

Objects created with Instantiate or AddComponent don’t get automatic injection. Call RuntimeManager.InjectDependencies() manually after creating them.
using UnityEngine;
using Molca;

public class DynamicObjectSpawner : MonoBehaviour
{
    [SerializeField] private GameObject prefab;

    private async void Start()
    {
        await RuntimeManager.WaitForInitialization();
    }

    public void SpawnObject()
    {
        // Instantiate the prefab
        GameObject instance = Instantiate(prefab);
        
        // Get the component that needs injection
        MyFeatureController controller = instance.GetComponent<MyFeatureController>();
        
        // Manually inject dependencies
        if (controller != null)
        {
            RuntimeManager.InjectDependencies(controller);
        }
    }
}
Why this works: The automatic scene injection pass only runs once during RuntimeManager initialization. Runtime-created objects must call InjectDependencies() explicitly to populate their [Inject] fields.

Step 4: Use InjectDependencies pattern for late initialization

If your component might be created before RuntimeManager is ready, use the InjectDependencies pattern to handle both cases.
using UnityEngine;
using Molca;
using Molca.Events;

public class LateInitializedComponent : MonoBehaviour
{
    [Inject] private EventDispatcher _eventDispatcher;

    private async void Start()
    {
        await RuntimeManager.WaitForInitialization();
        
        // If injection didn't happen automatically, do it manually
        if (_eventDispatcher == null)
        {
            RuntimeManager.InjectDependencies(this);
        }
        
        // Now safe to use
        _eventDispatcher?.RegisterEvent("AppReady", OnAppReady);
    }

    private void OnAppReady()
    {
        Debug.Log("Application ready");
    }
}
Why this works: This pattern handles both scene objects (which get automatic injection) and runtime-created objects (which need manual injection). The null check ensures injection happens exactly once.

Complete example

Here’s a complete example showing dependency injection in a custom training step that uses multiple services:
using UnityEngine;
using Molca;
using Molca.Events;
using Molca.ReferenceSystem;
using Molca.Sequence;

/// <summary>
/// A custom step that uses EventDispatcher and ReferenceManager via dependency injection.
/// </summary>
public class InteractiveTrainingStep : Step
{
    [SerializeField] private string targetObjectId = "TrainingTarget";
    [SerializeField] private string completionEventName = "TargetInteracted";

    // Injected services
    [Inject] private EventDispatcher _eventDispatcher;
    [Inject] private ReferenceManager _referenceManager;

    private GameObject _targetObject;

    private async void Start()
    {
        // Wait for RuntimeManager initialization
        await RuntimeManager.WaitForInitialization();
        
        // Verify injection succeeded
        if (_eventDispatcher == null || _referenceManager == null)
        {
            Debug.LogError($"{nameof(InteractiveTrainingStep)}: Dependency injection failed");
            return;
        }
    }

    protected override void OnStepActivated()
    {
        base.OnStepActivated();
        
        // Use ReferenceManager to find the target object
        _targetObject = _referenceManager?.GetReferencedObject(targetObjectId);
        
        if (_targetObject != null)
        {
            Debug.Log($"Step activated: interact with {_targetObject.name}");
            
            // Register for completion event
            _eventDispatcher?.RegisterEvent(completionEventName, OnTargetInteracted);
        }
        else
        {
            Debug.LogWarning($"Target object '{targetObjectId}' not found");
        }
    }

    protected override void OnStepDeactivated()
    {
        base.OnStepDeactivated();
        
        // Always unregister events to prevent memory leaks
        _eventDispatcher?.UnregisterEvent(completionEventName, OnTargetInteracted);
    }

    private void OnTargetInteracted()
    {
        Debug.Log("Target interacted, completing step");
        Complete();
    }
}
This step demonstrates:
  • Injecting multiple services (EventDispatcher, ReferenceManager)
  • Waiting for initialization in Start
  • Using injected services in lifecycle methods
  • Proper cleanup in OnStepDeactivated

Troubleshooting

  • Injected fields are null: Ensure you call await RuntimeManager.WaitForInitialization() before accessing injected services. Fields are null until the injection pass completes.
  • Runtime-created objects have null fields: Objects created with Instantiate or AddComponent don’t get automatic injection. Call RuntimeManager.InjectDependencies(instance) after creating them.
  • “Service not registered” error: The service you’re trying to inject isn’t registered with RuntimeManager. Check that the corresponding RuntimeSubsystem is attached to the RuntimeManager prefab and enabled.
  • Injection works in Editor but not in build: Verify that the RuntimeManager prefab is referenced in Molca Project Settings and that the prefab is included in the build.
  • Multiple injection calls cause issues: Calling InjectDependencies() multiple times on the same object is safe — it will re-inject fields but won’t cause errors.