游戏架构

游戏的架构分三层:游戏应用层、游戏逻辑层和游戏视图层 game architecture

游戏应用层负责如下图功能: application layer

游戏逻辑层: game logic

游戏视图层(人): game view human

游戏视图层(AI): game view AI

联机游戏网络架构: network architecture

视频接口使用OpenGL,音频接口使用FMod

编码的注意事项

代码规范:每个大阔号单独占一行。

保持编码风格的一致:函数命名如驼峰式,函数名的定义如get find等,都有统一的含义无论用在哪里。

不要将重要的代码隐藏,比如在构造函数中进行远程连接。避免在构造函数和函数重载中进行重要的。操重要的操作应该显式调用。如果拷贝构造函数中有复制几十m的文件操作,可以设置为private,否则被人调用你的接口时会以为是很小的文件操作。

类的继承关系应该尽量平缓,深度控制在2到3层。

区分组成和继承,这只是展示给别人看的,编译器不关心。

虚函数重载层次太多会造成上层修改导致连锁反映。定义虚函数时应该使用模版设计模式将业务分开,比如update操作应该区分为aiupdate、moveupdate、collisionupdate,这样重载就分开了,他们之间的执行顺序都在上层定义。

使用纯虚函数做接口,使用接口有很多好处,可以屏蔽实现细节。

使用工厂模式,有序的构造复杂对象,还可以用buildxxx延迟构造时间。

将将来可能会修改的东西封装起来,和不易变的分离。

持久化的数据使用stream来初始化。

在使用指针时,如果指针指的对象对外不可见,那么就可以用原始指针;如果是事件那样的对象,就应该用只能指针,你不一定知道有几个事件接受者。

可以说适当使用share_ptr和weak_ptr。

利用系统的内存管理功能往往不能很好的管理,自己创建。程序在运行起来后有3部分内存,一部分是全局变量,一直在内存中;一部分是栈空间,存储函数调用和局部变量;还有一部分是堆空间,动态的分配和回收。

内存访问时数组循环时按照行列效率相差很大。

内存地址对齐可以加速访问速度。

数据结构在选择时,如果是需要频繁遍历的可以用哈希表或树,否则可以用松散的结构。

自己写内存管理就是因为编译器会浪费许多小内存空间。

构造内存池来快速分配内存。 memory pool

构建的每个内部工具都要有命令行,以便自动化构建。

游戏初始化

游戏初始化顺序:

  1. 检查系统资源,如硬盘空间、内存、输入输出设备
  2. 检查CPU速度
  3. 初始化随机数生成器
  4. 载入用于调试的程序员选项
  5. 初始化内存cache
  6. 创建窗口
  7. 初始化音频系统
  8. 载入玩家游戏选项和存档
  9. 绘制界面
  10. 游戏系统初始化,如物理引擎、AI

系统在初始化的时候,应该在失败时能够给用户提示信息。

最好先初始化字符串子系统,使得在出错时可以给用户提示信息或者错误码。

全局变量最好是用指针而不要用对象。

在释放资源时应该和创建的顺序相反,否则可能死锁。

Game application Layer是入口点,应该是一个全局单利对象,而且对于不同操作系统应该重写这个类。

各种初始化的顺序和关闭的顺序应该相反,但是如果两个子系统之间相互依赖,那么只能先将一个子系统在不完整的情况下初始化,另一个在初始化后再通知第一个。

游戏对象的初始化顺序:

  1. 检测多实例
  2. 检查硬盘空间和内存
  3. 计算CPU速度
  4. 加载游戏资源缓存
  5. 加载展示的字符串
  6. 创建LUA脚本管理器
  7. 创建事件管理器
  8. 使用脚本管理器加载初始游戏设置
  9. 初始化应用窗口和设备
  10. 创建游戏逻辑和游戏试图
  11. 设置保存游戏的目录和其他临时文件
  12. 从资源缓存预加载选中的资源

在初始化时,如果两个子系统相互依赖,先使第一个子系统以半启动状态创建,然后初始化另一个,再回头通知第一个子系统另一个子系统已存在。

检测多实例:打开游戏时由于着急可能会连点几次,会导致多实例都初始化,通过多实例检测只让其启动一个实例。

检测内存时,需要检测安装的物理内存,也可能需要考虑虚拟内存,在使用虚拟内存时需要注意,错误的使用可能会导致加载缓慢,影响游戏效果。

文本、声音、贴图、视频等资源文件都打包成zip文件,一起加载到资源缓存,方便后续使用。但是一旦加载失败,可能导致崩溃,并无法弹出提示框,要拿捏好缓存大小的度。缓存数据需要能处理每种文件类型的加载器,将raw文件转换为系统可以识别的文件类型。

从资源缓存中读取json格式的文本文件,再存储在map中,加载文本应该优先处理,系统的各种菜单显示都需要他。

lua脚本管理器启动之后的初始化序列由lua脚本控制。

游戏的保存路径通常会被限制在win的用户目录,用SHGetSpecialFolderPath()可以获取到用户路径。

游戏的目录通常会是公司名/游戏名/版本号,用SHCreateDirectoryEx()可以自动创建不存在的路径。

按照不同资源文件的格式进行预加载,如.jpg和.ogg等。

关闭游戏不应该直接用exit(0)拉闸(yank the power cord),有两种退出方式:

  1. 玩家主动退出 先检测存档是否有变化,有变化就询问玩家是否需要保存。
  2. 操作系统关闭应用 系统关闭时会给应用程序发送WM_SYSCOMMAND消息,并查找wParam参数中的SC_CLOSE,这就告诉应用程序要被关闭。关机、低电量和ALT+F4都会引起。

心急的用户会按多次ALT+F4,会出发多个WM_SYSCOMMAND,需要对这种情况处理。

游戏打开时,当玩家切换到其他界面或者最小化游戏时,采取措施告诉他们游戏还在那里,快来玩我。比如让窗口隔段时间闪烁一下,引起玩家注意。

当处于一个模态窗口时,被点了关闭游戏或者关闭操作系统,应该执行模态窗口的默认选项,再发送WM_SYSCOMMAND消息关闭游戏。

关闭游戏时,以与初始化顺序相反的顺序释放资源。

游戏actor和组件架构

游戏中的所有实体都是actor,小到子弹,大到坦克,你能想到的任何东西都是一种actor。

用继承的方式可以实现actor,但是会有多重继承,这样就会有问题。

mult inherit

图中ClassA有一个属性name,ClassB和ClassC都继承自ClassA,那么他们都有name属性,ClassD在多重继承他们俩时,就会出现二义性,必须要选择二者之一的属性作为ClassD的属性,这样就会多存储不必要的相同属性,如果继承层次多并且属性数据很大时就会造成大问题。

那么应该使用组合的方法设计Actor,每个Actor设计成由某些组件组成的,设计合理的组件就可以动态的给Actor增删组件,以便改变Actor的行为。

actor component system

用工厂创建Actor,工厂的任务就是读取json来生产对应的actor。

通过设置actorid可以方便lua进行控制,而不需要了解处理的内部细节。