战斗框架设计

游戏内的战斗框架涉及多个模块,包括技能,施法单元等。

大芒果对wow的实现

可施法单元Unit将会执行castspell,对某个目标使用某个法术进行施法。看起来所有的AI功能都是由CreatureAI来做的,每个精灵都会有一个CreatureAI指针,用来管理自己的行为逻辑,包括移动,施法等。例如施法的时候,会取出与自己关联的实体对象Unit* pCaster,然后调用Unit的CastSpell的方法对target使用spell法术进行战斗。而CastSpell里,将会根据法术信息创建一个Spell即法术,然后调用法术spell的prepare方法准备开始对目标进行作用。prepare方法里将创建一个spellevent,然后如果需要立即释放就调用cast方法,里面进行一系列检查后,调用handle_immediate方法,如果确认没什么问题 ,将调用DoAllEffectOnTarget方法,对目标进行所有效果的施放,调用DoSpellHitOnUnit对目标执行伤害,此时把攻击者加入被攻击者的威胁列表里,设置为被攻击者的攻击者setAttackBy,此时被攻击者启动战斗逻辑,攻击者把被攻击者放到自己的列表里。

在Unit里,维护了两个Manager,一个是ThreatManager和HostileRefManager用来管理威胁自己的对象列表和自己威胁的对象列表。

在设计面向接口的过程中,我们可以这样做,抽象出一个FightService,此服务将会负责管理单元之间的战斗逻辑,由于战斗都是基于技能,那么服务将设立一个FightUnit,一个Sprite和FightUnit有一一对应的关系?

如何结合mecanim,nodecanvas。mecanim是动画行为表现,不同的动画之间的切换通过事件触发,比如角色五连击,要依据玩家是否有战斗按钮输入。主要就区分下各个系统之间的关系。可以参考下别家的做法,看看他们的战斗系统,技能如何管理,与精灵之间的关系,行为树的应用,状态机的应用等。这些都值得参考下。

像暗黑3里,法师按下Q键触发回血,如果此时回血CD已经走完,直接回血,然后播放声音“好多了”,如果此时血量很低,会播放声音“我倒想那么做”,这就可以用行为树编辑出来,而不会用mecanim。

另外一个游戏的设计

每个精灵都有一个battlemanager,一个skillmanager,battlemanager会用到skillmanager,要弄明白整个的结构,可以梳理下流程。

战斗会有一个叫做EntityParent的类,看起来是战斗单元,里面有CastSkill方法。

Entity里会监听一个UI事件,OnNormalAttack,当UI触发这个事件时,将会调用battleManager的NormalAttack方法,normalattack里将会执行castskill,并且设置了个计时器,待一段事件后,再次执行normalAttack。

当调用castskill方法时,需要传入skillID,然后从skilldata里取出相应的技能。

如果是角色自己在请求施放技能,就会执行一个RPC,告诉服务器要执行施法技能;如果不是角色在请求施法,就会调用battlemanager的castskill方法。

精灵的状态机

和我之前做的差不多,流程是在changestate的时候,先做currentstate的exit,再做newstate的enter,然后newstate的process。Entity自己存了一个当前状态

currentMotionState。这些个状态的切换是什么用处呢?

主要有这么几个状态:

呼吸状态(IDLE),//进出这个状态没有多余操作,在process中会根据当前精灵类型设置animator属性,以及速度等变量值。

行走(walking),//进入这个状态,如果是角色自己,那么会给animator设置移动速度,注意是给动画状态机设置的移动速度。为什么只给玩家设置呢?其他是服务器设置?

         // 离开这个状态,精灵(所有类型)的移动速度为1,如果是怪物会有别的速度设置,如果是角色自己,给animator设置移动速度为1

         // 如果是服务器控制的怪物的话,会设置移动速度,所以进入这个状态主要就是设置速度了,但是在哪里设置精灵现在切换到动画状态的呢?有可能是给            animator状态机设置的speed变量值,导致状态机可以切换了。

死亡(Dead),  // 进出此状态没有特殊操作

         // process里,会判断当前的动作名称,根据动作名的结尾串,设置相应的动作。animator里的变量action,将被设置成对应的整数值。

          然后触发声音组logicSoundEvent里的OnHitYelling事件,应该就是播放怪物的死亡叫声

拾取(picking),//这个状态的进出无操作,process里就是融合了picking这个动作。

攻击(attacking),//在切换到这个状态时,会传入技能ID,在process里,取出skilldata,skilldata里存有skillaction列表

          // 这里将会执行延时回调,触发OnAttackingEnd事件,也许是这样的设计,当前这个action有duration,当这个duration到了,就说明action执行完了

          // 然后触发OnAttackingEnd事件。有可能它们的设定就是以时间为准,而不是以美术设置的关键帧事件,这样服务器端也可以用这个时间来模拟战斗过程。

          // 最后执行entity的onAttacking方法--->battlemanager onattacking(这里没做什么)--->skill manager onattacking 直到这一步才会去设置战斗动画

          // 所以有些奇怪,因为其他状态都是自己去setaction,切换animator的动画,到这个战斗状态却是由技能管理器切换,可能是在这个状态里做太复杂了

          // 所以最好还是都抛事件,让外面去切换动画状态

