Init(args) - A next-gen DI framework

Init(args)

Asset Store | Documentation | Scripting Reference | Video Tutorial

Init(args) is a toolset that makes it possible to pass arguments to Objects from the outside during initialization, with a great amount of flexibility.

Whether you want to pass arguments in code during instantiation, or drag-and-drop Object references into interface type fields across scenes in the Inspector, Init(args) will be there to help make it easy and painless.

The set of tools have been seamlessly integrated into Unity using inspector tooling and familiar-feeling extension methods, so that they are as intuitive to learn and use as possible.

Did you ever wish you could just call AddComponent with arguments like this:

Player player = gameObject.AddComponent<Player, IInputManager>(inputManager);

Or maybe you’ve sometimes wished you could Instantiate with arguments like so:

Player player = playerPrefab.Instantiate(inputManager);

Init(args) enables you to do just this: inject upto twelve arguments to any Object during their initialization - with a remarkable amount of flexibility on how to do it!

Main Features

  • Add Component with arguments.

  • Instantiate with arguments.

  • Create new GameObject with multiple components and arguments.

  • Create ScriptableObject with arguments.

  • Specify arguments in the Inspector - with full interface support and automatic null validation.

  • Service system - a cleaner and more flexible replacement for singletons.

  • Attach plain old C# objects to GameObjects via simple wrapper components.

  • Assign to read-only fields and properties.

  • Type safety thanks to use of generics.

  • Dependencies injected using pure interface and constructor injection: no costly reflection necessary!

  • Make all your code easily unit testable by default.

  • Write loosely coupled, reusable code that follows SOLID principles.

Invert Your Dependencies

To fully appreciate how Init(args) can help to significantly improve your code architecture, one needs to understand a key principle in software engineering: inversion of control.

Your scripts often rely on some other scripts to work properly; these are called their dependencies.
A common and straight-forward way to retrieve references to those other scripts in Unity is using the singleton pattern or similar approaches. For example consider the following script:

using UnityEngine;

public class Player : MonoBehaviour
{
   private void Update()
   {
      if(InputManager.Instance.Input.y > 0f)
      {
         float speed = 0.2f;
         float distance = Time.deltaTime * speed;
         transform.Translate(Camera.main.transform.forward * distance);
      }
   }
}

While using this method does accomplish the job of retrieving the instance, it also comes bundled with some negative side effects that may end up hurting you in the long run - especially so in larger projects.

  • It can cause the dependencies of a class to be hidden, scattered around the body of the class, instead of all of them being neatly defined in one centralized place and tied to the creation of the object. This can leave you guessing about what prerequisites need to be met before all the methods of a class can be safely called. So while you can always use GameObject.AddComponent() to create an instance of a Player, it’s not apparent that an InputManager component and a main camera might also need to exist somewhere in the scene for things to work. This hidden web of dependencies can result in order of execution related bugs popping up as your project increases in size.
  • It tends to make it close to impossible to write good unit tests; if the Player class depends on the InputManager class in specific, you can’t swap it with a simpler mock implementation that you can control during testing.
  • Tight coupling with specific classes can make it a major pain to refactor your code later. For example let’s say you wanted to switch all classes in your code base from using the old InputManager to a different one; you would need to modify all classes that referenced the old class, which could potentially mean changing code in hundreds of classes.
  • Tight coupling with specific classes also means less potential for modularity. For example you can’t as easily swap all your classes to using MobileInputManager on mobile platforms and PCInputManager on PC platforms. This limitation can lead to having bulky classes that handle a bunch of stuff instead of having lean modular classes that you can swap to fit the current situation.
  • Tight coupling with specific classes leads to less flexibility. If your code uses Logger.Instance to log messages to the Console, you can’t easily swap a single instance to use DebugLogger instead for logging more detailed information to the Console.
  • Tight coupling can also make it impossible to move classes from one project to another. Let’s say you start working on a new game and want to copy over the Camera system you spent many a month perfecting in your previous project. Well if your CameraController class references three other specific classes, and they all reference three other specific classes and so forth, that might leave you with no choice but to start over from scratch.

This is where inversion of control comes in: instead of your scripts resolving their dependencies with specific implementations on their own, they will work with whatever objects are provided to them from the outside.

Now let’s take a look at this code that achieves inversion of control with the help of Init(args):

using UnityEngine;
using Sisus.Init;

