01. MonoBehaviour<T…>

  03. Features No Comments

Inity contains new generic versions of the MonoBehaviour base class, extending it with the ability to specify upto five objects that the class depends on.

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 arguments.

protected override void Init(IInputManager 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 TComponent that inherits from MonoBehaviour<TArgument>, you can add the component to a GameObject and initialize it with an argument using the following syntax:

gameObject.AddComponent<TComponent, TArgument>(argument);

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

prefab.Instantiate(argument);

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

new GameObject<TComponent>().Init(argument);

You can add the component to a scene or a prefab and use Unity’s inspector to specify the argument used to initialize the component by defining an Initializer for the component and adding one to the same GameObject.

public class TComponentInitializer : Initializer<TComponent, TArgument> { }

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 reuse 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 Five Dependencies

Only a maximum of five arguments can be passed to MonoBehaviour<T…> objects. In cases where you need to pass more than five dependencies to your objects you can do so by wrapping multiple dependencies inside one object.

This can be done either using custom data structures, or more conveniently, using tuples:

public class SixNumbers : MonoBehaviour<(int first, int second, int third, int fourth, int fifth, int sixth)>
{
	private int first;
	private int second;
	private int third;
	private int fourth;
	private int fifth;
	private int sixth;
	
	protected override void Init((int first, int second, int third, int fourth, int fifth, int sixth) args)
	{
		first = args.first;
		second = args.second;
		third = args.third;
		fourth = args.fourth;
		fifth = args.fifth;
		sixth = args.sixth;
	}
}

Then when initializing your object, instead of passing dependencies as separate arguments, you pass them as a single tuple type argument.

new GameObject<TComponent>().Init((1, 2, 3, 4, 5, 6));

 

Leave a Reply

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