这里的“游戏”我只讨论一般的小游戏,这里的渲染器也特指游戏中负责所有(或者说大部分)渲染工作的那个对象。而我这里说所的“架构”则是指如何安排这个“渲染器”与游戏中其他对象(类)的关系,之所以要讨论这种关系,是因为很多时候我们都需要改善游戏中逻辑部分和渲染部分之间的关系。如果这种关系耦合度太高,最直接的问题就是导致代码可扩展性不高。
渲染代码往往依赖于开发所使用的开发包。假使你直接使用DirectX或者OpenGL,你的渲染代码很有可能会直接涉及到各种DirectX或OpenGL中的概念。在代码级别,就是会牵扯进很多跟开发包相关的类,函数,结构体等等之类。
也许你会对DirectX或OpenGL做二次封装,开发出一些游戏引擎(或者只是单纯的图形引擎)。于是,现在的渲染代码就可以只跟你的游戏引擎相关(代码级别)。
无论如何,如果直接使用DirectX或OpenGL(一次封装都没做这是很烂的方式),当你移植游戏时,你很有可能就会面临从DirectX到OpenGL代码转化的问题(反之亦然)。而使用游戏引擎呢?游戏引擎隔离了底层具体的开发包(DirectX或OpenGL),但是如果要换引擎呢?换引擎这样的情况,我认为是完全存在的,例如现在在Windows下开发了一个基于HGE的游戏,现在我想把这个游戏移植到Linux下。而HGE是基于DirectX的,要移植到Linux下,你当然可以保持HGE的接口不变,而使用OpenGL重写HGE。这里,你就需要重新开发一个引擎!这很浪费时间!所以你可能会使用另一个在Linux下运行的引擎(跨平台的引擎也可以),例如ClanLib,于是,这里就涉及到了“换引擎”的问题。
从上文我们可以看出,基于很多原因(不仅仅是要把代码做移植,好的架构还利于代码的扩展,利于开发过程---特指编码过程----的平稳进行等等之类),游戏中渲染器的架构需要得到关注。
接下来我将讨论几种不同的架构:
一、这也是最烂的方式,游戏中无论在什么地方,只要需要渲染了,就加渲染代码。其结果就是各种与图形开发包相关的东西铺天盖地。这样的代码根本没任何优雅性可讨论。事实上,在很多游戏开发相关的书籍中早就提到过类似“将游戏逻辑和渲染分离开来”的观点。
二、这是我使用了很多次的架构方式,类图为:
![]() |
| 图1 |
Game类是一个Manager类,Renderer类里包含了游戏中所有的渲染代码,Monster是一个具体的怪物类,它没有类似于Render或者Draw之类的接口,这些接口都被放在Renderer类里,例如RenderMonster()。当游戏需要渲染这个Monster对象时,Game对象就调用Renderer类的RenderMonster接口。
于是,现在Monster类里(基于这种架构,其他所有的精灵类以及各种需要渲染的非精灵类)就没有任何与图形开发包相关的东西。而与图形开发包相关的东西则都集中到了Renderer类里。当你要移植代码时,只需要重写Renderer类即可。
但是这种架构还是有很多问题:
1、随着游戏越做越大,游戏中需要得到渲染的物体就会越来越多。这样,每次都给Renderer类添加一个RenderSomething之类的接口(注意,除了添加接口外,很有可能还要添加一些渲染所需要的对象,例如Surface之类的)。Renderer类最终将成为一个非常巨大的类,这违背了面向对象设计的原则(巨类导致的最直接的坏处就是改代码痛苦-----鼠标滚轮滚半天才找到目标代码。just a joke。)
2、事实上,Renderer类的RenderSomething之类的接口在被调用时还需要一些渲染参数。例如渲染一个精灵类的话,就需要知道要渲染哪一帧,以及渲染到哪里之类的信息。在某些时候,这些参数会让这些接口的declaration变的很恶心!(代码要优雅,先把格式写好看点------当然你愿意去参加“混乱代码大赛”的话倒可以朝这个方向发展,another joke)
三、我始终记得一句关于面向对象设计的话:“面向对象是用来模拟世界的”(大致是这样的)。这句话其实真的可以作为面向对象开发的一个指导性原则,它会让你在架构系统时,变得很容易。因此,套用这句话的话,上面那种架构方式就出现一些让人觉得别扭的地方了。怪物对象为什么不能自己渲染自己?(难道一个人表现自己的能力都没有?烂比喻!)按照那条原则,怪物类理所应该拥有一个Render之类的接口!
于是,架构二稍微做了些变化:
![]() |
| 图2 |
我们让每个需要渲染的物体,这里是Monster,都有一个Render接口以及一个Renderer对象指针。在创建该精灵对象时,Game类把自己创建的Renderer对象指针传给该精灵对象。该精灵对象再保存该指针到mRender成员。当渲染时,Game类不再直接调用Renderer对象里的RenderSomething之类的接口,而是很自然地调用Monster的Render接口。然后Monster的Render接口再通过自己保存的Renderer对象指针调用其RenderMonster接口。
这里,我们为了让设计更贴近真实世界,就多加了一条依赖关系。(这其实纯碎是某些完美主义程序员的癖好-----for instance, me :D)
| 共2页: 1 [2] 下一页 | ||
|
|
||||
| · NAC安全访问控制 · 网络布线测试仪器 · Windows Server 2008专.. · Windows远程桌面应用 · 网络故障排除宝典 · 运营商封堵ADSL共享 中.. · 解析35岁技术人的价值.. · 世纪枭雄比尔盖茨的王.. |
· 主流品牌防火墙配置 · ASP.NET开发教程 · 超级计算机TOP500专题 · Vista SP1对决XP SP3 · SQL Server 2008/2005.. · 程序员如何成长? · C#技术开发指南 · 虚拟化技术还有点“虚” |
|||
|
||||
| · SOA 面向服务架构 · SQL Server 2008/2005.. · Apache技术专题 · 三层交换技术专题 · SQL Server入门到精通 · Windows远程桌面应用 · C#技术开发指南 · Apache技术专题 |
· Windows集群服务应用 · C#技术开发指南 · 国际文档格式标准开战 · 路由器设置与口令恢复 · Linux 集群技术专题 · PHP开发应用手册 · SOA 面向服务架构 · 企业数据恢复指南 |
|||
|
||||
| · SQL Server入门到精通 · SQL Server 2008/2005.. · SOA 面向服务架构 · Apache技术专题 · C#技术开发指南 · 三层交换技术专题 · Apache技术专题 · C#技术开发指南 |
· Windows远程桌面应用 · 企业数据恢复指南 · Windows集群服务应用 · 路由器设置与口令恢复 · Linux 集群技术专题 · SOA 面向服务架构 · 了解统一威胁管理(UTM).. · 反垃圾邮件技术应用 |
|||