public class Player : MonoBehaviour<IInputManager, Camera>
{
    private IInputManager inputManager;
    private Camera camera;
 
    protected override void Init(IInputManager inputManager, Camera camera)
    {
        this.inputManager = inputManager;
        this.camera = camera;
    }

    private void Update()
    {
        if(inputManager.Input.y > 0f)
        {
            float speed = 0.2f;
            float distance = Time.deltaTime * speed;
            transform.Translate(camera.transform.forward * distance);
        }
    }
}

This small shift in how you architect your scripts does away with all of the aforementioned down sides with the previous example:

  • The Init function makes it very clear what other objects are needed for the Player class to function: an IInputManager and a Camera. Even if the Player class eventually grew to be 1000+ lines long, you could still find all the objects it depends on just by taking a look at the Init function, instead of having to read through the entire script.
  • The class no longer references the InputManager class directly, but instead communicates through the IInputManager interface. This makes it very easy to swap to using a different IInputManager implementation (for example during unit testing).
  • If you should go and modify the Player class in the future and introduce a new dependency to it, you will instantly get compile errors from all existing classes that are trying to create an instance of the Player class without providing this necessary dependency - this makes it really easy to find all the places you need to go modify to ensure the Player object gets set up properly in all places in your code base. If you were using the regular AddComponent without any arguments you would not have this safety net.

But then if you’re not using a singleton, how exactly do you determine what instance should be provided to the Player object?

An easy way to do this in code is to add the [Service] attribute to the class you want to use.

[Service(typeof(IInputManager), FindFromScene = true)]
public class InputManager : MonoBehaviour, IInputManager
{

This let’s Init(args) know that all components which require an IInputManager should be provided a reference to a single shared instance of InputManager from the scene.

All services are marked with a Service tag in the Inspector to make it easier for developers to understand how dependencies are wired together by looking at either the code or the Inspector.

A matching tag will also be shown in Init sections of components that accept an IInputManager argument, to indicate that the instance will be automatically provided, and there’s no need to manually drag-and-drop it in.

The Service tag can also be clicked to highlight the object that defines the service; in this case the InputManager script that contains the Service attribute. This further helps developers keep track of how dependencies are wired together.

Ping Definition

Whenever the need arises, you can easily swap a particular component to use some other Object instead of the shared service, simply by dragging and dropping another Object in.

Replacing a service with a mock during unit tests is also very easy.

using UnityEngine;
using NUnit.Framework;
using Sisus.Init;
using Sisus.Init.Testing;

public class TestPlayer
{
    private class FakeInputManager : IInputManager
    {
        public Vector2 Input { get; set; }
    }

    private FakeInputManager inputManager;
    private Player player;
    private Testable testable;

    [SetUp]
    public void Setup()
    {
        inputManager = new FakeInputManager();
        player = new GameObject<Player, Camera>().Init1(inputManager as IInputManager, Second.Component);
        testable = new Testable(player.gameObject);
    }

    [TearDown]
    public void TearDown() => testable.Destroy();

    [Test]
    public void LeftInput_Moves_Player_Left()
    {
        var startPosition = player.transform.position;
        inputManager.Input = Vector2.left;
        testable.Update();
        var endPosition = player.transform.position;
        Assert.AreEqual(startPosition.y, endPosition.y);
        Assert.Less(endPosition.x, startPosition.x);
    }
}

You can also turn any component into a service without writing any code - a feature which can be particularly useful when you don’t have access to the component’s source code.

Simply select Make Service Of Type… from a component’s context menu and pick the service type (class or interface type which determines the fields into which the service should be injected).

For more information about Init(args) head on over to the Asset Store page, refer to the Documentation or check out the Scripting Reference.

1 Like

Version 1.0.1 of Init(args) has been submitted to the Asset Store (should be live within a couple of days).

New features in the update:

ConstructorBehaviour<T…>

A new base class that can be used instead of MonoBehaviour<T…> with support for assigning received arguments into read-only fields and get-only properties without the need for any reflection to be used!

To do this derive from a ConstructorBehaviour<T…> base class and define a parameterless constructor which receives the initialization arguments from the constructor in the base class via its out parameters.

using UnityEngine;
using Sisus.Init;

public class Player : ConstructorBehaviour<IInputManager, Camera>
{
    private readonly IInputManager inputManager;

    public Camera Camera { get; }

