Learn to command

I wanted to share the wonderful development process that I recently met. I have not seen such an approach before, and people, as soon as they get acquainted with it, for a long time can not understand and accept this way of building games. And, to be honest, I myself did not understand everything in the first week. But after some mastering, I already forgot how to make games differently. The plans are to write a series of articles, but let's start small and gradually we will increase our understanding of what and why and with what it is.

As some might already have guessed, today I will talk about the “Command” pattern and how to use it to develop games using the Unity 3D engine. This is one of the key patterns in this approach. The code will be simplified, but working and should give an understanding of the process.

Prologue


Have you probably ever seen articles in which developers talk about how to use Actor's in Unity? If not, then I’ll quickly explain the point using an example: in your game there are a dozen game characters who must, for example, jump in different ways. Of course, the problem can be solved through everyone’s favorite polymorphism: make a basic unit and simply overload the virtual Jump method for each unit.

Something like this
public class UnitController : MonoBehaviour 
{
    public Rigidbody AttachedRigidbody;
    //...
    public virtual void Jump()
    {
	rigidbody.velocity = new Vector3 (0, 10, 0);
    }
    //...
}

public class RabitUnitController : UnitController 
{
    //...
    public override void Jump ()
    {
        //very high jump
    }
    //...
}


But in this case, if you want several ready-made units that jump in different ways to jump the same way, you will either have to slightly correct the class hierarchy, or just copy-paste the appropriate piece of code into all the classes you need (which is horrific).

With the help of Actor, this task is solved differently. Using this approach ^ you would also need to write a unit class, only now instead of the virtual Jump method, write a series of individual UnitJumper components and just hook the appropriate component to the correct unit. And at the time of the jump, call the Jump method on the attached component:

Actor code
public class UnitJumper : MonoBehaviour 
{
    public virtual void Jump(Rigidbody rigidbody)
    {}
}
public class RegularJumper : UnitJumper 
{
    public override void Jump (Rigidbody rigidbody)
    {
        base.Jump (rigidbody);
        rigidbody.velocity = new Vector3 (0, 10, 0);
    }
}
public class MajesticAFJumper : UnitJumper 
{
    public override void Jump (Rigidbody rigidbody)
    {
        base.Jump (rigidbody);
        rigidbody.velocity = new Vector3 (0, 15, 10);
        /*
         * some magic here
         */
    }
}


And so the controller became
public class UnitController : MonoBehaviour 
{
    [SerializeField]
    private UnitJumper _unitJumper;
    public Rigidbody AttachedRigidbody;
    //...
    public virtual void Jump()
    {
        if (_unitJumper != null)
            _unitJumper.Jump (AttachedRigidbody);
        else
            Debug.Log("UnitJumper Component is missing");
    }
    //...
}


Now everything has become simple and beautiful. Less problems with the hierarchy, the jump code is moved to a separate small class, which makes it easy to change. Each jump method can have as many parameters as you like, and you will be sure that changing them you will not break, for example, running. Also, modifying the jump method for a unit is now also very simple. In addition, the environment itself prompts us to follow such an architecture, and with the help of the [RequireComponent ()] attribute you can even mess around with the editor. Now you must ask why I am telling all this and what is the connection. So it's time for a logical transition to the Command pattern.

Logical transition


We have already moved away from writing all the jump code in our example into one class, but what if you want the units not only to jump differently on their own, but also, for example, to be able to change the jump method depending on the circumstances ( do a somersault, run along the wall)? This is where we will need the team.

The essence remains the same - to take all the elementary actions into separate classes. Only now we will add the necessary component to the unit immediately before use and this will allow us to change the behavior of the unit at any time and there will not be such a strong connection as in the case of Actor-s. We will write a small base class for the command, which so far will only serve to invoke the command on a given object.

Base team
public class Command : MonoBehaviour 
{
    public static T ExecuteOn(GameObject target)
        where T : Command
    {
        return target.AddComponent ();
    }
    private void Start()
    {
        OnStart ();
    }
    protected virtual void OnStart()
    {}
}


