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 six 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 Six Dependencies

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

This can be done either using custom classes (recommended for better serialization support), or using tuples:

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

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);
new GameObject<SevenNumbers>().Init(numbers);

Leave a Reply

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