    public Player() : base(out IInputManager inputManager, out Camera camera)
    {
        this.inputManager = inputManager;
        Camera = camera;
    }
}

While with MonoBehaviour<T…> providing arguments during initialization is optional, with ConstructorBehaviour<T…> this is mandatory; an exception will be thrown if an instance is instantiated without the arguments it requires. The idea behind this is to mimic the way constructors work with plain old C# objects, allowing you to restrict object creation to only be allowed when all the required dependencies are provided.

// This raises an exception:
var player = gameObject.AddComponent<Player>();
// This works:
var inputManager = Service<IInputManager>.Instance;
var camera = Camera.main;
var player = gameObject.AddComponent<Player, IInputManager, Camera>(inputManager, camera);

This new base class is mainly intended to be used for objects that are dynamically instantiated at runtime using AddComponent(args) or Instantiate(args), in which case the created instance is able to receive the arguments passed to the method in its constructor.

It is also possible to add ConstructorBehaviours directly into a scene in the Editor and provide arguments for them using Initializer components, just like you can with MonoBehaviour<T…>. However, in this situation the arguments can not be assigned through the constructor and have to instead be passed in through the Init method and assigned to the target fields using reflection. This means that performance isn’t as optimal as it would be when no reflection is involved. However this whole process happens automatically behind the scenes, and unless you have a very large number of ConstructorBehaviours in your scenes this probably isn’t a big deal.

Another thing to be aware of with ConstructorBehaviours is that the constructor gets called before Unity’s deserialization process takes place. This means that if you instantiate a ConstructorBehaviour from a prefab and assign any arguments into serialized fields, those will get overridden when the deserialization takes place. Because of this it is recommended to only assign arguments into non-serialized fields or properties in the constructor.
That notwithstanding, assigning arguments into serialized fields in ConstructorBehaviours does still work; the ConstructorBehaviour base class will detect when this happens and automatically handle re-injecting the arguments into the fields once the deserialization process has finished. However this injection also utilizes reflection, so it’s still better to avoid it when you can.

Null / NullOrInactive

Init(args) has been designed with inversion of control in mind and aims to make it as easy as possible to work with interfaces instead of specific classes in Unity.

One pain point when using interfaces in Unity is that checking them for null can be problematic. This is because in addition to being actually null, Objects in Unity can also be destroyed. You usually don’t even notice the difference when working with specific classes directly, because the Object class has a custom == operator which internally also checks whether the Object has been destroyed. However when an Object has been assigned to an interface type variable and this is compared against null, the overloaded == operator does not get used. This can result in null reference exceptions when trying to access the members of a destroyed Object.

Player player = FindObjectOfType<Player>();
IPlayer iplayer = player as IPlayer;

Destroy(player);

Debug.Log(player == null);  // Prints true
Debug.Log(iplayer == null); // Prints false

To help with this problem in version 1.0.1 a new property has been added into every base class in Init(args): Null. When an interface type variable is compared against this Null property it functions just like the overloaded == operator in the Object class and returns true when the instance has been destroyed.

Player player = FindObjectOfType<Player>();
IPlayer iplayer = player as IPlayer;

Destroy(player);

Debug.Log(player == null);  // Prints true
Debug.Log(iplayer == null); // Prints false
Debug.Log(iplayer == Null); // Prints true

It is also possible to utilize this safe null checking inside classes that don’t derive from any of the base classes in Init(args) by importing the static members of the NullExtensions class.

using UnityEngine;
using static Sisus.NullExtensions;

public static class PlainOldClass
{
    public static void Example()
    {
        Player player = Object.FindObjectOfType<Player>();
        IPlayer iplayer = player as IPlayer;

        Object.Destroy(player);

        Debug.Log(player == null);  // Prints true
        Debug.Log(iplayer == null); // Prints false
        Debug.Log(iplayer == Null); // Prints true
    }
}

In addition to the Null property there’s also a new NullOrInactive property. This functions identically to the Null property but also checks if the Object is a component on an inactive GameObject.

Player player = FindObjectOfType<Player>();
IPlayer iplayer = player as IPlayer;

player.gameObject.SetActive(false);

Debug.Log(player.gameObject.activeInHierarchy); // Prints true
Debug.Log(iplayer == NullOrInactive); // Prints true

This functionality can be particularly useful when utilizing Object Pooling and an inactive GameObject is meant to represent an object which doesn’t exist.

Hi Timo,

Hope you’re well. I’ve sent this question on the support form, but I’ll sent here as I can show more precisely the problem with prints.

I’m also using Corgi Game engine(and other plugins), and Corgi relies a lot on singleton MonoBehaviors to add to hierarchy. I’ve been having problems adding the arguments to the classes, as it appears the following warnings**(I send in the image)**. I’ve used the demo scene as reference, but I can’t add the dependencies the way they’re added in the scene idk why

But my main question is, besides adding a singleton dependency, how can I add them to a script from hierarchy? Because I need the settings that are set up on hierarchy and I would like to drag them to the dependencies that way.

Thank you in advance!

Eduardo BagarrĂŁo

Hey @BagarraoEduardo_1

The warning message seems to have happened from trying to drag the Script asset of the LevelManager to the Initializer field, when it is expecting a Component instance from the Hierarchy instead.

If the LevelManager component is only created at runtime you can create an IValueProvider component for the Object and drag that to the Initializer field instance:

using Sisus.Init;
using UnityEngine;
using MoreMountains.CorgiEngine;

public class LevelManagerWeakReference : MonoBehaviour, IValueProvider<LevelManager>
{
    public LevelManager Value => LevelManager.Instance;
}

You could even combine multiple IValueProvider implementations in just one component if you’d prefer that:

using Sisus.Init;
using UnityEngine;
using MoreMountains.CorgiEngine;
public class CorgiServiceProvider : MonoBehaviour, IValueProvider<GameManager>, IValueProvider<LevelManager>, IValueProvider<GUIManager>, IValueProvider<InputManager>
{
    GameManager IValueProvider<GameManager>.Value => GameManager.Instance;
    LevelManager IValueProvider<LevelManager>.Value => LevelManager.Instance;
    GUIManager IValueProvider<GUIManager>.Value => GUIManager.Instance;
    InputManager IValueProvider<InputManager>.Value => InputManager.Instance;
}

If the components already exist in the same scene in edit mode you should be able to just drag-and-drop them directly to the Object field.

UPDATE: I tested it, and it seems like dragging a component into an Initializers from another GameObject doesn’t currently work as expected. It tries to assign the dragged GameObject itself to the field instead of its components, and because of this thinks it’s of incompatible type. This is probably what is causing your issues. I will improve this in the next update.

Until the next update the only way to drag a Component from another GameObject is by opening two inspector windows , locking one of them to display the Initializer and the other one to display the Level Manager and then dragging the Level Manager component from one Inspector to the other.

1 Like

Init(args) version 1.0.2 is now live in the Asset Store.

Bug fixes in the update:

