与OGRE的第一次接触

OGRE最近花费了大把的时间,努力的学习如何与OGRE这个名声远播的巨兽成为好朋友。到目前为止,总算是能够称得上稍微和它混熟了一点。关于OGRE,这里提出几点我的学习笔记、目前观察到的个性特质,以及与它相处的心得感想。

首先,对于稍具经验的程序设计者来说,见到OGRE的第一眼印象,应该莫过于各种设计模式的广泛应用了。在OGRE的框架设计与系统实现层面中,使用了Abstract Factory、Factory Method、Singleton、lterator、Observer以及Manager等等许多注明的设计模式。说实在的,我还是头一次看到这么多不同种类的设计模式,能够同时应用在一个游戏绘图引擎之中,正好能够与以前所学的理论知识相互印证,学习起来非常爽快也很感动,这是令我大开眼界、大呼过瘾!

而其中,最显而易见的设计模式就是Singleton与Manager的结合使用。OGRE利用这两项模式的组合,制造出了一位一位各有擅长的【专业经理人】,使各个子系统所负的权责,都能够划分的非常清楚仔细。另外,利用Observer的概念,让使用者能够集成FrameListener类别,自定义进行绘图程序前后的相关处理程序,也是一个非常优秀的设计模式应用实例。

回想自己当初刚学习ogre时,额外看了许多书籍网站的资料,最后终于利用Singleton与Manager模式写作出几项游戏专案的子系统组件。结果深入认识了OGRE以后,才发现原来这些观念已经如此广泛运用在游戏引擎的设计框架之中。正所谓【他山之石,可以攻错】,对于程式设计者来说,吸引新知识以及学习别人的长处,的确是十分关键而不可或缺的能力啊。

如果以程序语言的角度观察,OGRE可以说是一个拜拜纯天然的C++绘图引擎。与其它多数的游戏引擎绘图引擎不同,OGRE身上没有旧时代一流下来的沉重包袱,或者为了从C语言转换至C++的【怪味道】等等过期的设计与写作方法。举例来说,OGRE对于namespace语法的全面使用,就是一个非常正确而且便利的做法:

  1. namespace Ogre  
  2. {  
  3.     class SceneManager  
  4.     {  
  5.         // INSERT useful code here.  
  6.     };  
  7. }  

从C语言时代逐渐演变的琢磨而来引擎的前辈们,由于担心发生命名冲突(Nameing Confliction)的问题,所以一般常见的做法,就是在原始码的类别与结构名称里,加上一个自定的前缀词(Prefix),形成OgreSceneManager、OgreSceneNode、OgreRenderSystem以及OgreXXX等等的类别名称。只要如OGRE般善用C++语言中的namespace语法,就能够拜托这些奇怪又累赘的前缀词,更加能够良好的切割类别名称的定义空间。

而在OGRE复杂庞大的内心世界中,除了独立运作的Root类别以外,其他的类别们主要可以分成场景管理、资源管理以及绘图系统三大类:

  • Scene Management:用来管理游戏中的所有物件的外显行为,相关的类别有SceneManager、SceneNode、MovableObject等等。
  • Resource Management:负责创建、分配于管理例如Font、Mesh、Texture、Material等等游戏中与实体档案相关的资料,相关的类别有ResourceManager、ArchiveManager、MaterialManager、TextureManager等等。
  • Rendering:掌握绘图程序系统,相关类别有Renderable、RenderWindow、RenderSystem、RenderQueue等等。

然而,如果OGRE中仅有这三项系统,其实难以满足不同专案的不同需求,达到引擎本身应有的广泛应用性与延展性。在不需要修改源代码的前提下,如何让使用者达到最大的自由度,甚至能够自由扩充原有的引擎功能?插件(Plugin)系统,就是OGRE给程序设计者的答案。有了场景、资源与绘图系统三大基础套件,在加上非常具有弹性的插件架构,就形成了OGRE之中最关键的黄金四角。而OGRE的心脏命脉,就在于万能是SceneManager类中!SceneManager像是一座巨大的物件工厂,掌握了OGRE中多数物件与资源的生杀大权,能够生产SceneNode、Material、Light以及Animation等等许多类别物件并且加以管理。

如果有使用过其它商业游戏引擎,或Scene Graph框架引擎的程序设计者,在初次与OGRE相识时,最难以适应的特点应该就是Node与Object分离而治的框架了。将SceneNode与SceneObject分离,是OGRE与其它绘图引擎最大的不同点。刚开始使用的时候,需要话费不少时间重新适应使用习惯;而在习惯之后,就能够发现这样的框架不但扩充弹性较佳,在系统效能上也比传统的SceneGraph框架有更为杰出的表现。

到此为止,听起来OGRE似乎是个有百利而无一害的亲切巨兽?然后,如同硬币一样,所有的食物都有正反面两面的角度。

对于反面的第一眼观察,我比较难以理解的是,为什么OGRE明明是以C++语言撰写的引擎,缺要使用Java式的命名规则,使所有的成员函数名称,都以首字字母小写的形式存在,例如个人getAnimation()与setAnimation()而非C++中常见的GetAnimation()与SetAnimation()形式。对于使用者来说,在配合自己所撰写的程序代码时,需要特别注意命名管理与编写风格上的统一。

