游戏编程模式----命令模式
游戏开发是一门很深的学问,作为程序员的我们要拥有架构整个项目的能力,所以设计模式是我们必须要学习的课程。但是很多设计模式在游戏开发中可能不太实用,所以我创建了此系列文章,详细记录我在学习设计模式过程中使用和学习到的比较好的设计思想,还有一些自己的总结与归纳!如果感觉到文章对你有帮助,欢迎关注点赞加收藏!!!
该系列文章绝不是简单的阐述概念,而是将知识点互相串通,融入使用。欢迎大家关注,文章会持续更新!!!
游戏编程模式电子书-中文版
第一节 命令模式
文章目录
命令模式的概念
概念:
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式是一种回调的面向对象实现。
在某些语言中的反射允许你在程序运行时命令式地和类型交互。
你可以获得类的类型对象,可以与其交互看看这个类型能做什么。换言之,反射是具现化类型的系统。
这写的啥?每个字我都认识,为什么连起来我就看不懂了???什么情况???
呃…要回答这个问题,我们首先需要理解什么是请求
请求?
请求就是我们调用其他函数,你可以这么简单理解
比如说,我们按下WASD分别调用MoveA(),MoveB(),MoveC(),MoveD(),这些函数。那么我们按下A就是发起一个请求对MoveA()
请求封装为对象?
这里我们可以直接在函数里面监听键盘事件,等按下A,就调用MoveA()函数
但是,我们可以将命令进行封装,命令是一个类,这个类里面定义了一些函数,记录了这个命令应该做什么。那么,我们需要调用MoveA()去控制主角往左走的时候,我们可以直接去实例化一个命令对象,将这个命令对象传给Player,由Player去解析这个命令对象,然后去完成相应的操作。
那么之前我们按下WASD分别调用MoveA(),MoveB(),MoveC(),MoveD(),现在我们按下WASD就是分别实例化不同的命令对象,然后去传给Player
对请求排队或记录请求日志
这样来说,我们现在已经将控制玩家的命令进行实例化成命令对象了,每次我们只需要传命令对象给Player,我们就可以控制它。
但是,Player是如何对这个命令进行解析的呢?
这个命令对象,内部有Todo方法,让我们去做什么,也有Undo方法,提供给我们撤销命令。比如说,我们按下A,实例化一个往左的命令给Player,但是如果我们这个不需要往左了,我们要撤销这个命令,但是我们已经执行到一半了,应该怎么办?我们这个往左的命令对象,比如说是让我们往左1m,那么我们这个命令对象就应该记录命令执行前的位置。当我们调用命令对象的Undo撤销命令的时候,我们就往命令执行前的位置回退即可。
这里,我们就可以引入请求排队的概念了,如果我们按下了A和D,这个时候我们Player接受到两个命令,一个往左的命令对象,一个往右的命令对象,我们用个队列来存好,然后按顺序执行命令即可。
记录请求日志,也就是我们将队列入队的时候,进行记录即可
支持可撤销的操作?
前面我们举例了,我们每一个Player都有一个命令队列,那么,我们执行我们的一个命令对象的时候,如果我们选择不做了,比如Ctrl+Z,撤销操作,只需要调用命令对象的Undo函数即可。比如我们已经执行了WASD四个命令对象,我们撤销这四个命令,就反方向去撤销命令,UndoD,UndoS,UndoA,UndoW即可
综上所诉,就是命令模式的核心思想
如果你看完还是脑袋晕晕的话,那就请看下面我对命令模式的不同使用场景的详细举例
命令模式的使用场景
1.配置玩家按键输入
在每个游戏中都有一块代码读取用户的输入——按钮按下,键盘敲击,鼠标点击,诸如此类。 这块代码会获取用户的输入,然后将其变为游戏中有意义的行为:
下面是一种简单的实现:
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();
}
这个函数通常在游戏循环中每帧调用一次,我确信你可以理解它做了什么。 在我们想将用户的输入和程序行为硬编码在一起时,这段代码可以正常工作,但是许多游戏允许玩家配置按键的功能。
那么我们修改了配置按键,是不是我们要修改代码?然后重新运行项目?
为了支持这点,需要将这些对jump()和fireGun()的直接调用转化为可以变换的东西。
“变换”听起来有点像变量干的事,因此我们需要表示游戏行为的对象。进入:命令模式。我们将这些函数,封装成一个个的命令。
我们定义了一个 基类Command 代表可触发的游戏行为:
class Command
{
public:
virtual ~Command() {
} //构造函数
virtual void execute() = 0; // 命令函数
};
然后我们为不同的游戏行为定义相应的子类:
class JumpCommand : public Command
{
public:
//继承命令基类,重写虚函数,让该JumpCommand执行jump()
virtual void execute() {
jump(); }
};
class FireCommand : public Command
{
public:
//继承命令基类,重写虚函数,让该FireCommand执行fireGun()
virtual void execute(