被击(Hit),   // 当进入这个状态时,会传入攻击者和受击者的ID,通过一些列的动画判定,决定当前精灵受击应当播放的具体action,然后设置animator的action,

         // 此时会触发一个OnHitYelling事件,播放被击音效。

准备(prepare,主要是给技能施放前准备的),//这个状态说是在攻击前做准备用,其实什么也没做

锁定(lock),//这个状态就是和locking动画做crossfade,为什么没有让mecanim去做这件事呢?

charging(突进),//这个状态只有一句设置动画的代码 setAction(4),说明就是个切换动画

翻滚(roll),//翻滚状态,在process里,将会直接设置当前精灵的animator事件,没做其他的事情。

切换状态的时机:

 fsmstate还是附着在entity身上,battlemanager里做了一些状态切换,还有其他一些地方也做了切换。

 

BattleManager

是个基类,在创建的时候就要监听自己所归属那个精灵的状态事件,

主要有这么几个:

OnPrepareEnd    //这个状态是给精灵做技能前准备的,例如旋转角色,朝向,靠近目标等

OnAttackingEnd //攻击结束状态,回到IDLE状态

OnHitAnimEnd   //受击结束

ORollEnd,//滚动结束

OnHit,//受击,这里会判断是否播放特效等一些操作

 

从职责上看,battlemanager收到这些事件后会尝试改变精灵的当前状态机 。是战斗的入口,操作skillmanager

 

基类里的castskill做的是切换玩家的动作,告知状态机说现在切换状态了,切换到attacking状态,但是他这个状态可不是mecanim的状态,mecanim的状态仅仅只是动画。

 

 

技能

SkillManager技能管理器,每个精灵有这么一个,使用的是skilldata数据,这个数据是如何初始化的?

关于SkillData有通用属性,学习限制属性,施放条件三大类属性

通用属性

技能名称,技能等级,技能描述,技能ICON,技能职业,技能组,武器,依赖的技能列表。

学习属性

等级限制,职业限制,消耗的金币,可增加的伤害,增加的掉率等

施放条件

一组CD(注意公共CD的意义是:当你一个技能完成,无论如何都要等一个CD才能开始下一个技能)

施放时间,

施放范围

有依赖的buff列表,独立的buff列表,当前的buff,对应的skillaction列表,全部action的持续时间。

技能树和技能池的区别?

 

关于SkillAction

包括表现效果和技能效果两部分,这个和动作表现关系非常密切,打出来的效果,哪些美术配置出来的参数需要程序运行时了解,都配置在这个对象里。

表现效果包括:

相机震屏ID,声音,持续时间,攻击动作到的是哪一帧,技能的特效列表,是否要关闭碰撞,是否冻结周围,是否分身

技能效果

目标类型,攻击对象的上限,当前的buff,

各种带表现效果的buff(释放buff,目标buff等),动作开始时长,结束时长

 

每个skillData里有一组SkillAction,其实是职责上进行了拆分。skillaction更多的是跟动画表现相关。

 

技能cd在哪里做的?PlayerSkillManager里有一个is skill cool down函数,此函数将判定当前的技能cd是否到了。

关于SkillBuffData 

名称,优先级,持续时间,可用的技能列表,vip等级,特效列表,互斥buff列表,替换buff列表等。

时间变量

skill的定义里有castTime,这算是什么呢,施法时间?这个时间不跟着具体的skillaction走,从现在来看,好像是没什么用处。

skillAction里的时间变量,

duration 这个变量没看出有什么用处。

actionTime,这个时间貌似就是攻击时长,时长结束后,就执行伤害

nextHitTime,好像是可以有两次伤害,一个skillaction里可以连续有两次伤害。貌似只在前端使用。

damageTriggerFrame,貌似只在xml配置了,但是并没有起到作用。

actionDuration,并没有用到,xml里也配置了

actionBeginDuration,这个应该算是前摇

actionEndDuration,这个算是后摇

 Data的配置文件,每种data有个成员是 filename,指出了文件所在的目录。例如MapData的filename 是xml/map_setting

SkillManager会收听一个onAttacking事件,负责执行真正的action,触发attackingfx产生特效,然后告知entity播放特效,再告知fxmanager播放具体的特效。attackingMove产生移动,因为SkillAction有附加的速度,因此会告知Entity的motor执行移动操作。