  • Fixed issue with Initializers / Any not being assigned a component value from a GameObject which was drag-and-dropped to them using the Inspector, even if the GameObject contained a component that was of an assignable type.

  • Fixed issue with Initializers / Any not accepting UnityEngine.Object references that implement IValueProvider when they were drag-and-dropped to them using the Inspector.

New features in the update:

Inspector Runtime State

All base classes in Init(args), such as MonoBehaviour<T…>, now have a Custom Editor that visualizes all non-serialized fields of the Object in the Inspector in Play Mode.

This makes it possible to see the state of read-only fields and properties assigned to in the constructor of a ConstructorBehaviour<T…>.

It also makes it possible to see the fields of a plain old class object that is wrapped by a Wrapper component even in cases where the wrapped object doesn’t have the Serializable attribute.

Confirmed that the update solved the issue that I was having. Thank you for the help! :wink:

1 Like

Bit of impulse buy here maybe more examples tutorials to get into this method of doing things

anyway Windows Build errors…

Assets\Sisus\Init(args)\Scripts\Initializer\InitializerT1.cs(6,15): error CS0246: The type or namespace name 'EditorOnly' could not be found (are you missing a using directive or an assembly reference?)

Assets\Sisus\Init(args)\Scripts\Initializer\InitializerUtility.cs(17,15): error CS0246: The type or namespace name 'EditorOnly' could not be found (are you missing a using directive or an assembly reference?)

Assets\Sisus\Init(args)\Scripts\Initializer\WrapperInitializerT1.cs(8,15): error CS0246: The type or namespace name 'EditorOnly' could not be found (are you missing a using directive or an assembly reference?)

Assets\Sisus\Init(args)\Scripts\Utility\InitArgs.cs(11,15): error CS0246: The type or namespace name 'EditorOnly' could not be found (are you missing a using directive or an assembly reference?)
UnityEditor.BuildPlayerWindow+BuildMethodException: 13 errors
  at UnityEditor.BuildPlayerWindow+DefaultBuildMethods.BuildPlayer (UnityEditor.BuildPlayerOptions options) [0x002ca] in <2cd7ebc9c2ef4276a8edbc7de85c89ce>:0 

