先说一下技能的整体设计框架:
技能是基础配置+多个阶段。
buff是基础配置+多个触发时机。
阶段和触发方式下,都支持配置多个事件。
- 技能基础配置
- 阶段1
- 阶段基础配置
- 事件1(例:给别人加buff事件)
- 事件2(例:给自己加buff事件)
- 阶段2
- 阶段基础配置
- 事件1(例:治疗事件)
- 事件2(例:伤害事件)
- buff基础配置
1、当攻击时
- 治疗事件
- 伤害事件
2、当被击时
- 治疗事件
- 被动位移事件
3、当暴击时
- 治疗事件
- 主动位移事件
4、当被暴击时
5、当命中时
6、当被命中时
7、当出生时
8、当死亡时
9、当眩晕时
10、当被眩晕时
... ...
在击杀了一名单位后,会投骰随机获得1到6的额外赏金。在死亡时,会扣除100到300金币。
那么该buff需要为每个触发时机添加一个cd时间。buff中有一个map记录各个时机的触发时间戳。buff配置格式如下:
- buff基础配置
- 当被攻击时
- 伤害事件
- 周期性触发
- 恢复事件
技能&buff,是上层两个不同的调用入口。里面真正实现效果的,被封装为事件。
技能&buff仅仅是框架,提供安装、触发、拆卸事件的功能。
事件对于技能&buff是通用的。
服务器端处理事件:
1、伤害事件
2、给自身加buff
3、给别人加buff
4、治疗事件
5、位移事件
6、嘲讽事件
7、召唤NPC事件
8、执行lua表达式事件
9、复活事件
客户端处理事件:
1.角色动画
2.警示语言
3.警示特效
4.屏幕特效
5.播放特效
6.子弹
7.播放声音
8.相机抖动
9.相机播放animation
10.武器动画
11.读条
12.动画控制
13.连接特效
14.拖尾特效
15.客户端表达式
releaser:技能或buff的释放者
owner:技能实体或buff的拥有者
tar:技能或buff的目标
1、扇形
2、圆形
3、长方形
4、环形
5、单体
取出玩家在地图上所处的格子id,计算出玩家位置,搜索视野范围内指定距离的玩家。
注意1:不同的视野方案,会有不同的搜索处理。(这里举例九宫格视野:以玩家为中心,在附近九个视野格子内,搜索出所有单位)
注意2:这是第一次搜索,直接用圆形搜索,只判断视野内的单位是否在圆形范围内。(因为扇形、矩形、环形都可以从圆形结果中做剔除)
举个例子,技能配置的目标数量:5。
1、搜索到所有满足条件的单位,然后按距离排序,返回前5个
假设附近九宫格内有20个单位,遍历后有8个单位满足距离条件,12个单位在距离范围之外。那么排序这8个单位,返回前5个单位作为此次搜索的结果。
(一般有距离排序需求的技能是:嘲讽、治疗。如果你放一个范围嘲讽技,但是离你最近的敌方单位没有被嘲讽,反而距离远的单位被嘲讽到,表现上会有些奇怪。)
2、搜索到满足条件的指定数量对象后,结束搜索并返回搜索结果列表
假设附近有20个单位,搜索出5个满足条件的单位,就提前退出结束搜索。(比如12345单位正好满足条件,那么就不再处理第6个单位)
3、搜索到所有满足条件的单位后,从可选列表中,随机返回指定数量。
1.排除受击目标
2.残血优先
3.某某类型优先(比如优先英雄,没英雄时,小兵也是目标)
1、朝向
2、角度
3、弧度
在SLG项目中,由于1个玩家会创建多个部队,所以需要区分[本部队]和[己方其它部队]。
enum ETarRela
{
ETR_NONE = 0 , // 无
ETR_SELF = 1<<0, //本部队
ETR_OWN = 1<<1, //己方部队
ETR_FRIEND = 1<<2, //友方部队
ETR_NEUTRAL = 1<<3, //中立部队
ETR_ENEMY = 1<<4, //敌方部队
}
如上图所示:玩家A释放了一个傀儡B,玩家C攻击了傀儡B,傀儡B正在攻击玩家D
此时设计不同的技能,举例设计以下4种技能,该技能都被赋予A持有:
1、释放一个傀儡,当傀儡被攻击,回复自身100血量。
对傀儡B来说,其实拥有了一个被动技能。当它被攻击时,会给释放者A回血。一般做法是:给傀儡B加一个被动buff。
(这个被动buff需要记录释放者A。并且将玩家A传递给恢复事件,在恢复事件中拿到玩家A的对象,然后执行加血接口)
2、释放一个傀儡,当傀儡被攻击,回复自身100血量,傀儡会对攻击者造成100的反击伤害
技能2的回血功能和技能1一样实现。另外技能2的被动buff还有一个功能,会对攻击者造成反击伤害。
所以这个被动buff需要配置:
a.当受击时,触发治疗事件。(治疗事件传入releaser)
b.当受击时,触发伤害事件。(伤害事件传入cre)
(这个被动buff需要记录释放者A、攻击者玩家C。并且将玩家A传递给恢复事件,玩家C传递给伤害事件)
3、释放一个傀儡,当傀儡被攻击,回复自身100血量,傀儡会对攻击者造成100的反击伤害,并且傀儡启动自毁装置,化作一道流光对傀儡当前攻击的目标造成减速效果
(需要记录释放者A、当前产生攻击行为的两方:玩家C和傀儡B、傀儡B的攻击目标:玩家D)
4、释放一个傀儡,当傀儡被攻击,回复自身100血量,傀儡会对攻击者造成100的反击伤害,并且傀儡自爆,在傀儡周围选取2个目标,对其造成眩晕
(需要记录释放者A、当前产生攻击行为的两方:玩家C和傀儡B、傀儡执行一次搜索,搜索出E、F)
在程序数据结构设计上:
玩家A:被记为释放者,命名releaser
傀儡B:被记为拥有者,命名owner
玩家C:被记为互动方,命名cre
玩家D:被记为当前目标,命名curTar
所以伤害事件中,设计上需要有这4种单位,才能满足上述的技能设计:releaser、owner、cre、curTar
状态是一个可叠加的数据,比如buff-1给玩家A添加了2s时长的眩晕,buff-2给玩家A添加了10s的眩晕。当buff-1的时长到了以后,此时不能直接将眩晕的状态清除掉,而是将眩晕状态的层数减1。当10s的时长到了以后,再减去眩晕的层数,此时层数为0,会清掉眩晕状态。
这里也一直有个有个遗留问题:不支持相同buffId叠加
int state[32];
伤害分担
伤害递减
伤害事件是一个比较特殊的事件,会附带位移、self加buff、other加buff
等行为。
并且伤害事件是唯一一个,需要附加当伤害事件后,执行xx事件的事件
。就是事件中还会嵌套事件
,其它事件没有这么复杂,理论上也可以嵌套。
p16的技能都是在服务端由ai释放的。
a. 在释放时是直接push到技能模块的队列中,接下来就是在tick中执行队列中的技能。
p11的技能是由玩家释放的,客户端上传UseSkill的消息到服务器。
a.服务器会判断技能是否瞬发类型,如果是瞬发类,那么流程类似t16会直接进技能模块的队列。
b.但如果不是瞬发类,而是持续类的话,则会先进生物体object的action队列
被动技一般使用buff来实现。
比如被动技石化
,效果是:当受击时,增加10点防御值。一般是给玩家挂个buff,在处理玩家受击的逻辑处,遍历身上所有buff,如果某个buff有配置触发节点当受击时
,那么执行这个节点下面挂载的事件列表。比如石化
这里是AttrAdd(self, “defense.value”, 10)。
void BuffOnBeAttack(MapUnit* attacker)
{
for(auto& buff : mBuffs)
{
if(!buff->eventOnBeAttack){continue;} // 如果没有配置当受击时,无需处理;
buff->OnBeAttack(attacker);
}
}
由于节点和事件,都是交给策划配置的。那么可能会出现一些循环触发的问题,需要程序在代码上,设计机制来避免。
1. 当攻击时,下面挂一个伤害事件。(自我循环)
[当攻击时,触发伤害事件] >> [伤害事件执行后,触发当攻击时] >> [当攻击时,触发伤害事件]。。。
2. 当受击时,下面挂一个伤害事件。(双方循环)
[A攻击B以后,触发B的当受击时,执行伤害事件] >> [B的伤害事件打到A,触发A的当受击时,执行伤害事件]
>> [A又产生伤害事件,打到B]。。。
3. 不同技能间的组合循环(组合循环)
技能1:当攻击时,治疗自身,回复100血量
技能2:当治疗时,攻击1次敌方
4. 当我暴击率是100%的时候(条件循环),设计了下面这种技能
技能3:当暴击时,攻击1次敌方
那么我1次攻击,触发暴击后 >> 当暴击时,攻击敌方,此次攻击又触发暴击 >> 当暴击时,攻击1次敌方。。。
解决方案是所有被动技的触发入口只封装1个函数。
在函数开始处,声明一个static set