01. MonoBehaviour<T…>

  04. Features No Comments

Init(args) contains new generic versions of the MonoBehaviour base class, extending it with the ability to receive up to twelve objects during initialization.

For example the following Player class depends on an object that implements the IInputManager interface and an object of type Camera:

public class Player : MonoBehaviour<IInputManager, Camera>

When you create a component that inherits from one of the generic MonoBehaviour base classes, you’ll always also need to implement the Init function for receiving the initialization arguments.

public IInputManager InputManager { get; private set; }
public Camera Camera { get; private set; }

protected override void Init(InputManager inputManager, Camera camera)
{
   InputManager = inputManager;
   Camera = camera;
}

The Init function is called when the object is being initialized, before the Awake function.

Unlike the Awake function, Init always gets called even if the component exists on an inactive GameObject, so that the object will be able to receive its dependencies regardless of GameObject state.

OnAwake

Do not add an Awake function to classes that inherit from one of the generic MonoBehaviour classes, because the classes already define an Awake function. If you need to do something during the Awake event, override the OnAwake function instead.

OnReset

Do not add a Reset function to classes that inherit from one of the generic MonoBehaviour classes, because the classes already define a Reset function. If you need to do something during the Reset event, override the OnReset function instead.

Creating Instances

If you have a component of type Player that inherits from MonoBehaviour<IInputManager, Camera>, you can add the component to a GameObject and initialize it with an argument using the following syntax:

gameObject.AddComponent<Player, IInputManager, Camera>(inputManager, camera);

You can create a clone of a prefab that has the component attached to it using the following syntax:

prefab.Instantiate(inputManager, camera);

You can create a new GameObject and attach the component to it using the following syntax:

new GameObject<Player>().Init(inputManager, camera);

You can use the Inspector to specify the initialization arguments for your component by creating an Initializer for it and adding it to the same GameObject that contains your component.

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

In rare instances you might want to manually initialize an existing instance of the component without doing it through one of the pre-existing methods listed above. One example of a scenario where this might be useful is when using the Object Pool pattern to re-initialize pre-existing instances.

In order to manually call the Init function you must first cast the component instance to IInitializable<TArgument>.

var initializable = (IInitializable<TArgument>)component;
initializable.Init(argument);

Initialization Best Practices

It is generally recommended to only use the Init function to assign the received dependencies to variables, and then use OnAwake, OnEnable or Start for other initialization logic, such as calling other methods or starting coroutines.

There are a couple of different reasons for this recommendation:

  • Init can get called in edit mode for example for any classes that have the InitOnResetAttribute. As such calling other methods from the Init function could result in unwanted modifications to being done to your scenes or prefabs in edit mode.
  • Because Init can get called on inactive GameObjects it means that any calls to StartCoroutine will fail in this situation. As such starting coroutines is usually better to be done during the OnAwake event instead of in the Init function.
  • If a component uses the constructor to receive its Init arguments, the Init function can get executed in a background thread. Since most of Unity’s internal methods and properties are not thread safe, calling any of them from an Init function might be risky.

More Than Twelve Dependencies

Only a maximum of twelve arguments can be passed to MonoBehaviour<T…> objects. If you object requires more services than this, it is recommended to try and do some refactoring to reduce that number, for example, by splitting the class into two or more smaller classes. If this is not feasible, you can pass more than twelve services to your objects by wrapping multiple services inside one container object.

You can define a custom class for your service container (recommended for better readability), or use a tuple:

public class ThirteenNumbers : MonoBehaviour<(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m)>
{
	private int a, b, c, d, e, f, g, h, i, j, k, l, m;
	
	protected override void Init((int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m) args)
	{
		a = args.a;
		b = args.b;
		c = args.c;
		d = args.d;
		e = args.e;
		f = args.f;
                g = args.g;
                h = args.h;
                i = args.i;
                j = args.j;
                k = args.k;
                l = args.l;
                m = args.m;
	}
}

Then when initializing your object, instead of passing all the dependencies as separate arguments, you just pass the single container object.

var numbers = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);
new GameObject<ThirteenNumbers>().Init(numbers);

Leave a Reply

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