作者:bsucat
目录
0. 前言
1. RM的运作原理
1.1 概览
1.2 想象中的结构
2. 图像模块
2.1 10s的秘密
2.2 性能与偏见
2.3 绘制流水线
(待续)
0. 前言
什么人适合阅读这篇文章?
这篇文章涉及到的知识范畴,可能让大家感到陌生。确实,这篇文章不太适合新手阅读,对于大部分人来说这篇文章所讲的内容是没有用的。但是对想要了解RM内部运行原理的朋友来说,这篇文章的内容应该能值得大家一起分享、探讨。
这篇文章里讲到的RGSS版本?
由于RM并不是一个公开源代码的商业软件,各位也对RM的反编译很反感( 我也没那个能力),所以这篇文章的内容,只是我凭个人经验对RM底层引擎的一个猜测性的分析。如果有不对的地方,请大家务必谅解,烦请纠正。
另外,由于我个人对RMXP版本比较熟悉,所以这篇文章主要分析的是RGSS而不是RGSS2 。RGSS2较之RGSS,在底层实现里并无太大的变化,只是更加规范,并增加了部分功能而已。所以这篇文章的大部分内容,对于RGSS2也是适用的。
1. RM的运作原理
1.1概览
可能有不少人诟病RM有这样那样的缺点,但是RPG Maker XP确实是一款设计得非常现代的2D游戏制作工具。从组织结构来讲,RMXP使用的是非常流行的游戏内容和游戏引擎分离的结构,游戏逻辑灵活地独立成一个模块,大概如下图所示。
不要觉得这个很简单哦,实际上要设计好一个引擎上的脚本系统并非易事,还好RM已经帮我们做到了。下面看看RM是怎么实现的。
一般来说,脚本有两种作用方式,一种是宿主语言(比如C)加载脚本处理部分内容,但控制权仍在宿主语言,另一种是则是宿主加载脚本后失去控制权,控制权在脚本语言。RM很明显是属于后者,RM在做了一系列初始化的工作后,调用ruby的C API(比如ruby_run?我对ruby的C API并不熟悉,只是猜测)来让ruby解释器取得了控制权。所以我们能感受到的整个游戏的运行过程,就可以完全由RGSS来控制了。
RGSS在整个RM的软件层次里,已经属于最高级的层次。如下图所示。
在脚本层之下的,便是游戏的引擎。RM通过扩展ruby库,将引擎的功能抽象化,并封装了部分操作。使用者并没有必要关心底层引擎究竟是怎么运行的,比如怎么使用DirectX ,怎么管理内存,怎么处理windows的消息循环等等。只要用户知道怎么使用RGSS,便可顺利地使用引擎做出自己的游戏。
这一部分被封装好的内容,是对用户透明的,通常情况下你无需了解。但是对于求知欲很重的我们来说,今天要探讨的,恰恰就是这部分内容了J。
1.2 想象中的结构
整个游戏的运行,在默认情况下,至少有以下三个文件存在,那就是:Game.exe,Game.ini和rgssxxxx.dll。赶快来猜猜,里面都有些什么吧(不是纯粹的瞎猜哦)。
Game.exe是游戏的主程序,Game.ini是配置文件,这个毫无疑问。Game.exe在一开始运行的时候,就从Game.ini里读取了相关的信息,比如窗口的标题是什么,一会动态加载的dll文件的名字(就是rgssxxxx.dll),以及哪里去找ruby解释器将要运行的脚本(比如默认的Script.rxdata)等等内容。解析ini文件很简单,调用windows提供的ini文件处理API就行了,所以我估计RM也是这么干的(不然呢?!)。
Game.exe本身并没有多少真正和游戏引擎相关的内容,甚至没有windows的消息循环(这个后面会分析),它更像是一个“傀儡”。而操纵它的幕后黑手便是rgssxxxx.dll。在这个dll里,封装了一个用DirectX实现的游戏引擎。Game.exe通过调用它提供的API,最终实现了我们想要的一切(好吧,也许你还想要得更多……)。
在一个游戏程序里,一般来说,大概流程如下:
这个循环,在RGSS的默认脚本里,也有所体现:
在这个循环里,update这一句,担负着的便是游戏逻辑处理的任务,Graphics.update从字面上看,明显是游戏画面的处理(实际上有更多内容……),那么窗口消息的处理跑到哪里去了呢?
2.
图像模块
2.1 10s的秘密
假如你有自己写过一个差劲的寻路脚本,导致游戏窗口弹出“脚本已备份”的提示栏,相信你对RGSS每10s至少要调用一次Graphics.update的限制并不陌生。有的人认为这个10s限制非常的没有道理,是RM的性能瓶颈。但经过我的仔细思考,事实并非如此。
熟悉windows编程的朋友们想必都知道,windows程序都是消息驱动型的,假如没有消息循环的更新,整个程序必定会陷入卡死的状态。前面我说过,在RM的Game.exe程序运行时,ruby解释器占用了整个程序的控制权。我们姑且假设在Game.exe中,一旦调用脚本,便无法返回。这样,窗口消息更新的任务,就只能交给脚本了。
假设脚本中不调用窗口消息的更新,整个程序必定陷入卡死状态,所以为了防止用户写出的糟糕脚本导致程序运行过慢而无法终止程序,很有必要加上只要10s没有调用消息更新,则自动终止程序的限制。
所以,没错,消失的窗口消息处理,实际上就封装在了Graphics.update里面。
所以Graphics.update的功能,现在看起来就像是这样了:窗口消息处理+渲染图像。但是,仅仅是这样吗?
不知道你有没有注意到,Graphics.frame_rate和Graphics.frame_count这两个Graphics模块的属性值,这说明RM是有帧速控制的。而从两个属性在RM文档里的说明来分析,帧速的控制很明显封装在了Graphics.update里来处理(至于什么是帧速控制?!这个可不是什么新鲜的东西,请自行搜索“regulating frame rate”)。
目前我们已知Graphics.update包含了窗口消息处理、帧速控制和图像渲染,而且它实际上可能还包含了更多东西(修改标题什么的?!天知道?!),不过那些我们现在都不关心了。接下来我想仔细说说的,就是图像渲染。
2.2 性能与偏见
RM的图像引擎用的是DirectDraw。这一点请不用怀疑,这可能是这篇文章中我最敢发誓是正确的观点……
不过rgssxxxx.dll并没有直接链接DirectDraw的库文件,而是在运行时加载的。这里我顺便提一下,微软从DirectX 8开始就不提供DirectDraw的文档支持了,而在之后,更是直接抛弃了DirectDraw 。但是,DirectX是一个COM组件,所以即使微软不再更新ddraw,它还是可以欢畅地在任何兼容它的新版本里运行的。
使用RM的人常常会有两个偏见。第一个是认为Ruby的解释器相对较慢,导致性能不佳;第二个则是认为DirectDraw是一个已经夭折的产品,基于它的RM引擎,必然性能表现不佳。
关于第一个偏见,我对ruby解释器速度的相关测试并不了解,但是就我个人使用的感受来说,一个设计得良好的RGSS脚本结构,不可能会慢到哪里去。很多人写脚本的时候并不考虑性能的开销,每一帧里都要做大量无用的循环检查,累得ruby解释器气喘吁吁(可怜的Ruby娘!J),这才是真正拖慢ruby解释器速度的元凶。
好吧,下面回到我们图像模块的正题上来,也就是第二个关于DirectDraw的偏见。固然,微软已经彻底地抛弃了DirectDraw,而DirectDraw也只能算一个半成品而已但这并不意味着DirectDraw就是一个低性能的东西。实际上,DirectDraw能满足制作2D画面的绝大部分需求,而且效率很不错。
可能有人心中会有疑问,既然DirectDraw能满足2D游戏的开发,为什么会有用Direct3D来开发的2D引擎?前面我说过了,DirectDraw只是一个半成品,很多功能微软并没有做,需要开发者自己去扩展,这无疑增加了开发者的使用难度。而之前我在国外某论坛上看洋人们讨论过这个问题,最后众洋人得出的结论是现在众多的硬件厂商在已经放弃产品对2D加速,也就是Blitter的支持(没错,Bitmap类里的blit兴许就是调用了它),而3D加速几乎是现在每块显卡都能支持的(我也不知道这结论是否属实,反正是洋人们说的!),所以Direct3D是更明智的选择……
话说回来,以上两点绝对不是导致你的游戏运行缓慢的性能瓶颈。当出现游戏运行迟缓的情况时,还是首先检查下你自己都写了些什么低效率的脚本吧,而不是一味地责怪RM愚笨!