命令模式
原网站:https://gpp.tkchu.me/command.html
定义
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式是一种回调的面向对象实现。
按键配置
在QQ飞车中,按键[shift]和[ctrl]通常是玩家用来选择[漂移]和[氮气加速]的按钮。但是,为了更好地配合落地喷和小喷,一些玩家会将[漂移]和[氮气加速]绑定在按键[Q]和[E]上,某些地下城玩多的玩家(例如我)还会将其绑定在[X]和[Z]上。
按键绑定行为的简单实现如下:
void InputHandler::handleInput()
{
if (isPressed(BUTTON_SHIFT))
{
drift();
}
else if (isPressed(BUTTON_CTRL))
{
speedUp();
}
}
但这段代码将[漂移]和[氮气加速]绑定在了[shift]和[ctrl]按键上,并不支持玩家自定义配置按键的功能。
因此,为了支持自定义,我们需要将这些对drift()和speedUp()的直接调用转化为可以变化的东西。
我们需要表示游戏行为的对象。
首先,定义一个基类,表示可出发的游戏行为:
class Command
{
virtual ~Command() {}
virtual void execute() = 0;
};
随后,为不同的游戏行为定义相应的子类:
class DriftCommand: public Command
{
public:
virtual void execute()
{
drift();
}
};
class SpeedUpCommand: public Command
{
public:
virtual void execute()
{
speedUp();
}
};
在代码的输入处理部门,为每一个按键存储一个指向命令的指针:
class InputHandler
{
public:
void handleInput();
// 绑定命令的方法......
// 这里我们可以自定义按键的行为配置
private:
Command* buttonSHIFT_;
Command* buttonCTRL_;
Command* buttonQ_;
Command* buttonE_;
// ps-我的玩法
Command* buttonX_;
Command* buttonZ_;
}
现在,输入处理部分修改为:
void InputHander::handlerInput()
{
if (isPressed(Button_SHIFT))
{
buttonSHIFT_->execute();
}
else if (isPressed(Button_CTRL))
{
buttonCTRL_->execute();
}
}
角色说明
上述的例子可以让玩家们自定义控制角色的动作,但是顶层的drift(),speedUp()之类的函数只能够支持玩家的机车进行[漂移]和[氮气加速],而不支持让小橘子跟你同台竞速(我要打恩佐!!!)。也就是说,那些电脑定义的角色,无法采用上述命令进行[漂移]和[氮气加速]。
因此,我们需要将函数控制的角色对象传进去:
class Command
{
public:
virtual ~Command() {}
virtual void execute (GameActor& actor) = 0;
}
GameActor代表游戏世界中角色的“游戏对象”类。我们将其传给execute(),如此一来,命令类的子类基于可以调用所选游戏对象上的方法:
class DriftCommand : public Command
{
public:
virtual void execute (GameActor& actor)
{
actor.drift();
}
}
现在,我们可以使用这个类与游戏中的任意角色进行同台竞速了(通通屈服于我的雷诺下)。
在输入控制部分和在对象上调用命令部分,我们还缺少了一部分代码。
首先,修改handleInput(),让他可以返回命令:
Command* InputHandler::handleInput()
{
if (isPressed(BUTTON_SHIFT))
{
return buttonSHIFT_;
}
if (isPressed(BUTTON_CTRL))
{
return buttonCTRL_;
}
return nullptr;
}
然后,需要一些接收命令的代码,作用在玩家角色上:
Command* command = inputHandler.handleInput();
if (command)
{
command->execute(actor);
}
撤销和重做
如果一个命令对象可以做一件事情,那么它亦可以撤销这件事(漂移撞墙了,我还可以撤销重来吗(难道是复位?))。
这种概念主要在一些策略游戏中使用,下棋(肯定不是云顶之奕)或者我的世界(没玩过,不了解)。或许ctrl+z就是这种命令。
还是用飞车的例子吧(当然在游戏中不会撤销,不要到游戏中尝试)。其[氮气加速]的代码可能如下:
class SpeedUpCommand : public Command
{
public:
// cnt_ 是指氮气的次数
SpeedUpCommand (int cnt_, int x_, int y_):
{
}
virtual void execute (GameActor& actor);
{
actor->speedUp(cnt_);
}
private:
int cnt_, x_, y_;
}
输入控制代码在玩家下决定时创造一个实例(不太透彻)。
Command* handleInput()
{
GameActor* actor = getActor();
if (isPressed(BUTTON_CTRL))
{
int cnt_ = actor->cnt_() - 1;
return new SpeedUpCommand(actor, cnt_);
}
return nullptr;
}
为了让指令可被取消,我们为每个类定义一个另一个需要实现的方法:
class Command
{
public:
virtual ~Command () {}
virtual void execute () = 0;
virtual void undo () = 0;
}
undo()方法用于回滚execute()方法造成的游戏状态改变。这里是添加了撤销功能后的[氮气加速]命令:
class SpeedUpCommand : public Command
{
public:
SpeedUpCommand(int cnt_, int x_, int y_)
{
}
virtual void execute (GameActor& actor)
{
// 保存加速之前的位置
xBefore_ = actor->x();
yBefore_ = actor->y();
actor->speedUp(cnt_)
}
virtual void undo(GameActor& actor)
{
// 如果没有回到原位置的命令,那岂不是可以实现无限氮气
actor->CtrlAndZ(cnt_, xBefore_, yBefore_);
}
private:
int cnt_;
int xBefore_, yBefore_;
int x_, y_;
}
当玩家选择撤销命令时,那么玩家的位置会回到氮气加速那一刻的位置,同时氮气的数量也会返回。
结语
举的例子或许不太恰当,甚至自己还没领域透彻,建议读者去原网站[https://gpp.tkchu.me/command.html]阅读更加充分的解释和更加恰当的举例。。

2万+

被折叠的 条评论
为什么被折叠?



