The Object Resolver is a lightweight service locator: a dictionary mapping types to instances. Scripts register themselves, other scripts resolve what they need, and cleanup unregisters everything. It’s the middle ground between messy singletons and full dependency injection frameworks.
This means scripts don’t hunt for references—they ask a central registry. The resolver controls what’s available and when, so you can scope access per scene or game state. For example:
- Register core services (audio, save system) at game start, unregister gameplay scripts when returning to menu
- Limit which services get passed to UI scripts vs gameplay scripts via interface filtering
- Avoid static memory bloat by unregistering when a scene unloads
Basic Implementation
public class ObjectResolver
{
private readonly Dictionary<Type, object> _instances = new();
public void Register<T>(T instance) => _instances[typeof(T)] = instance;
public void Unregister<T>() => _instances.Remove(typeof(T));
public T Resolve<T>() => (T)_instances[typeof(T)];
public bool TryResolve<T>(out T instance)
{
if (_instances.TryGetValue(typeof(T), out var obj))
{
instance = (T)obj;
return true;
}
instance = default;
return false;
}
}Usage Example
public class GameBootstrapper : MonoBehaviour
{
[SerializeField] private AudioService audioService;
[SerializeField] private SaveService saveService;
[SerializeField] private PlayerController player;
private ObjectResolver _resolver;
void Awake()
{
_resolver = new ObjectResolver();
// Register services
_resolver.Register(audioService);
_resolver.Register(saveService);
_resolver.Register(player);
// Pass resolver to scripts that need it
player.Initialize(_resolver);
}
void OnDestroy()
{
// Clean up when scene unloads
_resolver.Unregister<PlayerController>();
}
}
public class PlayerController : MonoBehaviour
{
private AudioService _audio;
public void Initialize(ObjectResolver resolver)
{
_audio = resolver.Resolve<AudioService>();
}
public void Jump()
{
_audio.PlaySound("jump");
}
}The magic: Unlike singletons, you control the lifetime—unregister gameplay scripts when leaving a level and they’re gone from memory. You can also create multiple resolvers for different scopes (core vs gameplay), limiting what each part of your code can access.