Init(args)

What is Init(args)?


Init(args) is a dependency injection framework for Unity, built with a keen focus on ensuring all aspects of the toolset blend in seamlessly with the native editor experience.


What problems does Init(args) solve?


The Unity Editor's scene-based workflow is a powerful tool, and the ability to drag-and-drop references to fields in the Inspector is already a great foundation for injecting dependencies to Objects! As such, the aim with Init(args) has not been to reinvent the wheel on this front, but rather to fix various shortcomings it has and unlock its full potential.


In vanilla Unity, all components are responsible for finding references to the objects they depend on. One way to accomplish this is by adding serialized fields, which enables users to assign references using the Inspector. As cool as this is, it has its limitations, and can't be used in every situation:


  1. Can't assign Objects to interface fields.
  2. Can't assign Objects across scenes or prefabs.
  3. Dragging the same managers to components again and again is laborious.
  4. To swap a manager to a different one you have to manually update references in all components.
  5. Can't assign dynamic values, which makes working with things like localized texts, addressable assets and randomized values more difficult.

Due to these limitations, components often have to combine the usage of serialized fields with other means of resolving dependencies: singletons, GetComponent, GetComponentInChildren, FindObjectOfType, FindWithTag, static methods... and so on. This hodgepodge way of fetching the dependencies of components also has its own problems:


  1. It can add a lot of unnecessary noise to your components' code.
  2. Dependencies can be hidden anywhere within the code.
  3. Dependencies are usually hardwired to specific classes.
  4. Dependencies are located using hardwired search methods.

And even where you are able to piece together a relatively dignified component using some combination of these methods, it will still most likely be virtually impossible to write any unit tests for it!


Init(args) can resolve all of these issues for you.


Keeping It Simple


With Init(args) you won't have to spend large amounts of time learning about perplexing concepts, like bindings, containers, decorators or contexts... in fact, you can use just the Inspector to fulfill most of your dependency injection needs! - but with so much more flexibility than ever before.


And what about when you do want to initialize objects with arguments in code? Here as well, you can make use of all the commands you are already familiar with, like AddComponent and Instantiate - just with the added ability to pass in some arguments.


This small but significant change means that your components are no longer closed islands, black boxes with hidden dependencies; when you instantiate a component with its dependencies, you can feel assured that it will work. This also means that writing unit testing for your components becomes so frictionless and easy, that creating them can become downright addictive!


Pure Dependency Injection


The main focus of Init(args) is on enabling pure dependency injection, both with the Inspector for scene objects and prefabs, as well as in code. What this means is that reflection is used as sparingly as possible, and instead generic methods and interfaces are used to provide a strongly typed pathways through which dependencies can be delivered.


This approach has various benefits, such as solid performance compared to reflection-based solutions, and user errors being caught as early as possible at compile-time - and this fail-fast design is also in effect on the Editor side, with warnings being logged to the Console about any missing arguments detected in your scenes in edit mode.


The use of pure dependency injection can also help a lot with the legibility of your project; dependencies between objects are not obfuscated, and you can easily follow the trail of dependency breadcrumbs both in your IDE and in the Editor - a vital ability to have when debugging your application.


Just a Pinch of Magic Sprinkled On Top...


In addition to Init(args) offering all the tools you'll ever need for pure dependency injection in Unity, there's also a powerful system that can be used to, as if by magic, create shared instances of select classes and automatically deliver them to clients everywhere (or, if you so choose, limited to certain scenes or transform hierarchies).


These shared instances are called "Services", and you can create a new one from any class, just by adding the [Service] attribute to it. You can think of services like an enhanced version of singletons.


  1. Even easier to create (just add an attribute).
  2. Automatically delivered to all client components.
  3. Services can easily make use of other services (without execution order issues).
  4. Service arguments can be overridden for individual components when needed.
  5. Supports loading from Addressables, Resources folders and scenes.
  6. Services can be replaced with mocks during unit tests.

The service system makes use of reflection, but only during initialization, after which accessing cached services is very fast.


Inspector Features:


  1. Full interface support - with an intuitive drag-and-drop and dropdown based Inspector UI.
  2. Value providers - dynamic arguments, such as randomized values, or references to runtime objects.
  3. Null Argument Guard - Get warnings about missing arguments - in edit mode and at runtime.
  4. Cross-Scene References - Assign Objects to fields from other scenes.
  5. Service tag - Turn any component into a Service from the context menu.

Code Features:


  1. [Service] attribute - Easily create globally shared services that are automatically injected to clients.
  2. AddComponent with arguments.
  3. Instantiate with arguments.
  4. new GameObject with components and arguments.
  5. Smart Execution Order - Objects are initialized in order based on their dependencies to avoid execution order related issues.
  6. Null / NullOrInactive - easily test if interface type variable contains a destroyed Object.
  7. Wrappers - Attach plain old C# objects to GameObjects.
  8. Read-Only Support - Assign to read-only fields and properties during initialization.

Also Included: Demo scene with a fully functional example game!



Links

Forum - Have any questions or ideas for new features? Discuss here.

Documentation - Online Documentation for Init(args).

Scripting Reference - Descriptions of all classes and class members in Init(args).