浅谈MMORPG的战斗系统
第一次做技能时的血与泪
先上一个图。这是策划对于一个副本中关底boss 新增一个技能的需求(已简化处理)
由于对于技能模块的不熟悉,我没办法第一时间区分哪些逻辑是走的配置表,哪些逻辑需要单独建一份Lua 在运行时去调试。 哪些属于客户端的部分, 哪些需要在服务器实现,技能cd怎么处理等等。也是花了很长一段时间才基本上把项目的战斗模组搞清楚,接下来就来浅谈一下广义上的 MMO 的战斗模块的架构。
1.战斗的对象是谁?
战斗的对象是谁,又或者是谁可以参与到战斗流程中来。这句话看似是废话,当然是玩家了 还有敌人等等。不,我不是想说这个,从程序的角度来看。可以这么划分。
1.1 具有战斗行为的对象是:
- Obj_Player(玩家) 。当然还可以是和你同服的其他玩家,继承自Obj_Char (隶属于一个实体)
- Obj_Npc . 这是广义上的NPC, 包含场景中的敌人、副本中的怪物还有你的助战队友(进副本时,某些时候可以和任务NPC 一起)
- Obj_Fellow(伙伴)。
- Obj_ZombiePlayer(AI Robot)
1.2 对这些对象的解释:
- Obj_Fellow(伙伴)。是一种特殊的队友,需要强调的是,目前它只有辅助玩家的技能,可以理解都是Buff技能、没有主动攻击的行为。宠物 是一种伙伴。
- Obj_ZombiePlayer 是拷贝玩家的数据 和技能 进行攻击行为。 与玩家【在副本挂机模式】一样,会根据先决条件,将后续要释放的技能依次加入队列中。流式释放。
众所周知、战斗行为主要是通过释放技能进行表现(普通攻击属于一种技能),而技能效果主要通过Impact 表展现(配置技能效果的表格)
2.战斗行为的展现主体 – 技能
- 技能主要是来展现战斗行为还有一些特殊的操作行为(传送使用物品采集资源等)
- 技能按释放方式分为两大类:主动技能被动技能(玩家独有)
- 技能按逻辑分为:瞬发技聚气技引导技陷阱技特殊操作技能脚本技能等
- 技能的表现效果由客户端的动作逻辑和特效逻辑等客户端战斗模块展现技能的计算结果由服务端战斗模块运算
- 玩家释放技能一般由玩家客户端发起(部分特殊技能是服务端逻辑发起的比如一些传送技和使用物品的操作类技能)Obj_Npc Obj_Fellow Obj_ZobiePlayer 释放技能是由各自AI逻辑驱动(服务器发起较多)
- 服务端所有技能逻辑派 生自基类SkillBaseLogic 不同的技能逻辑通过重载基类接口实现不同的逻辑效果
3.客户端和服务器
3.1 客户端的主要战斗组件
- SkillLogic 客户端流程处理逻辑 。负责驱动客户的技能表现流程 播放 中止 结束,按钮回调。
- AnimationLogic根据技能表格配置的动作加载对应的动作资源,控制挂载的Animation组件播放动作表现。同时根据动作表配置,驱动EffectLogic播放特效和SoundManage播放音效 CameraController播放震屏效果。
- EffectLogic根据表格配置加载对应的特效资源,将加载完的资源绑在指定的节点位置,控制特效的位置 播放 结束,刀光、剑气等等。
图 3.1 游戏客户端作为技能的展现方
3.2 服务端的战斗运算模块
上文已经说了,技能的表现效果由客户端的动作逻辑和特效逻辑等客户端战斗模块展现技能的计算结果由**服务端战斗模块运算。**我们来看看服务器做了哪些运算。
图 3.2 游戏服务器运算实际技能效果,广播客户端变更hp/mp, HUD抬头
3.3 技能流程(客户端发起【玩家】)
图 3.3 玩家通过按钮释放技能逻辑图
- UseSkillCheckExcludeTarget 的主要是从SkillBase表 和SkillEx 表中配置要求决定此技能当前能否触发
- 以及当前玩家状态释放可以释放此技能,比如满足MP,且CD 没有冷却。
为了保证画面的连贯性,客户端在校验技能合法后,就已经开始走 动画 和 特效、音效等等表现逻辑了。注意此时才开始发包,也就意味着在和当前玩家同屏的玩家会推迟看到玩家 释放技能的画面 ,至于伤害结算。那更是得再等了。 不过也正常,对于MMO 这种多人同屏打副本的游戏模式而言,重要的信息的可靠性(一个剑气就是100点,而不能是90点)而不是画面上【真正意义上的多人同步】,所以为了降低广播消息的滞后性对于other玩家的游戏体验,状态同步机制往往会采用 动作预测比对,不一致回滚等操作”提前“预估玩家的行为。当然这都是 属于 【状态同步】的机制性问题了,还是说回正题。
3.4 技能流程(服务器接收【玩家】)
图 3.4.1 玩家通过按钮释放技能-服务器收包逻辑
- 由于技能释放通过有一段时间,这期间可以触发Impact 逻辑(技能效果),在结束时通过,面板伤害+ 装备修正 结算数值。这里的逻辑是放在Tick里去监听的,这部分内容需要额外起一个线程去处理。 只需要在 Scene_Routine (可以理解为主线程) 线程的Tick里 注册了 SkillLogic :: Tick() 即可。在技能计数结束时,线程池销毁SkillLogic_Routine 线程,并向客户端 回包(伤害结算),同时做完Scan_All 扫描后(检测视野范围的在线 玩家),向其广播该玩家的 行为。
下面是一种Impact(技能效果逻辑.h 文件)
class ImpactLogic_004 : public ImpactBaseLogic
{public:enum TAB_PARAM{CONTINEUE_TIME, //参数1 : 持续时间(毫秒)ATTR_TYPE01, // 参数2: 战斗属性 1 ....ATTR_TYPE13, // 参数13: 战斗属性 13ISINC_HP , // 回血上限同时是否恢复真气值 ( 1,0)}enum DYNAMPARA{PASSIVITYPE_01,//被动技能对类型1修正百分比(k X 0.01)}ImpactLogic_004(){};~ImpactLogic_004();override void InitImpactDataInfo(Obj_Char& rChar, ImpactStruct& rImpact);//通用初始化override void StartEffect(Obj_Char& rChar); // 触发效果override void OnFadeOut(Obj_Char& rChar); //效果结束回调逻辑protected:void calcuateImpact1(int_32 nIndex, TAB_PARAM param1,TAB_PARAM param1);
}
比如说我自己定义了一个方法叫 calcuateImpact1(int_32 nIndex, TAB_PARAM param1,TAB_PARAM param1);
里面有两个参数,可能就是上面的战斗属性 1 和 13 ,假如他们的值是70 和 5, 我在方法中定义为 造成了70点面板伤害 ,触发眩晕的基础时间是 5s ,这就自定义了一种 逻辑。
图 3.4.2 impact技能效果-服务器处理逻辑
3.5 技能的其他发起形式
技能还可以由别的战斗主体发起,这里就不多做介绍了。直接上图
图 3.5.1 NPC(敌人)发起技能逻辑(服务器)
图 3.5.2 伙伴发起技能逻辑(服务器)
图 3.5.3 克隆玩家发起技能逻辑(服务器)
最后,简单的概括一下伤害结算部分。实际就是繁琐的读表 运算 再读表了。谈一谈涉及的概念吧。
4.伤害
- 战斗过程中根据攻防双方战斗属性造成的掉血都是通过伤害转换成最终的掉血量
- 目前所有伤害的来源都来着两个地方:0号效果逻辑根据攻防双方战斗属性计算出的伤害量和伤害反射逻辑反射的伤害
- 伤害依次经过伤害修正(放大减免)伤害免疫伤害反射伤害吸收最终转换为掉血量
4.1 战斗属性
1.战斗属性分为两部分:初始属性(只有int值部分) 修正属性(修正的固定值和修正的百分比) 最终属性=初始属性*修正属性
2.各个数值玩法系统的数值养成部分都是属于修正属性部分数值玩法系统属性发生变动时置脏在Tick中重算所有置脏的修正属性 再计算出最终属性值
4.2 仇恨值
1.发出攻击行为和受到攻击行为都会将对方放进自己的仇恨列表
2.Npc攻击目标从仇恨列表中选取 仇恨列表为空 退出战斗状态
3.玩家的仇恨列表主要用于判定是否处于战斗状态
4.仇恨列表的心跳会根据对方是否有效是否存活是否在指定时间内有仇恨产生进行列表移除