另外,OGRE对于例外处理(Exception Handling)机制的大量使用,令我产生不少使用上的疑惑。举个例子来说,即使是在很寻常的getAnimation()函数中,只要使用者传入了目前不存在其中的字符串名称,OGRE就会立即抛出异常:

  1. String kAnimationName = "char_idle"// 假設 char_idle 不存在於 mSceneMgr 中  
  2. Animation* pkAnim = mSceneMgr->getAnimation(kAnimationName); // 這裡會拋出例外狀況!  
  3. if (pkAnim == NULL) {  
  4.     // Create animation and/or report error  
  5. }  

所以为了正确的取得Animation物件,必须要使用这样的做法:

  1. Animation* pkAnim = NULL;  
  2. String kAnimationName = "char_idle";  
  3. // 先檢查存在性(不會拋出例外狀況)  
  4. if (mSceneMgr->hasAnimation(kAnimationName)) {  
  5.     // 確認存在就可以取值  
  6.     pkAnim = mSceneMgr->getAnimation(kAnimationName);  
  7. }  

原来可以直接检查getAnimation()返回值是否为NULL的方式,在OGRE中需要多出一个检查步骤,对于许多程序员来说,可能一时也难以习惯这样的做法。而在OGRE中处处可见、随手可抛出的例外状况的情境之下,很容易出现以下这样非常糟糕的代码编写方式“:

  1. try {  
  2.     // Do something might throw exceptions!  
  3.     Animation* pkAnim = mSceneMgr->getAnimation(kAnimationName);  
  4. }  
  5. catch (...) {  
  6.     // 忽略,不做任何處理!  
  7. }  

为了能够顺利创建出Animation物件,不产生例外状况,程序员甚至会使用以下的做法:

  1. Animation* pkAnim = NULL;  
  2. float fLength = 1.0f;  
  3.   
  4. try {  
  5.     // 如果 kAnimationName 不存在,就會拋出例外而進入 catch 區塊  
  6.     pkAnim = mSceneMgr->getAnimation(kAnimationName);  
  7. }  
  8. catch (...) {  
  9.     // 在 catch 區塊中創建所需的 Animation 物件  
  10.     pkAnim = mSceneMgr->createAnimation(kAnimationName, fLength);  
  11. }  
相信比较有经验的程序员应该都知道,C++ Exception Handling机制应当要使用在极少发生的例外状况中,而不是用来取代所有的可能合理发生的空值状态。这样的使用方法,我个人觉得很不恰当,除了对程序执行效率上的冲击意外,在程序员疏忽大意的情况下,更容易造成错误的编码习惯。如果,世界上只存在着两种类型的程序员,其一是【赞同使用异常处理情况】,其二是【反对使用异常处理情况】的话,我应该是属于【反对在C++中使用异常处理情况】的那一类。

而至目前为止,在与OGRE的相处过程中,我所遭遇到的最重大的问题,就是资源物件名称的唯一性。在OGRE的资源管理系统中,几乎所有的资源都是以string类型作为索引键值,存放在std::map结构中。这种设计方法的有点,在于程序员能够很有效率的检查资源的存在性并且进行存取动作。问题是,资源管理系统完全不允许重复索引键值的物件产生。当程序员手动建立物件时,能够轻易的避免键值重负的问题;然而,当资源物件是由实体档案读取进来的时候,只要载入同一个档案两次以上,OGRE就会毫不留情的发出哀嚎,强制中断程序运行。

追根究底,原因是在于目前的OGRE并不存在完善的资源物件Clone机制,这个缺陷对于欲建立大型游戏专案的程序员来说,应该是一个十分头痛难缠的问题;目前只能够由引擎的使用者,在建立资源午间之前,自行传入独一无二并且保证绝不重复的Unique String值,以确保程序执行的正确性。最近在OGRE官网论坛中,看到有人提出了这项资源索引键值冲突的问题,OGRE坐着的解决方案是利用一个Callback函数,让使用者可以自订遇到键值名称冲突时的处理程序,相关的代码应该会于下个OGRE主要的版本更新中给出。

最后,在文件方面,OGRE官方的正式教学文件太过于贫瘠,SDK中附上的Manual里竟然只有寥寥无几的几页内容。反而是在官网Wiki上的教学文章内容比较丰富,也有许多Step by Step的主题教学与实际的源代码范例,使初学者能够学习到一个比较系统化的知识。值得安慰的是,OGRE源代码的注释内容写的非常不错!只要参考API Reference文件的说明,在单一类别的使用上就不会遇到太大的问题,同时也能够自行下载到源代码,仔细的研究探索其中的编写细节。

至于在实体书籍方面,目前仅有《Pro OGRE 3D Programming》唯一一本。这本书在OGRE的基本概念与设计理念概述,都有相当详尽的介绍,可惜在进阶的主题上着墨不深,但仍然是一本OGRE必读的入门书籍。另外在中国的确,有许多游戏都是使用OGRE开发完成的,因此对于OGRE的研究不少,在网络上也能够找到非常多关于OGRE的资料。对于有志使用OGRE开发游戏的程序员来说,多多浏览官方发Forums也是必不可少的日常工作。

总而言之,比起我之前曾经使用过的商业引擎,OGRE可以说是一个设计框架很完善的新时代绘图引擎,但是在许多使用层面上的细节,仍然有待更进一步的更新与加强。即使某些部分的功能还不够完善,但就算是对于自行编写绘图引擎的程序员来说,OGRE也绝对是一个非常出色的参考典范。期待新的OGRE版本出来以后,能有更具突破性的长足进步!

以上,是我目前对于OGRE的一点使用经验与心得感想。如有其它想法,也欢迎提出讨论。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值