  at UnityEditor.BuildPlayerWindow.CallBuildMethods (System.Boolean askForBuildLocation, UnityEditor.BuildOptions defaultBuildOptions) [0x00080] in <2cd7ebc9c2ef4276a8edbc7de85c89ce>:0
UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)

@Slashbot64 Thank you for letting me know about the build errors, I’ll fix these and submit an update asap!

I recommend taking a look at the included Demo scene to get a better feel about how one can use Init(args).
But adding some more written examples is also a good idea :slight_smile:

An update that fixes the build errors has been submitted to the asset store for review.

You can contact me if you need to get the fixed package before it has gone through the asset store review process (this typically takes a day or a few).

UPDATE: The update is now live.

Version 1.0.3 of Init(args) is now out. Improvements in this update include:

  • Added new Services component. These can be attached to any Scene object or prefab and used to define a collection of services straight through the Inspector. Clients that can use the services can be filtered based on where they are in the scene hierarchy relative to the Services component. This component can be useful for defining localized services (e.g. only available inside a certain scene) as well as dynamic services (e.g. only available when a menu is open).
  • Services are now automatically initialized in such and order that services that are needed by other services get initialized before the services that need them! This can help avoid execution order related NullReferenceExceptions when trying to access services inside other services.
  • Added ability to control Initializer null argument validation in a more granular way, and improved edit mode validation in general to get rid of potentially false warnings.
  • Improved Initializer editor Type popup fields. They now have a search box for filtering the listed types and can display any number of types, which means things like System.Object fields are much better supported.
  • Added ability to automatically generate Initializers and Wrappers for classes from the MonoScript context menu.

Let me give a practical example of how the new Services component can be used to provide Init arguments to your components and help completely decouple all your components from each other.

In this example we will create a system consisting of the following components:

  • UIManager component which can be asked to show a Dialog to the user.

  • Dialog component which handles passing the dialog text to the UI and invoking a callback if its Confirm method gets called.

  • DialogButton component which handles calling Confirm on the Dialog if the user clicks a button.

To start lets define the interfaces which our components can use to communicate with each other without knowing anything about each others’ concrete classes:

using System;

public interface IUIManager
{
    bool IsDialogOpen { get; }
    void ShowDialog(string text, Action onConfirmed);
}

public interface IDialog
{
    bool IsOpen { get; }

    void Show(string text, Action onConfirmed);
    void Confirm();
    void Cancel();
}

Then let’s write the actual code for the components.

We will make use of the MonoBehaviour<T…> base class in Init(args) to allow them to receive arguments during initialization.

using Sisus.Init;
using System;

public class UIManager : MonoBehaviour<IDialog>, IUIManager
{
    private IDialog dialog;

    public bool IsDialogOpen => dialog != Null && dialog.IsOpen;

    protected override void Init(IDialog dialog) => this.dialog = dialog;

    protected override void OnAwake() => CloseDialog();

    public void ShowDialog(string text, Action onConfirmed) => dialog.Show(text, onConfirmed);

    public bool CloseDialog()
    {
        if(IsDialogOpen)
        {
            dialog.Cancel();
            return true;
        }

        return false;
    }
}
using Sisus.Init;
using System;

public class Dialog : MonoBehaviour<IText>, IDialog
{
    private IText text;
    private Action onConfirmed;

    public bool IsOpen => gameObject.activeInHierarchy;

    protected override void Init(IText text) => this.text = text;

    public void Show(string text, Action onConfirmed)
    {
        this.onConfirmed = onConfirmed;
        this.text.Content = text;
        gameObject.SetActive(true);
    }

    public void Confirm()
    {
        onConfirmed?.Invoke();
        gameObject.SetActive(false);
    }

    public void Cancel() => gameObject.SetActive(false);
}
using UnityEngine.UI;
using Sisus.Init;
using UnityEngine;

public class DialogButton : MonoBehaviour<IDialog, Button, DialogButtonType>
{
    private IDialog dialog;
    private Button button;
    private DialogButtonType type;

    protected override void Init(IDialog dialog, Button button, DialogButtonType type)
    {
        this.type = type;
        this.dialog = dialog;
        this.button = button;
    }

