设计与架构
-
分层:将庞大的系统,分割成功能独立的子系统。然后各个子系统进行协作,一起完成庞大的最终系统。
- 分层的细化:将每个层,进行细化,划分为更多的子层。
- 对层进行分区:区在层的内部,粒度比层小。
- 提取机制:预定义的能完成预期目标,基于抽象角色的协作方式。例如消息机制
通常的分层为:展现层、业务层、数据层。
通常的代码分层为:子系统、模块、类。
分层是对职责进行分离;机制是通用单元的分离。
-
接口定义: 协作定义接口。
设计模式
设计模式存在的意义:
- 在扩展功能时,产生的影响很小。
- 在替换原有代码时,产生的影响很小。
- 在移除旧代码时,产生的影响很小。
设计模式的准则:
- 一个类,不需要知道所依赖类的具体实现,只需要知道其接口即可。
- 优先使用组合,减少继承。
- 不好解决时,添加中间类。
常用设计模式
Command: 把函数作为命令。命令可以Undo,Redo等。
装饰模式:1. 将接口进行转换成期望的接口 2. 给原有类添加新的功能
Flyweight:共享元。多个对象,可以共享同一个数据。(核心相同部分只有一份,通过引用,减少内存占用。)(如森林,基本上树都相同)
Observer:观察者。事件机制。当事件触发时,只管触发,并不关心谁来处理此事件。(Fire and Forget)
struct IEvent {};
struct IObserver {
virtual void On(struct IEvent* ev) = 0;
};
template<typename TEvent>
class EventDispatcher<TEvent> {
std::vector<struct IObserver*> observers;
public:
void FireEvent(struct IEvent* ev){
for(auto o : observers) {
o.On(ev);
}
}
void AddObserver(struct IObserver* o){}
}
Singleton: 单例。确保类有且只有一个实例。这在如C++里,不能确定全局变量的初始化时机时,用于替代全局变量。
State: 状态:对象状态改变时,同时改变其行为。
class StateMachine {
struct IState* state;
public:
void SetState(IState* state) ;
void RunState();
};
迭代器:对聚集对象进行遍历。(针对易越界的情况)
如何接手大型游戏项目
快速适应大型已有项目非常具有挑战,通常会面临各种混乱的情况:代码风格混乱;文件结构混乱;各种模块代码耦合;设计文档缺失;文档不清等等。
- 面对混乱的局面,首先需要冷静,绝对冷静。
- 解耦合:化繁为简,便于可控。将游戏代码以功能划分模块。然后划分每个功能模块之间的桥接模块。
- 区分设计模式:MVC,事件机制,状态机等。对于一个成熟的游戏项目来说,通常会有游戏引擎和游戏模块组成。在游戏模块中,通常有UI、网络通信(RPC)、配置文件解析、SDK集成、关卡切换管理、特效、动画管理等。
- 程序由变量和函数组成,肯定有对应的引用。特别是类中的公有成员。
- 所有编写的代码,只是一份蓝图和布局,是编写者思想、解题思路的体现。代码只有在运行起来,才分配内存空间。计算机只在乎数据:要处理的数据存在还是不存在?如何对数据进行处理?
- 重构代码:不修改逻辑和数据布局的情况下,对代码以对阅读者更友好的方式进行修改。常见的往往在于修改名称(变量名称,是变量数据用途的注解),以及将揉合的庞大类,进行分拆。
名称是给人看的,机器只识别数据。代码质量高低,决定于人的主观意识。项目的理解快慢,取决于阅读者水平以及编写者对代码的处理能力。 编写者处理能力包括:
- 代码命名是否一致,且清晰简单
- 代码布局(分层,分目录)
- 逻辑是否清晰,是否干净利落,不参杂
- 是否有干净利落的文档
阅读者水平包括:
数据设计:
数据分为: - Table 数据: 从配置表直接读取的数据。Table数据往往是固定的数据,其数值只读 - Data 数据: 在游戏中,对此对象建模所需要的数据模型。