命令模式
本文的参数书是《游戏编程模式》,作者:Bob Nystrom。当然学习资料是在网上下载的PDF版本,等学完了这本书,一定要买来这本书的纸质版留一个纪念。
GoF是这样定义命令模式的:
将一个请求封装为一个对象,从而使你可用不同的请求的客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
每个游戏都有一些用户输入要处理,比如键盘敲击,鼠标点击等等。例如:
下面是一种简单实现:
void InputHandler::handleInput()
{
if(isPressed(BUTTON_X)) jump();
else if(isPressed(BUTTON_Y)) fireGun();
else if(isPressed(BUTTON_A)) swapWeapon();
else if(isPressed(BUTTON_B)) lurchIneffectively();
}
这样的做法,通常要在游戏的每一帧循环调用函数。但是却不能支持更多的功能,比如当游戏允许玩家配置按键功能的时候就不那么实用了。(扯一句题外话,想当年老罗吹嘘锤子T1的时候,讲到有对称按键并且支持按键功能自定义,感觉好屌的样子,然后就屁颠屁颠的买了T1。现在想想这也许仅仅是软件上的一个小小的改变。)
为了实现这些功能,我们通过一个基类代表游戏触发的行为:
class Command
{
public:
virtual ~Command(){}
virtual void execute() = 0;
};
当你有一个接口只包含一个没有返回值的方法时,很可能你可以使用命令模式。
然后我们为不同的游戏行为定义相应的子类:
class JumpCommand : public Command
{
public:
virtual void execute() { jump(); }
};
class FirCommand: public Command
{
public:
virtual void execute(){ firGun(); }
};
//大概就是这样的思路了
在代码的输入处理部分,为每一个按键存储一个指向命令的指针
class InputHandler
{
public: void handleInput();
//绑定命令的方法
private:
Command* buttonX_;
Command* buttonY_;
Command* buttonA_;
Command* buttonB_;
};
现在输入部分这样处理
void InputHandler::handleInput()
{
if(isPressed(BUTTON_X)) buttonX_->execute();
else if(isPressed(BUTTON_Y)) buttonY_->execute();
else if(isPressed(BUTTON_A)) buttonA_->execute();
else if(isPressed(BUTTON_B)) buttonB_->execute();
};
以前每个函数直接调用函数,现在会有一层间接寻址:
重点在下面
又有一个新的需求,假设我们要控制游戏玩家角色之外的角色怎么办,这个时候就需要关联角色了:
class JumpCommand: public Command
{
public:
virtual void execute(GameActor &actor)
{
actor.jump();
}
};
现在可以让游戏中的任何角色跳来跳去了。在输入控制部分和在对象上调用命令之间,还缺少了输入的控制部分,handleInput如下:
Command* InputHandler::handleInput()
{
if(isPressed(BUTTON_X)) return buttonX_;
if(isPressed(BUTTON_Y)) return buttonY_;
if(isPressed(BUTTON_A)) return buttonA_;
if(isPressed(BUTTON_B)) return buttonB_;
//没有按下任何按键,就什么都不做
return NULL;
};
这里还不能立即执行,因为还不知道哪个角色会传进来。这里我们享受了命令是具体调用的好处——延迟到调用执行时再知道。
然会,需要一些接受命令的代码,作用在玩家角色身上。像这样:
Command* command = inputHandler.handleInput();
if(command)
{
command->execute(actor);
}
将actor视为玩家角色的引用,但是这里我们也可以控制其他的任何角色,只需要向命令传入不同的角色。在实际应用中,大部分游戏角色被AI控制,这时候我们可以在AI和角色之间使用相同的命令模式,这样AI代码只需要生成Command对象。
AI命令流和玩家控制同时作用时足以是游戏充满多样化,这些在游戏展示和自动演示时会很有用。如果将指令序列化,再通过网络传输它们,我们可以接受玩家的输入,将其通过网络发送到另一台机器上,然后重现,这是网络多人游戏的基础。
贴一张图,感觉很高大上
另外在命令模式使用时还可以为命令提供一个撤销方法,在游戏进行中和游戏编辑过程中是很有用的,这里贴张图算了,具体实现也不难,无非就是保存历史操作记录。
控制其它游戏对象的游戏代码
class Command
{
public:
virtual ~Command(){}
virtual void execute(GameActor &actor) = 0;
};
这里GameActor是代表游戏世界中的角色的“游戏对象”类
class JunpCommand: public Command
{
public:
virtual void execute(GameActor& actor)
{
actor.jump();
}
};
现在可以跳来跳去了
Command* command = inputHandler.handleInput();
if(command)
{
command->execute(actor)
}
2018年10月19日