    private void OnEnable() => button.onClick.AddListener(OnClicked);
    private void OnDisable() => button.onClick.RemoveListener(OnClicked);

    private void OnClicked()
    {
        switch(type)
        {
            case DialogButtonType.Cancel:
                dialog.Cancel();
                return;
            case DialogButtonType.Confirm:
                dialog.Confirm();
                return;
            default:
                Debug.LogError($"Unrecognized ButtonType: {type}.", this);
                return;
        }
    }
}

We will also need to create an Initializer to pass all Init arguments for the DialogButton. It will not receive all its arguments automatically without an Initializer because it needs two arguments which we will not be setting up as services: Button and DialogButtonType.

using Sisus.Init;
using UnityEngine.UI;

public sealed class DialogButtonInitializer : Initializer<DialogButton, IDialog, Button, DialogButtonType> { }

Now we can build a menu system out of these components.

We will use the Services component to turn the UIManager component into a service of type IUIManager.
We will configure the Services component to allow all GameObjects nested under the UIManager to access the service.

7813233--988230--services-uimanager.png

On the GameObject containing the Dialog component will we add two Services components.

The first one we use turn the Dialog into a service which all GameObjects nested underneath the root UI GameObject can access via the IDialog interface.

The second one we only use to provide the Init argument for the Dialog component: the IText component. This is just a simple wrapper for a component responsible for showing the text on the UI, such as UnityEngine.Text or TextMeshProUGUI.
We configure For Clients to In Game Object so that the IText service is only access components in this same GameObject.
(Note that it would probably actually make more sense to just use an Initializer to provide the IText argument to the Dialog component, but I wanted to demonstrate how the Services component can be used to provide services confined within a single GameObject)

7813233--988236--services-dialog.png

And finally we will add the DialogButton as a child of the Dialog and use the Initializer we created earlier to specify the Button and DialogButtonType Init arguments that will be used to initialize the component.
The Initializer automatically detects that IDialog is a service we can use and lets us know we don’t need to specify it manually by just displaying Service in the Inspector next to that parameter’s name.

7813233--988233--services-button.png

Now we have a fully functioning UI system in place that can be used to display dialogs containing any text to the user and performing any actions when the user gives their confirmation!

Here is a simple example component that uses this system to display a dialog to the user and invoke an UnityEvent if the user clicks the Confirm button:

using Sisus.Init;
using UnityEngine;
using UnityEngine.Events;

public class ShowDialog : MonoBehaviour<IUIManager>
{
    [SerializeField]
    private string text = "";

    [SerializeField]
    private UnityEvent onConfirmed = new UnityEvent();

    private IUIManager uiManager;

    protected override void Init(IUIManager uiManager) => this.uiManager = uiManager;

    public void Show() => uiManager.ShowDialog(text, onConfirmed.Invoke);
}

Here’s our menu system in use:
7813233--988254--services-result.gif

Version 1.0.4 of Init(args) is out now.

New features added:

Lazy Initialization
Support was added for lazy initialization of classes decorated with the ServiceAttribute.

If you set LazyInit = true then an instance of the service will not be created when the game starts like usually, but this will be delayed to only take place when the first client requests an instance.

This makes it possible to use the ServiceAttribute with FindFromScene load method, even in cases where the service component doesn’t exist in the first scene of the game when the game is loaded.

In games with lots of services, lazy initialization can also be used to reduce the initial loading time of the game by spreading loading to happen over a longer period of time.

Lazy initialization was implemented using the static constructor feature in C# which means it is guaranteed to only happen once per service type and to be fully thread safe. It also means that subsequently clients can retrieve instances of a service through a simple auto-implemented property without any additional overhead being introduced with reference fetching always happening through something like Lazy.Value.

Wrapped Services
Additionally it is now possible to add the ServiceAttribute to a Wrapper component or asset, and set the service type to the type of the wrapped object (or any interface it implements), instead of the wrapper itself.

When using ServiceAttribute with load methods such as FindFromScene or ResourcePath the service initialization system is now smart enough to recognize this pattern and can retrieve the service instance through the Wrapper.WrappedObject property of the wrapper.

This makes it possible to have plain old class object services while still allowing designers to easily configure their values through the Inspector.

It also makes it possible to register plain old class objects as services while keeping them decoupled from the ServiceAttribute.

