现在已经讲完了技能引擎4大运行流程。现在开始说说已经整理好的一些技能的内核数据,对这些数据的理解和理解上面的4大流程同样重要,而且这4大流程很依赖这些内核数据。
技能施展方式:一共有5种,Source对Coordinate,Source对Area,Source对TargetObject,Source对Collision,Source对None。Source对Coordinate是对一个地图坐标进行施展,Source对Area是对一个区域进行施展,Source对TargetObject是对一个对象进行施展,如敌方、友方,Source对Collsion是对一个近身发生了碰撞的对象进行施展,比如普通的肉搏攻击,无冬中死灵法师的鬼王之触,Source对None,是不需要指定施展的对象,比如魔兽中的流星雨,战争践踏。技能施展方式是技能库在创建时就定好的,比如创建一个鬼王之触,那么应该提前给技能引擎发出这是Source对Collision的施展方式,这样技能引擎在检查可行性和满足技能条件时才能按正确的方式去执行。施展方式还影响到GUI部分,比如施展Source对Area那么在GUI上应该提前给出一个类似魔兽的区域技能的形象表示。任何ARPG的技能系统都外乎这几种施展方式。
技能施展距离:施展距离是提供给技能条件满足流程用的。比如弓箭手要跑到距离以内才能发射,而跑动这一过程,就是满足机能条件的过程。
技能有效半径:如果技能是Source对Area,那么有效半径会影响到GUI和技能检查、满足这2大流程。别说你没玩过魔兽,没玩过NWN。
技能目标对象:如果技能是Source对TargetObject或则Source对Collision,那么技能目标对象会影响到GUI,技能检查,技能条件满足。
技能目标坐标:如果技能是Source对Coordinate,或则Source对Area,那么目标坐标会影响到GUI,技能检查,技能满足。
技能的冷却时间和施展时间:因为ARPG、RTS的内部机制都是非连续流程,所以和时间有关的等待处理都不应该交给技能的脚本部分,这些很程序化的东西都不应该交给Designer去做,而且他们根本也不会做这些处理。冷却时间影响到GUI,和技能检查流程,但不影响到命令队列。施展时间不响到GUI,但影响技能检查流程和命令队列。
技能的自定义数据表:每个技能都有一张自定义数据表,这些数据不会被技能引擎用到,这些数据数据只被技能脚本使用。这张表和角色数模类似,但这张表不仅仅是数字,应该还有像Bool,字符串这些数据,我要再次说明的是,这些数据只被技能脚本所使用。
触发数据:触发数据有,TriggerSource,TriggerTarget。我得做法是TriggerSource为角色,角色中包含了技能引擎,数模,物品等等,总之,角色中包含了很多底层和核心规则的数据,TriggerTarget为当前技能引擎子项,在子项中则中包含以上的所有数据。我这样干是保证所有的数据被集中对象化,便于核心规则和游戏规则的交互。因为当数据量庞大以后,必要的进行对象和集中化会大大减轻制作负担。
值得说明的地方是技能部分的脚本的开发方式。就是在制作游戏规则时,如果要适应核心规则标准。一般的人,如果没做过,或则不了解什么是核心规则,那么肯定不行。我在近几天思考+实现核心规则系统时,将暴雪和Bioware的整个技能的粒子系统研究了很久。暴雪的技能特效系统大都是骨骼动画+纹理的方式实现,Bioware则比较复杂一些,程序+动画+纹理+粒子的技能,这两者就最终的技能视觉效果而言,我认为暴雪做的比较漂亮,因为大部分技能效果都是专业美术在做,在控制,这样干表现强度肯定是超过用传统的粒子系统的。Bioware则在技能的内部逻辑处理上是比较值得一提的,你可以从Bioware的众多技能属性中了解到这点,非常庞大的D&D规则。但以上两者最终提供的都是基于游戏规则的ToolSet或则是WorldEditor。比如使用BioWare的ToolSet都是半专业的制作者,也许他们了解很多人文方面的知识,但大多都不了解专业制作的知识,包括一些专业素质,他们大都都是不具备那些专业知识的半专业制作者。而使用暴雪的WorldEditor那些制作者,我认为那些人连半专业都不算,只算比较高级的玩家,可能他们懂点程序,因为那个WorldEditor根本就不是专门制作RPG的工具,魔兽游戏的性质就注定了这不是专门的RPG制作。
我提出核心规则的思想是在早几个月的时候,之前我和许多同行探讨过一些关于游戏规则的制作,当时感觉很多东西在这个行业中,因为深入制作过游戏的人毕竟是少数,所以很多东西很乱。而核心规则是可以将Programer和Designer以及美术紧密联系起来的,而且核心规则对大家而言,也形成了规范,并且可行性也很大,关键是可以很好的有一致的标准。
不好意思,走题了。现在继续说技能引擎的脚本开发方式。
我先说说我的做法。我做的技能脚本库都是独立的,每一个技能对应一个脚本文件。当引擎启动时,这些技能脚本被成批的装入。而每个技能脚本中,都有一个Register的功能,在技能脚本被成批装入引擎时,就向技能引擎注册。这些注册数据,就是前面我提到的内核数据。同时也会向技能引擎注册很多事件接口,然后再在这些事件接口中实现粒子效果和很多数字运算。粒子系统的实现有很多种,目前我已经支持了纹理,骨骼动画,Tri动画,以及几何粒子点阵几种方式,粒子系统是另一套制作标准。如果你以前收到过我的可行性分析,你会发现技能部分和模型封装系统很像,机制很接近,某些技能用的还是封装系统,比如带有Tri和骨骼动画粒子效果的技能。像法师的冰风暴,冰从天而降的过程是骨骼+纹理,在制作技能的时候,是先将骨骼动画和纹理数据导入到封装包,然后再在那些技能的事件中去播放+数字计算,最后实现成一个完整冰风暴技能。
另外,因为每个角色的分类所属不一样,在技能的施展前,也有不一样的处理。比如施展技能的原是本方(玩家),那么是通过GUI层的交互向核心规则发出请求。如果所属是敌方,友方,NPC,或则World方,那么就不是通过GUI层,而是通过AI层向核心规则发出请求。关于AI部分得制作我在后会加以分析。
还有一点,比如在游戏规则中要制作像光环之类的技能,这时候一般都会涉及到角色的数模。因为这并不属于核心规则,最后我认为虽然光环的实现比较复杂,但还是应该在脚本中去实现。
关于光环技能的制作细节:在核心规则中应该提供根据一个根据坐标和半径参数取得区域对象的方法,还应该在角色的运动过程中提供一个角色位置改变的事件,有了这两点基础这样就有光环制作的基础了。具体的方法为,在带有光环的角色中截获当前角色的世界坐标改变事件,这肯定是在脚本中实现的,然后将角色周围的对象按半径得到,然后再截获那些对象的世界坐标改变改变事件,如果那些对象超出了当前光环的半径,那么就恢复它们在光环前的数模,如果是在半径中,就根据光环技能的特性修改他们的数模。值得提一下的重点是,因为角色往往都在不断运动,要注意角色的运动事件捕捉,每次捕捉了角色的运动事件以后,然后又恢复角色时,应该同时也恢复角色状态。这点非常类似Windows的Hook和UnHook机制。当然,在具体的核心规则引擎事件机制内部是不应该用消息机制的,因为消息有队列特性,很不好管理,我建议用指针链。每次触发事件时,调用指针链中的所有Proc。我用的方法就是创建了一个叫做EventList的一个类,核心规则中的所有的事件机制,都用EventList来触发,这样就实现了多重事件的并发。说了这么多,其实光环技能的实现方法有很多很多,也许你会用其它方法。然而不管你用什么方法,一定要坚持做成标准,这是做核心规则的基本意义。
然后再说明一下技能的条件检查部分。我举个例子,比如像无冬之夜的技能在使用前必须先学习,而学习又有很多条件,比如敏捷必须达到多少。等等。这些东西都是游戏规则,在实现上,应该是在每个技能脚本中有一个学习检查的标准,而学习检查的整个标准应该让游戏规则来定。比如角色要学习火球术,那么这就是在角色学习时所触发给技能脚本的事件。而这样的事件又不是让核心规则定的标准,而是属于游戏规则定的标准。因为在角色在游戏中学习这种规则是非常高层的,凡是高层规则的东西,都不应该放在核心规则中。关于这点,因为我们在实际工作的过程中,Designer在程序领域的战斗经验一般都是很缺乏的,很多Designer只能做做书面的标准制定,根本就做不了程序上的标准实现,所以在核心规则被Designer使用时,应该有一系列供参考的框架。这还不仅仅是角色的学习功能,还有很多地方,凡是涉及到对核心规则要做很深入的脚本制作时,都应该有一列拿来参考的框架。可以试想一下,当核心规则被一家游戏公司的所有制作成员都完全掌握以后,大家都按这种标准来,无疑是非常高效的。但制定这种核心规则并不容易,需要有很深厚战斗力才能让可行性得到保障。
角色物品:角色物品系统得实现机制和角色技能的机制类似,但又不能将技能的那些标准原封不动的套在物品系统上。只能说两者很接近,而最终的要求不一样。
在制作角色物品系统时,对功能的要求一般是这样的。物品要可以被很高效的创建,管理,物品要可以有使用的机制(比如使用治疗药剂),和制作可使用物品的标准,物品要能够和角色的数模发生很密切的联系,物品要有分类的功能,如装备、消耗品(分类属于游戏规则部分,分类的概念我们稍候来明确)
角色物品在游戏规则中的实现应该是这样的。一共有2条路,实现核心规则时应该慎选。因为一但选择定了,那么整个系统就会在该标准的基础上定型,然后就不能走回去了,因为这两条路差别非常大。第一条路是物品系统主要依赖一个公共的物品库,然后角色所涉及到的使用均是按该物品库的ID来,然后物品在被使用时那些所触发的事件是带角色关联的。可能这样说你会更容易理解一些,我们用形象一点的角度来看,先是引擎将物品库成批的装入到内存,这时候物品就已经驻留了。比如当我们使用治疗药剂时,是发送一条请求给驻留内存的治疗药剂,这时驻留内存的药剂从请求包含的那些参数得到触发的角色,然后给角色生命提升。这整个过程所需要的参数均是来自内存的互相传递,机制是属于驻留型的。第二条路也是依赖一个公共的物品库,不过不是将物品成批的驻留内存,而是角色所涉及的物品被指定在物品库的某个位置,当物品被使用时就按该位置去执行,这时,执行的机制就和第一条路完全不一样了。如果你用C++写过Windows程序,应该了解Instance的概念,对公共的Instance编程和对私有的Instance编程差别是非常大的。
如果选择将物品驻留的方式,那么在Designer实现游戏规则时,就是在一个可共享公共脚本中对物品进行脚本编程,在这种公共脚本收到来自角色的请求时,是根据请求参数来决定对角色做某些影响。这样干的好处是,概念和结构比较容易掌握,脚本可以被高速高效的执行。坏处是,处理一些变量要相对麻烦一些,还有就是比较消耗内存。因为在脚本中一样的有Instance,只是不是Windows的Instance,而是每个脚本特有的Instance,当脚本从引擎中导入那些类,函数,变量以后,这时脚本的Instance中会有大量的串,还有那些函数的指针。对于一个成形的引擎,一个脚本的Instance少说也是百来KB,也就是说,一个物品被驻留以后少说也是百来KB(就算几百KB也是正常范围)。我们来计算一下,10个就几M,100个就几十M,1000个就几百M,那么10000个物品呢?当然,这只是理论,在实际中,有几千个物品那都已经是大到不行的物品库了。所以我的建议是,如果选择用驻留方式,注意一下内存开销,还有就是注意留出一些Private机制的变量给驻留物品脚本使用。
如果选择物品脚本被一次性运行的方式,这种方式的机制是,物品脚本被复制成了多份,每一个角色都有一个私有的物品脚本空间,而物品驻留方式是多个角色共享物品脚本空间。这种方式的优点是可以创建机器物品库,可以让Designer将物品库做到非常的庞大,而不用考虑内存开销,因为物品库不会一次性的被使用完。坏处是不能让脚本高效运行,每次运行脚本都要先编译成Cache代码,虽然说使用Cache代码可以避免重复编译,但第一次编译脚本时还是非常花时间的,我用的脚本引擎是FreePascal,每次编译成Cache都要花掉0.2/S,我认为0.2/S这是非常大的问题,如果编译10个物品那就是2秒,这意味着整个游戏在脚本的第一次编译期间会停顿2秒。当然,就算用物品驻留方式也还是要走过编译Cache代码这一步。