The above code serves only for conveniently adding components to the object, and the OnStart () method is for now (but only for now) exclusively for intellisense.

Now that we have the base class, we can implement a simple jump.

Example Jump Class
public class RegularJumpCommand : Command
{
    protected override void OnStart ()
    {
        base.OnStart ();
        gameObject.GetComponent ().velocity = new Vector3(0, 10, 0);
    }
}


And now, to make the unit jump, we only need to execute the command on it:

Team call
public class SomeController : MonoBehaviour 
{
    //don't forget to set this in editor
    public UnitController _targetUnit;
    private void Start()
    {
        if (_targetUnit != null)
        {
            Command.ExecuteOn (_targetUnit.gameObject);
        }
    }
}


The first thing that catches your eye is that our velocity values ​​are constant. Therefore, just to make a jump a little higher will not work. Previously, we would solve this by passing arguments to the jump method, let's do it here too. Let's rewrite our beautiful team:

Team with arguments
public class Command : MonoBehaviour 
{
    private object[] _args;
    public static T ExecuteOn(GameObject target, params object[] args)
        where T : Command
    {
        T result = target.AddComponent ();
        result._args = args;
        return result;
    }
    private void Start()
    {
            OnStart (_args);
    }
    protected virtual void OnStart(object[] args)
    {}
}


Now the height and direction of our jump can be changed by passing arguments to the command (do not forget to quote). Since Start () is called a bit later than creating the object, the arguments will be passed correctly to our OnStart (object [] args) method.

The jump command will remain almost unchanged, but now we can use the arguments passed from the outside in it:

Using Arguments in a Command
public class RegularJumpCommand : Command
{
    protected override void OnStart (object[] args)
    {
        base.OnStart (args);
        gameObject.GetComponent  ().velocity = (Vector3)args [0];
    }
}


The call to the command will change a bit more:

Calling a command with arguments
public class SomeController : MonoBehaviour 
{
    // don't forget to set this in editor
    public UnitController _targetUnit;
    private void Start()
    {
        if (_targetUnit != null)
        {
            Command.ExecuteOn (_targetUnit.gameObject,
                                                   new object[]{new Vector3(0, 10, 0)});
        }
    }
}


After the manipulations, the teams became flexible and now a separate class will only be needed for somersaults. But to initialize the parameters, you only need to use the OnStart method (object [] args).

The second problem that we still have is that every time we jump, the expensive GetComponent () method will be called. To solve this, let's remember that since the Actor we still have a controller that holds links to all important components and we will ask it for everything we need from the team. We can also pass the controller into arguments and I propose to do this a little more formalized. Let's write a child class with a controller for the command:

Command with controller
public class CommandWithType : Command
    where T : MonoBehaviour
{
    protected T Controller
    {
        get;
        private set;
    }
    protected override void OnStart (object[] args)
    {
        base.OnStart (args);
        Controller = args [0] as T;
    }
}


In the command itself, after that, only the number of the argument that we use has changed, but do not forget about this either. But there was a convenient way to get the controller without resorting to GetComponent (). And you definitely need to call base.OnStart (args), otherwise we won’t be able to use the controller:

Controller usage
public class RegularJumpCommand : CommandWithType
{
    protected override void OnStart (object[] args)
    {
        base.OnStart (args);
        Controller.AttachedRigidbody.velocity = (Vector3)args [1];
    }
}


The challenge to the team also became a little different:
public class SomeController : MonoBehaviour 
{
    //don't forget to set this in editor
    public UnitController _targetUnit;
    private void Start()
    {
        if (_targetUnit != null)
        {
            Command.ExecuteOn (_targetUnit.gameObject,
                                                   new object[]{_targetUnit ,new Vector3(0, 10, 0)});
        }
    }
}


