设计模式-命令模式

命令模式

原网站: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]阅读更加充分的解释和更加恰当的举例。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值