|
地板
楼主 |
发表于 2011-10-22 19:02:27
|
只看该作者
13.2 了解Yake
以上谈完了原语设计部分,这一节讲要用到的库,就是YAKE了,这一节可能要占很长篇幅,因为Yake本身是一个很大的架构
我发现Yake是一个很好的游戏引擎(它自称是VR和游戏引擎,,但是它明显没有提供虚拟世界的内部逻辑,而只是提供了VR和Game对于计算机的表现逻辑而已,为什么选择Yake呢?就因为它功能全面至于运行效益也要考虑),它集成了我们以上提到的多数组件(而且DLL形式的组件可以选择不编译而且各个DLL之间没有相互引用关系对于Yake主体来说完全是Plugable的)(但是我们将按需去掉一些组件,将它集成到GameGeneric上去,比如将LuaBind逻辑和Lua提到GameGeneric上去,把YakeBase中的DLL封装逻辑和Logging机制去除,因为我们将把Yake编译成一个静态库再为GameGeneric所用,把oSceneLoaderLib去除,因为根本不需要这个功能-不需要把场景图中每个节点都利用XML展现出来,我们只想用XDMB的二进制Wrapper场景文件(场景文件有它自己的非XML形式的加密格式),而用XDMB去封装场景调入逻辑),我们应了解它的架构先,并理解其存在哪些API,但是并不深入探索它们是如何实现的,因为我们仅仅是想使用它 (这就表明,我们仅仅要做库的顾客而不是让它去主导我们自己的架构)
对库的理解要深入到函数API级(只是了解一下并不探索其实现因为我们只是顾客只满足于调用它并在自己的逻辑中封装它,但是要当高级顾问时却要了解它与其它API的联系,即该API的实现,因为此时我们调用到了该API的低层,这势必要清楚它与其它API的联系),而不仅仅满足了解其库的架构(了解Yake总体大架构还是基本,如果能完成理解它的主体即引入其它库的那些逻辑就更好拿来调用了),对Yake的研究可以让你学到不少东西
我为什么要强调“我们只是顾客呢”,人们并不真正理解那些从一出生就耳闻目见的字眼的真正意义,我想信如果不是我加括号,很多人也很难理解出括号内这层意义出来(而显然这是在我们前面反复讲到的)
能写出像Yake之类高抽象的代码(除了要懂模板和OO这二种范型的各种技术细节之外,还有策略,可以称为是OO和模板之上的抽象泛型),更重要的是具备我上面提到的那些设计思想以及对游戏的认识,流行于程序员之间的用卡来表达设计的方法,最初就是思想的活动,提出一些思想字眼(设计用词),然后再形成具体类文件的名字写到卡片上,最终形成一个计算机观点能接受的范型设计。
正如Yake所言,它是一个VR引擎,又是一个Game引擎,它的Base部分提供了DLL装卸机制和Log机制,这就一定意义上对应我们接下来要谈到的“可复用”策略(ORDL),它除了RAF,Client,Server之外的表现部分是VR的表现部分,而Raf,Client,Server这些都是Game Show部分(显然Yake没提供VR Logic和Game Logic部分),它的LuaBind就是脚本级扩展接口,而Base的DLL和Log机制就是程序员级的扩展接口,下面我们来深刻了解一下这个Base Generic并探索它可以被如何完善的地方,首先来看一个概念
面向复用的设计库(Ori Reuse Design Lib),这个库是一个策略库(我们把用模板范型和OO范型来表达设计的技术实现统称为策略,这样策略就不只表示DP了),Generic Design Poliy Lib(通用策略设计库),那么这是一个什么样的库呢,这个库基于以下思想
这个库的另一个别名是:GenericInterLogicPolicyLibrary,Compliler Time 实现与Design Devidded Policy Lib,或Complier Time face可复用 Plugable Pocidy Lib(请原谅我用这么长的描述,但这不是标题,而且如果缺少任何一个字眼都不完整),如果把Yake的Base Generic进行完善,它就会这么一个库
这个Policy用了一些语言内的范型,比如OO和模板,还用了语言外的范型,比如Plugable的DLL,这样的库为设计服务(注意这句话),它主要达到一种什么样的目的呢, 通过在设计期就分开实现和设计,这样就达到了在设计期就达到充分的可复用(而可复用显然无论是设计还是实现都最先考虑的问题,OO和面向构件都是为了可复用而设计,因此这个库实际上就整合了这二个方面,语言之内是OO,语言之外是构件),(你可以看到这个库为VREngine和NotWar3的各个中间抽象层次所用)
与设计期有关的东东常称为元,比如MetaProgramming,强调设计期的作用,还比如MetaData(强调对数据的描述而不是存储,关系数据库的拥护者认为存储才是数据库真正要考虑的东西,而存储是具体数据库的事,这个道理就像我在本书最后一部分提到的Game与VR的关系,到底具体数据库才是数据库,还是对数据的描述才是数据存储?(什么是游戏,具体的游戏是游戏,还是游戏这种泛型说法才是游戏)这是鸡生鸡蛋生蛋的问题,以我看,鸡蛋问题并非不可解,答案就是先是鸡后是蛋,因为在问题中,鸡字蛋字都是具体的)因此上述库还可称为MetaDesignPolicy
运行期=计算机实现=泛型实现(RTTI),因此可复用的最大限度是你的泛型实现
图的左边是“与平台有关的表现”,称为表现Generic(需用与计算机平台有关的多范型表达),右边是“不与平台有关的纯思想”,这样就做到了在设计期就充分考虑了实现与逻辑分开(设计与编码是统一的,设计期就充分考虑了可复用,那么在编码时自然会继承这个优势,一切编码和实现级的的不可复用问题和难于复用问题都可在设计期找到答案,设计与实现本来就是统一多于其矛盾的,他们的矛盾之处在于泛型设计与原语设计的矛盾,比如在设计期把游戏中的一片叶子当成一个对象有时是一种好设计,然而当被用于多泛型实现时,在运行时就会造成效率的低下,这就是不成功的多范型设计,当然,实现与逻辑分开这个说法绝非这么简单,因此我们在SHOW和LOGIC内部也需要考虑将其OO化或策略化以增强其内部的实现与逻辑),诚然,OO可以一定程序上做到“实现与逻辑分开”,但是还有策略,然而这是语言内部的,在语言外部是库(Plugable的DLL形式发挥了重要的作用),这个技术也可提供设计时的可复用(当然可复用永远是相对的,在应用上述图作出的应用架构,在各个Genric外至少是实现与逻辑分开的,当然各个Generic的SHOW或LOGIC部分也要做到可复用),这样就是
1. 要保证在实现与Logic内部也是实现与逻辑分开的和充分的Plug化
2. 要保证Logic部分绝对是思想而没有与平台相关的细节
3. 在每一个Genric层次,可以无Logic,但不可没有Show
以的图可对应到NotWar3的原语设计部分,而它们共同的基础就是这个面向复用的策略库
即
1. Extend Generic=Game Generic+Lua Bind Generic(或者Game Generic自成一个Generic,此时就要求Lua Bind不是仅对Game Genric进行Bind,而是对下面各个Genric进行Bind)
2. VR Generic=Yake+World Logic Generic
3. Game Generic
4. 它们的下面是Yake Base
即见下面这个总原语图
请注意到,上述图中很多用词显然仅属于思想过程的中间描述(即仅仅是描述原语的用词,比如GameGeneric中的Generic这个词就表示它可能是一种架构逻辑也可能是实现逻辑,这里就用Generic来整个描述这个层次抽象, 即Generic=”一切中间逻辑”代词,它并不一定被封装成一个库也并不一定要在后来的多范型设计中要求被体现,可能会在源程序中用名字空间来表达,C++中的名字空间表达了一种什么样的本质呢?一个名字空间就是一个应用架构中某个子架构,或称为抽象层次的总称,而我们这里提出了四个即1.Design Policy Generic2,VR Generic3,Game Generic4,Extend Generic,其中3,4是特别的,如果3是为4服务,那么3,4实际上可以合并,如果Game有它自己的架构,那么3,4是分开的,Generic,Generic就是一种组织所有某个抽层的架构和实现的形式,名字空间也使我们的源程序能够很好地硬盘上被组织,Java的源程序包中的文件夹形式就是一种表现),因为我们此时在进行原语设计,不必考虑面向代码将其反映出来,这样我们考虑问题的范围会更广一点(实际进行多范型设计时要反映出来的就是那些能实际作用代码的用词,像上面大部分后带Generic饰词的中间逻辑集都要被体现,我们在实作部分会给出一个经过修饰的原语图,从原语设计到多范型设计到UML设计我们称为设计的演化),但无论如何要明白,一些对设计过程中用到的思想辅助用词是不必考虑进设计的代码实现的。原语设计跟多范型设计中间存在比较大的距离(真正的原语设计不是超级原语,虽然它名为原语,但它是在可控范围内权衡做到的原语,真正的多范型也并不是一团没有根据的计算机实现,它要也要面向前面的原语设计得出的结论,它们之间的联系要优于它们之间的区别被我们考虑)
不必拘泥以上的思想模型(任何人构架的逻辑模式和逻辑用词都可以不一样这不是严格的,但最终的应用反映在代码上要能工作这却是严格的),你画出的原语设计图同样正确,再简单也没关系,也不一定要成档,因为这只是严重参杂了个人看法的东东(只有当后来的多范型设计时才慢慢走入编程的束缚,因为这是参照编程界现成方案的设计,因此大家做出的设计都差不多一样)
这里再谈一下可复用与“逻辑实现”分开之间的联系(主要出现在设计期和重构期)
我们一直提倡逻辑与实现分开(这是设计的基本准则),这样设计有没有做到呢?,这里要说一下实现与逻辑分开,然而即便是这个说法本身也有点含糊,说实话这绝对是一个具体问题具体分析的活,(就以上面的设计来说,我至少可以找出几个逻辑与实现的说法,比如,但是大体上可以分为二类,Generic外部的和内部的,反正我自认为上图已经体现了足够好的逻辑与实现分开,如果你不这么认为,那么你一定有比我更好的方案,不妨说出来听听或指出我的缺点,对于实现与逻辑分开,我曾认真思考过为GameLogicGeneric中的每个个体增加一种联系到GameShowGeneric,但是这个设计我后来放弃了,这样做看起来合理实际上只会增加实现与逻辑的偶合性而且在实现上也是很复杂的,而且压根就不需要这样做因为GenericInterLogicPolicyLib已经是一种很好的方案,已经是一种根本的实现与逻辑分开策略,,需要作一层复杂的中间封装逻辑,这层抽象可能被集成到GameGeneric中)一个游戏GameGeneric就是LogicGeneric加上ClientSideGeneric的总和(逻辑上下或平等互饰构成逻辑总和,应用于是被建立起来)并由此定义出一个游戏比如NotWar3(这才是真正的编码与实现部分,就是几个Demo中要谈到的),或者我们可以把实作出的LogicGeneric置于ClientGeneric的底层(,这正是我们要采取的方式,作为中间逻辑的库之间应如何架构,它们之间的逻辑应如何,这也是我们要考虑的)。
我的一些不成熟的尝试和不成熟的思想导致的设计缺陷
1. 我曾设想Lua部分写Game.dll,我把总架构做到了扩展逻辑之后作为Lua代码,因为我认为那是具体游戏的事而不是GameEngine的事了,因为我还觉得“具体游戏才是游戏”,而这里的GameEngine似乎称为“用计算机模拟游戏世界的引擎”(ComputerSimutWorldEngine)更为合适,因此GameEngine.DLL最好的说法是VREngine,但是另一方面,也有另外一种做法那就是把扩展逻辑和VR逻辑中间放置一个Game总架构逻辑,这样G总架构逻辑就在Lua之前了,会是C++的代码,这样也是一种非常好的选择)游戏是一个对等或C/S的模型,因此分PeerSideGeneric和ServerSideGeneric,(我一开始设计时GameGeneric曾把这二个设计原语加了进去,说实话这就不是一个好的设计,因为只要在具体游戏的逻辑中才会出现客户端和服务端之说,而GameGeneric管理一些更为低层的逻辑,这样做就犯了设计的大忌,过早把实现混合成架构做进了设计中)
2. lua扩展是不是限制了lua级扩展的瓶颈(要深克明白脚本机扩展导出与动态修改的作用区别),因为导出的逻辑有限?当然这个扩展接口并不能导出全部的中间逻辑,实际上也没有必要(LUA级的扩展不需要导出从一开始到GameGeneric的全部中间逻辑)这里要深刻理解这个扩展接口(因为多范型设计与细节有关,只要明白各个中间抽象层次应有的细节,才能依据它来进行多范型设计),扩展接口架构在GameEngine之上(但并不需要导出这个构架),但是扩展逻辑不只是封装GameEngine这个中间逻辑层的某些接口(即它不是一个纯粹架构于GameGeneric之上的中间逻辑,虽然在原语设计图中它是,然而在范型实现中并不是这样),而是分别对其它中间逻辑层次有引用(即它必须对OIS有导出逻辑才能在扩展接口集中提供一个屏蔽鼠标的LUA接口,必须对RAF作导出逻辑才能在扩展集中提供启动一个游戏的功能,必须对CAMERA作导出才能在LUA级提供一个变换视角的游戏—而这个功能正是LUA级扩展必要的,,因为玩家写地图时总要调用到变换视角的功能,,比如通过LUA扩展出的一个泛RPG的FPS游戏,LUA护展级应提供什么功能和接口,可以参照WAR3WORLDEDITOR中的全部开关功能)LUA级的扩展全部是函数级的接口,而不是OO级的架构,,一方面是因为用LUA编程的玩家你不能要求他们有OO范型的思维,另一方面是因为实际的通过LUA扩展的过程只要用面向过程就够了并不需要形成一个架构,无非就是转动视角,控制游戏开启这些简单对外的逻辑,对内的扩展只能通过为Game.DLL写DLL来扩展----(而且LUA本身支持OO也不是很好)当然也存在另外一种做法,这就是把GameGeneric这整个抽象层次作为为Extend Generic服务的基础,也即它并不指代一个“Game”,并不打算把它设计成一个架构,而只是简单的对要导出的库(比如VREngine的方方面面)扩展封装,这样GameGeneric实际上就是1,GameGeneric加2,Extend的综合了 |
|