转自:游戏服务器架构:如何设计开发战斗系统的技能和buff系统 - 掘金
在网络游戏中的战斗形式多种多样,不同游戏的战斗逻辑也有很大的差异。但是一般都会涉及技能系统和buff系统,两种之间相互关联,技能可以产生buff作用在目标上,影响目标。同时buff也会影响技能的释放效果,两者都可以算得上游戏战斗系统最重要的元素。
在游戏战斗中有许多各种各样复杂的buff和技能,这些buff和技能是由基础的buff和技能效果机制组合形成的。通过基础的机制效果的组合,形成各种各样的复杂的buff和技能。这些基础的buff效果机制和技能效果机制支持策划通过配置表配置数据,来影响这些buff和技能的实际效果。
如下图,一般在技能释放的时候会检查当前是否有沉默或者影响技能释放的buff,根据当前的buff,技能来计算命中率,如果未命中,可能需要叠加一些buff的次数,或者让身上的某一个buff消失,或者让敌方、友方增加增益减益buff,如果命中,需要根据当前技能,buff的增益减益效果来计算伤害,同时由于技能一般会携带一至多个效果Effect,那么针对这些Effect可能会产生新的Buff施加给包含自己的友方或者敌方(具体看策划配置和技能的逻辑了)。
下面介绍我对于buff系统的理解,算是抛砖引玉吧。
buff的来源可能是战斗单元释放的技能,可能是buff效果产生新的buff,可能是战斗一开始因为佩戴某件装备。
buff产生的效果(Effect)各种各样例如,增加\降低属性值,一定时间内流失/恢复生命、魔法等,眩晕,沉默,护盾,嘲讽,反伤,攻击时攻击增伤、受击时减伤,死亡时造成对手造成伤,击杀目标时回血等等。同一个战斗单元可以有多个buff。
为了增加buff系统的可用性和灵活性,我们可以把一个buff拆分成一到多个基础的buff效果,通过实现基础的buff效果,灵活组合形成多种多样的buff。例如给英雄施加一个buff效果是护盾同时在buff移除时恢复英雄10%的血量,这一个buff就有两个基础效果,一个是护盾,另一个是buff移除时回复血量,一个buff可以有一到多个buff效果,而一个英雄有多个buff。
为了管理战斗单元存在的诸多buff,需要一个buff管理器管理buff的添加、生效、移除等整个生命周期,供外部使用的接口也只能是buffManger,比如添加buff,发送buff列表,检查buff效果状态,在触发点调用来执行buff效果等等。buff基础效果、buff、buff管理器关系如下:
buff管理着一到多个buff类对象。每个Buff类包含了当前buff效果的指针(指针里包含了效果ID,buff配置数据,buff生成时间、buff类型、buff移除标志等) ,Buff在哪些对象上发挥作用的指针,Buff释放者的指针,Buff的叠加次数。
//战斗单位身上的状态
struct Buff
{
Effect* pEffect;//当前buff产生的效果信息
FightUnit* victimerUnit; //buf作用的对象
FightUnit* caster; //buf的施加者
uint8_t leftRound;
int modValue_1; //类型1修正值
int modValue_2; //类型2修正值
uint8_t castRound; //buf施加的出手序号
public:
void calcModValue(FightUnit* pUnit, FightUnit* pVictimer, FightAttack* pFa, uint32_t harmValue = 0);
};
复制代码
//效果
struct Effect{
public:
uint32_t effectId;//效果ID,从配置表拿到
bool isbadeffect; //增益还是减溢buff
bool isaddafterhit; //0无论技能是否命中,都添加 1 命中才添加
uint32_t groupNo;//当前效果组,从配置里拿到
uint32_t priority;//优先级
EFFECTTARGETSEL ets;
uint8_t targetselecttypepara;
uint32_t addRatio;
bool isCalcRatio;
EffectCalc ec1;
EffectCalc ec2;
uint8_t effectcontrol;//是否受控制抗性影响
uint8_t round; //持续轮数
public:
static map effectMap;
static int loadEffect(char* szFileName);
static Effect* getEffect(uint32_t effectId);
static void getTargets(FightUnit* pUnit,
FightForce* pForce,
FightAttack* pFa,
Effect* pEffect,
list& targetList);
};
//战斗单位
struct FightUnit{
public:
FightUnit();
virtual ~FightUnit();
public:
bool isNpc;
union { PlayerPartner* pHero; NpcPartner* pNpcHero; }Hero;
uint32_t maxHp;
uint8_t orgChakra;//临时拷贝的查克拉
FightProp partnerProp;
PHALANX_POS pos;
FightForce* myForce;
bool isAttack; //是否是攻方
BuffManger bufs; //自己身上的buffmng
list castBufs; //施展的buf
uint8_t fightUnitType;//战斗单元类型-自己伙伴/影分身/援护人
uint64_t playerid;//可能是玩家id,也可能是援护人id
public:
void havePassiveSkill(uint32_t psTypeId, int psTbl);
void getPsParam(uint32_t psTypeId, int psTbl);
uint8_t getTriggerPsCount(uint32_t psId);
void setTriggerPsCount(uint32_t psId, uint8_t triggerCount);
FightProp* getFightProp();
public:
bool canAttack();
Buff* hasBuf(uint8_t et);
Buff* hasInBuf(uint32_t effectId);
void doBuf(FightAttack* pFa);
void delBuf(uint32_t effectId);
void replaceBuf(Buff* pOrgBuf, Buff* pDestBuff);
void playBuf(Buff* pBuff, FightAttack* pFa, bool bCrit = false);
void addBufTo(FightUnit* pVctimer, Effect* pEffect, FightAttack* pFa, SkillTem* pSkill);
uint32_t getPropValue(uint16_t propId, uint32_t harmValue = 0);
uint32_t calcHit(FightUnit* pVictimer, SkillTem* pSkill);
uint32_t calcCrit(FightUnit* pVictimer, SkillTem* pSkill);
uint32_t calcEffectCrit(SkillTem* pSkill);
uint32_t calcHarm(FightUnit* pVictimer, SkillTem* pSkill);
uint32_t calcCritHarm(uint32_t harm, FightUnit* pVictimer);
uint32_t calcEffectCritHarm(uint32_t harm);
uint32_t calcBlock(FightUnit* pVictimer);
int calcBufRatio(FightUnit* pVictmer);
uint32_t applyHarm(int& harm);
void clearBuf(FightAttack* pFa);
void caclPvpLevelSuppress(int& harmValue);
void clearRoundBuff(FightAttack* fa); //计算有益或者有害buff数量
uint32_t calcBuffNumByType(uint8_t type); //0有益buff 1有害buff
private: map psTriggerCountMap; //被动技能触发的次数};
复制代码
buffManger保存这一组buff,buffManger所属的战斗单元等数据。控制着buff的整个生命周期
class BuffManger{
public:
buffManger(FightUnit* _owner);
virtual ~buffManger();
//一些函数实现,比如添加/移除buff函数,timetick函数,buff触发函数,检查buff效果函数等等
privete:
FightUnit* owner;std::vector vecBuffs;
unsigned short buffPerformStatus[eBuffPerfrom_Max];
}
复制代码
buff效果多种多样,buff效果可能是某种状态,比如眩晕,在战斗单元行动或释放技能时,检查战斗单元的buffManger遍历所有的vecBuffs下所有buff的buff效果是否有眩晕效果,如果有不能行动或者释放技能。也可能是加属性值的,添加或者移除buff时根据buff效果重新计算战斗单元的属性值。或者是触发式,比如流失对于回合制的便可能是每回合造成减血,对于非回合制的可能是每秒造成减血。触发有很多点会导致触发,比如死亡时,攻击时,受击时,暴击时,格挡时等等,这样每次到触发点,要遍历所有buff的所以机制找到符合条件的执行buff效果。
为了减少遍历次数,可以在buffManger中添加buffPerformStatus数组变量,模仿只能指针做一个计数。每一个成员代表这个buffManger的buff效果的计数,如果没有相应的buff效果计数值为零,添加buff时如果有相应的buff效果,计数加一,buff移除相应buff效果也移除,计数减一。这样我们在检查某种buff状态,比如眩晕,就不需要遍历所有的buff了。触发时如果有相应的buff效果再去遍历。
这样这个buff系统模型可以适合大部分战斗,稍微改改不同的战斗都可以使用.
网络游戏的战斗系统中,技能是战斗单元释放作用在目标上造成一系列的技能效果。一个战斗单元可以拥有多个技能,根据玩家操作或者自动,判断技能释放满足条件(cd,魔力等条件)释放技能,这时候根据技能生成一个技能Action对象,最终在目标上生效。
每一个战斗单元需要一个skillManger对象管理其拥有技能(skill对象),skill对象保存技能的基础配置数据,技能上次释放时间等信息,用于生成skillAction对象。而skillAction对象根据时间线执行,最终在目标上产生技能效果。
战斗单元释放技能变成一个技能Action,技能Action可以分成:开始、吟唱、出手、飞行、生效、结束。具体每一阶段的持续时间由配置表控制,每一阶段的持续时间与前端播放动作时间一致,后端在每一阶段开始会发消息给前端告知技能所处阶段以及持续时间等信息,然后前端根据所处阶段播放对应的动作。
如果是技能是远程射出子弹,出手阶段结束,生成子弹飞行,可以叫子弹阶段,根据时间间隔采点,检查碰撞生效,子弹消失,进入结束阶段,整个技能Action结束。
skill类
class skill{
public:skill(const cSkillCfg* _cfg,
unsigned int _skillId, unsigned short _skillLv);
virtual ~skill() {}//使用技能是检查技能的CD 时间精确到毫秒
bool checkSkillCD(unsigned long long currentTm) const;
//使用技能是的一些操作,比如记录此次释放技能的时间
void onUseSkill(unsigned long long currentTm);//根据需要添加一些处理函数
public:
const cSkillCfg* cfg;//关于技能的配置信息
unsigned int skillId;//技能ID
unsigned short skillLv;//技能等级
unsigned long long lastUseTime;//上次释放技能的时间用于计算CD
};
复制代码
skillManger 管理战斗单元的技能
class skillManager{
public:
skillManager(FightUnit* _owner);
virtual ~skillManager();//战斗开始先根据战斗单元数据,初始化技能表,
并将生成的技能存skills表中
void setup();//取出要释放的技能,进行操作
skill* getSkill(unsigned short skillIndex);//管理战斗单元的所有技能,根据需求添加逻辑
public:
FightUnit* owner;//所属战斗单元
std::vector skillList;//技能列表};
复制代码
技能释放根据skill生成skillAction处理技能的具体生效情况
class skillAction{
public:
skillAction(fightScene* _scene, skill* _skill, fightUnit* _fighter);
//传入战斗场景的指针,战斗技能,技能释放者
virtual ~skillAction() {}
void doSkillOnTimer(unsigned long long currentTm);
//在战斗主循环中调用,每次调用到,根据时间检查所处阶段,以及结束时间,推进技能逻辑
//与战斗效果相关函数处理,根据技能效果编号定义一组技能生效基础函数,将函数指针存存入列表,根据效果编号调用
public:
fightScene* scene;skill* s;
fightUnit* fighter;
eSkillActionStage skillStage;
//当前所处阶段(开始、吟唱、出手、飞行、生效、结束)
unsigned long long stageEndTime; //当前阶段结束时间}
复制代码
技能在生效的效果有一到多个,通过组合形成不同技能,也需要实现一些基础的技能机制效果,组合形成不同的技能。一个技能在造成伤害的同时可以给目标加一个debug,这个技能就有两个基础技能机制效果组成,技能伤害+添加buff。这样我们只需要实现基础的技能效果机制,将不同的技能机制效果的实现函数的指针存放在数组中,根据技能机制效果编号调用函数,实现技能效果。
作者:游戏开发老司机
链接:https://juejin.cn/post/6979390391254532110
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。