1 架构、性能和游戏
1.1 什么是软件架构
软件架构:代码的组织方式。
好的架构:适度抽象,在轻松应对快速变化的同时不过于复杂冗余。
核心思想:解耦,最小化解决单个问题需要的代码学习和知识储备。
1.2 有什么代价
添加更多代码并对抽象层进行开发、调试和维护。
添加补丁的同时需要谨慎的组织代码维护架构。
增加了解决问题时理解代码的难度。
1.3 性能和速度
灵活的模式通常依赖于运行成本较大的机制。例如虚函数派发、指针、消息、接口。
折中方法:先保持灵活性,直到设计稳定下来再去除抽象提高性能。
1.4 设计的灵活性
为了验证想法的代码大部分需要抛弃。因此原型是一个完全正确的编程实践,只要保证以后重写。
1.5 寻求平衡
开发本身也需要权衡利弊。
- 良好的架构
- 优秀的性能
- 快速的编码
1.6 简单性
一个思路是努力试着编写最干净、直接的函数来解决问题。
用一小块逻辑解决一大片用例永远是最优雅的。其本质是寻找到了用例背后的模式关系。
If I had more time, I would have written you a short letter. —— Blaise Pascal
1.7 TIPS
- 抑制住抽象和解耦的冲动,除非必要,否则别浪费时间
- 大胆尝试,探索设计空间,但不要留下一个烂摊子继续往前
- 底层优化能晚则晚
- 最重要的是:开心就好!
2.命令模式
2.0 概述
将一个request封装成一个对象,从而允许使用不同的请求、队列或日志将客户端参数化,同时支持请求操作的撤销与恢复。 —— GoF
A command is a reified method call. 命令就是一个具象化(实例化)的方法调用。 —— Robert Nystrom
2.1 配置输入
按钮与游戏行为(方法)的映射,为了支持自定义配置,就需要将直接调用转换为可以swap out的变量分配。
首先对将不同游戏行为的调用(回调)具象为命令。虽然函数指针就可以做到这一点,但并不完整,所以这里定义命令基类class Command。
class Command{
public:
virtual ~Command() {};
virtual void execute() = 0;
}
为不同游戏行为(方法)分别创建不同命令子类,并对执行操作虚函数进行重载绑定。
class JumpCommand : public Command {
public:
virtual void execute() { jump(); }
};
// ...
最后就可以通过间接调用层的命令指针处理输入。
class InputHandler{
public:
void handleInput();
// Methods to bind commands to buttons
private:
Command* buttonX_;
// ...
}
void InputHandler::handleInput(){
if (isPressed(BUTTON_X)) buttonX_->execute();
// else if...
}
// 这种方式是将执行功能集成在了命令内部,应该是比较低级的命令,之前写过的完全抽象成数据的命令,需要装载数据到执行模块才能用的比较高级。不过提供了添加一个装载到执行器并执行的基类方法的想法。
// 早知道不敲这么多憨憨代码了,诚招打字员
2.2 关于角色的说明
命令模式更普遍的应用场景是作为游戏AI引擎和角色之间的接口。通过命令模式,AI模块和角色行为实现了解耦。AI模块只需要简单的提供命令流以供角色(引擎)执行。同样,对于多人网络游戏,命令序列化、传送、再回放的过程是关键部分。
2.3 撤销和重做
如果一个命令对象可以do,也应该可以轻松的undo,尤其是在回合策略类游戏中。为此,输入处理程序需要对每次输入的动作创建一次性命令实例。
class Command{
public:
virtual ~Command() {};
virtual void execute() = 0;
virtual void undo() = 0;
};
为了撤销状态,可以在 execute() 执行的时候先记录当前状态。通过在命令里增加记录状态的变量实现。
为了支持多次撤销,我们需要维护一个命令列表和一个对当前命令的引用。在执行、撤销、重做或选择新命令后根据情况对命令列表和当前命令引用进行更新。
2.4 类风格化还是函数风格化
函数式编程在解决许多问题时拥有高效性,例如用 js 实现命令模式。
function makeMoveUnitCommand(unit, nextStatus) {
var beforeStatus;
// return command object
return {
execute: function() {
beforeStatus = unit.status();
unit.moveTo(nextStatus);
},
undo: function() {
unit.moveTo(beforeStatus);
}
}
}