05. Initializer

  03. Features No Comments

A major benefit of injecting dependencies to your classes through the Init method is that it makes it easy to decouple your components from specific implementations when you use interfaces instead of specific classes as your argument types. The list of arguments that the Init method accepts also makes it very clear what other objects the client objects depends on.

On the other hand, the ability to assign values using Unity’s inspector is also a very convenient and powerful way to hook up dependencies; you can completely change object behaviour without having to write a single line of code!

The Initializer system is a solution that aims to marry the best of both worlds.

Benefits Of Using Initializers

  • Single Responsibility Principle – They take on the responsibility of resolving the dependencies for your components, allowing your main components to focus on other things and follow the single-responsibility principle.
  • Using Non-Serializable Members – They take on the responsibility of handling serialization for all inspector-assigned values. This means you can use auto-implemented properties to your hearts content without having to add boiler-plate code for private backing fields with the SerializeField attribute. Even using read-only fields and get-only properties becomes possible.
  • Interface Support – With Initializers interfaces become first class citizens: you can drag and drop to assign interface type arguments, and they are serialized automatically as well (even if they derive from UnityEngine.Object).
  • Automatic Service Resolution – All Service dependencies are resolved automatically, so you don’t need to waste time dragging and dropping that same manager to dozens of fields. This also means that if you change a service to use a different implementation later on, all references to it across the entire project are updated automatically!
  • Locate Services – You can click the Service Tag on any service arguments in the Initializer’s inspector to locate the service in question wherever it’s located in the scene hierarchy or defined in a script asset.
  • Automatically Assign Initial Values – You can add the InitOnReset attribute on the Initializer to automate the component setup process without having to clutter the client component with these implementation details.
  • Clearly See All Dependencies – You can see all dependencies of a component easily the same way on both the code side and the Inspector side: just by looking at its Init section. No need to read through the whole code to locate all serialized fields and singleton references scattered around the code.
  • Edit Mode Null Guard – They can automatically warn you about missing references in edit mode.
  • Runtime Null Guard – They can automatically warn you about missing references at runtime.

Creating Initializers

The Init section

The easiest and recommended way to create an Initializer for a component is to have Init(args) generate it for you automatically.

When you have a component that derives from MonoBehaviour<T…> or implements IInitializable<T…>, an Init section will automatically appear at the top of the components of that type in the Inspector.

To generate an Initializer for the the component class, click on the + button in the Init section and select “Generate Initializer”.

This will cause a new Initializer class to get automatically generated for your component class. It will be saved at the same location where your component script is located and named the same as your component class but with the “Initializer” suffix added.

When your component class already has an Initializer generated, you can use this same + button to attach an instance of the Initializer class to your component instead.

Script Asset Context Menu

In some case you might want to generate an Initializer for a class but it either is not a component type, or doesn’t have an Init section in the Inspector yet.
For example you might want to generate an Initializer for a plain old class object which you will be attaching to a GameObject using a Wrappers.

In such cases you can select the script asset that defines the class in the Project view and select “Generate Initializer” from the context menu.

Creating Manually Using Code

It is also easy to create new Initializers in code by deriving from the Initializer<T…> base classes with the type of the client class and the types of its initialization arguments as the generic types.

For example, to define an Initializer for component Player, which derives from MonoBehaviour<IInputManager, Camera>, you would write the following:

public class PlayerInitializer : Initializer<Player, IInputManager, Camera> { }

To define an Initializer for wrapper PlayerComponent that wraps Player which accepts IInputManager and Camera in its constructor, you would write the following:

public class PlayerInitializer : Initializer<PlayerComponent, Player, IInputManager, Camera>
{
    protected override State CreateWrappedObject(IInputManager inputManager, Camera camera)
    {
        return new Player(inputManager, camera);
    }
}

PropertyAttributes And Initializers

Sometimes you may want to customize how the Initializer arguments appear in the inspector by adding PropertyAttributes to them.

To do this, you will need to add a private nested Init class inside the Initializer, and then define a field for each Init argument accepted by the Initializer’s client with their order ordering and types matching those of the arguments in the client’s Init method.

Any property attributes you attach to these fields will then get used when the corresponding initialization arguments are drawn in the Inspector.

public class PlayerInitializer : Initializer<Player, IInputManager, float>
{
   #if UNITY_EDITOR
   private class Init
   {
      public IInputManager inputManager;

      [Range(0f, 100)]
      public float speed;
   }
   #endif
}

Leave a Reply

Your email address will not be published. Required fields are marked *