• 游戏编程之命令模式


    1、什么是命令模式

    最近看了《游戏编程模式》这本书,里面介绍了游戏开发时常用的设计模式,当然这些设计模式不只是在开发游戏时才管用,它们同样适用于其他软件开发,适用于各种语言。这里我记录一下自己的学习笔记以及结合unity的使用方法。命令模式是常用的设计模式之一,它的定义是这样:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。这个定义听起来似乎晦涩难懂,下面用unity游戏开发的例子来说明:
     

    2、对客户进行参数化

    比如在游戏开发中,产品经理给你提了这样一个需求:按下按键A,控制角色攻击;按下按键B,控制角色奔跑;按下按键C,控制角色跳跃。面对这样一个简单的需求,我们或许会这样写:
    void HandleInput()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            Attack();
        }
        else if (Input.GetKeyDown(KeyCode.B))
        {
            Run();
        }
        else if (Input.GetKeyDown(KeyCode.C))
        {
            Jump();
        }
    }
    然后,产品经理又提了需求,用户可以自定义按键功能,在很多游戏中都有做这样的功能,为了实现这样的功能,我们应该将这些对Attack()和Run()的调用转化成可以变换的东西,下面用命令模式来重写一下这个功能:
    先定义一个抽象类Command作为基类,再定义具体的子类来重写Excute();

    public abstract class Command{
        public abstract void Excute(GameActor actor);
    }
    
    public class AttackCommand : Command
    {
        public override void Excute()
        {
            //攻击逻辑
        }
    }
    
    public class RunCommand : Command
    {
        public override void Excute()
        {
            //奔跑逻辑
        }
    }
    
    public class JumpCommand : Command
    {
        public override void Excute()
        {
            //跳跃逻辑
        }
    }
    在MonoBehaviour的Update函数中,每帧去监听用户输入,并返回对应的command

    public class GameControl : MonoBehaviour
    {
        private Command buttonA;
        private Command buttonB;
        private Command buttonC;
    
        private void Start()
        {
            buttonA = new AttackCommand();
            buttonB = new JumpCommand();
            buttonC = new RunCommand();
        }
    
        private void Update()
        {
            Command cmd = HandleInput();
            if (cmd != null)
            {
                cmd.Excute(actor);
            }
        }
    
        //处理用户输入
        private Command HandleInput()
        {
            if (Input.GetKeyDown(KeyCode.A))
            {
                return buttonA;
            }
            else if (Input.GetKeyDown(KeyCode.B))
            {
                return buttonB;
            }
            else if (Input.GetKeyDown(KeyCode.C))
            {
                return buttonC;
            }
            else
            {
                return null;
            }
        }
    
    }

    这样,在按键触发和函数调用中间就加了一层Command,如果要自定义按键功能,直接修改Button对应的Command就行了。现在我们也可以修改一下上面的代码,让我们可以用这套机制去控制任意角色对象,只需将要控制的角色对象传进来即可:
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class GameActor { }
    
    public class Actor1 : GameActor { }
    
    public class Actor2 : GameActor { }
    
    public abstract class Command{
        public abstract void Excute(GameActor actor);
    }
    
    public class AttackCommand : Command
    {
        public override void Excute(GameActor actor)
        {
            //攻击逻辑
        }
    }
    
    public class RunCommand : Command
    {
        public override void Excute(GameActor actor)
        {
            //奔跑逻辑
        }
    }
    
    public class JumpCommand : Command
    {
        public override void Excute(GameActor actor)
        {
            //跳跃逻辑
        }
    }
    
    
    public class GameControl : MonoBehaviour
    {
        private Command buttonA;
        private Command buttonB;
        private Command buttonC;
    
        private GameActor actor;
    
        private void Start()
        {
            buttonA = new AttackCommand();
            buttonB = new JumpCommand();
            buttonC = new RunCommand();
    
            actor = new Actor1();
        }
    
        private void Update()
        {
            Command cmd = HandleInput();
            if (cmd != null)
            {
                cmd.Excute(actor);
            }
        }
    
        //处理用户输入
        private Command HandleInput()
        {
            if (Input.GetKeyDown(KeyCode.A))
            {
                return buttonA;
            }
            else if (Input.GetKeyDown(KeyCode.B))
            {
                return buttonB;
            }
            else if (Input.GetKeyDown(KeyCode.C))
            {
                return buttonC;
            }
            else
            {
                return null;
            }
        }
    
    }

    3、支持可撤销的操作

    命令模式在需要支持可撤销操作的情况下也能轻松应对,假如我们需要给玩家提供撤销移动操作的功能时,我们可以先把玩家输入产生的command存入栈中(或者其他数据结构),在撤销时,从栈中取出栈顶的Command,再调用该Command的Undo(),就实现了撤销功能(Undo()为撤销方法,与Excute()相反),代码如下:
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class GameActor {
        public Transform selfTra;
        public void Move(Vector3 offset)
        {
            selfTra.Translate(offset);
        }
    }
    
    public class Actor1 : GameActor { }
    
    public class Actor2 : GameActor { }
    
    public abstract class Command{
        public abstract void Excute(GameActor actor);//执行
        public abstract void Undo(GameActor actor);//撤销
    }
    
    public class MoveCommand : Command
    {
        public Vector3 moveOffset;
        public MoveCommand(Vector3 offset)
        {
            moveOffset = offset;
        }
    
        public override void Excute(GameActor actor)
        {
            actor.Move(moveOffset);
        }
    
        public override void Undo(GameActor actor)
        {
            actor.Move(-moveOffset);
        }
    }
    
    public class CommandControl : MonoBehaviour
    {
        private Command moveCommand;
        private GameActor actor;
        private Stack<Command> commandStack;
    
        private void Start()
        {
            moveCommand = new MoveCommand(Vector3.one);
            actor = new Actor1();
            commandStack = new Stack<Command>();
        }
    
        private void Update()
        {
            Command cmd = HandleInput();
            if (cmd != null)
            {
                commandStack.Push(cmd);
                cmd.Excute(actor);
            }
        }
    
        //需要撤销操作时调用这个函数
        public void PlayReverse()
        {
            if (commandStack.Count > 0)
            {
                commandStack.Pop().Undo(actor);
            }
        }
    
        //处理用户输入
        public Command HandleInput()
        {
            if (Input.GetKeyDown(KeyCode.A))
            {
                return new MoveCommand(new Vector3(2, 4, 5));
            }
            if (Input.GetKeyDown(KeyCode.B))
            {
                return new MoveCommand(new Vector3(1, 2, 4));
            }
            else 
            {
                return null;
            }
        }
    
    }
    上面代码中, 每次产生一个command时就将它存到Stack中,当需要撤销操作时,就取出Stack顶部的command,并执行它的Undo(),按照这种方法,可以实现多重撤销。
     

    4、总结

    通过上面的例子,我们再看命令模式的定义:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。现在我们差不多明白了命令模式的用法,它优点很明显,缺点也是有的:第一个优点是类间解耦,调用者和接收者之间没有任何依赖关系,调用者在实现功能时只需调用Command抽象类的Excute方法即可,不需要关注是哪个接收者执行;第二个优点是可扩展性,Command的子类可以很容易地扩展;缺点是如果有大量命令,那么Command的子类将会非常庞大。我们在实际开发中,应该发挥出命令模式的优点,并结合其他模式,减少Command子类庞大的问题。

  • 相关阅读:
    vue-element-admin 权限的添加
    vue 图标通过组件的方式引用步骤
    linux系统环境下配置vue项目运行环境
    5.5 卷积神经网络(LeNet)
    5.4 池化层
    5.3 多输入通道和多输出通道
    5.2 填充和步幅
    html && CSS
    P2827 [NOIP2016 提高组] 蚯蚓
    5.1 二维卷积层
  • 原文地址:https://www.cnblogs.com/IAMTOM/p/10190554.html
Copyright © 2020-2023  润新知