Using command mode to realize command input and cancellation

First of all, the purpose is to realize the input function similar to the handle. Suppose a handle has two keys A/B, which can input different commands and replace them (such as operation of blood bottle / jump / shooting). In addition to this, there is an additional requirement that the player can record the latest X operations entered by the player and can cancel them.

Let's write an Actor class first. As the controlled protagonist, only one HP field is written here, because we temporarily set the function of A/B key to add blood and deduct blood.

    public class Actor { 
        public int HP;
        public Actor(int hp)
        {
            HP = hp;
        }
    }

Then write the Command base class. You need to set a controlled Actor (the protagonist / AI can be replaced in the game), and include a method to execute and undo.

    public class Command
    {
        protected Actor actor;

        public Command(Actor a)
        {
            actor = a;
        }

        ~Command() { }
        public virtual void Excute() { }

        public virtual void Undo() { }
    }

A CommandA is written below. The operation to be performed is to add 10 HP to an Actor. In addition, a CommandB is written to perform blood deduction, because it is almost not shown with CommandA.

    public class CommandA : Command
    {
        ~CommandA() { }

        public CommandA(Actor a) : base(a) { }

        public override void Excute()
        {
            actor.HP += 10;
            Debug.Log("actor Added 10 hp, Now hp: " + actor.HP);
        }

        public override void Undo()
        {
            actor.HP -= 10;
            Debug.Log("actor Revoking the bonus, Now hp: " + actor.HP);
        }
    }

The following is to implement a command stack, which can record the latest X commands (I assume five), and can be revoked one by one.

    public class CommandNode  //The node in the command stack, the current command recorded, and its previous and next commands
    {
        ~CommandNode()
        {
            Debug.Log("I was released");
        }

        public Command m_command;

        public Command Command{ get { return m_command; } }

        public CommandNode(Command command)
        {
            m_command = command;
        }

        public CommandNode preCommand;
        public CommandNode nextCommand;
    }

    public class CommandStack
    {
        private int m_iCapacity = 0;  //stack capacity
        private int m_iCount = 0;  //Number of commands in the stack

        private CommandNode firstNode;  //First command stored
        private CommandNode lastNode;  //Last command

        public CommandStack(int capacity)
        {
            m_iCapacity = capacity;
        }

        public bool IsEmpty()
        {
            return m_iCount == 0;
        }

        public void Push(Command command)
        {
            if(IsEmpty())  //If the stack is empty, set the first command and the last command to the current
            {
                CommandNode node = new CommandNode(command);
                node.preCommand = null;
                node.nextCommand = null;
                firstNode = node;
                lastNode = node;
            }
            else  //stack is not empty, set the current command as the last command
            {
                CommandNode node = new CommandNode(command);
                node.preCommand = lastNode;
                node.nextCommand = null;
                lastNode.nextCommand = node;
                lastNode = node;
            }
            m_iCount++;
            if (m_iCount > m_iCapacity)  //The number of commands exceeds the capacity. Remove the first one
            {
                firstNode = firstNode.nextCommand;
                firstNode.preCommand = null;
                m_iCount--;
            }
        }

        public Command Pop()
        {
            Command result = lastNode.Command;  //Back to last command
            if (lastNode.preCommand != null)  //If there is a command before the last command, set it as the last command
            {
                lastNode = lastNode.preCommand;
                lastNode.nextCommand = null;
            }
            else  //If not, the stack is empty, and the first and last commands are empty
            {
                firstNode = null;
                lastNode = null;
            }
            m_iCount--;
            return result;
        }
    }

After writing the above and below are my main control classes.

    public class CommandPatternMain : MonoBehaviour
    {
        private Actor actor = new Actor(100);

        CommandStack commands = new CommandStack(5);
        Command command;
        Command tempCommand = null;

        private Command m_CommandA;
        private Command m_CommandB;

        private void Start()
        {
            SetCommands();
        }

        private void SetCommands()
        {
            m_CommandA = new CommandA(actor);
            m_CommandB = new CommandB(actor);
        }

        private void Update()
        {
            command = InputHandler();
            if(command != null)
            {
                command.Excute();
                commands.Push(command);
            }

            if(Input.GetKeyDown(KeyCode.Space) && !commands.IsEmpty())
            {
                tempCommand = commands.Pop();
                if(tempCommand != null)
                    tempCommand.Undo();
            }
        }

        private Command InputHandler()
        {
            if (Input.GetKeyDown(KeyCode.A)) { return m_CommandA; }  
            if (Input.GetKeyDown(KeyCode.B)) { return m_CommandB; }

            return null;
        }
    }

There are two fixed commands A and B in this class, which are executed when the player presses the key corresponding to A and B. As for the specific command, we have assigned A value in the SetCommands method. Of course, the game can be replaced at any time. Every time the player executes A command, A command stack with capacity of 5 will be pushed. When the player presses the Cancel button, the Pop will give the last command and execute the Undo method. Let's take A look at the test results. We first add five times of blood, then deduct three times of blood, then cancel five times, then add five times of blood, then deduct three times of blood, and then cancel five times. The results are as follows:

Posted by eabigelow on Mon, 11 Nov 2019 23:50:44 -0800