游戏中的命令模式

“将一个请求封装成一个对象,从而允许你使用不同的请求、队列或日志将客户端参数化,同时支持请求操作的撤销与恢复”

回放在游戏中很常见,一个很简单的实现方法就是记录每一帧的游戏状态以便能够回放,但是这样会使用大量的内存。

实际上,许多游戏会记录每一帧每个实体所执行的一系列命令,为了回放游戏,引擎只需要模拟正常游戏的运行,执行预先录制的命令即可。

如果我们把命令序列化,我们便可以通过网络发送数据流,可以把玩家的输入,通过网络发送到另外一台机器上,然后进行回放,这是多人网络游戏很重要的一部分。

class Command
{
public:
    virtual ~Command();
    virtual void execute(GameActor& actor) = 0;
};

然后为每个不同的游戏动作创建子类。

class JumpCommand : public Command
{
public:
    virtual void execute(GameActor& actor){
        actor.jump();
    }
};
class FireCommand : public Command
{
public:
    virtual void execure(GameActor& actor){
        actor.fireGun();
    }
};
人工智能----->命令流----->角色

undo撤销的支持。为Command抽象undo方法。

class MoveUnitCommand : public Command
{
public:
    MoveUnitCommand(Unit* unit, int x, int y): unit_(unit), x_(x), y_(y)
    {
    }
    virtual void execute()
    {
        xBefore_ = unit_->x();
        yBefore_ = unit_->y();
        unit_->moveTo(x_, y_);
    }
    virtual void undo()
    {
        unit_->moveTo(xBefore_, yBefore_);
    }
private:
    Unit* unit_;
    int x_, y_;
    int xBefore_, yBefore_;
};

可以拉成双向链表

older CMD<-->CMD<-->CMD<-->CMD<-->CMD<-->CMD newer
                    undo  current redo 

不同的语言可能有不同的风格,选择OOP还是函数式编程也是不同的,例如在JavaScript中可以这么做。

function makeMoveUnitCommand(unit, x, y)
{
    // ...
    return function(){
        unit.moveTo(x, y);
    }
}

通过闭包来添加对撤销的支持

function makeMoveUnitCommand(unit, x, y){
    var xBefore, yBefore;
    return{
        execute: function(){
            xBefore = unit.x();
            yBefore = unit.y();
            unit.moveTo(x, y);
        },
        undo: function(){
            unit.moveTo(xBefore, yBefore);
        }
    };
}