5、注意显卡的差异。现在显卡有两大主流ATI和NV,虽然显卡可以被主流的操作系统所支持,但是必须注意在不同的游戏平台上还是使用不同的GPU,而在GPU之间也相应有自己的功能扩展,因此在使用特定的扩展功能时必须检查一下是否被显卡所支持。blog.mypm.net
6、注意shader语言的差异。可编程图形语言的出现是最重要的一项发明,现在几乎每一个游戏都在使用这项技术,而正由于它的重要性现在出现了多个标准,HLSL只能用于DX中,而OpenGL由于标准的开放性更加混乱,每一个显卡厂商都根据自己的产品推出相应的扩展指令来实现shader,而NV更推出了GC可以同时适用于DirectX和OpenGL,这是一个非常好的想法,不过由于这不是一个开放的标准因此没有得到其他厂商的支持,在ATI显卡上运行GC代码你会发现比在NV显卡慢了几个数量级,由于上面的情况你需要根据不同的平台相应进行封装,方法和第4条一样。下面的建议值得你去考虑,当你使用DirectX平台时应当使用HLSL,而对于OpenGL可以封装为两个模块,根据显卡的不同进行切换,也可以使用GC特别为NV的显卡封装一个模块来对它进行优化。项目管理者联盟
这里需要补充一点,如果可以的话尽量和OGRE一样为不同的操作系统进行封装,这样方便在不同的系统之间进行切换。项目管理者联盟
接着看看如何实现游戏无关性,通常游戏引擎如果要实现游戏的无关性是非常困难的,这也就是说要求你的引擎适合所有的游戏类型,这太难了,考虑一下一个RPG游戏引擎如果用来做一个RTS游戏那简直是不可能,类似的你不可能拿Q3引擎来做RTS游戏,但是如果引擎设计的非常良好的话还是可以实现部分的游戏无关性。也就是说你可以将引擎的一部分模块设计成通用的模块,这样在开发其他类型的游戏时可以重用这部分的代码,这部分代码包括底层显示,声音,网络,输入等部分,在设计它们时你必须保证它们具有良好的通用性。项目管理培训
在这些问题之后你应当考虑程序的国际化问题。这也是非常重要的方面,因为你的游戏可能在其它国家发行,这主要是注意语言方面的问题,尤其是字符串的处理,在C++的标准库中提供了一个String容器,它提供了对国际化的良好支持,因此在引擎中你需要从头到尾的使用它。项目管理者联盟
接下来我们看看本文最重要的内容,如何组织一个引擎的架构。这是引擎最重要的部分,为什么重要呢?如果我们把引擎看作一间房子的话,那么架构可以看作是房子的框架,当你完成这个框架后就可以向框架内添砖加瓦盖房子了。下面让我们来看看如何构建这个框架,通常一个大型的软件工程是按照模块化的方式来构建的,编程之前要进行必要的需求分析,将软件工程根据不同的功能划分为几个较大的功能模块,对比较复杂的模块你可能还需要将它分为几个子模块,并需要给出各个模块之间的逻辑关系。当你编写一个引擎时也需要进行相应的功能分析,让我们看看如何来划分引擎的功能模块,如果按照上面的游戏无关性和相关性进行分析的话我们可以发现它可以分为游戏相关层和无关层两层,游戏相关层由于包含了游戏的逻辑性代码也被称为逻辑层。逻辑层应该位于引擎的最顶层,如果你在开发一个局域网或在线游戏的话,按照网络程序的C/S开发模式,这一层应该分为两个模块,服务器和客户端模块,它包含了和特定游戏相关的所有功能,如AI,游戏角色,游戏事件管理,网络管理等等。在它下面就是游戏无关层了,包括了引擎核心模块,GUI模块,文件系统管理模块等等,其中引擎的核心模块是最重要的部分,逻辑层主要通过它来和底层的模块打交道,它应该包含场景管理,特效管理,控制台管理,图形处理等等内容。在向下就是一些底层模块了,如图形渲染模块,输入设备模块,声音模块,网络模块,物理模块,角色模型模块等等,所有的这些底层模块必须通过核心模块来和逻辑层进行交互,因此核心模块是整个引擎的枢纽,所有的模块都通过它来进行交互。项目管理者联盟
下面看看应该如何来进行模块的设计,这里有一些通用的规则是你应当遵守的:blog.mypm.net
1、减少模块之间的关系复杂度。我们知道通常每一个模块内部都存在大量的对象需要在各个模块之间进行相互的调用,如果我们假设每一个模块内部对象的数量为N的话,那么每两个模块之间的关系复杂度为N*N,这样的复杂度是不可接受的,为什么呢?首先是它非常不利于管理,由于各个模块都存在大量的全局对象,并存在相互依存的关系,并且各自建立的时间各不相同,这就存在初始化顺序的矛盾,考虑这种情况,一个模块中存在一个对象需要另外一个模块中的对象才能进行初始化,当这个对象进行初始化时而另外的对象在之前并没有初始化就会引发程序的崩溃。其次,不利于多人进行同时的开发,由于各个模块存在相互依存的关系,当复杂度非常高时就会出现模块与模块的高度依存,也就是说一个模块没有完成下一个模块就无法完成,因此就需要一个模块一个模块按照它的依存关系进行编程,而无法同步进行。因此在设计模块时的第一件事情是减少模块之间的复杂度,为此你在设计模块时必须为模块设计一个交互接口,并约定所有模块之间的交互必须通过这个接口来进行,这样模块之间的关系复杂度就降低为1*1了,非常方便管理,同项目管理者联盟文章
时这非常利于多人之间进行开发,假如每个人负责一个模块的开发的话,那么你只需要先完成这个接口类,其他人就可以利用这个接口进行其他模块的开发,而不必等到你完成所有的类再进行,这样所有的模块都是同步进行,可以节省大量宝贵的开发时间。
2、对类的抽象接口而不是类的实现编程。这是《Design Patten》一书作者对所有软件编程者的建议,它也对游戏编程有很大的指导意义。对模块中所有被其它模块使用的类都要建立一个抽象接口,其它模块要使用这个抽象接口进行编程,这样其它模块就可以在不需要知道类是如何实现的情况下进行编程。这样做的好处是在接口不改变的情况下任意对类的实现进行改变而不必通知其它人,这对多人开发非常有用。项目管理者联盟
3、根据调用对象的不同对类进行分层。实际上本条还是对第2条的补充,分层还是为了更好隐藏底层的实现。通常一个类不仅被其它模块使用还要被自身模块所调用,而且它们需要的功能也不同,因此我们可以让一个类对外部显现一个接口而对内部也显现一个接口,这样做的好处和上面一样,因为一个复杂的模块也是多人在进行编程的。项目管理者联盟
4、通过让一个类对外显现多个接口来减少类的数量。减少关系复杂度的一个方法是减少类的数量,因此我们可以把完成不同功能的类合并成一个类,并让它对外表现为多个接口,也就是一个类的实现可以继承多个接口。项目管理者联盟
上面的建议只是起到参考作用,具体实现时你应该根据情况灵活使用,而不是任意乱用。
下面的内容涉及到具体的编程技巧,对于引擎中的全局对象你可以使用Singleton,如果你不了解它是什么可以阅读《Design Patten》,里面有对它的详细介绍,具体的使用可以通过OGRE引擎获得。service.mypm.net
调用模块内的对象可以通过类厂来实现。COM可以看作是一种典型的类厂,DX就是使用它来进行设计的,而著名的开源引擎Crystle Space也是通过建立一个类似的COM物体来实现的,但是我并不对它很认可,首先构建一个类似COM的类厂非常复杂,开销有点大,其次COM的一个优点是可以对程项目管理者联盟
序实现向下兼容,这也是DX使用它的重要原因,而一个游戏引擎并不需要。OGRE中也实现了一个类厂结构,这是一个比较通用的类厂,但是使用起来还是需要写一段代码。我比较欣赏VALVE的做法,它通过使用一个宏就解决了这个问题,非常高效,使用起来也非常方便。这个做法很简单,它把每个项目管理者联盟
模块中需要对外暴露的接口都连接到一个内部维护的链表上,每一个接口都和一个接口名相连,这样外部模块可以通过传入一个接口名给CreateInterface函数就可以获得这个接口的指针了,非常简单。下面看看它的具体实现。它内部保存的链表结构如下:项目管理者联盟
class InterfaceReg项目管理者联盟
{项目管理者联盟
public:项目管理者联盟
InterfaceReg( InstantiateInterfaceFn fn , const char *pName );项目管理培训
public:
InstantiateInterfaceFn m_CreateFn;项目管理者联盟
const char *m_pName;项目管理者联盟
InterfaceReg *m_pNext;项目经理圈子
|