A flexible service locator and dependency injection system for Unity with support for hierarchical service providers.
Register services with the global ServiceLocator:
var myService = new MyService();
ServiceLocator.RegisterService(myService);Synchronous - Throws InvalidOperationException if not found:
var service = ServiceLocator.GetService<MyService>();Try Pattern - Safe, returns bool:
if (ServiceLocator.TryLocateService<MyService>(out var service)) {
// use service
}Asynchronous - Callback invoked when service is available:
ServiceLocator.LocateServiceAsync<MyService>((service) => {
// service will be called immediately if available,
// or later when registered
});ServiceLocator.DeregisterService<MyService>();The system supports automatic dependency injection using the [Inject] attribute on fields, properties, methods, and constructors.
public class MyComponent
{
[Inject] private MyService _serviceField;
[Inject] public MyService ServiceProperty { get; set; }
}
// Manual injection
var component = new MyComponent();
ServiceLocator.Injector.Inject(component);public class MyComponent
{
private MyService _service;
[Inject]
public void Setup(MyService service)
{
_service = service;
}
}public class MyComponent
{
public readonly MyService Service;
[Inject]
public MyComponent(MyService service)
{
Service = service;
}
}
// Create and inject in one step
var component = ServiceLocator.Injector.CreateAndInject<MyComponent>();Control injection behavior with flags:
// Optional - Won't throw if service is missing
[Inject(flags: InjectorFlags.Optional)]
private MyService _optionalService;
// DontReplace - Won't overwrite if already set
[Inject(flags: InjectorFlags.DontReplace)]
private MyService _existingService;
// Combine flags
[Inject(flags: InjectorFlags.Optional | InjectorFlags.DontReplace)]
private MyService _service;Specify a different type for injection:
[Inject(serviceType: typeof(ConcreteService))]
private IService _service;Inherit from InjectedMonoBehaviour to automatically inject services from the GameObject hierarchy and global ServiceLocator:
public class PlayerUI : InjectedMonoBehaviour
{
[Inject] private PlayerInventory _inventory;
[Inject] private PlayerStats _stats;
// Services automatically injected in Awake()
// First searches up GameObject hierarchy, then falls back to global ServiceLocator
}If you can't inherit from InjectedMonoBehaviour, use extension methods:
public class MyComponent : MonoBehaviour
{
[Inject] private PlayerInventory _inventory;
void Awake()
{
// Inject from hierarchy then global
this.InjectFromHierarchy();
// Or inject only from global ServiceLocator
// this.InjectFromGlobal();
// Or inject from a specific container
// this.InjectFromContainer(myContainer);
}
}Mark fields/properties with [Provide] to make them discoverable by child components:
public class PlayerController : MonoBehaviour
{
[Provide] public PlayerInventory Inventory;
[Provide] public PlayerStats Stats;
}
// Child components will find these automatically via InjectedMonoBehaviour- Child searches up - Walks up the transform hierarchy (parent by parent)
- Checks for providers - Looks for
IServiceProvidercomponents or[Provide]attributes - Falls back to global - If not found in hierarchy, checks global
ServiceLocator - Closest wins - The nearest parent with the service takes precedence
public class GlobalOnlyComponent : InjectedMonoBehaviour
{
protected override bool SearchHierarchy => false;
[Inject] private GlobalAudioManager _audio; // Only from ServiceLocator
}You can also manually search the GameObject hierarchy:
// Try pattern
if (gameObject.TryGetServiceFromHierarchy<PlayerInventory>(out var inventory))
{
// use inventory
}
// Direct get (throws if not found)
var stats = gameObject.GetServiceFromHierarchy<PlayerStats>();public class ServiceProvider : MonoBehaviour
{
[Provide] public AudioManager Audio = new AudioManager();
[Provide] public InputManager Input = new InputManager();
}For more control, implement IServiceProvider:
public class CustomProvider : MonoBehaviour, IServiceProvider
{
public bool TryGetService(Type serviceType, out object service)
{
if (serviceType == typeof(MyService))
{
service = CreateMyService();
return true;
}
service = null;
return false;
}
}Or use the built-in ServiceProviderMonoBehaviour component for reflection-based discovery (useful for Inspector visibility).
Create parent-child container relationships for scoped services:
var globalContainer = new ServiceContainer();
globalContainer.RegisterService(new GlobalService());
var sceneContainer = new ServiceContainer(globalContainer); // globalContainer is parent
sceneContainer.RegisterService(new SceneService());
// Search modes
sceneContainer.GetService<GlobalService>(ServiceSearchMode.GlobalFirst); // Checks parent first
sceneContainer.GetService<SceneService>(ServiceSearchMode.LocalFirst); // Checks local first
sceneContainer.GetService<SceneService>(ServiceSearchMode.LocalOnly); // Only checks localInject services directly from one object to another without using a container:
public class Parent
{
[Provide] public MyService Service = new MyService();
}
public class Child
{
[Inject] private MyService _service;
}
var parent = new Parent();
var child = new Child();
// Inject from parent to child
parent.InjectLocalServices(child);- Use
InjectedMonoBehaviourfor Unity components that need services - Mark providers with
[Provide]to make services discoverable in hierarchy - Register global services in a bootstrap/initialization script
- Use hierarchy for local services (player inventory, UI managers)
- Use global
ServiceLocatorfor truly global services (audio, input, game state) - Prefer constructor injection for non-MonoBehaviour classes
- Use
[Inject]fields for MonoBehaviours (they can't have custom constructors)
// Global bootstrap
public class GameBootstrap : MonoBehaviour
{
void Awake()
{
ServiceLocator.RegisterService(new AudioManager());
ServiceLocator.RegisterService(new InputManager());
}
}
// Parent provides local services
public class PlayerController : MonoBehaviour
{
[Provide] public PlayerInventory Inventory = new PlayerInventory();
[Provide] public PlayerStats Stats = new PlayerStats();
}
// Child automatically receives services
public class PlayerUI : InjectedMonoBehaviour
{
[Inject] private PlayerInventory _inventory; // From parent
[Inject] private AudioManager _audio; // From global
void Start()
{
// Services are already injected by Awake()
Debug.Log($"Health: {_inventory.Health}");
_audio.PlaySound("ui_open");
}
}