08. Wrapper

  03. Features No Comments

The Wrapper class is a component that acts as a simple wrapper for a plain old class object.

It makes it easy to take a plain old class object and attach it to a GameObject and have it receive callbacks during any Unity events you care about such as Update or OnDestroy as well as to start coroutines running on the wrapper.

Let’s say you had a plain old class called Player:

public class Player { }

To create a wrapper component for the Player class, make a class that inherits from Wrapper<Player>.

public class PlayerComponent : Wrapper<Player> { }

This wrapper implements IInitializable<Player>, which means that a new instance can be initialized with the Player object passed as an argument using any of the various methods listed in the Creating Instances section.

For example, the Player can be attached to a GameObject using the following syntax:

Player player = new Player();
gameObject.AddComponent<PlayerComponent, Player>(player);

Unity Events

Wrapped objects can receive callbacks during select Unity events from their wrapper by implementing one of the following interfaces:

  1. IAwake – Receive callback during the MonoBehaviour.Awake event.
  2. IOnEnable – Receive callback during the MonoBehaviour.OnEnable event.
  3. IStart – Receive callback during the MonoBehaviour.Start event.
  4. IUpdate – Receive callback during the MonoBehaviour.Update event.
  5. IFixedUpdate – Receive callback during the MonoBehaviour.FixedUpdate event.
  6. ILateUpdate – Receive callback during the MonoBehaviour.LateUpdate event.
  7. IOnDisable – Receive callback during the MonoBehaviour.OnDisable event.
  8. IOnDestroy – Receive callback during the MonoBehaviour.OnDestroy event.

For example, to receive callbacks during the Update event the Player class would need to be modified like this:

public class Player : IUpdate
{
        public void Update(float deltaTime)
        {
                // Do something every frame
        }
}

Coroutines

Wrapped objects can also start and stop coroutines in their wrapper.

To gain this ability the wrapped object has to implement the ICoroutines interface.

public class Player : ICoroutines
{
        public ICoroutineRunner CoroutineRunner { get; set; }
}

The wrapper component gets automatically assigned to the CoroutineRunner property during its initialization phase.

You can start a coroutine from the wrapped object using CoroutineRunner.StartCoroutine.

public class Player : ICoroutines
{
        public ICoroutineRunner CoroutineRunner { get; set; }

        public void SayDelayed(string message)
        {
                CoroutineRunner.StartCoroutine(SayDelayedCoroutine(message));
        }

        IEnumerator SayDelayedCoroutine(string message)
        {
                yield return new WaitForSeconds(1f);

                Debug.Log(message);
        }
}

The started coroutine is tied to the lifetime of the GameObject just like it would be if you started the coroutine directly within a MonoBehaviour.

You can stop a coroutine that is running on the wrapper using CoroutineRunner.StopCoroutine or stop all coroutines that are running on it using CoroutineRunner.StopAllCoroutines.

public void OnDisable()
{
        CoroutineRunner.StopAllCoroutines();
}

Why Wrapped Objects?

The main benefit with using wrapped objects instead of a MonoBehaviour directly is that it can make unit testing the class easier.

You no longer subscribe to unity events via private magic functions hidden inside the body of the class, but have to explicitly implement an interface and expose a method for this. This makes it easier to invoke these functions in unit tests.

You also no longer need to think about scene management or creating GameObjects during unit testing which helps make it faster and easier to write reliable unit testing code.

Additionally the pattern that wrapped objects use to handle coroutines makes it easy to swap the coroutine runner class during unit tests, making it possible to even unit tests coroutines, for example with the help of the EditorCoroutineRunner class.

An additional benefit with using wrapped objects is that you can assign dependencies to read-only fields and properties in the constructor, which makes it possible to make your classes immutable. Having your wrapped objects be stateless can make your code less error-prone and makes it completely thread safe as well.

Leave a Reply

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