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