里面执行AttackEffect,查找出客攻击的Entity列表,对于主角来说,执行AttackDummy攻击怪物;对于怪物来说,执行AttackPlayer;其中将执行CalculateDamage计算伤害,然后抛出一个OnHit事件,如果怪物死亡(仅针对客户端怪物),就执行RPC调用,通知服务器。

每个精灵的BattleManager都会监听OnHit事件,当收到这个事件后,处理受击逻辑,包括播放被击特效,击退,击飞等效果(击退等移动效果由Entity内部的moto管理)。

看起来,它的状态机是因为当时4.3版本没有Mecanim提供的状态机脚本支持而自己实现的状态机,耦合还是很重的。

做的时候,要先设计下技能数据,玩家点击战斗按钮进入战斗状态,同时传入当前的战斗技能,从技能表里取出对应的技能项。

但是战斗的判定该怎么做呢?例如,角色发起了攻击,必然要根据技能找到目标。

现在的问题是,角色发起攻击,动作到了目标点,技能系统需要同时拿到攻击者的属性和目标的属性,计算出伤害值。需要有个SkillCalculate脚本,来进行计算一个攻击者,一个目标的各种伤害值。

 ===========================================================================

关于行为树的应用。行为树只负责精灵的逻辑控制,务必整理一个架构。

角色:当玩家开启自动战斗时,就让行为树启动逻辑。 

伤害计算放在了CalculateDamage,这个类里只有静态方法,相当于一个静态对象,它没有数据成员,所有的数据都是从Entity里拿到的,例如计算攻击者此次的伤害值,就是 传入一个攻击动作ID,攻击者ID,被击者ID,然后函数将取出攻击者和被击者的数据,进行计算,然后返回结果给SkillManager。SkillManager里会执行对攻击者消耗的计算以及血量的计算。

因此skillmanger里做了攻击属性的逻辑,那么battlemanager的职责是什么呢?

===========================再次整理下流程==============

当玩家进入攻击状态StateAttacking.cs,执行Process方法,这里用到一个技能ID, 即spellID,根据这个spellID,从SkillData管理组里找出对应的SkillData来,这就是一个技能ID对应的技能数据了。

然后会对SkillData里的每一个skillAction,执行ProcessHit方法,这里根据skillaction里的duration会做一个定时器,时间到了后会触发OnAttackingEnd的事件,最重要的是执行了Entity的OnAttacking ===》BattleManager的OnAttacking ===》SkillManager的OnAttacking,这里是攻击的具体表现,根据SkillAction里的各种表现效果的开关执行

播放特效AttackingFX===》震屏,entity playsfx ===》sfxManager playsfx (spellID)

播放战斗移动 AttackingMove===》 moter move

播放buff AttackBuff===》循环skill action里的加buff和减buff, 对每个buffer id ===》entity clientAdd(Del)Buff ===》BuffManager clientAdd(Del)Buff ===》entity stateflag

 然后是最重要的两个步骤,AttackEffect以及通知服务器攻击指令(RpcCall : CliEntitySkillReq)

AttackEffect会先找出本次的攻击列表,然后根据施法者自己是什么类型,比如自己是玩家或者普通怪,会执行不同的攻击函数,随后对每个被攻击者执行OnHit操作。这些攻击函数包括了以下类型:

 

======================服务器端的设计====================

Skill相关的文件夹在server/scripts/cell/skill目录下,很奇怪,为啥VS解决方案mono里并没有这些文件夹。SkillSystem里的castskill方法里执行了施法操作,遍历每个skillaction,取出每个skillaction的beginduration,endduration,相加就是整个技能的持续时间,按照顺序以及间隔时间做计时器,执行每个skillaction的cast方法。

在每个skillaction的cast里,首先检查自己是否死了,然后检查当前这个skillaction有没有位移,然后看这个skillaction是不是需要召唤怪物,然后调用skillcalculate的FindTargets方法找出技能目标队列,根据其他条件对这个技能目标对象进行过滤,然后对目标队列执行伤害判定,加血判定,buff判定。

其中伤害判定,SkillAction:doDamageAction,通过SkillCalculate的GetDamage方法取出当前技能的伤害值和伤害类型,被攻击者的血量执行相应的判定,然后被攻击者将执行DoDamageAction方法,

buff系统的设计?

 

=======================我的设计==================

1.如何去创建一个精灵?应该是SpriteModule收到一个创建精灵的消息后,实例化一个精灵。

2.由于精灵身上绑定有状态机组件,因此精灵的状态机是在编辑器里定义的,是否要直接取出这个控制器?

3.编辑功能怎么搞?full inspector?

4.战斗是battlecomponent,监听所有和战斗相关的事件,操作技能模块执行功能。战斗与属性如何管理?

5.

posted @ 2015-06-01 17:07  DesignYourDream  阅读(3028)  评论(0编辑  收藏  举报