← All patterns
Architectural

Service Locator

Give game code one well-known registry to ask for services, so it never needs to know where they came from.

What Is It?

A Service Locator is a central registry that maps a type to an instance. At startup a bootstrap script calls ServiceLocator.Register<IAudioService>(new UnityAudioService()); later, any script that wants to play a sound calls ServiceLocator.Get<IAudioService>().Play("jump").

Callers depend on the interface and on the locator, but not on the concrete class or on where it lives in the scene. That removes the scattered FindObjectOfType calls and hard singletons that otherwise creep into a Unity project.

A good locator also has a plan for missing services. Returning a Null Object — a NullAudioService whose methods do nothing — lets a test scene with no audio setup run happily instead of throwing a NullReferenceException in every enemy's Start().

When Is It Used?

Use it for truly global, cross-cutting services: audio, analytics, save/load, input, localisation — things nearly every system touches.

It is a pragmatic step up from singletons in projects that are not ready for a DI framework: you get swappable implementations with almost no setup.

Avoid it for gameplay relationships (a turret and its target, a door and its key). Those should be explicit references, not global lookups hidden inside method bodies.

Interactive Demo

Click the controls and watch the objects collaborate. The console mirrors what the C# code below would log.

Code

Assets / Scripts / Architectural/ ServiceLocator ›EnemyClient.cs
using UnityEngine;

namespace Patterns.Architectural.ServiceLocator
{
    /// <summary>
    /// A typical client. It asks the locator for services by interface and
    /// never cares which implementation answers.
    /// </summary>
    public class EnemyClient : MonoBehaviour
    {
        private IAudioService audio;
        private IAnalyticsService analytics;

        private void Start()
        {
            // Cache lookups once instead of resolving every frame.
            audio = ServiceLocator.Get<IAudioService>();
            analytics = ServiceLocator.Get<IAnalyticsService>();
        }

        public void Die()
        {
            audio.Play("enemy_death");
            analytics.Track("enemy_killed", name);
            Destroy(gameObject);
        }
    }
}
4 files · namespace Patterns.Architectural.ServiceLocatorC# · UTF-8 · LF

Advantages & Disadvantages

+ Advantages

  • Swapping an implementation at runtime is one Register call — great for debug overlays and tests.
  • Far less wiring than DI: no installers, no constructor plumbing through layers.
  • Pairs naturally with Null Objects, so optional services fail silently and safely.

− Disadvantages

  • Dependencies are hidden inside method bodies; you cannot tell what a class needs from its signature.
  • It is global mutable state, so registration order and scene reloads can cause subtle bugs.
  • Overuse turns it into a junk drawer that every class reaches into, recreating the coupling it was meant to remove.

Tips

  1. 01Register services in a bootstrap with [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] so they exist before any Awake.
  2. 02Clear the registry on domain reload (SubsystemRegistration) when Enter Play Mode Options skip domain reloads, or stale services survive between runs.
  3. 03Cache the result of Get<T>() in Start for hot paths rather than looking it up every frame.
  4. 04Log a warning the first time a Null Object is handed out, so missing registrations are noticed instead of silently ignored.
  5. 05If you find yourself wanting scoped or per-scene services, that is the signal to graduate to Dependency Injection.