Initializer Services
Similarly it is now also possible to add the ServiceAttribute to an Initializer, and set the service type to the type of the initialized Object (or any interface it implements), instead of the Initializer itself.

When using ServiceAttribute with the load method FindFromScene, the service initialization system is now smart enough to handle this scenario. It can find the Initializer for the service from the scene, trigger the Initializer to immediately initialize the object, and then register the initialized Object as a service.

This method or registering Services can be used to decouple your MonoBehaviour services from the ServiceAttribute.

Hey Sisus, hope you’re doing fine!

I’m using this plugin on a project that is running on Unity 2020.3.26f1, but it gives the following error when building the game:

However, when I remove the plugin the project builds without problem. Do you know what it can be? I’m using Mac OS and it’s set to IL2CPP build with .NET Standard 2.0.

Thank you so much in advance!

Best Regards,
Eduardo BagarrĂŁo

Hey @BagarraoEduardo_1 , thank you for letting me know about this!

It looks like some changes I added in the last update are probably missing a vital using directive outside of the editor platform.

I’ll investigate and get this fixed today.

Hello @SisusCo ,

Thank you for the fast answer! Looking forward for the upcoming fix :wink:

Best regards,
Eduardo BagarrĂŁo

@BagarraoEduardo_1 Builds errors are fixed now; I sent you a PM with the updated package.

I’ll also submit the fix to asset store for review during the weekend, so it should become available for download from there some time next week.

Hello Sisus,

It’s working perfectly now! Thank you so much once again!

Best Regards,
Eduardo BagarrĂŁo

1 Like

Version 1.0.5 is now out.

The main improvement in this version is a major overhaul of the inspector UX of Initializers.

Initializers can be used to specify the arguments that are passed to the Init function of a component.
Using Initializers to set up components instead of just exposing serialized fields can offer a bunch of benefits:

  • Initializers can expose and serialize interface arguments (even if the implementing class derives from UnityEngine.Object).
  • Initializers can warn you about missing references at runtime.
  • Initializers can warn you about missing references in edit mode.
  • Initializers allow you to use properties and read-only fields on the client component since Unity doesn’t need to be able to serialize them.
  • Initializers retrieve all Service dependencies for you automatically, so you don’t need to waste time dragging in the same references or worry about missing references if you make changes to your manager components.
  • You can add the InitOnReset attribute on the Initializer to automate the component setup process even further without having to clutter the client component with these implementation details.
  • You can see all dependencies of a component just by looking at its Init function (both on the code side and the Inspector side), instead of having to locate all its serialized fields.

With the latest update working with Initializers is now a much smoother experience.

7886779--1003558--generate-initializer.gif

You will now see a collapsible Init section at the top of any components that accept initialization arguments.

You no longer have to write any code to create an initializer for your components; simply press the + button inside the Init section and select “Generate Initializer”, and the class will be generated for you. This means you can now get all the benefits of using Initializers with basically zero extra work.

If an Initializer class already exists for the component, then the + button can be used to add an Initializer for the component instead.

Once you have added an Initializer for a component you will see a new Null Argument Guard icon on the right side of the Init section. You can customize which null guards are active for the component by clicking the Null Argument Guard icon. By default you will get warnings in your Console if any null arguments are detected on your components in edit mode and at runtime at the moment the initialization takes place.

The Null Argument Guard can be in four different states that are communicated through its icon:

  • Grey icon with a line crossed over it means that null guards are not active.

  • Yellow icon with an exclamation mark means that the component contains null arguments.

  • Blue icon with a check mark means the component contains no null arguments and only some null guards are enabled (for example, edit mode warnings are enabled but runtime exceptions are disabled).

  • Green icon with a check mark means the component contains no null arguments and all null guards are enabled.

Hey Timo!

Hope you’re doing well. I’ve updated to the v1.0.5 and now there are appearing some errors on the project:

Do you know that it can be?

  • MacOS 12.2
  • Unity 2020.3.26f1
  • Metal Graphics API

Thank you in advance!

Best Regards,
Eduardo BagarrĂŁo

Hey @BagarraoEduardo_1

Reinstalling the demo using the ‘Demo Installer’ package should fix the issue.

I had to make a few small changes to the IInitializer interface in order to be able to pull off all improvements I wanted to add in version 1.0.5 and I forgot that the demo scene had a class that manually implemented the interface instead of deriving from one of the base classes.

Sorry for the inconvenience!

1 Like