A flexible & efficient way to manage and access services in Unity
ServiceLocator is based on a ScriptableObject implementation, offering various methods for registering, unregistering, and retrieving services.
Add Service Locator to your Unity project via Package Manager:
- Open Window > Package Manager
- Click + > Add package from git URL
- Enter:
https://www.pkglnk.dev/service-locator.git
ScriptableObject-based implementation for seamless Unity integration- Synchronous, asynchronous, and promise-based service retrieval
- Coroutine support for service access
- Multi-service retrieval in a single call
- Cancellation support with
CancellationToken, includingMonoBehaviour.destroyCancellationToken - Robust error handling and service rejection mechanisms
- Automatic scene tracking for services with a fallback cleanup method when scenes are unloaded
- Service validation for checking if MonoBehaviour services are valid or destroyed
- Configurable feature toggles via the Service Locator Window settings tab
- Fluent API options
If you like my work then please consider showing your support by buying me a brew
A DefaultServiceLocator asset is included in the package for immediate use. The ServiceLocator property drawer automatically populates with the first ServiceLocator asset found in the project. To create a custom instance:
- Right-click in the Unity Editor's Project panel.
- Navigate to
Create -> ServiceLocatorto create a newServiceLocatorasset.
Register services using the Register<T> method:
publicinterfaceIMyService{voidDoSomething();}publicclassMyService:IMyService{publicvoidDoSomething(){Debug.Log("Doing something!");}}// Obtain your ServiceLocator instanceServiceLocatorlocator;// ... reference to your ServiceLocator assetlocator.Register<IMyService>(newMyService());ServiceLocator offers multiple retrieval methods:
- Async/Await - Single Service:
IMyServicemyService=await_serviceLocator.GetServiceAsync<IMyService>();myService.DoSomething();- Promise-based - Single Service:
_serviceLocator.GetService<IMyService>().Then(service =>service.DoSomething()).Catch(ex =>Debug.LogError($"Failed to get service: {ex.Message}"));- Coroutine-based - Single Service:
StartCoroutine(_serviceLocator.GetServiceCoroutine<IMyService>(service =>{service?.DoSomething();}));- Immediate (Try-Get) - Single Service
if(_serviceLocator.TryGetService<IMyService>(outvarmyService)){myService.DoSomething();}Retrieve multiple services in one call (up to 6 supported):
- Async/Await - Two Services
var(myService,anotherService)=await_serviceLocator.GetServicesAsync<IMyService,IAnotherService>();myService.DoSomething();anotherService.DoAnotherThing();_serviceLocator.GetService<IMyService,IAnotherService,IThirdService>().Then(services =>{services.Item1.DoSomething();services.Item2.DoAnotherThing();services.Item3.DoThirdThing();}).Catch(ex =>Debug.LogError($"Failed to get services: {ex.Message}"));ServiceLocator now includes a fluent API for cleaner, more readable service retrieval, particularly when working with multiple services.
// Single servicevarmyService=await_serviceLocator.GetAsync<IMyService>().WithCancellation(destroyCancellationToken);// Multiple servicesvar(myService,anotherService,thirdService)=await_serviceLocator.GetAsync<IMyService>().AndAsync<IAnotherService>().AndAsync<IThirdService>().WithCancellation(destroyCancellationToken);// Single service_serviceLocator.Get<IMyService>().WithCancellation(destroyCancellationToken).Then(service =>service.DoSomething()).Catch(ex =>Debug.LogError($"Failed to get service: {ex.Message}"));// Multiple services_serviceLocator.Get<IMyService>().And<IAnotherService>().And<IThirdService>().WithCancellation(destroyCancellationToken).Then(services =>{var(service1,service2,service3)=services;service1.DoSomething();service2.DoAnotherThing();service3.DoThirdThing();}).Catch(ex =>Debug.LogError($"Failed to get services: {ex.Message}"));// Single serviceStartCoroutine(_serviceLocator.GetCoroutine<IMyService>().WithCallback(service =>service?.DoSomething()));// Multiple servicesStartCoroutine(_serviceLocator.GetCoroutine<IMyService>().And<IAnotherService>().And<IThirdService>().WithCallback((service1,service2,service3)=>{service1?.DoSomething();service2?.DoAnotherThing();service3?.DoThirdThing();}));This fluent approach improves code readability significantly over the traditional approach, especially when retrieving multiple services. It maintains all the same functionality, including cancellation support, while providing a more intuitive and chainable API.
The fluent API supports up to 6 services in a single chain, just like the traditional API methods.
ServiceLocator now includes extension methods that provide standardized error handling for async operations, reducing the need for repetitive try/catch blocks.
// Without extension - requires try/catch blockprivateasyncvoidStart(){try{varservice=await_serviceLocator.GetServiceAsync<IMyService>();service.DoSomething();}catch(OperationCanceledException){// Handle cancellation}catch(Exceptionex){Debug.LogError($"Failed to retrieve service: {ex.Message}");}}// With extension - cleaner code with default error loggingprivateasyncvoidStart(){varservice=await_serviceLocator.GetServiceAsync<IMyService>().WithErrorHandling(defaultValue:null);service?.DoSomething();}The error handling extensions provide several features:
varservice=await_serviceLocator.GetServiceAsync<IMyService>().WithErrorHandling(defaultValue:null,// Value to return if the operation failserrorHandler: ex =>{// Custom error handlerNotifyUser("Service unavailable");AnalyticsManager.LogError(ex);},rethrowException:false// Whether to rethrow the exception);Error handling extensions work seamlessly with the fluent API:
// Single service with fluent API and error handlingvarservice=await_serviceLocator.GetAsync<IMyService>().WithCancellation(destroyCancellationToken).WithErrorHandling(defaultValue:null);// Multiple services with fluent API and error handlingvar(service1,service2)=await_serviceLocator.GetAsync<IMyService>().AndAsync<IAnotherService>().WithCancellation(destroyCancellationToken).WithErrorHandling(defaultValue:(null,null),errorHandler: ex =>LogManager.LogError("Failed to load services",ex));// Process services safely with null checksif(service1!=null){service1.DoSomething();}if(service2!=null){service2.DoAnotherThing();}These error handling extensions provide:
- Automatic contextual logging: Each error is logged with file, method, and line number
- Default value support: Specify fallback values when services aren't available
- Customizable error handling: Provide custom handlers for specific error scenarios
- Simplified code: Remove boilerplate try/catch blocks for cleaner service access
- Cancellation handling: OperationCanceledException is handled gracefully
Error handling works for any Task-based operation, not just service retrieval, making it useful throughout your codebase.
Unregister a service when no longer needed:
_serviceLocator.Unregister<IMyService>();Use MonoService for automatic registration/unregistration::
publicinterfaceIMyService{voidDoSomething();}publicclassMyMonoService:MonoService<IMyService>,IMyService{publicvoidDoSomething(){Debug.Log("Doing something from MonoService!");}protectedoverridevoidAwake(){base.Awake();// Additional initialization if neededServiceReady();// Registers the service}}You can create abstract base classes that inherit from MonoService to establish service patterns:
// Base abstract class for all mini-game score servicespublicabstractclassMiniGameScoreService<T>:MonoService<T>,IMiniGameScoreServicewhereT:class,IMiniGameScoreService{// Common score tracking functionalityprotectedint_score;publicvirtualintGetScore()=>_score;publicvirtualvoidAddScore(intpoints){_score+=points;OnScoreChanged?.Invoke(_score);}publiceventAction<int>OnScoreChanged;// Other shared functionality...}// Concrete implementation for a specific gamepublicclassMyGameScoreService:MiniGameScoreService<IMyGameScoreService>,IMyGameScoreService{// Game-specific scoring logicpublicvoidAddComboBonus(intcomboCount){AddScore(comboCount*10);}protectedoverridevoidAwake(){base.Awake();_score=0;// Initialize scoreServiceReady();// Register with ServiceLocator}}Usage:
publicclassGameController:MonoBehaviour{[SerializeField]privateServiceLocator_serviceLocator;protectedvirtualasyncvoidAwake(){// Get the specific mini-game score service// This works because MyGameScoreService registers as IMyGameScoreServicevarscoreService=await_serviceLocator.GetServiceAsync<IMyGameScoreService>();scoreService.AddComboBonus(5);// This would get the specific implementation, not a generic IMiniGameScoreService// var genericScoreService = await _serviceLocator.GetServiceAsync<IMiniGameScoreService>(); // Won't work!// You can still access the common interface methodsscoreService.AddScore(100);}}The ServiceLocator package includes a powerful debugging tool to help you monitor and manage your services during development.
To open the Service Locator Window:
- In the Unity Editor, navigate to Tools > Service Locator > Service Locator Window
- The window displays all ServiceLocator assets in your project and their registered services
The Service Locator Window now includes two tabs:
Services Tab: View and manage all registered services Settings Tab: Configure which ServiceLocator features are enabled
- Real-time monitoring: Automatically updates to show all registered services
- Scene-based organization: Services are grouped by the scenes they belong to
- Quick navigation: Click on a service to select its GameObject in the hierarchy
- Script access: Open the script file associated with a service directly from the window
- Unloaded scene tracking: Identifies services from unloaded scenes
- Play mode awareness: Updates when entering/exiting play mode
- Feature toggles: Enable/disable specific ServiceLocator features
- Preprocessor directives: Automatically adds or removes the corresponding preprocessor directives
- Reset to defaults: Option to reset all settings to their default values (all enabled)
- Sync from project: Sync settings with the current project's scripting define symbols
- Toggle Async Services
- Toggle Promise Services
- Toggle Coroutine Services
- Toggle Scene Tracking
- Toggle Logging
- Quick debugging: Quickly identify which services are registered and in which scenes
- Missing services: If a service isn't registered when expected, check the window to confirm
- Scene management: Track which services persist across scene loads
- Development: Monitor service registration/unregistration during gameplay
Using Cancellation Token
ServiceLocator supports CancellationToken for canceling service retrieval, particularly useful with MonoBehaviour.destroyCancellationToken to handle cleanup when objects are destroyed.
Example: Canceling Async Service Retrieval
publicclassServiceUser:MonoBehaviour{[SerializeField]privateServiceLocator_serviceLocator;privateasyncvoidStart(){try{varservice=await_serviceLocator.GetServiceAsync<IMyService>(destroyCancellationToken);service.DoSomething();}catch(TaskCanceledException){Debug.Log("Service retrieval canceled due to object destruction.");}}}Behavior: If the MonoBehaviour is destroyed before the service is retrieved, the task cancels automatically, preventing memory leaks or invalid operations.
Example: Canceling Promise-based Retrieval
publicclassPromiseUser:MonoBehaviour{[SerializeField]privateServiceLocatorlocator;privatevoidStart(){locator.GetService<IMyService>(destroyCancellationToken).Then(service =>service.DoSomething()).Catch(ex =>Debug.Log($"Retrieval failed or canceled: {ex.Message}"));}}Behavior: The promise rejects with a TaskCanceledException if the object is destroyed, ensuring safe cleanup.
The IsServiceValid() method provides a convenient way to check if a service is registered and valid, particularly important for MonoBehaviour-based services that might be destroyed:
publicclassServiceValidator:MonoBehaviour{[SerializeField]privateServiceLocator_serviceLocator;privatevoidUpdate(){// Check if the service is still valid before using itif(_serviceLocator.IsServiceValid<IMyService>()){_serviceLocator.TryGetService<IMyService>(outvarservice);service.DoSomething();}else{// Service is not registered or has been destroyedDebug.Log("Service is not valid, waiting for a new one to register...");}}}Behavior: This method not only checks if the service is registered but also verifies that Unity MonoBehaviour-based services haven't been destroyed, providing safer service access.
The ServiceLocator uses CancellationTokenSource.CreateLinkedTokenSource internally to provide robust cancellation behavior for multi-service operations. This offers several benefits:
- All-or-nothing cancellation: If any service in a multi-service request fails or cancels, all other pending requests are automatically cancelled
- Single control point: One cancellation token affects all dependent operations
- Reduced resource leakage: Proper cleanup of all related operations when cancellation occurs
- Consistent application state: Prevents partial results where some parts of a tuple are filled while others aren't
Example: Cancelling Multi-Service Operations
Utilise MonoBehaviour.destroyCancellationToken to cancel multi-service operations when the object is destroyed with ease.
publicclassMultiServiceUser:MonoBehaviour{[SerializeField]privateServiceLocator_serviceLocator;privateasyncvoidStart(){try{// Request multiple services with the same tokenvar(service1,service2,service3)=await_serviceLocator.GetServicesAsync<IAuthService,IUserService,IDataService>(destroyCancellationToken);// All services successfully retrievedProcessServices(service1,service2,service3);}catch(OperationCanceledException){Debug.Log("Multi-service operation was cancelled");}}}ServiceLocator provides mechanisms to handle errors and explicitly reject service promises.
Example: Handling Errors with Async/Await
publicclassErrorHandler:MonoBehaviour{[SerializeField]privateServiceLocatorlocator;privateasyncvoidStart(){try{varservice=awaitlocator.GetServiceAsync<IMyService>();service.DoSomething();}catch(Exceptionex){Debug.LogError($"Failed to retrieve service: {ex.Message}");}}}Behavior: Catches any exceptions, such as cancellation or rejection, thrown during retrieval.
Example: Rejecting a Service with a Custom Exception
publicclassServiceRejector:MonoBehaviour{[SerializeField]privateServiceLocatorlocator;privatevoidStart(){locator.GetService<IMyService>().Then(service =>service.DoSomething()).Catch(ex =>Debug.LogError($"Service rejected: {ex.Message}"));// Simulate a failure to initialize the servicelocator.RejectService<IMyService>(newInvalidOperationException("Service initialization failed"));}}Behavior: RejectService triggers the Catch handler with the custom exception, allowing you to handle initialization failures gracefully.
Example: Combining Cancellation and Rejection
publicclassCombinedHandler:MonoBehaviour{[SerializeField]privateServiceLocator_serviceLocator;privatevoidStart(){_serviceLocator.GetService<IMyService>(destroyCancellationToken).Then(service =>service.DoSomething()).Catch(ex =>{if(exisTaskCanceledException){Debug.Log("Retrieval canceled due to destruction.");}else{Debug.LogError($"Service retrieval failed: {ex.Message}");}});// Simulate rejection if initialization failsif(someCondition){_serviceLocator.RejectService<IMyService>(newInvalidOperationException("Initialization failed"));}}}Behavior: Handles both cancellation (e.g., object destruction) and explicit rejection (e.g., initialization failure) in a single Catch block.
Manually clean up services and promises:
_serviceLocator.Cleanup();// Clears services and cancels pending promisesWhen a service is unregistered (either manually or through scene unloading), any pending promises for that service will be automatically rejected with an ObjectDisposedException, allowing consumers to handle the unavailability gracefully.
ServiceLocator automatically tracks which scene each MonoBehaviour-based service belongs to. When a scene is unloaded, any services from that scene are automatically unregistered to prevent memory leaks. If service clean is handled correctly this fallback should not be needed, but it is a good safety net and a warning will be logged for each service that is not cleaned up via the fallback.
Example: Automatic Scene Cleanup
// When TestScene is unloaded, all services registered from that scene// will be automatically unregistered, and any pending promises will be rejectedBehavior:Thisprevents"zombie"servicesfrom persisting after their scene has been unloaded,which could lead to memory leaks or unexpected behavior.
### Initialization and De-initializationServiceLocator initializes/de-initializes automatically, but you can control it manually:
```csharp
_serviceLocator.Initialize();// Manual initialization_serviceLocator.DeInitialize();// Manual cleanupContributions are welcome! Please refer to CONTRIBUTING.md for guidelines on how to contribute.
ServiceLocator is licensed under the MIT license. See LICENSE for more details.
For more complex dependency management, consider Dependency Injection frameworks like Zenject. ServiceLocator is ideal for simpler service management needs in Unity.



