广东汇众教育提供游戏开发:游戏引擎中的通用编程技术你是否正在考虑构建一个游戏引擎呢?你对如何构建一个游戏引擎是否已经有了一个明确的计划呢?你是否已经对如何组织游戏引擎各个模块之间的关系有了一个通盘的考虑?如果没有,那么本文将对你建立一个良好的游戏架构提出一些有益的方案,如果你已经对上面的问题有了一个明确的答案,那么本文不是你需要阅读的内容。本文的目的是给那些没有任何建立完整游戏引擎经验的人提供一些入门性的知识,使他们初步了解一下如何来构建一个游戏引擎,构建游戏引擎应该注意哪些方面的问题,并提供了一些成熟的设计模版并指出这些设计模版使用的范围,我希望这些内容对那些中级编程人员也有一个良好的参考作用。这里必须再次提醒你,本文介绍的是一些通用的游戏编程技巧,虽然是通用但是可能并不是非常全面,可能存在这样或那样的缺陷,因此如果你希望它发挥最大的效用必须恰当的使用它,而不是不分场合的滥用。切记切记,一个初学者最容易犯的错误就是任意使用一些设计模版而不顾它的使用范围。在开始构建一个游戏引擎时你需要先考虑哪些方面的问题呢?这是你必须认真考虑的问题,我的答案是首先必须考虑代码的可读性,尤其是在多人进行开发时更必须高度重视,如果你写的代码其他人需要花费非常大的精力进行阅读,那么根本谈不上提高工作效率,下面是提高代码可读性的一些良好建议:1、建立一份简单明了的命名规则。一份良好的命名规则可以大幅提高代码的可读性,规则必须简单明了,通常只需要两三分钟的阅读应该可以让其他人掌握,例如在代码中直接使用匈牙利命名法这种大家熟知的规则,使用字母I作为接口类的首字母,使用C开头作为实现类的首字母,使用g_开头的变量名作为全局变量,s_开头作为静态变量名,m_开头作为内部变量名,使用_开头作为类内部使用的函数名等等,通过名字就可以使你大概了解对象的使用范围和基本功能。2、不要讨厌写注释。一个编程者易犯的错误就是不写注释,认为它会增加自己的工作量,但是他没有考虑到相应的工作量已经转移到代码阅读者的身上,可能看代码的人会花费比写注释时间两倍或者三倍的时间来阅读代码,这是一种非常不负责任的行为,通过一段简短的注释可以广东汇众教育提供使阅读者迅速的了解代码的功能,从而把时间更多的用到功能的扩展上。下面是一些良好的建议:尽量对每一个变量标明它的功能。对每一个函数声明的地方标明它的功能,对于复杂的函数还应当写清参数和返回值的作用,注意是在声明函数的头文件中。在关键的代码处写清它的作用,尤其是在进行复杂的运算时更应如此。在每一个类声明的地方简要的介绍它的功能。3、减少类的继承层次。通常对于游戏编程来说每一个类的继承层次最好不要超过4层,因为过多的继承不仅会减少代码的可读性,同时使类表指针变长,代码体积增大,减低类的执行效率。还要注意要减少多重继承,因为不小心它会形成编程者非常讨厌的“钻石”形状。同时还要注意如果能使用类的组合的话那么就尽量减少使用类的继承,当然这是设计技巧的问题。4、减少每行代码的长度。尽量不要在一行代码中完成一个复杂的运算,这样做会增加阅读难度,同时不符合现代CPU的执行,由于CPU现在都使用了超长流水线的设计,它非常适合执行那些每行代码非常短而行数非常多的代码,例如对一个复杂的数学运算,写成一行不如每一步骤写一行。以上建议是我的一些粗略看法,如果你还有什么好的看法可以给我指出来,同时上面的建议并不是绝对的,例如类的继承并不是绝对不能超过4层,如果你需要的话可以使用更多的继承,前提是这样带来的好处大于代码执行效率的损失。接着看看要考虑什么,在GameProgrammingGems3的《一个基于对象组合的游戏架构》一文指出了几个值得考虑的问题,首先是平台相关性与独立性和游戏相关性与独立性的问题,也就是说应当作到引擎的架构与平台和游戏都无关。为什么要做到与平台无关性呢?这是因为你必须在开始架构引擎考虑它的可移植性,如果在开始你没有注意到这个问题,那么一旦在游戏完成后需要移植到其他的游戏平台上,你会发现麻烦大了,你需要修改的地方实在是太多了,所有与平台相关的API调用都需要修改,所有使用了平台特定功能的模块也需要修改,这是一个非常耗费精力的事情,可能需要花费和开发一个游戏一样的时间,而如果你在开始的时候就考虑到这个问题,那么非常简单,只需要写一个相应平台的模块替换掉原来的模块即可.这样精力就可以放在如何充分的利用特定平台的能力来提高游戏的表现力上,而不是代码修改上。下面简单的谈一下如何广东汇众教育提供使引擎作到与平台无关。1、注意操作系统的差异。现在主流的操作系统主要是Windows和Linux两种,当然还有Unix和Mac,在编程时你必须注意这一点,当你需要包含Windows的头文件时,你必须将它包含在宏_WIN32中,下面是一个简单的例子:#ifdef_WIN32#includewindows.h#endif而你使用Windows平台特定的API时也应当如此,这样在其他平台上编译时可以保证Windows平台相应的代码不会被编译进去。对于其他平台也应当如此。2、注意编译器的差异。现在通用的编译器主要有VC,BC和gcc几种,在进行Windows平台编程时,你通常会使用VC或BC,而对Linux平台编程时通常使用gcc,使用VC编译器你不可能编译出用于Linux平台的代码,因此在编程时也需要注意,你可以使用上面的方法通过特定的宏来将不同的编译器分离开。举一个简单的例子:#ifdef_WIN32#ifdef_MSC_VERtypedefsigned__int64int64;#endif#elifdefined_LINUXtypedeflonglongint64;#endif在不同的编译器中对64位变量的命名是不同的,因为它并不是C++标准的一部分,而是编译器的扩展部分。另外一个例子是编译器使用的内联汇编代码,在VC中你可以使用_asm来指明,而对于Linux平台的编译器你需要使用它的专用关键字了。3、注意CPU的差异。对于不同平台来说它通常会使用不同的CPU,不过幸好Windows和Linux都支持X86的CPU,这也是PC游戏的主流CPU平台,而XBOX使用的也是X86的CPU,除非你需要移植到PS2平台,否则这将大大减轻你的编程负担,在X86平台上提供了一个cpuid的指令可以非常方便的检查CPU的特性,如是否支持MMX,SSE,SSE2,3DNow!技术等,通过它你可以使用特定的CPU特性来加速你的代码执行速度。4、注意图形API的差异。现在图形API主要存在两种主流的平台DirectX和OpenGL,DirectX只能用于Windows平台,而OpenGL几乎被所有的平台所支持。因此你需要为不同的图形API进行封装,将它做广东汇众教育提供成不同的模块,在需要的时候进行切换。完成这个工作最好的方法是使用后面介绍的类厂模式。5、注意显卡的差异。现在显卡有两大主流ATI和NV,虽然显卡可以被主流的操作系统所支持,但是必须注意在不同的游戏平台上还是使用不同的GPU,而在GPU之间也相应有自己的功能扩展,因此在使用特定的扩展功能时必须检查一下是否被显卡所支持。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模块,文件系统管理模块等等,其中引擎的核心模块是最重要的部分,逻辑层主要通过它来和底层的模块打交道,它应该包含场景管理,特效管理,控制台管理,图形处理等等内容。在向下就是一些底层模块了,如图形渲染模块,输入设备模块,声音模块,网络模块,物理模块,角色模型模块等等,所有的这些底层模块必须通过核心模块来和逻辑层进行交互,因此核心模块是整个引擎的枢纽,所有的模块都通过它来进行交互。下面看看应该如何来进行模块的设计,这里有一些通用的规则是你应当遵守的:1、减少模块之间的关系复杂度。我们知道通常每一个模块内部都存在大量的对象需要在各个模块之间进行相互的调用,如果我们假设每一个模块内部对象的数量为N的话,那么每两个模块之间的关系复杂度为N*N,这样的复杂度是不可接受的,为什么呢?首先是它非常不利于管理,由于各个模块都存在大量的全局对象,并存在相互依存的关系,并且各自建立的时间各不相同,这就存在初始化顺序的矛盾,考虑这种情况,一个模块中存在一个对象需要另外一个模块中的对象才能进行初始化,当这个对象进行初始化时而另外的对象在之前并没有初始化就会引发程序的崩溃。其次,不利于多人进行同时的开发,由于各个模块存在相互依存的关系,当复杂度非常高时就会出现模块与模块的高度依存,也就是说一个模块没有完成下一个模块就无法完成,因此就需要一个模块一个广东汇众教育提供