Now everything has become very good: we have teams that can do without a controller (show ads, post something somewhere) and teams that need a controller (go, run, fly). Commands with the controller are sharpened for work with the family of classes and will not be available to other families, which introduces additional orderliness. And also we still have the advantages of Actor. And you could not help but notice how small and neat they are. The controllers also only benefited from this: they became the same laconic containers of links to the components we need (later we, of course, will give them more weight). But this is still only the beginning, and with such functionality we will not go very far.

In order not to go so far from the jump, let's see what we still missed. Let us bring this awkward but useful example to its logical conclusion.

The first thing that catches your eye: after ten jumps, ten completely useless teams will hang on the object. The second thing to note is that velocity changes at the start and this does not guarantee the correct operation of the physical engine.

So far we will concentrate on the first, and go on and the second will decide itself, as it happens. The most logical thing is for the team to clean up after itself, as soon as it has screwed up. Add the command completion functionality and two flags to know if the team has completed its execution (more on that later). And after small transformations, the team evolves (but this is not even its last form).

Cleaning up the team
public class Command : MonoBehaviour 
{
    private object[] _args;
    private bool _started = false;
    private bool _isReleased = false;
    public bool IsRunning
    {
        get{ return _started && !_isReleased;}
    }
    public static T ExecuteOn(GameObject target, params object[] args)
        where T : Command
    {
        T result = target.AddComponent ();
        result._args = args;
        return result;
    }
    private void Start()
    {
        _started = true;
        OnStart (_args);
    }
    protected virtual void OnStart(object[] args)
    {}
    private void OnDestroy()
    {
        if (!_isReleased)
            OnReleaseResources ();
    }
    protected virtual void OnReleaseResources()
    {
        _isReleased = true;
    }
    protected void FinishCommand()
    {
        OnReleaseResources ();
        Destroy (this, 1f);
    }
    protected virtual void OnFinishCommand(){}
}


Now, at the right moment, the team, as a decent citizen and a member of society, will self-destruct, all you have to do is call the FinishCommand () method after all the necessary manipulations. Destroy () is slightly delayed so that everyone who needs it can use the command before disappearing (take data from it, but more on that later), and the IsRunning flag is needed by the team itself so that it does not start ahead of time and does not continue after completion. All unsubscribing from events and freeing resources can be easily done in OnReleaseResources () or in OnFinishCommand (). And do not be afraid that you accidentally write On Destory () and you will suffer for a long time (as I once did).

Now with all this we can solve the second problem:

Change velocity in FixedUpdate
public class RegularJumpCommand : CommandWithType
{
    private Vector3 _velocity;
    protected override void OnStart (object[] args)
    {
        base.OnStart (args);
        _velocity = (Vector3)args [1];
    }
    private void FixedUpdate()
    {
        if (!IsRunning)
            return;
        Controller.AttachedRigidbody.velocity = _velocity;
        FinishCommand ();
    }
}


Now the velocity value will change at the moment of the first iteration of the physical engine after Start-a. The team at this stage of its development will wonderfully cope with tasks such as casting spells, running, various jumps and visual effects.

But what about ?!


This still will not come to use wherever you want. If you need to download a config or validate user actions (not let children buy buns for parental money) or just use teams as part of a zigzag run. Quite often there are situations when you need to know whether the team completed, whether it was successfully completed, and if so, then take the necessary data from it (for which the destruction was delayed). In short: you can't figure it out without callbacks. Personally, I like the new Unity UI system because of them, but only when they are added or removed from the code (doing this in the editor is a sin, do not do it that way).

You can do everything on events, you don’t even have to think, but you don’t really want to keep links to all running commands and unsubscribe at the right moment. And the pain is just below the back, in the case when I forgot to unsubscribe, few people can bring pleasure. Let's first stipulate what we need to do in order to start using the commands to their full potential and so that later there are no edits. The main task is to make a callback for the successful and unsuccessful completion of the command. Make them convenient for signing and without having to follow the unsubscribe. It will also be convenient when passing the command to pass the command itself to the callback argument, so as not to keep it as a separate field in the class. And yet we have not implemented a way to stop the team from outside.

First: we’ll make a small wrapper for those callbacks. No sooner said than done, we programmers are simple people. We got something like this:

