The extra fast DI (Dependency Injection) library running on Unity Game Engine.
"V" means making Unity's initial "U" more thinner and solid ... !
- Fast Resolve: Basically 5-10x faster than Zenject.
- Minimum GC Allocation: In Resolve, we have zero allocation without spawned instances.
- Small code size: Few internal types and few .callvirt.
- Assisting correct DI way: Provides simple and transparent API, and carefully select features. This prevents the DI declaration from becoming overly complex.
- Immutable Container: Thread safety and robustness.
- Constructor Injection / Method Injection / Property & Field Injection
- Dispatch own PlayerLoopSystem
- Flexible scoping
- Application can freely create nested Lifetime Scope with any async way for you like.
- Accelerated mode with SourceGenerator (Optional)
- Diagnositcs window on unity editor
- UniTask Integration
- ECS Integration beta
Visit vcontainer.hadashikick.jp to view the full documentation.
Requires Unity 2018.4+
- Navigate to your project's Packages folder and open the manifest.json file.
- Add this line below the "dependencies": { line
-
"jp.hadashikick.vcontainer": "https://github.com/hadashiA/VContainer.git?path=VContainer/Assets/VContainer#1.16.9",
-
- UPM should now install the package.
- The package is available on the openupm registry. It's recommended to install it via openupm-cli.
- Execute the openum command.
-
openupm add jp.hadashikick.vcontainer
-
- Download the .unitypackage from releases page.
- Open VContainer.x.x.x.unitypackage
First, create a scope. References are automatically resolved for types registered here.
public class GameLifetimeScope : LifetimeScope
{
public override void Configure(IContainerBuilder builder)
{
builder.RegisterEntryPoint<ActorPresenter>();
builder.Register<CharacterService>(Lifetime.Scoped);
builder.Register<IRouteSearch, AStarRouteSearch>(Lifetime.Singleton);
builder.RegisterComponentInHierarchy<ActorsView>();
// Register with enum Keys
builder.Register<IWeapon, Sword>(Lifetime.Singleton).Keyed(WeaponType.Primary);
builder.Register<IWeapon, Bow>(Lifetime.Singleton).Keyed(WeaponType.Secondary);
builder.Register<IWeapon, MagicStaff>(Lifetime.Singleton).Keyed(WeaponType.Special);
}
}
Where definitions of classes are
// Define an enum for weapon types
public enum WeaponType
{
Primary,
Secondary,
Special
}
public interface IRouteSearch
{
}
public class AStarRouteSearch : IRouteSearch
{
}
public class CharacterService
{
readonly IRouteSearch routeSearch;
public CharacterService(IRouteSearch routeSearch)
{
this.routeSearch = routeSearch;
}
}
public class ActorsView : MonoBehaviour
{
}
and
public class ActorPresenter : IStartable
{
readonly CharacterService service;
readonly ActorsView actorsView;
readonly IWeapon primaryWeapon;
readonly IWeapon secondaryWeapon;
readonly IWeapon specialWeapon;
public ActorPresenter(
CharacterService service,
ActorsView actorsView,
[Key(WeaponType.Primary)] IWeapon primaryWeapon,
[Key(WeaponType.Secondary)] IWeapon secondaryWeapon,
[Key(WeaponType.Special)] IWeapon specialWeapon)
{
this.service = service;
this.actorsView = actorsView;
this.primaryWeapon = primaryWeapon;
this.secondaryWeapon = secondaryWeapon;
this.specialWeapon = specialWeapon;
}
void IStartable.Start()
{
// Scheduled at Start () on VContainer's own PlayerLoopSystem.
}
}
You can also resolve with object-based Key directly from the container:
// Resolve by Key
var primaryWeapon = container.Resolve<IWeapon>(WeaponType.Primary);
var secondaryWeapon = container.Resolve<IWeapon>(WeaponType.Secondary);
// Try resolve with Key
if (container.TryResolve<IWeapon>(WeaponType.Special, out var specialWeapon))
{
// Use specialWeapon
}
// Other supported Key types include strings and integers
builder.Register<IEnemy, Goblin>(Lifetime.Singleton).Keyed(1); // Integer Key
builder.Register<IEnemy, Orc>(Lifetime.Singleton).Keyed("boss"); // String Key
var goblin = container.Resolve<IEnemy>(1);
var boss = container.Resolve<IEnemy>("boss");
The Key
attribute supports injection with identifiers. You can use it in the constructor, method, or property:
// Field injection with Key
public class WeaponHolder
{
[Inject, Key(WeaponType.Primary)]
public IWeapon PrimaryWeapon;
[Inject, Key(WeaponType.Secondary)]
public IWeapon SecondaryWeapon;
}
// Property injection with Key
public class EquipmentManager
{
[Inject, Key(WeaponType.Primary)]
public IWeapon PrimaryWeapon { get; set; }
[Inject, Key(WeaponType.Secondary)]
public IWeapon SecondaryWeapon { get; set; }
}
// Method injection with Key
public class CharacterEquipment
{
public IWeapon PrimaryWeapon { get; private set; }
public IWeapon SecondaryWeapon { get; private set; }
[Inject]
public void Initialize(
[Key(WeaponType.Primary)] IWeapon primaryWeapon,
[Key(WeaponType.Secondary)] IWeapon secondaryWeapon)
{
PrimaryWeapon = primaryWeapon;
SecondaryWeapon = secondaryWeapon;
}
}
- In this example, the routeSearch of CharacterService is automatically set as the instance of AStarRouteSearch when CharacterService is resolved.
- Further, VContainer can have a Pure C# class as an entry point. (Various timings such as Start, Update, etc. can be specified.) This facilitates "separation of domain logic and presentation".
- With the
Keyed
method andKey
attribute, you can register and resolve multiple implementations of the same interface with object-based identifiers (including enums, strings, and integers).
LifetimeScope can dynamically create children. This allows you to deal with the asynchronous resource loading that often occurs in games.
public void LoadLevel()
{
// ... Loading some assets
// Create a child scope
instantScope = currentScope.CreateChild();
// Create a child scope with LifetimeScope prefab
instantScope = currentScope.CreateChildFromPrefab(lifetimeScopePrefab);
// Create a child with additional registration
instantScope = currentScope.CreateChildFromPrefab(
lifetimeScopePrefab,
builder =>
{
// Extra Registrations ...
});
instantScope = currentScope.CreateChild(builder =>
{
// ExtraRegistrations ...
});
instantScope = currentScope.CreateChild(extraInstaller);
}
public void UnloadLevel()
{
instantScope.Dispose();
}
In addition, you can create a parent-child relationship with LifetimeScope in an Additive scene.
class SceneLoader
{
readonly LifetimeScope currentScope;
public SceneLoader(LifetimeScope currentScope)
{
this.currentScope = currentScope; // Inject the LifetimeScope to which this class belongs
}
IEnumerator LoadSceneAsync()
{
// LifetimeScope generated in this block will be parented by `this.lifetimeScope`
using (LifetimeScope.EnqueueParent(currentScope))
{
// If this scene has a LifetimeScope, its parent will be `parent`.
var loading = SceneManager.LoadSceneAsync("...", LoadSceneMode.Additive);
while (!loading.isDone)
{
yield return null;
}
}
}
// UniTask example
async UniTask LoadSceneAsync()
{
using (LifetimeScope.EnqueueParent(parent))
{
await SceneManager.LoadSceneAsync("...", LoadSceneMode.Additive);
}
}
}
// LifetimeScopes generated during this block will be additionally Registered.
using (LifetimeScope.Enqueue(builder =>
{
// Register for the next scene not yet loaded
builder.RegisterInstance(extraInstance);
}))
{
// Loading the scene..
}
See scoping for more information.
public class FooController : IAsyncStartable
{
public async UniTask StartAsync(CancellationToken cancellation)
{
await LoadSomethingAsync(cancellation);
await ...
...
}
}
builder.RegisterEntryPoint<FooController>();
See integrations for more information.
See diagnostics for more information.
VContainer is inspired by:
MIT