游戏编程
游戏架构
游戏的架构分三层:游戏应用层、游戏逻辑层和游戏视图层
游戏应用层负责如下图功能:
游戏逻辑层:
游戏视图层(人):
游戏视图层(AI):
联机游戏网络架构:
视频接口使用OpenGL,音频接口使用FMod
编码的注意事项
代码规范:每个大阔号单独占一行。
保持编码风格的一致:函数命名如驼峰式,函数名的定义如get find等,都有统一的含义无论用在哪里。
不要将重要的代码隐藏,比如在构造函数中进行远程连接。避免在构造函数和函数重载中进行重要的。操重要的操作应该显式调用。如果拷贝构造函数中有复制几十m的文件操作,可以设置为private,否则被人调用你的接口时会以为是很小的文件操作。
类的继承关系应该尽量平缓,深度控制在2到3层。
区分组成和继承,这只是展示给别人看的,编译器不关心。
虚函数重载层次太多会造成上层修改导致连锁反映。定义虚函数时应该使用模版设计模式将业务分开,比如update操作应该区分为aiupdate、moveupdate、collisionupdate,这样重载就分开了,他们之间的执行顺序都在上层定义。
使用纯虚函数做接口,使用接口有很多好处,可以屏蔽实现细节。
使用工厂模式,有序的构造复杂对象,还可以用buildxxx延迟构造时间。
将将来可能会修改的东西封装起来,和不易变的分离。
持久化的数据使用stream来初始化。
在使用指针时,如果指针指的对象对外不可见,那么就可以用原始指针;如果是事件那样的对象,就应该用只能指针,你不一定知道有几个事件接受者。
可以说适当使用share_ptr和weak_ptr。
利用系统的内存管理功能往往不能很好的管理,自己创建。程序在运行起来后有3部分内存,一部分是全局变量,一直在内存中;一部分是栈空间,存储函数调用和局部变量;还有一部分是堆空间,动态的分配和回收。
内存访问时数组循环时按照行列效率相差很大。
内存地址对齐可以加速访问速度。
数据结构在选择时,如果是需要频繁遍历的可以用哈希表或树,否则可以用松散的结构。
自己写内存管理就是因为编译器会浪费许多小内存空间。
构造内存池来快速分配内存。
构建的每个内部工具都要有命令行,以便自动化构建。
游戏初始化
游戏初始化顺序:
- 检查系统资源,如硬盘空间、内存、输入输出设备
- 检查CPU速度
- 初始化随机数生成器
- 载入用于调试的程序员选项
- 初始化内存cache
- 创建窗口
- 初始化音频系统
- 载入玩家游戏选项和存档
- 绘制界面
- 游戏系统初始化,如物理引擎、AI
系统在初始化的时候,应该在失败时能够给用户提示信息。
最好先初始化字符串子系统,使得在出错时可以给用户提示信息或者错误码。
全局变量最好是用指针而不要用对象。
在释放资源时应该和创建的顺序相反,否则可能死锁。
Game application Layer是入口点,应该是一个全局单利对象,而且对于不同操作系统应该重写这个类。
各种初始化的顺序和关闭的顺序应该相反,但是如果两个子系统之间相互依赖,那么只能先将一个子系统在不完整的情况下初始化,另一个在初始化后再通知第一个。
游戏对象的初始化顺序:
- 检测多实例
- 检查硬盘空间和内存
- 计算CPU速度
- 加载游戏资源缓存
- 加载展示的字符串
- 创建LUA脚本管理器
- 创建事件管理器
- 使用脚本管理器加载初始游戏设置
- 初始化应用窗口和设备
- 创建游戏逻辑和游戏试图
- 设置保存游戏的目录和其他临时文件
- 从资源缓存预加载选中的资源
在初始化时,如果两个子系统相互依赖,先使第一个子系统以半启动状态创建,然后初始化另一个,再回头通知第一个子系统另一个子系统已存在。
检测多实例:打开游戏时由于着急可能会连点几次,会导致多实例都初始化,通过多实例检测只让其启动一个实例。
检测内存时,需要检测安装的物理内存,也可能需要考虑虚拟内存,在使用虚拟内存时需要注意,错误的使用可能会导致加载缓慢,影响游戏效果。
文本、声音、贴图、视频等资源文件都打包成zip文件,一起加载到资源缓存,方便后续使用。但是一旦加载失败,可能导致崩溃,并无法弹出提示框,要拿捏好缓存大小的度。缓存数据需要能处理每种文件类型的加载器,将raw文件转换为系统可以识别的文件类型。
从资源缓存中读取json格式的文本文件,再存储在map中,加载文本应该优先处理,系统的各种菜单显示都需要他。
lua脚本管理器启动之后的初始化序列由lua脚本控制。
游戏的保存路径通常会被限制在win的用户目录,用SHGetSpecialFolderPath()
可以获取到用户路径。
游戏的目录通常会是公司名/游戏名/版本号
,用SHCreateDirectoryEx()
可以自动创建不存在的路径。
按照不同资源文件的格式进行预加载,如.jpg和.ogg等。
关闭游戏不应该直接用exit(0)拉闸(yank the power cord),有两种退出方式:
- 玩家主动退出 先检测存档是否有变化,有变化就询问玩家是否需要保存。
- 操作系统关闭应用 系统关闭时会给应用程序发送WM_SYSCOMMAND消息,并查找wParam参数中的SC_CLOSE,这就告诉应用程序要被关闭。关机、低电量和ALT+F4都会引起。
心急的用户会按多次ALT+F4,会出发多个WM_SYSCOMMAND,需要对这种情况处理。
游戏打开时,当玩家切换到其他界面或者最小化游戏时,采取措施告诉他们游戏还在那里,快来玩我。比如让窗口隔段时间闪烁一下,引起玩家注意。
当处于一个模态窗口时,被点了关闭游戏或者关闭操作系统,应该执行模态窗口的默认选项,再发送WM_SYSCOMMAND
消息关闭游戏。
关闭游戏时,以与初始化顺序相反的顺序释放资源。
游戏actor和组件架构
游戏中的所有实体都是actor,小到子弹,大到坦克,你能想到的任何东西都是一种actor。
用继承的方式可以实现actor,但是会有多重继承,这样就会有问题。
图中ClassA有一个属性name,ClassB和ClassC都继承自ClassA,那么他们都有name属性,ClassD在多重继承他们俩时,就会出现二义性,必须要选择二者之一的属性作为ClassD的属性,这样就会多存储不必要的相同属性,如果继承层次多并且属性数据很大时就会造成大问题。
那么应该使用组合的方法设计Actor,每个Actor设计成由某些组件组成的,设计合理的组件就可以动态的给Actor增删组件,以便改变Actor的行为。
用工厂创建Actor,工厂的任务就是读取json来生产对应的actor。
通过设置actorid可以方便lua进行控制,而不需要了解处理的内部细节。