游戏设计模式:命令模式

[Unity]游戏设计模式:命令模式

之前做过两款游戏的开发,遇到了许许多多的问题,从中体会到有个好的结构对游戏开发至关重要。在研究如何解决问题的时候,幸运的找到《游戏编程模式》这一救命神器!在此分享一下,也为了复习一下书中所学。
以下内容是我结合书中所学总结而来,我不太喜欢专业且抽象的描述,如有错误,还请指正!

1.动机

命令模式实现控制的直接解耦,把来自输入,AI的指令或者其他一些控制封装成数据对象,作为中间层,分离输入到行为的直接操作。在我的第一个项目中,Input直接写在了英雄控制器下,而后就调用到行为函数,简直愚蠢至极!把它们分开!通俗一点,命令模式大概是这样:就像你去点美团外卖,你只用发布一个买外卖的订单命令,里面有你的订单信息,然后提交订单,系统会帮你指派,你不用管谁去帮你炒菜!而你的订单就是一个命令。

2.举例

我用一个病菌的行为来说吧,首先定义了一个病菌对象。

public class Germ
{
   private int curCount;
   private int divideMuti;
   private int infectTargetID;

   public Germ(byte divideMuti, int infectTargetID)
   {
        curCount = 1;
        this.divideMuti = divideMuti;
        this.infectTargetID = infectTargetID;
   } 
    
   public void Divide()
   {
       Debug.Log("Germ Dividing! Count: " + (curCount *= divideMuti));
   }
    
   public void Infect()
   {
       Debug.Log("Germ Infecting! Target: " + infectTargetID++);
   }
}

病菌有两个行为,分裂和感染,如果按照输入直接控制行为,那它的代码是这样的。

void Update() {
    if (Input.GetKeyDown(KeyCode.D))
       Divide();
    else if (Input.GetKeyDown(KeyCode.I))
       Infect();
}

显然,我看这段代码更像是测试时随便码上的。这样写虽然没错,但是想象一下,它不能被扩展了,比如想改变按键触发时的行为,你可能会说代码直接换下执行函数不就行了,可是游戏发布后你的玩家可没那么聪明!让我们分离输入到行为的直接控制,抽象出一个命令来进行调用。
直接上代码!

public abstract class Command
{
    public abstract void Excute(Germ germ);
}

public class DivideCommand : Command
{
    public override void Excute(Germ germ)
    {
        germ.Divide();
    }
}

public class InfectCommand : Command
{
    public override void Excute(Germ germ)
    {
        germ.Infect();
    }
}

public class NoneCommand : Command
{
   public override void Excute(Germ germ){}
}

定义了一个命令的抽象类Command,Excute(Germ germ)是执行方法,传入了一个病菌对象的引用,为什么这么做?你可以用这个命令控制你想控制的病菌,而不是只能控制一个。设想下当你有个技能是精神控制,把被你控制的角色用这些命令来驱动,那你可以为所欲为!我继承了两个用于实现抽象命令的具体类,最后还有一个什么也不做的命令,其目的在于命令传递中,你不想把null值到处流窜。
命令定义好之后,还需要一个处理这些命令到底如何分配,代码如下:

public abstract class Handler
{
    protected Command[] commands;
    protected Command noneCommand;
    
    public Handler(Command[] commands)
    {
        noneCommand = new NoneCommand();
        this.commands = commands;
    }
    
    public abstract Command Handling();
}

public class InputHandler : Handler
{
    public InputHandler(Command[] commands) : base(commands){}
    
    public override Command Handling()
    {
        if (Input.GetKeyDown(KeyCode.D))
            return commands[0];
        else if (Input.GetKeyDown(KeyCode.I))
            return commands[1];
        else
            return noneCommand;
    }
}

处理命令的Handler类构造需要一个Command的数组,InputHandler中实现了处理命令的方式,命令的调用根据数组的不同,可以生成不同的按键映射,当然这不是最好的方式,但比之前的要好!
现在,我们来写一段测试代码,如下:

public class CommandPattern : MonoBehaviour
{
    private Germ curGerm;
    private InputHandler inputHandler;
    
    private void Start()
    {
        curGerm = new Germ(2, 1);
        Command[] comds = new Command[2] { new DivideCommand(), new InfectCommand() };
        inputHandler = new InputHandler(comds);
    }
    
    private void Update()
    {
        Command comd = inputHandler.Handling();
        comd.Excute(curGerm);
    }
}

测试正常运行!按下D键,病菌分裂,按下I键,病菌感染目标。我可以随时更换要操作的病菌对象!

3.扩展

有了命令模式,我想我还可以做很多事。之前的代码抽象了处理命令的处理器Handler,再重写一个新的处理逻辑AIHandler,可以自动控制病菌的行为。

public class AIHandler : Handler
{    
    public AIHandler(Command[] commands) : base(commands){}
    
    public override Command Handling()
    {
        if (Time.frameCount % 60 == 0)
        {
            return commands[Random.Range(0, commands.Length)];
        }
        else {
            return noneCommand;
        }
    }
}

每60帧随机产生一种命令,控制病菌的行为,当然这很Low,你可以写出更酷炫的实现方式!
更改测试代码

public class CommandPattern : MonoBehaviour
{
    public bool InputHandlerSwitch;//输入处理器开关。
    
    private Germ curGerm;
    private InputHandler inputHandler;
    private AIHandler iHandler;
    
    private void Start()
    {
        curGerm = new Germ(2, 1);
        Command[] comds = new Command[2] { new DivideCommand(), new InfectCommand() };
        inputHandler = new InputHandler(comds);
        iHandler = new AIHandler(comds);
    }
    
    private void Update()
    {
        Command comd = InputHandlerSwitch ? inputHandler.Handling() : iHandler.Handling();
        comd.Excute(curGerm);
    }
}

启用InputHandlerSwitch时,Germ受输入控制,关闭时自动控制。

命令模式不止于此,你还可以把命令存起来,实现撤销,重做,等功能。回放功能也如此,按照时间顺序把命令从队列中依次取出,用命令控制你要回放的对象行为,倒放也可以弄…
谢谢大家赏脸,如有错误还请指正,本人第一次写博客,请多指教!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值