Callback
    public class Callback 
        where T : Command
    {
        public readonly Action Succeed;
        public readonly Action Fault;
        public Callback (Action succeed)
        {
            this.Succeed = succeed;
        }
        public Callback (Action succeed, Action fault)
        {
            this.Succeed = succeed;
            this.Fault = fault;
        }
    }


Simple and convenient. Note that by default if there is one callback, then we automatically believe that we are only interested in the successful completion of the command and it will be called only in this case. The next logical step will be to make a container for these very callbacks, because one will always be enough. And we got this:

Callbacktoken
    public class CallbackToken 
        where T : Command
    {
        private List> _callbacks;
        private T _command;
        public CallbackToken (T _command)
        {
            this._command = _command;
            _callbacks = new List>();
        }
        public void AddCallback(Callback callback)
        {
            _callbacks.Add (callback);
        }
        public void RemoveCallback(Callback callback)
        {
            _callbacks.Remove (callback);
        }
        public void FireSucceed()
        {
            foreach (Callback calback in _callbacks)
            {
                calback.Succeed(_command);
            }
        }
        public void FireFault()
        {
            foreach (Callback callback in _callbacks)
            {
                if (callback.Fault != null)
                {
                    callback.Fault (_command);
                }
            }
        }
    }


It remains only to add CallbackToken to our team and call it at the right time. And do not forget to make it possible to complete the team successfully, not successfully, and from the outside. And immediately the final code:

Team with Callback
public class Command : MonoBehaviour 
{
    private object[] _args;
    private bool _started = false;
    private bool _isReleased = false;
    public CallbackToken CallbackToken
    {
        get;
        private set;
    }
    public Command ()
    {
        CallbackToken = new CallbackToken (this);
    }
    public bool IsRunning
    {
        get{ return _started && !_isReleased;}
    }
    public static T ExecuteOn(GameObject target, params object[] args)
        where T : Command
    {
        T result = target.AddComponent ();
        result._args = args;
        return result;
    }
    private void Start()
    {
        _started = true;
        OnStart (_args);
    }
    protected virtual void OnStart(object[] args)
    {}
    private void OnDestroy()
    {
        if (!_isReleased)
            OnReleaseResources ();
    }
    protected virtual void OnReleaseResources()
    {
        _isReleased = true;
    }
    protected void FinishCommand(bool result = true)
    {
	if (!IsRuning)
		return;
        OnReleaseResources ();
        OnFinishCommand ();
        if (result)
            CallbackToken.FireSucceed ();
        else
            CallbackToken.FireFault ();
        Destroy (this, 1f);
    }
    protected virtual void OnFinishCommand(){}
    public void Terminate(bool result = false)
    {
        FinishCommand (result);
    }


Now the FinishCommand () method will accept the execution as an argument, and the Terminate () method will be used to interrupt the operation of the command from the outside.

Now let's see what the subscription looks like:

Callback Subscription
public class SomeController : MonoBehaviour 
{
    //don't forget to set this in editor
    public UnitController _targetUnit;
    private void Start()
    {
        if (_targetUnit != null)
        {
            Command.ExecuteOn (_targetUnit.gameObject,
                                                   new object[]{_targetUnit ,new Vector3(0, 10, 0)})
                .CallbackToken.AddCallback (new Callback(OnJumpFinish));
        }
    }
    private void OnJumpFinish (Command command)
    {
        Debug.Log(string.Format("{0}", "Successfully jumped"));
    }
}


Now we can easily solve the second task: to take data from the command (after all, we get it in the callback method), simply by making a public get-er for the necessary information and voila.

The end!


In conclusion, I want to say that the approach is very successful for developing games on Unity. Everything is simple and beautiful, easy to modify, and utilitarian commands (download, post) are easily transferred to any project. If you like this article, I will not only be pleased, but there will also be an incentive to talk about finite state machines, MVC, strategies and how it lives and coexists in one project.

PS: do not forget that the code here is for informational purposes only and was tested only in the editor.

Also popular now: