战斗框架设计

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

大芒果对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,//受击,这里会判断是否播放特效等一些操作

 

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

 

SkillManager 

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

SkillAction和SkillData是两组数据,当播放一个技能时,需要指定一个actionID。

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

attackbuff产生buff

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

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

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

 

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

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

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

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

### ARPGR 游戏战斗系统的设计框架 ARPGR(动作角色扮演游戏)的战斗系统设计需要综合考虑多种因素,包括实时性、公平性和可扩展性。以下是针对该系统的实现方案: #### 1. 客户端与服务端的角色划分 为了保证游戏结果的公正性,核心逻辑应尽可能放在服务端完成。尽管客户端可以负责渲染和部分预处理工作,但最终的结果判定和服务端数据同步至关重要[^1]。 - **客户端职责**:主要负责玩家输入捕捉、动画播放以及视觉效果展示。 - **服务端职责**:执行所有的战斗逻辑判断,例如伤害计算、技能触发条件验证等。 #### 2. 同步策略的选择 由于 ARPG 类型的游戏通常具有较高的操作频率,因此选择合适的同步方式尤为重要。常见的同步方法有两种——状态同步和事件同步。 - **状态同步**:定期向所有参与方广播当前的状态信息。这种方法适合低延迟环境下的简单场景,但对于复杂战斗可能造成带宽压力较大。 - **事件同步**:仅当发生特定行为时才发送消息通知其他节点更新其本地副本。这种方式能够有效减少网络流量消耗,在高并发情况下表现更优[^3]。 对于大多数现代 ARPG 而言,混合使用这两种技术会取得较好的平衡点。 #### 3. 多线程架构支持大规模用户交互 随着游戏玩家数量的增长,单一进程难以满足性能需求。通过引入多线程或多进程模式来提升吞吐量变得必要[^2]。 - 将整个虚拟世界分割成若干区域,每个区域能够独立运行并维护自身的实体列表及其相互关系; - 不同分区之间可以通过网关(Gate)进行通信连接起来形成完整的生态系统; 这种分布式部署不仅有助于缓解单机资源紧张状况,还便于后续水平扩容操作实施。 #### 4. 使用脚本语言增强灵活性 考虑到不同类型怪物AI设定差异巨大且频繁调整的需求,建议采用易于修改调试的语言编写业务规则层代码片段(如Python/Lua)[^2] 。这样既可以加快迭代速度又能降低硬编码错误风险。 ```lua function onAttack(target, attacker) local damage = calculateDamage(attacker.strength, target.defense) -- Apply critical hit chance logic here. if math.random() < CRITICAL_HIT_RATE then damage = damage * CRIT_MULTIPLIER end applyEffectToTarget(damage, target) return damage end ``` 上述伪代码展示了如何利用Lua定义攻击流程的一部分细节。 --- ### 总结 综上所述,构建一个稳健高效的ARPG战斗系统需兼顾多个层面的技术考量,从基础的数据传输协议制定到高层级的战略规划都需要精心策划才能达成预期目标。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值