unity-basics.md
June 18, 2026 ยท View on GitHub
Unity Basics
Demonstrates basic integration with Unity game engine, showing how Pure.DI can be used for dependency injection in Unity projects.
using Shouldly;
using Pure.DI;
using UnityEngine;
public class Clock : MonoBehaviour
{
const float HoursToDegrees = -30f, MinutesToDegrees = -6f, SecondsToDegrees = -6f;
[SerializeField] Scope scope;
[SerializeField] Transform hoursPivot;
[SerializeField] Transform minutesPivot;
[SerializeField] Transform secondsPivot;
[Dependency]
public IClockService ClockService { private get; set; }
public void Awake()
{
scope.BuildUp(this);
}
public void Update()
{
var now = ClockService.Now.TimeOfDay;
hoursPivot.localRotation = Quaternion.Euler(0f, 0f, HoursToDegrees * (float)now.TotalHours);
minutesPivot.localRotation = Quaternion.Euler(0f, 0f, MinutesToDegrees * (float)now.TotalMinutes);
secondsPivot.localRotation = Quaternion.Euler(0f, 0f, SecondsToDegrees * (float)now.TotalSeconds);
}
}
public interface IClockConfig
{
TimeSpan Offset { get; }
}
[CreateAssetMenu(fileName = "ClockConfig", menuName = "Clock/Config")]
public class ClockConfig : ScriptableObject, IClockConfig
{
[SerializeField] int offsetHours;
public TimeSpan Offset => TimeSpan.FromHours(offsetHours);
}
public interface IClockService
{
DateTime Now { get; }
}
public class ClockService : IClockService, IDisposable
{
private readonly IClockConfig _config;
public DateTime Now => DateTime.UtcNow + _config.Offset;
public ClockService(IClockConfig config)
{
_config = config;
}
public void Dispose()
{
GC.SuppressFinalize(this);
// Perform any necessary cleanup here
}
}
public partial class Scope : MonoBehaviour
{
[SerializeField] ClockConfig clockConfig;
void Setup() =>
DI.Setup()
.Bind().To(() => clockConfig)
.Bind().As(Lifetime.Singleton).To<ClockService>()
.Builders<MonoBehaviour>();
void OnDestroy()
{
Dispose();
}
}
Running this code sample locally
- Make sure you have the .NET SDK 10.0 or later installed
dotnet --list-sdk
- Create a net10.0 (or later) console application
dotnet new console -n Sample
dotnet add package Pure.DI
dotnet add package Shouldly
- Copy the example code into the Program.cs file
You are ready to run the example ๐
dotnet run
Note
Unity integration requires special considerations due to Unity's component-based architecture and lifecycle management.
The following partial class will be generated:
partial class Scope: IDisposable
{
#if NET9_0_OR_GREATER
private readonly Lock _lock = new Lock();
#else
private readonly Object _lock = new Object();
#endif
private object[] _disposables = new object[1];
private int _disposeIndex;
private ClockService? _singletonClockService72;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Clock BuildUp(Clock buildingInstance)
{
if (buildingInstance is null) throw new ArgumentNullException(nameof(buildingInstance));
Clock transientClock;
Clock localBuildingInstance = buildingInstance;
if (_singletonClockService72 is null)
lock (_lock)
if (_singletonClockService72 is null)
{
ClockConfig transientClockConfig = clockConfig;
_singletonClockService72 = new ClockService(transientClockConfig);
_disposables[_disposeIndex++] = _singletonClockService72;
}
localBuildingInstance.ClockService = _singletonClockService72;
transientClock = localBuildingInstance;
return transientClock;
}
#pragma warning disable CS0162
[MethodImpl(MethodImplOptions.NoInlining)]
public UnityEngine.MonoBehaviour BuildUp(UnityEngine.MonoBehaviour buildingInstance)
{
if (buildingInstance is null) throw new ArgumentNullException(nameof(buildingInstance));
if (TryBuildUp(buildingInstance))
{
return buildingInstance;
}
throw new ArgumentException($"Unable to build an instance of typeof type {buildingInstance.GetType()}.", "buildingInstance");
}
#pragma warning restore CS0162
#pragma warning disable CS0162
[MethodImpl(MethodImplOptions.NoInlining)]
public bool TryBuildUp(UnityEngine.MonoBehaviour buildingInstance)
{
if (buildingInstance is null) throw new ArgumentNullException(nameof(buildingInstance));
switch (buildingInstance)
{
case Clock Clock:
BuildUp(Clock);
return true;
default:
return false;
}
return false;
}
#pragma warning restore CS0162
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Resolve<T>()
{
return Resolver<T>.Value.Resolve(this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Resolve<T>(object? tag)
{
return Resolver<T>.Value.ResolveByTag(this, tag);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public object Resolve(Type type)
{
throw new CannotResolveException($"{CannotResolveMessage} {OfTypeMessage} {type}.", type, null);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public object Resolve(Type type, object? tag)
{
throw new CannotResolveException($"{CannotResolveMessage} \"{tag}\" {OfTypeMessage} {type}.", type, tag);
}
public void Dispose()
{
int disposeIndex;
object[] disposables;
lock (_lock)
{
disposeIndex = _disposeIndex;
_disposeIndex = 0;
disposables = _disposables;
_disposables = new object[1];
_singletonClockService72 = null;
}
while (disposeIndex-- > 0)
{
switch (disposables[disposeIndex])
{
case IDisposable disposableInstance:
try
{
disposableInstance.Dispose();
}
catch (Exception exception)
{
OnDisposeException(disposableInstance, exception);
}
break;
}
}
}
partial void OnDisposeException<T>(T disposableInstance, Exception exception) where T : IDisposable;
private const string CannotResolveMessage = "Cannot resolve composition root ";
private const string OfTypeMessage = "of type ";
private class Resolver<T>: IResolver<Scope, T>
{
public static IResolver<Scope, T> Value = new Resolver<T>();
public virtual T Resolve(Scope composite)
{
throw new CannotResolveException($"{CannotResolveMessage}{OfTypeMessage}{typeof(T)}.", typeof(T), null);
}
public virtual T ResolveByTag(Scope composite, object tag)
{
throw new CannotResolveException($"{CannotResolveMessage}\"{tag}\" {OfTypeMessage}{typeof(T)}.", typeof(T), tag);
}
}
}