Design Patterns Part 1: Unserialisable Pure Class

There are multiple ways to serialise a class in unity. Today we explore the design pattern of serialising a class by “Assumption”. We avoid storing any unnecessary data and rebuild the instance from scratch in OnEnable().

unserialised class
What Inspector Shows
Untitled
What Unity Serializes

Next Part Of Series: Part 2: Full Serialisable

All References are set up OnEnable() method, that way when assembly reload happens the unserializable instances (= assumption) would be rebuild from start.

public class DesignPatternUnserializableClass : MonoBehaviour
{
   public float Speed;
   public bool IgnoreForces;
   public uMovementDirection Move;
   public uMoveConstantVelocity Velocity;

   private void OnEnable()
   {
      Move = new uMovementDirection(){Transform = transform};
      Velocity = new uMoveConstantVelocity(){rb = GetComponent<Rigidbody>(), IgnoreForces=IgnoreForces};
   }
}

Here we construct the instances using object initializer syntax.

One Drawback of this pattern is redudancy. Here variable IgnoreForces is declared twice. Once in the monobehavior who handles its serialisation and once in the unserialised class. This school of thought treats a monobehaviour as a “Main” class that handles and set ups all needed code before it starts. Therefore the role of Monobehaviour here is to act as “Glue Code” to combine reusable classes together.

public class uMoveConstantVelocity
{
   public Rigidbody rb;
   public bool IgnoreForces;
}

You would ask why do that? Why not place everything inside a Monobehaviour class like 99% of the unity community does. Because… That way a Monobehaviour becomes a God Class, a class that has more than one responsibilities. The problem with that is that soon, a code fragment inside that monobehavior could become un-reusable. Those code fragments “know too much” they have full access to monobehaviour instance, and cannot be transfered to another monobehaviour (which may belong to another game object) without refering to original monobehaviour. What if i later decide to use my Pure class on a scriptable object? you cant.

One benefit of Unserialisable pure classes is that they don’t care about serialisation. Do what the hell you want with them, as ugly as it sounds, use as many interfaces you want. You push the responsibility of serialisation up, to the calling monobehaviour. If you have a really complex Pure class that many monobehaviours reuse then this design pattern fits.

 public class uMoveConstantVelocity
 {
    public void Move(Vector3 direction)
    {
    }
 }

You would ask why did i choose to pass direction through a method instead storing it as a class variable? Simple. It is not uMoveConstantVelocity’s Responsibility to generate the direction vector, the caller will decide which direction to move. If i had stored it as a class variable, i would have to modify the variable before method was called (=> needless runtime memory cost).

What would i have done if that method needed 4 or more arguments? I would have asked the caller to provide me interface that allows me to generate the arguments or get them through a property accessor. Interfaces do not have a “construction” cost, as they do not store an instance inside it. They only expose a set of methods.

Full Source Code:

namespace Disanity.Test
{
    using UnityEngine;

    /// Requirement: Each Type of Field, Known ahead of time.
    ///
    /// <para/>Pros:
    /// 1) Faster to Initialise
    /// 2) Flat serialisation structure (simple var on monobehavior)
    ///
    /// <para/>Cons:
    /// 1) Ugly OnEnable method.
    /// 2) Redudancy of variables: Monobehaviour vs Corresponding Classes (it is still faster).
    ///
    public class DesignPatternUnserializableClass : MonoBehaviour
    {
        public float Speed;
        public bool IgnoreForces;
        public uMovementDirection Move;
        public uMoveConstantVelocity Velocity;

        private void OnEnable()
        {
            Move = new uMovementDirection(){Transform = transform};
            Velocity = new uMoveConstantVelocity(){rb = GetComponent<Rigidbody>(), IgnoreForces=IgnoreForces};
        }

        private void FixedUpdate()
        {
            var direction = Move.Direction() * Speed;
            //if (direction.magnitude > 0) Debug.Log("Move Direction: " + direction);
            Velocity.Move(direction);
        }
    }

    public class uMovementDirection
    {
        public Transform Transform;

        public Vector3 Direction()
        {
            //read movement input
            float h = Input.GetAxis("Horizontal");
            float v = Input.GetAxis("Vertical");

            var movementDirection = v * Transform.forward + h * Transform.right;
            Util.NormaliseIfAboveOne(ref movementDirection);
            return movementDirection;
        }
    }

    public class uMoveConstantVelocity
    {
        public Rigidbody rb;
        public bool IgnoreForces;

        /// Used to track changes to current velocity
        private Vector3 _oldVelocity;

        public void Move(Vector3 direction)
        {
            if (rb.isKinematic)
            {
                rb.MovePosition(rb.position + direction);
            }
            else
            {
                if (IgnoreForces)
                {
                    rb.velocity = direction;
                }
                else
                {
                    var newSpeed = rb.velocity - _oldVelocity;
                    rb.velocity = newSpeed + direction;
                    _oldVelocity = direction;
                }
            }
        }
    }
}
Advertisements

2 thoughts on “Design Patterns Part 1: Unserialisable Pure Class

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s