Imported from wobushiren79/DemonLordRoguelike (
Demon Lord Roguelike/.claude/skills/buff-system/SKILL.md). Install upstream withnpx skills add wobushiren79/DemonLordRoguelike --skill buff-system. Copyright stays with the author.
BUFF系统开发指南
核心概念
BUFF数据三件套
BuffBean - BUFF静态数据(buffId + 实例化时确定的trigger_value/rate/chance/num/time)
BuffEntityBean - BUFF运行时实例数据(施加者/目标/剩余次数/堆叠层数/条件值)
BuffInfoBean - BUFF配置数据(来自BuffInfo配置表,含 class_entity / stack_mode / pre_info 等)
BUFF 实体类型体系
BuffBaseEntity # 抽象基类(事件回调 + ShowBuffEffect + CheckIsPre;UpdateBuffTime 的时长门槛走虚方法 GetTriggerTimeForUpdate,默认=配置 trigger_time,子类可重写做动态时长)
├── BuffEntityAttribute # 属性BUFF(实现 IAttributeModifierSource)
│ ├── BuffEntityAttributeAttackTime # 改攻击前摇/动画时间的属性BUFF(独立通道)
│ │ └── BuffEntityAttributeAttackTimeSingleTarget # 单体定向攻速BUFF:随机锁定一只防守生物攻速翻倍(攻击时间×0.5),实现 IBuffSingleTarget(曾用于馈赠「急性子」,现役无配置、机制留存)
│ ├── BuffEntityAttributeSingleTarget # 单体定向属性BUFF:随机锁定一只防守生物 ATK/HP/DR 翻倍(rate=1),实现 IBuffSingleTarget(曾用于馈赠「大力出奇迹/膘肥体壮/钢铁憨憨」,现役无配置、机制留存)
│ ├── BuffEntityAttributeDynamicRate # 动态率属性BUFF基类:加成率运行时算而非配置写死(重写 CollectModifiers+ChangeData 取 GetDynamicRate,仅走 PercentAdd)
│ │ ├── BuffEntityAttributeScaleByDefenseCount # 通用功能类:属性%随"当前场上存活防守魔物数N"缩放,率=(N-1)×每只率(曾用于馈赠「都是兄弟」,现役无配置、可被其它同功能馈赠复用)
│ │ ├── BuffEntityAttributeScaleByKillCount # 通用功能类(兼 IBuffSingleTarget):选取时随机锁定一只防守生物,属性%随"该只自身累计击杀敌人数"缩放,率=该只killNum×每只率(曾用于馈赠「杀红了眼」,现役无配置、可被其它同功能馈赠复用)
│ │ └── BuffEntityAttributeDecayByAllyCount # 通用功能类:属性%随"当前场上存活防守魔物数N"反向衰减,率=基础率-每友方衰减率×(N-1)、下限0(trigger_value复用为衰减百分点:50=每友方-50%,因扭蛋isRandom整数化0.5会归零);现役用于R稀有度BUFF「独行者A-D」(11001300001~04,各+ATK/ASPD/DR/HP 300%)
│ ├── BuffEntityAttributeMulti # 多属性BUFF:一次随机率(trigger_value_rate)同时改多个属性;class_entity_data="属性:倍率|属性:倍率"(如 "ATK:1|HP:-1"=ATK+率、HP等量负率),实现"一属性增益、对应属性等比减益"。扭蛋R级双刃BUFF(狂战士/快枪手/铜墙铁壁/大块头 各A/B/C)。与单属性同为"纯属性BUFF"(IsBuffEntityAttributeOnly),走属性烘焙路径
│ └── BuffEntityAttributeDurationByApplierATK # 动态时长属性BUFF:实际持续时间=配置trigger_time+施加者实时ATK×0.1(常量DurationAddPerATK),重写 GetTriggerTimeForUpdate,施加者在场实时刷新快照、离场/死亡沿用最后快照;属性修正部分仍走基类管线不变。烂泥史莱姆3003「粘液减速」1000200001(攻击模式400001触碰挂MSPD-40%,基础1.1s+ATK加成)
├── BuffEntityBaseHPChange # 周期改血BUFF(按目标最大生命结算):每 trigger_time 触发、共 trigger_num 次,HP变化=value+目标最大HP×rate(正回血/负扣血,扣血走UnderAttack管线);子类 BuffEntityBaseHPChangeArea=范围版;持续回血12000100001
├── BuffEntityBaseHPChangeByApplierATK # 周期掉血BUFF(按施加者实时攻击力结算):每次触发伤害=施加者当前ATK×|rate|+value,施加者不在场/已死亡跳过本次,走UnderAttack管线;骷髅魔法师(火)「烧伤」1000500001(攻击模式201003命中50%概率挂,3秒×3次,rate=-0.5)、毒液史莱姆3004「中毒」1000400001(攻击模式400002触碰100%挂,1秒×10次,rate=-0.2=史莱姆实时ATK的20%/跳)
├── BuffEntityInstant # 瞬时BUFF(SetData中立即触发并isValid=false)
│ ├── BuffEntityInstantCloneDefenseCreature # 深渊馈赠「增殖」:随机复制一个防守生物
│ ├── BuffEntityInstantRewardMoreItem # 深渊馈赠「奖励多多」:累加 FightBeanForConquer.rewardAddItemNum(领奖时+1奖励物品)
│ └── BuffEntityInstantRewardMoreSelect # 深渊馈赠「再来一瓶」:累加 FightBeanForConquer.rewardAddSelectNum(领奖时+1可选次数)
├── BuffEntityConditional # 条件触发(UpdateBuffTime 只走总时长,不走周期)
│ ├── BuffEntityConditionalAttack # 攻击/受击事件触发:发起一次自定义AttackMode
│ ├── BuffEntityConditionalAttackAgain # 触发立即再攻击一次(复用当前AI意图)
│ ├── BuffEntityConditionalThorns # 荆棘反伤(SSR稀有度「荆棘之躯」:受击时把名义承伤×rate反弹给攻击者,上限自身当前HP;反伤走正常UnderAttack管线(伤害数字/伤害统计/击杀事件),重写EventForUnderAttack自做归属过滤(attackedId==自己、attackerId!=自己),不调base避免空pre_info全场累积)
│ ├── BuffEntityConditionalLifeSteal # 伤害吸血(SSR稀有度「吸一吸」:自身造伤×rate转自身HP,走RegainHP正式治疗路径(可喂养治疗计数类BUFF);按attackerId判定,BUFF来源伤害(反伤/再攻击)同样触发,AOE每目标各触发一次)
│ ├── BuffEntityConditionalInvincible # 上场无敌(SSR稀有度「真男人」:挂上即无敌trigger_value秒(5~10,仅Gaming计时)、color_body染金,到期清旗标并移除;驱动FightCreatureBean.isInvincible(UnderAttack最前拦截,跳0伤害字+miss音效),SetData/ClearData/UpdateBuffTime三处自理旗标+RefreshBaseAttribute+RefreshBodyColor)
│ ├── BuffEntityConditionalAttribute # 属性变化时触发
│ ├── BuffEntityConditionalDead # 自身死亡结束时触发
│ ├── BuffEntityConditionalDeadAttack # 死亡时发动一次攻击
│ ├── BuffEntityConditionalDeadRebirth # 死亡时重生(一次性,触发后自删、最多二连爆;落点=isPositionReleased?死亡地点:原格——冲锋自爆型死亡地点重生并立即再次冲锋;落点被占且非自身时放弃重生但BUFF照耗,详见下文「死亡重生」节)
│ ├── BuffEntityConditionalDeadAreaHPChange / DeadAreaDRChange # 死亡时区域改HP/DR
│ ├── BuffEntityConditionalDeadCreateCrystal # 死亡时生成水晶
│ ├── BuffEntityConditionalAddDropCrystal # 死亡掉落水晶时叠加
│ ├── BuffEntityConditionalCreateCrystal # 生成水晶(事件类)
│ └── BuffEntityConditionalDemonLordMPRegain # 给魔王(防守核心)回复魔力(SR稀有度「左膀右臂」击杀1个生物/「舍己为王」累计承伤100,回复 trigger_value 点(10~20);经 ChangeMP 钳制[0,MP上限]+RefreshMPShow 刷新显示)
├── BuffEntityPeriodic # 周期性触发(无次数限制)
│ ├── BuffEntityPeriodicAttackAgain # 周期性强制再攻击
│ ├── BuffEntityPeriodicAttackMultiInstant # 周期性多次瞬时攻击(深渊馈赠「闪电」落雷:BUFF 只管周期触发→触发瞬间快照全场敌人→不放回抽 N 个主目标(一轮内多道雷主目标互不重复;敌人少于雷数时只发同等数量的雷;溅射不受限——同一目标可被多道雷重复溅射、被溅射过的目标仍可作主目标)→第1道立即+后续0.1秒间隔连发;每道雷=发射一个 AttackModeInstantAreaThunder 攻击模块,粒子/AOE/伤害全走 AttackMode 框架(AOE半径/单雷命中上限配在攻击模块表 collider_area_size/hit_max,AOE 多目标伤害按命中次序依次减半保底1,单次攻击内局部去重,伤害=魔王实时攻击力×trigger_value 由BUFF发射时注入,CRT=0 不暴击);class_entity_data="次数,攻击模块ID";深渊馈赠「闪电」3000300001~005 → 攻击模块 300031~300035,Lv1~5 半径0.2~1.0、单雷命中上限1~5)
│ ├── BuffEntityPeriodicAttackRoad # 周期性随机道路冲撞(深渊馈赠「失控的矿车」:随机选 N 条道路各从最左端驶出 1 辆矿车沿路向右碾压;车数>路数时每路先各 1 辆、多出的随机重复分配,同路第 2 辆起按 0.5 秒间隔错开出车(UpdateBuffTime 驱动批次队列);伤害=魔王实时攻击力×trigger_value 发车时注入、CRT=0,逐目标减半由攻击模块处理;场上无敌人本轮不触发;class_entity_data="车数,攻击模块ID";馈赠 2000003001~005 → BUFF 3000400001~005 → 5 级共用攻击模块 300041=AttackModeRangedPiercingRoad)
│ ├── BuffEntityPeriodicAttackBoomerang # 周期性回旋镖(深渊馈赠「死亡回旋」:从魔王位置+CreatureInfo.attack_start_position偏移发射回旋镖,锁定点=目标位置(高度取魔王发射点高度);不放回抽目标,一轮内多镖目标互不重复、敌人少于镖数只发同等数量;第1镖立即发射,后续0.2秒间隔队列发射(照闪电UpdateBuffTime间隔模式);去程命中后伤害逐目标减半,越过目标一格后返回魔王身边;速度曲线=时间参数化:去程前半程全速后半程线性减速至0(折返点静止)、返程从0匀加速至ReturnMaxSpeedRate(1.5)倍;弹体自旋=-720度/秒绕Z轴(InitAttackModeShow写spinSpeed/spinAxis走DSP自旋子桶,每发随机相位);class_entity_data="镖数,攻击模块ID";馈赠 2000004001~005 → BUFF 3000500001~005 → 5 级共用攻击模块 300051=AttackModeRangedBoomerang)
│ ├── BuffEntityPeriodicAttackBounceAxe # 周期性弹跳斧头(深渊馈赠「跳跳斧」:从魔王位置+CreatureInfo.attack_start_position偏移扔出斧头;目标=随机一排的最远敌人——按 roadIndex 分组、同路按 x 降序=离魔王最远优先;不放回抽取(随机路取最远),一轮内多斧首目标互不重复、敌人少于斧数只扔同等数量;第1斧立即扔出,后续0.2秒间隔队列发射;伤害=魔王实时ATK×trigger_value 发射时注入、CRT=0;弹跳(分段抛物线)/追踪/逐目标伤害减半由 AttackModeRangedArcBounce 处理,弹跳次数经 StartAttack 前写 bounceMax 注入(照 targetRoad 先例);class_entity_data="斧数,攻击模块ID,弹跳次数";馈赠 2000005001~005 → BUFF 3000600001~005 → 5 级共用攻击模块 300061=AttackModeRangedArcBounce,弹跳搜索半径配在其 collider_area_size 第1项=1)
│ ├── BuffEntityPeriodicAttackOrbit # 常驻环绕书本(深渊馈赠「知识的力量」:随机选 1 只最前排(x 最大)存活魔物为宿主,其周围 XZ 水平面环绕 N 本书(半径0.75、高度取魔王位置+攻击起始偏移、转速Lv1~5=0.6/1.2/1.8/2.4/3弧度/秒、角度均分);宿主死亡/出现更前排魔物(x 更大)→改选当前最前排(并列 x 随机1只、并列不换防抖动),场上无己方魔物→书本全销毁、重新有魔物再生成;书本=常驻攻击模块 AttackModeOrbit 不自毁、触碰命中(attack_search_type=11球形)每只书对同一敌人0.5秒冷却、伤害=魔王实时ATK×trigger_value(0.4)命中瞬间取、CRT=0;关卡切换书本被回收→下一帧整套重建;BUFF 只每帧驱动宿主刷新/生成/销毁/宿主同步,trigger_time=9999 使周期触发永不发生(TriggerBuffPeriodic 禁用);class_entity_data="书本数,攻击模块ID,转速";馈赠 2000006001~005 → BUFF 3000700001~005 → 5 级共用攻击模块 300071=AttackModeOrbit)
│ ├── BuffEntityPeriodicAttackRebound # 常驻回弹菱块维持(深渊馈赠「回弹菱块」:每 trigger_time(1秒) 自检一次——场上由本BUFF发射且仍存活(isValid)的菱块不足 class_entity_data[0] 颗时立即补足,满编本轮无事;菱块=永久弹(不随时间销毁、不随轮次累积、总数恒定=弹数),关卡切换存量弹被 ClearAttackModePrefab 清掉后靠本机制自动补回,馈赠升级替换/清空时 ClearData 主动销毁全部存量弹;发射=魔王位置+CreatureInfo.attack_start_position偏移+攻击模块偏移,初始角度以 +x 为 0° 的前向锥 ±75° 随机(LaunchAngleHalfRange 常量,保证射入道路范围),场上无敌人也照常发射;伤害=魔王实时ATK×trigger_value(1)发射时注入(保底1)、CRT=0;四壁反弹(±5°随机偏转)/同目标1秒命中冷却由 AttackModeRangedRebound 处理,弹速按级配在攻击模块行 speed_move=2~4;class_entity_data="弹数,攻击模块ID";馈赠 2000007001~005 → BUFF 3000800001~005 → 攻击模块 300081~300085=AttackModeRangedRebound,视觉 AttackModeVisual_Pingpang_1,图标 ui_abyssalblessing_pingpang)
│ ├── BuffEntityPeriodicAttackShockwave # 周期性冲击波(深渊馈赠「第六次冲击」:每 trigger_time(10秒) 从魔王位置+CreatureInfo攻击起始偏移(+攻击模块start_pos_offset)处发出一道圆环冲击波 AttackModeShockwaveRing,半径扩张至覆盖整条道路(最大半径=道路右缘+余量−圆心x,攻击模块按当场路长计算),扫到的敌人受伤害并被击退0.5(交 AI 击退意图 StartKnockback:方向固定 +x 沿道路向后推不带z分量、固定0.2s推完、攻击循环打断、结束回闲置重索敌);伤害=魔王实时ATK×trigger_value(0.1~0.3)发射时注入(保底1)、CRT=0,场上无敌人本轮不触发;class_entity_data="攻击模块ID";馈赠 2000008001~005 → BUFF 3001100001~005 → 5 级共用攻击模块 300091=AttackModeShockwaveRing,视觉 Effect_Shockwave_1(1700001,配在攻击模块表300091 effect_hit列) 走 EffectHandler.ShowEnduringSingletonEffect(全局单例通道,SingletonEffectParam multiplier 同步半径/时长),图标 ui_abyssalblessing_boom;首个带击退机制的馈赠)
│ ├── BuffEntityPeriodicAttackFireBottle # 周期地形火焰瓶(深渊馈赠「瓶装炼狱火」:每 trigger_time(10秒) 触发瞬间快照全场存活敌人、不放回随机抽取 class_entity_data[0] 个主目标(同轮多瓶不瞄同一目标,敌人少于瓶数只丢同等数量),第1瓶立即+后续0.2秒间隔队列投掷(照闪电 UpdateBuffTime 间隔模式);每瓶=发射 AttackModeRangedArcGround(抛物线飞向该目标当前位置**固定落点不追踪**、纯投掷物不命中、飞行段弹体自旋-720°/s绕-Z轴,落地燃放半径1.2地形火焰持续5秒,每1秒对范围内敌人跳伤);伤害=魔王实时ATK×trigger_value(0.2)投掷时注入(保底1)、CRT=0、不递减(多片叠加多次跳伤),场上无敌人本轮不触发;class_entity_data="瓶数,攻击模块ID";馈赠 2000009001~005 → BUFF 3001200001~005 → 5级共用攻击模块 300101=AttackModeRangedArcGround,视觉 AttackModeVisual_FireBottle_1(贴图复用馈赠图标)+燃烧段 EffectHandler.ShowEnduringSingletonEffect(effect_hit=1800001) 全局单例粒子 Effect_FloorFire_1(1800001),图标 ui_abyssalblessing_fire)
│ └── BuffEntityPeriodicPickupCrystal # 周期性自动拾取水晶
└── BuffEntityPecurrent # 周期性触发(有次数限制 = trigger_num)
BUFF 前置条件
BuffBasePreEntity # 前置基类(GetEventRole + CheckIsPre)
├── BuffPreEntityForAttackDamage # 累计造成伤害 (EventRole = Attacker)
├── BuffPreEntityForUnderAttackDamage # 累计受到伤害 (EventRole = Attacked)
├── BuffPreEntityForHPRateLess # 当前HP百分比低于阈值 (EventRole = Attacked)
├── BuffPreEntityForKillNum # 击杀数量 (EventRole = None)
├── BuffPreEntityForRegainHPReceived # 累计"被治疗"HP (EventRole = Attacked,走 RegainHP 事件)
├── BuffPreEntityForRegainHPCast # 累计"施放治疗"HP (EventRole = Attacker,走 RegainHP 事件)
└── BuffPreEntityForOnFieldTime # 在场存活时间(秒) (EventRole = None,纯时间驱动,读 timeUpdateTotal)
时间驱动条件:
BuffPreEntityForOnFieldTime读buffEntityData.timeUpdateTotal(BUFF 逐帧UpdateBuffTime累积), 该累积仅在gameState == Gaming时推进(GameHandler.Update门控),故暂停/深渊馈赠选择期间不计时。 配套实体BuffEntityConditionalAttributeTime(继承BuffEntityConditionalAttribute)在UpdateBuffTime里未达标时逐帧调HandleForEvent,跨过阈值当帧刷新属性。此类条件class_entity_events留空(无需事件订阅)。
BuffPreEventRole(None/Attacked/Attacker)用于在 BuffBaseEntity.EventForUnderAttack / EventForRegainHP 中
判断本次事件是否归属当前BUFF目标。任何新增的前置条件实体必须重写 GetEventRole(),
否则在Attacker/Attacked事件中会被错误过滤掉。治疗事件复用同一角色语义:Attacked=被治疗者(attackedId)、Attacker=治疗者(attackerId)。
关键架构(必读)
1. 事件绑定 — BuffEventDispatcher
事件订阅/反订阅由 Assets/Scripts/Game/Buff/BuffEventBinding.cs 集中管理,
不再在 BuffBaseEntity 用 switch 硬编码。
//新增 BUFF 触发事件时,只需在 dicBindings 追加一行:
{ EventsInfo.YourNewEvent,
new BuffEventBinding<YourArgType>(e => e.EventForYourNewEvent) },
然后在 BuffBaseEntity 中新增 EventForYourNewEvent 虚方法即可,子类按需重写。
2. 属性修改管线 — ModifierPipeline (AttributeModifier.cs)
属性BUFF不再用串行的 ChangeData 累乘(叠序敏感),而是通过 IAttributeModifierSource 收集
AttributeModifier,再由 ModifierPipeline.Apply 按通道统一计算(叠序无关):
| 通道 | 行为 | 适用 |
|---|---|---|
Flat |
直接相加 value += m.value |
平A加成(ATK +100) |
PercentAdd |
同通道 rate 累加,最后乘一次 (1 + Σrate) |
普通百分比加成(+15% 与 +20% = +35%) |
PercentMul |
每个 (1+rate) 连乘 |
独立倍率(克制系数、独立buff) |
Override |
取最高 priority 的值 |
锁血/锁攻速/特殊状态 |
最终公式:v = (base + flatSum) * (1 + pctAddSum) * pctMulProduct,若有Override则取覆盖值。
BuffEntityAttribute.EmitModifiers 默认把 trigger_value 走 Flat、trigger_value_rate 走 PercentAdd;
但 CRT / EVA 例外:rate 本身就是百分比绝对值,直接 Flat 累加。
3. 堆叠策略 — BuffStackMode
向已有同ID BUFF的生物再次添加同ID BUFF时,由 BuffInfoBean.stack_mode 决定行为:
| 值 | 模式 | 行为 |
|---|---|---|
| 0 | Refresh |
刷新次数/计时 + 施加者,不叠层(默认,兼容旧行为) |
| 1 | Stack |
stackCount += 1(受 stack_max 限制),层变化时 RefreshBaseAttribute |
| 2 | Independent |
走新增分支,独立实例分别计时(多源DOT) |
| 3 | Ignore |
完全忽略新BUFF |
| 4 | ReplaceStrongest |
仅当新 trigger_value 更大时替换 |
合并由 BuffHandler.ApplyStackingPolicy 处理。Instant类型BUFF强制走独立分支(每次添加都触发)。
4. 对象池
BuffManager.dicBuffEntityPool按类名缓存BuffBaseEntity实例BuffManager.queueBuffEntityPool缓存BuffEntityBean实例BuffManager.dicBuffPreEntity长期缓存前置条件实例(永不清理)
回收路径:RemoveBuffEntity → 标记 isValid=false → ClearData()(注销事件 + 置空 buffEntityData)→ 入池。
5. Instant 类型识别 — Type 继承检查
BuffInfoBean.IsInstantBuffEntity() 通过 Type.GetType(class_entity) + IsAssignableFrom 判断,
不再用类名前缀,避免改名后静默失效。结果按实例缓存。
创建新BUFF
1. 选择基类
| 触发时机 | 基类 | 备注 |
|---|---|---|
| 持续生效的属性加成 | BuffEntityAttribute |
实现 CollectModifiers,由 ModifierPipeline 统一计算 |
| 改攻击时间 | BuffEntityAttributeAttackTime |
走独立分支(BuffHandler.ChangeAttackTimeDataForBuff) |
| 添加时立即触发一次 | BuffEntityInstant |
复活、克隆等一次性效果 |
| 特定事件触发 | BuffEntityConditional 或其子类 |
必须配置 class_entity_events |
| 周期性无次数 | BuffEntityPeriodic |
trigger_num = 0 |
| 周期性有次数 | BuffEntityPecurrent |
trigger_num > 0,耗尽即销毁 |
2. 实体类骨架
属性BUFF(带条件门控)
public class BuffEntityAttributeWhenLowHP : BuffEntityAttribute
{
public override void CollectModifiers(List<AttributeModifier> sink)
{
if (buffEntityData == null || !buffEntityData.isValid) return;
//自定义条件:HP < 50% 才生效
var targetCreature = GetFightCreatureEntityForTarget();
if (targetCreature == null) return;
float hpRate = (float)targetCreature.fightCreatureData.HPCurrent
/ targetCreature.fightCreatureData.GetAttribute(CreatureAttributeTypeEnum.HP);
if (hpRate > 0.5f) return;
//条件满足,正常 emit
EmitModifiers(sink, buffEntityData.buffData, attributeType, buffEntityData.stackCount, this);
}
}
条件触发BUFF
public class BuffEntityConditionalCustom : BuffEntityConditional
{
public override bool TriggerBuffConditional(BuffEntityBean buffEntityData)
{
bool ok = base.TriggerBuffConditional(buffEntityData); // 包含 triggerChance 判定 + ShowBuffEffect
if (!ok) return false;
//执行自定义效果...
return true;
}
public override void HandleForEvent()
{
base.HandleForEvent();
if (CheckIsPre(buffEntityData))
{
buffEntityData.conditionalValue = 0;
TriggerBuffConditional(buffEntityData);
}
}
}
事件订阅通过 BuffInfoBean.class_entity_events 配置事件名(如 EventsInfo.GameFightLogic_UnderAttack),
BuffBaseEntity.SetData 自动调用 BuffEventDispatcher.Register。
即时BUFF
public class BuffEntityInstantCustom : BuffEntityInstant
{
public override bool TriggerBuffInstant(BuffEntityBean buffEntityData)
{
base.TriggerBuffInstant(buffEntityData);
var target = GetFightCreatureEntityForTarget();
if (target == null) return false;
//执行一次性效果...
return true;
}
}
周期性BUFF
public class BuffEntityPeriodicCustom : BuffEntityPeriodic
{
public override bool TriggerBuffPeriodic(BuffEntityBean buffEntityData)
{
if (!base.TriggerBuffPeriodic(buffEntityData)) return false;
//每 trigger_time 秒执行一次...
return true;
}
}
3. 配置 BuffInfo 关键字段
| 字段 | 类型 | 说明 |
|---|---|---|
id |
long | BUFF唯一ID |
buff_type |
int | 1攻击模块 / 2生物自带 / 3深渊馈赠 / 11/12/13稀有度BUFF |
rarity |
int | 稀有度(用于UI显示) |
buff_parent_id / buff_level |
long/int | 等级BUFF父链;同 parent 的新等级会替换旧的(仅深渊馈赠路径) |
trigger_creature_type |
int | 0所有 1防御 2进攻 99防守核心 |
class_entity |
string | BUFF实体类名(如 BuffEntityAttribute) |
class_entity_events |
string | 监听事件名(必须在 BuffEventDispatcher.dicBindings 中已注册) |
class_entity_data |
string | 实体数据(属性BUFF填 CreatureAttributeTypeEnum;Attack类填 attackModeId) |
pre_info |
string | 前置条件 preId:value|preId2:value2 |
trigger_value / trigger_value_min |
float | 触发值。扭蛋(isRandom)创建时在 整数闭区间 [min,max] 内随机(如1~2只得1/2,不出小数) |
trigger_value_rate / trigger_value_rate_min |
float | 触发值百分比。扭蛋(isRandom)创建时按 整数百分点闭区间 [min,max] 随机(如10%~20%只得11%/12%等整数,不出11.5%) |
trigger_chance |
float | 触发几率(0-1,0=必然触发)。固定值不随机(原 trigger_chance_min 已废弃删除) |
trigger_num |
int | 触发次数,0为无限 |
trigger_time |
float | 触发间隔(秒),-1为无限 |
stack_mode |
int | 堆叠策略,见 BuffStackMode |
stack_max |
int | 最大堆叠层数(0=无上限,仅Stack模式生效) |
trigger_effect |
long | 触发特效ID |
color_body |
string | 身体染色(hex) |
4. 添加新事件类型
- 在
EventsInfo添加事件常量 - 在
BuffEventDispatcher.dicBindings追加一行:{ EventsInfo.YourEvent, new BuffEventBinding<TArg>(e => e.EventForYourEvent) }, - 在
BuffBaseEntity添加public virtual void EventForYourEvent(TArg arg)虚方法 - 在子类按需重写
5. 添加新前置条件
public class BuffPreEntityForCustomCondition : BuffBasePreEntity
{
//【必填】声明在 UnderAttack 事件中的归属角色
public override BuffPreEventRole GetEventRole() => BuffPreEventRole.Attacker;
public override bool CheckIsPre(BuffEntityBean buffEntityData, float preValue)
{
var creature = GetTargetCreatureEntity(buffEntityData.targetCreatureUUId);
if (creature == null) return false;
return buffEntityData.conditionalValue >= preValue;
}
}
然后在 BuffPreInfo 配置表添加一行,class_entity 填类名即可。
使用 BUFF 系统
添加 BUFF 到生物(带创建概率)
BuffBean buffData = new BuffBean(buffId, isRandom: true, createRate: 1f);
BuffHandler.Instance.AddFightCreatureBuff(
new List<BuffBean>() { buffData },
applierCreatureUUId,
targetCreatureUUId
);
AddFightCreatureBuff 内部会:
- 调用
CheckBuffCreate用createRate过滤 - 走
ApplyStackingPolicy判定是否合并到已有BUFF - 触发
EventsInfo.Buff_FightCreatureChange
添加深渊馈赠 BUFF(全局,作用于防守核心)
AbyssalBlessingEntityBean blessing = new AbyssalBlessingEntityBean(abyssalBlessingId);
BuffHandler.Instance.AddAbyssalBlessing(blessing);
AddAbyssalBlessing 内部按馈赠表自身的 parent_id/level(不是 BUFF 的 buff_parent_id/buff_level,那是已废弃的旧设计)做同族升级替换:
AbyssalBlessingInfoCfg.GetFamilyRootId(id)回溯族根 →RemoveAbyssalBlessingByRootId移除同族旧级 → 解析新级buff_ids(逗号分隔)逐个加到防守核心 → 触发Buff_AbyssalBlessingChange。详见abyssal-blessing-systemSKILL「等级链替换机制」。- ⚠️ 时序:必须在防守核心创建后调用(战斗进行中或
GameFightLogic.PreGameForAfterCreateDefenseCore钩子);此前调用会LogWarning跳过不添加(核心未创建时曾直接 NRE,已加防御)。
移除 BUFF
BuffHandler.Instance.RemoveFightCreatureBuffs(creatureUUId); // 全部
BuffHandler.Instance.RemoveFightCreatureBuffs<BuffEntityAttribute>(id); // 指定类型
死亡流程注意:调用 RemoveFightCreatureBuffs 之前,应先 TriggerEvent
GameFightLogic_CreatureDeadEnd,让 BuffEntityConditionalDead 能完成触发。
事件顺序契约(2026-08 恢复的重要回归修复):AIIntentCreatureDead.IntentUpdate 现为先 TriggerEvent(GameFightLogic_CreatureDeadEnd) 再 RemoveFightCreatureEntity——2026-05-24 曾被误翻转为先移除,BUFF 先被 RemoveFightCreatureBuffs 清掉后收不到 DeadEnd 事件,导致 BuffEntityConditionalDead 全系(DeadRebirth 重生/死亡爆发/死亡掉水晶/死亡范围伤害等)失效,本次已恢复事件先行。事件先行时动态数量 BUFF(都是兄弟/独行者等按 IsDead() 过滤计数)不受影响。
到期自动移除:BuffHandler.UpdateForActivieBuffs 每帧把 isValid=false 的 BUFF 移除回收,并触发 Buff_FightCreatureChange(参数为移除前取出的 applier/target UUID)→ GameFightLogic.EventForBuffFightCreatureChange 自动 RefreshBaseAttribute() + RefreshBodyColor(),生物 BUFF 与馈赠 BUFF 两条更新链路均生效(2026-08 修复:此前到期静默移除,导致减速/中毒变色永久残留)。
死亡重生(BuffEntityConditionalDeadRebirth)
- 重生落点:
isPositionReleased ? Vector3Int.RoundToInt(positionDead) : positionCreate——冲锋自爆型生物(原占位格已释放,FightCreatureBean.isPositionReleased,见 creature-system SKILL「charge_attack」)在死亡地点重生并立即再次冲锋;普通生物维持原格(positionCreate)重生。 - 落点占位护栏:
GetDefenseCreatureByPos(落点)命中且不是自身时放弃重生(重生BUFF照常消耗——此时旧实体仍在主列表,所以判定必须排除自身)。放弃重生不动卡片状态,卡片正常走休整CD。 - 触发时序:DeadEnd 事件在旧实体清理之前派发(契约恢复后),重生创建与旧实体清理同帧完成;重生体首次索敌在下一帧,冲锋型"重生即再爆"两次爆炸间隔≥1.1秒(死亡动画时长),不会同帧双爆。重生BUFF触发后自删(一次性),最多二连爆。
- 卡片 CD 同步(改动在 CreatureHandler,既有 BUG 修复):旧实体的
RemoveFightCreatureEntity只在「场上已无该 UUID 存活实体」时才把卡片置 Rest 进 CD——重生替换场景新实体在场,卡片保持 Fighting 不进 CD,根治"重生后卡片照进CD、CD结束可再放同UUID生物导致DictionaryList.Add静默失败生成幽灵实体(不可命中/清场泄漏)"的缺陷。
查询生物BUFF
List<BuffBaseEntity> buffs = BuffHandler.Instance.manager.GetFightCreatureBuffsActivie(creatureId);
bool hasBuff = buffs?.Any(b => b.buffEntityData.buffId == targetBuffId) ?? false;
攻击时间修正(专用通道)
float timeAttackPre = 1.0f, timeAttacking = 0.5f;
BuffHandler.Instance.ChangeAttackTimeDataForBuff(creatureUUId, ref timeAttackPre, ref timeAttacking);
只会遍历 BuffEntityAttributeAttackTime,不走 ModifierPipeline(因为对应的是时间常量而非属性)。
该方法除遍历生物自身的战斗BUFF外,还会扫描深渊馈赠池 dicAbyssalBlessingBuffsActivie 中的攻速类BUFF:
若该BUFF实现 IBuffSingleTarget 且其 SingleTargetCreatureUUId == creatureUUId 才生效,
以此支持"随机一只防守生物攻速翻倍"这类单体定向馈赠(见下「单体定向深渊馈赠」;曾用于「急性子」)。
单体定向深渊馈赠(随机一只防守生物属性/攻速翻倍)
⚠️ 现役无配置:原 4 个单体定向馈赠(大力出奇迹/膘肥体壮/钢铁憨憨/急性子)已于 2026-07 从配置表删除,本节机制(接口/实体类/过滤落点)完整保留,新增同类馈赠可直接复用。
普通深渊馈赠BUFF(dicAbyssalBlessingBuffsActivie)对所有匹配生物生效(每个生物 RefreshBaseAttribute 都会收集整个馈赠池)。
单体定向馈赠要求只作用于随机一只防守生物,为此引入标记接口:
public interface IBuffSingleTarget { string SingleTargetCreatureUUId { get; } }
- 实现类:
BuffEntityAttributeSingleTarget(ATK/HP/DR,rate=1 即翻倍)、BuffEntityAttributeAttackTimeSingleTarget(攻速,攻击时间rate=0.5)。 - 选取时机锁定目标:两类的
SetData调用GameHandler...GetGameLogic<GameFightLogic>()?.fightData?.GetRandomDefenseCreatureUUId()(实例方法在FightBean上) 从dlDefenseCreatureData随机取一只,存其 UUID;ClearData归还对象池时清空。 - 单体过滤落点(两处):
- 属性类:
FightCreatureBean.CollectFromBuffList在trigger_creature_type过滤之后追加一句——buff is IBuffSingleTarget st && st.SingleTargetCreatureUUId != creatureData.creatureUUId则continue,从而该 modifier 只进入被锁定那只生物的dicAttribute计算。 - 攻速类:
BuffHandler.ChangeAttackTimeDataForBuff扫描馈赠池时按同一 UUID 比对。
- 属性类:
- 复制魔物(增殖)不继承单体定向:
BuffEntityInstantCloneDefenseCreature克隆出的新魔物是新 UUID,与单体定向馈赠锁定的原魔物 UUID 不匹配,故克隆体不继承也不显示原魔物的单体定向馈赠;克隆体只继承「作用于全体防守生物」的馈赠(靠trigger_creature_type过滤、与 UUID 无关)。 - 卡片展示口径统一:
AbyssalBlessingUtil.IsAbyssalBlessingTargetCreature(buff, creatureData, fightType)(在Assets/Scripts/Utils/AbyssalBlessingUtil.cs) 封装「trigger_creature_type 过滤 + 单体定向 UUID 过滤 + 仅 IAttributeModifierSource/BuffEntityAttributeAttackTime 算作用于生物」三连,供战斗卡片(UIViewCreatureCardItemForFight)展示「作用于本魔物的馈赠」复用,确保展示与实际效果同步。 - 不污染存档:
dlDefenseCreatureData内的CreatureBean与玩家存档共享引用,故绝不能改creatureAttribute;本方案只改运行时计算出的dicAttribute/攻击时间,馈赠在征服全通关领奖后随ClearAbyssalBlessing清空。 - 可重复选取(level=0):每次选取新建一个BUFF实例、各自锁定一只新随机生物叠加(同族 level=0 不触发替换);一局可获次数由配置
max_count控制(历史配置max_count=1即整局限 1 次,候选层BuffHandler.GetAbyssalBlessingPickCount门控,与本 BUFF 叠加逻辑独立)。 - ⚠️ 选取后立即刷新已在场生物(事件驱动):属性类(ATK/HP/DR)依赖
dicAttribute重算,BuffHandler.AddAbyssalBlessing末尾TriggerEvent(Buff_AbyssalBlessingChange),由GameFightLogic.EventForAbyssalBlessingChange监听并立即对防守核心 + 全部防守生物RefreshBaseAttribute(BuffHandler 只触发事件、不直接刷新,职责更解耦)。否则征服「普通关卡→普通关卡」走ContinueNextLevelInSameScene保留现场、不重载场景也不重算属性,加成要等到下次场景重载(切BOSS关StartNextGameForBoss重建生物实体)才生效——典型BUG「普通关选了不生效、切BOSS才生效」。攻速类每次攻击实时缩放不依赖刷新,一并刷新也无害。
动态率属性BUFF(加成率随战况实时计算)
⚠️ 馈赠侧现役无配置、稀有度BUFF侧现役「独行者」:原 6 族动态率馈赠(都是兄弟/杀红了眼,各 3 属性 5 级链)已于 2026-07 从配置表删除,
HasDynamicRateAbyssalBlessing()(馈赠侧)当前恒 false;但 2026-08 新增 R 稀有度BUFF「独行者A-D」走生物侧缓存HasDynamicRateCreatureBuff(),广播链路现役生效。本节机制(基类/子类/广播链路)完整保留,新增同类馈赠/稀有度BUFF可直接复用。
普通属性 BUFF 的 rate 由配置 trigger_value_rate 写死、恒定;「随场上魔物数缩放 / 随击杀数缩放」要求 rate 随运行时战况(场上魔物数量 / 累计击杀数)每次重算属性时实时变化,为此引入抽象基类 BuffEntityAttributeDynamicRate : BuffEntityAttribute(Assets/Scripts/Game/Buff/BuffEntity/Attribute/):
- 模式:基类同时重写两条属性路径,二者都调抽象方法
protected abstract float GetDynamicRate()取当前百分比、仅走PercentAdd通道(用于 ATK/DR/HP;trigger_valueFlat 不参与):CollectModifiers(sink)(战斗热点路径):rate=GetDynamicRate(),>0才 emit 一个PercentAddmodifier。ChangeData(CreatureAttributeTypeEnum, float)(卡片详情预览路径,供CreatureBean.GetAbyssalBlessingChangeAttribute调用):targetData *= 1 + GetDynamicRate()。- 便捷方法
protected FightBean GetFightData()=GameHandler...GetGameLogic<GameFightLogic>()?.fightData。
- 抽象基类不在配置
class_entity中直接引用;子类是通用功能类(按缩放来源命名、不绑馈赠名,可被其它同功能馈赠复用),实现具体公式:BuffEntityAttributeScaleByDefenseCount(属性随"当前场上存活防守魔物数"缩放,曾用于「都是兄弟」):rate = (场上存活防守魔物数 N - 1) * trigger_value_rate。N =fightData.dlDefenseCreatureEntity.List中!IsDead()的数量;N<=1时为 0(减 1 扣除自身,只有 1 只不享加成)。BuffEntityAttributeScaleByKillCount(单体定向,兼实现IBuffSingleTarget;选取时SetData用GetRandomDefenseCreatureUUId()随机锁定一只防守生物,属性随"该只生物自身累计击杀敌人数"缩放,曾用于「杀红了眼」):rate = fightData.fightRecordsData.GetRecordsForCreatureData(锁定UUID,false)?.killNum * trigger_value_rate(仅魔物击杀;killNum 按creatureUUId持久累积,该只阵亡后下一关重新上场 UUID 不变、之前加成保留,征服 run 内跨关卡累积不重置)。「只作用锁定那只」的过滤由FightCreatureBean.CollectFromBuffList的IBuffSingleTarget落点自动完成,配置trigger_creature_type=1作防守类兜底过滤。BuffEntityAttributeDecayByAllyCount(属性随"当前场上存活防守魔物数"反向衰减,现役用于 R 稀有度BUFF「独行者A-D」11001300001~04):rate = trigger_value_rate - (trigger_value/100) × (N - 1),下限 0(衰减至 0 即不加成)。N 口径同 ScaleByDefenseCount(dlDefenseCreatureEntity.List中!IsDead()数,减 1 扣自身,多持有者各算各的)。trigger_value复用为每友方衰减百分点(配 50=每友方-50%)——因BuffBean(isRandom:true)对trigger_value做整数化闭区间随机(RoundToInt(0.5)=0会丢衰减系数),衰减率必须按百分点整数配置、类内 /100 还原;trigger_value_rate(_min)配 3.0=+300% 恒定不随机。描述模板用{Percentage}(基础值300) 与{Value}(衰减率50) 双占位符。配置trigger_creature_type=1(仅防守)。
- 依赖 GameFightLogic 广播重算(事件驱动):rate 变化后必须重算
dicAttribute才生效。高频事件(魔物放置/增殖、魔物死亡、敌人击杀)由GameFightLogic按守卫广播RefreshAllDefenseCreatureAttribute()对全体防守魔物重算——守卫为双路 O(1) 缓存 Or:BuffHandler.HasDynamicRateAbyssalBlessing()(馈赠池含动态率BUFF,AddAbyssalBlessing置位、ClearAbyssalBlessing复位)||BuffHandler.HasDynamicRateCreatureBuff()(场上生物带动态率BUFF:AddFightCreatureBuff内经收口方法CreateFightCreatureBuffEntity创建实例时is BuffEntityAttributeDynamicRate即单调置位、ClearFightCreatureBuff复位;独行者等稀有度动态率BUFF走此路),任一为 true 即广播,避免普通对局无谓开销。落点:GameFightLogic.EventForGameFightLogicCreatureDeadEnd(死亡,在CheckGameEnd()之前);CreatureHandler.CreateDefenseCreatureEntity末尾推送新事件EventsInfo.GameFightLogic_DefenseCreatureCreate(参数 FightCreatureEntity),由GameFightLogic.EventForDefenseCreatureCreate监听后按守卫重算(CreatureHandler 只负责生成、推事件,重算职责归 GameFightLogic)。详见abyssal-blessing-systemSKILL「动态数值馈赠」。
事件名速查(已注册到 BuffEventDispatcher)
| 事件 | 参数 | 默认回调 |
|---|---|---|
GameFightLogic_UnderAttack_Dead |
FightUnderAttackBean |
EventForUnderAttackDead |
GameFightLogic_UnderAttack |
FightUnderAttackBean |
EventForUnderAttack(带前置角色过滤) |
GameFightLogic_RegainHP |
FightUnderAttackBean(借用:attackerId=治疗者/attackedId=被治疗者/attackerDamage=治疗量) |
EventForRegainHP(带前置角色过滤,仅真实回血>0派发) |
GameFightLogic_CreatureDeadDropCrystal |
FightDropCrystalBean |
EventForCreatureDeadDropCrystal |
GameFightLogic_CreatureDeadStart |
FightCreatureEntity |
EventForCreatureDeadStart |
GameFightLogic_CreatureDeadEnd |
FightCreatureEntity |
EventForCreatureDeadEnd |
新增事件参考前文「添加新事件类型」。
BUFF伤害 bean 归属口径(2026-08-12 修正):
FightUnderAttackBean.SetData(BuffEntityBean)→ attackerId=BUFF施加者(applier)、attackedId=BUFF持有者(target);此前两者反向,导致毒/烧伤跳伤在结算统计里记为"持有者自己打自己、施加者承伤"、击杀算自杀。修正后跳伤正确计入施加者的造伤/击杀/吸血统计,且会触发持有者的荆棘反伤(反弹给施加者)——属预期语义。
属性类型枚举(CreatureAttributeTypeEnum)
None = 0
HP // 生命 (枚举成员按顺序自增:HP=1)
MP // 魔法 (MP=2,魔王/核心向,普通生物一般不消费)
DR // 护甲(防御) (DR=3)
ATK // 攻击力 (ATK=4)
MSPD // 移动速度 (MSPD=5)
ASPD // 攻击速度 (ASPD=6)
CRT // 暴击率 (CRT=7,rate 走 Flat 绝对百分点)
EVA // 闪避 (EVA=8,rate 走 Flat 绝对百分点)
RCD // 复活CD (RCD=9,扭蛋稀有度BUFF可挂 class_entity_data=RCD 负rate 减少召唤CD)
MPR // 魔法回复% (MPR=10,魔法向)
MPF // 魔法回复 (MPF=11,魔法向)
CMP // 召唤魔力消耗 (CMP=12,仅作BUFF修正标签、非生物常驻战斗属性;GetAttribute(CMP)=基础CMP×(1+等级/稀有度增加倍率),扭蛋稀有度BUFF负rate 再减少召唤耗魔)
> ⚠️ **无 `HPRegeneration`/生命回复 属性**:真实枚举里没有该成员(曾误列 `HPRegeneration=11`,实际 index 11 是 MPF 魔法回复)。游戏也**没有被动每秒回血刻**,回血只走主动治疗攻击 `AttackModeRegain`。设计 R 属性BUFF时不要用 HPRegeneration,续航向请改用 HP/EVA/DR 等已存在属性。
扭蛋稀有度 R BUFF 池(buff_type=11)现含:HP/DR/ATK 增益、ASPD 攻速增益(+50
100%)、RCD 召唤CD减益、CMP 召唤魔力消耗减益(均 -2550%)。RCD/CMP 减益走负 rate:RCD 走GetAttribute(RCD, true)(基础值/角色加点/装备/自身BUFF→再按需叠加深渊馈赠全局池;includeAbyssalBlessing=true 开启,原 GetRCD 已并入 GetAttribute),CMP 在GetAttribute(CMP)管线内叠加(先 基础CMP×(1+等级/稀有度增加倍率),再叠加BUFF;GetAttributeInt(CMP)为 int 封装)。
扭蛋/稀有度 BUFF 分档设计规则(buff_type 11/12/13,必读)
稀有度 BUFF 池按稀有度分三档,每档对「效果性质」有硬性约束——新增/设计稀有度 BUFF 必须落到正确档位。生成入口 BuffUtil.CreateRandomRarityBuff 只按 buff_type 取池随机、不校验效果性质,归档正确性完全靠本规则人工保证:
| 稀有度 | buff_type | 效果性质(硬约束) | 适用实体类 |
|---|---|---|---|
| R | 11 | 纯属性 BUFF:只做属性数值加/减益,常驻生效、无触发条件、无特殊副作用(允许"纯属性动态率":数值随战况实时算但仍只改属性,如独行者随友方数衰减) | BuffEntityAttribute(改攻击时间用 BuffEntityAttributeAttackTime);多属性/双刃用 BuffEntityAttributeMulti(一次随机率同时加一属性、等比减另一属性);动态率用 BuffEntityAttributeDynamicRate 子类(如 BuffEntityAttributeDecayByAllyCount 独行者) |
| SR | 12 | 条件/周期被动触发 BUFF:满足条件(累计造成/受到伤害、击杀数、血量阈值、累计治疗、在场时间)或按固定周期被动触发后产生效果 | BuffEntityConditional*(非死亡类,含时间驱动 BuffEntityConditionalAttributeTime)/ BuffEntityPeriodic* / BuffEntityPecurrent;条件走 pre_info+BuffPreEntityFor*,事件走 class_entity_events(时间驱动类留空) |
| SSR | 13 | 特殊类 BUFF:质变/规则改写型,「什么情况都可能发生」——死亡重生、死亡反击、死亡区域治疗/加防、克隆增殖、生成/改变水晶掉落、改变奖励掉落、荆棘反伤、伤害吸血、上场无敌等 | BuffEntityConditionalDead* / BuffEntityInstant* / BuffEntityConditionalThorns/LifeSteal/Invincible / 各类特殊实体 |
- R 档可用属性:
HP / DR / ATK / ASPD / MSPD / CRT / EVA / RCD / CMP(CRT/EVA 的 rate 走 Flat;另有 MP/MPR/MPF 魔法向,普通生物一般不消费)。无 HPRegeneration 生命回复属性(见下方枚举说明)。R 只能是这些属性的常驻加/减益,不得带触发条件、死亡/召唤等特殊行为(动态率类的"随战况变数值"不算触发条件——效果常驻生效,只是数值实时算)。 - 多属性双刃 R(
BuffEntityAttributeMulti):class_entity_data用属性:倍率|属性:倍率(如ATK:1|HP:-1),各属性的实际率 =trigger_value_rate × 倍率,共享同一次随机ロール(如 ATK+30% ⇒ HP-30%,减益随机量恒等于增益随机量)。trigger_value/min留空(=0)只用百分比率;描述模板两句都用同一个{Percentage}(正值),如「攻击力增加{Percentage}%,生命减少{Percentage}%」。当前 30 条(6 族 × AE 5 变体):狂战士(ATK+)、快枪手(ASPD+)、铜墙铁壁(DR+)、大块头(HP+)——副属性 HP/DR/ATK/ASPD 等比减(AC)或 RCD/CMP 反向(D/E,率 0.20.5);魂牵梦绕(RCD-)、折上折(CMP-)——副属性减半代价(率 0.50.75),id 段11 0007~0012 0000X。 - 动态率 R(
BuffEntityAttributeDynamicRate子类):BuffEntityAttributeDecayByAllyCount「独行者A-D」11001300001~04(各 +ATK/ASPD/DR/HP 基础 300%,每多 1 友方衰减 50%,下限 0;trigger_value=50 复用为衰减百分点——isRandom 整数化会归零 0.5,详见「动态率属性BUFF」节)。注意:动态率类类名不匹配IsBuffEntityAttributeOnly()(字符串相等判定),故不走烘焙路径、作为运行时 BUFF 实体正常挂接,动态率经CollectModifiers生效,无双算。- 两条属性路径:运行时
CollectModifiers逐属性 emit(PercentAdd 负率=等比减益);预览/烘焙ChangeDataForConfig(无实例) 与ChangeData(实例) 逐对匹配targetAttributeType应用。解析结果按BuffInfoBean.GetAttributeMultiPairs()缓存。 - 判定收口:
BuffBean.IsBuffEntityAttributeOnly()(单属性 or 多属性)决定「纯属性BUFF」→ 走属性烘焙(CreatureBean.GetBuffChangeAttribute)、不作为运行时BUFF实体添加(GetListBuffData(getBuffAttributeBase:false)排除,避免与烘焙重复计算)。
- 两条属性路径:运行时
- 高稀有度累积低档:SSR 生物 = R+SR+SSR 各 1 条、SR 生物 = R+SR 各 1 条(
CreatureBean.RandomRarityBuffForCreate逐级授予)。设计 SR/SSR 时应默认玩家已持有低档属性底子,SR/SSR 要体现「质」的差异而非再堆纯属性。 - 归档自检:设计一条稀有度 BUFF 前先问——它是「常驻纯属性」(→R)、「满足条件/到周期才触发」(→SR)、还是「改变战斗规则的特殊效果」(→SSR)?错档会让扭蛋体验错乱(例如 R 抽出死亡重生)。
稀有度 BUFF 生成工具(BuffUtil)
稀有度 BUFF(buff_type=11 R / 12 SR / 13 SSR)的「按稀有度随机抽一条」规则已抽到游戏层工具 Assets/Scripts/Utils/BuffUtil.cs 统一收口,扭蛋(GashaponItemBean)与魔物进阶(UICreatureVat)共用同一口径:
| API | 返回 | 说明 |
|---|---|---|
BuffUtil.GetRarityBuffType(RarityEnum) |
BuffTypeEnum |
稀有度 → 稀有度 BUFF 类型。仅 R/SR/SSR 有对应类型(CreatureRarityR/SR/SSR);N/UR/L 返回 None |
BuffUtil.CreateRandomRarityBuff(RarityEnum) |
BuffBean |
扭蛋通用规则:按 GetRarityBuffType 取该类型的 BUFF 列表随机抽 1 条,new BuffBean(id, isRandom:true);无对应类型返回 null |
BuffUtil.CreateAscendRarityBuff(RarityEnum newRarity, List<CreatureBean> materials) |
BuffBean |
魔物进阶规则:默认走通用随机;素材魔物在 newRarity 槽位的 BUFF 按 buff id 聚合,每个 id 提供 25%×数量 的直接命中概率;命中则继承该 id 并用 BuffBean.CreateRandomWithFloor 重随机数值(结果≥素材原值),未命中回退通用随机。UR/L 无类型返回 null(只升稀有度不授 BUFF) |
BuffUtil.GetCreatureAscendBuffChances(RarityEnum newRarity, List<CreatureBean> materials) |
List<CreatureAscendBuffChanceStruct> |
进阶详情展示用(与 CreateAscendRarityBuff 同口径算概率):每个素材 BUFF id 一项 rate=25×数量,列表末尾追加一项「随机增益」(buffId=-1,名 BuffUtil.AscendRandomBuffName)表示剩余概率 100-素材命中总和;UR/L 无类型返回空列表(不授 BUFF 不展示)。供 UICreatureVat 孵化缸进阶详情面板实时展示。返回结构体 CreatureAscendBuffChanceStruct 及内部聚合结构体 CreatureAscendMaterialBuffStruct 同放 Assets/Scripts/Struct/CreatureAscendStruct.cs。结构体还透传 floorValue/floorValueRate(素材命中该 id 时抬高的数值下限,取各素材最大原值;兜底项为0),供进阶详情气泡在解锁「进阶增益范围预览」研究(UnlockEnum.CreatureVatBuffPreview)后按抬高后的下限显示 min~max 范围 |
「按稀有度逐级授予稀有度 BUFF」收口在
CreatureBean.RandomRarityBuffForCreate()(孕育GashaponItemBean.RandomRarity与测试面板UITestBase.OnClickForAddTestCreature共用),单档随机再调BuffUtil.CreateRandomRarityBuff(原GashaponItemBean.RandomRarityBuff已删除)。新增/调整稀有度 BUFF 仍只需在excel_buff_info配表,属性类 BUFF 自动进入对应 buff_type 池。
BuffBean.CreateRandomWithFloor(带下限随机工厂)
//沿用扭蛋整数闭区间随机口径,但随机下限抬到 max(配置min, floor),保证结果≥下限
public static BuffBean CreateRandomWithFloor(long id, float floorValue, float floorValueRate, float createRate = 1f)
专供魔物进阶「继承素材 BUFF 并重随机数值」使用:floorValue / floorValueRate 传素材原 BUFF 的 trigger_value / trigger_value_rate,保证重随机结果不会低于素材原值。
常用代码模板
创建带创建概率与触发几率的BUFF
BuffBean buffData = new BuffBean(100001);
buffData.createRate = 0.3f; // 30% 概率创建
buffData.trigger_chance = 0.5f; // 创建后每次触发50%命中
buffData.trigger_value = 100;
buffData.trigger_value_rate = 0.2f;
BuffHandler.Instance.AddFightCreatureBuff(
new List<BuffBean>() { buffData }, applierId, targetId);
获取属性BUFF对最终属性的修改(预览路径)
//战斗中:FightCreatureBean.RefreshBaseAttribute 已统一走 ModifierPipeline。
//预览/卡片路径:用兼容层
float baseATK = 100;
float final = BuffEntityAttribute.ChangeData(
buffData, CreatureAttributeTypeEnum.ATK, baseATK, stackCount: 1);
让自定义来源参与 ModifierPipeline
public class MyEquip : IAttributeModifierSource
{
public void CollectModifiers(List<AttributeModifier> sink)
{
sink.Add(new AttributeModifier {
attributeType = CreatureAttributeTypeEnum.ATK,
channel = ModifierChannel.PercentMul,
value = 0.5f, // ×1.5
source = this,
});
}
}
文件位置速查
| 功能 | 文件路径 |
|---|---|
| BUFF基类 | Assets/Scripts/Game/Buff/BuffEntity/BuffBaseEntity.cs |
| 事件分发 | Assets/Scripts/Game/Buff/BuffEventBinding.cs(IBuffEventBinding 接口已抽到 Interface/) |
| 属性修改管线 | Assets/Scripts/Game/Attribute/AttributeModifier.cs(通用属性管线,已移出 Buff/;含 IAttributeModifierSource 接口,BUFF/装备/天赋共用,非 BUFF 专属) |
| BUFF接口 | Assets/Scripts/Game/Buff/Interface/(IBuffSingleTarget 单体定向 / IBuffEventBinding 事件绑定,均以 IBuff 打头) |
| HP/DR共享基类 | Assets/Scripts/Game/Buff/BuffEntity/BuffEntityBase*Change*.cs |
| 属性BUFF | Assets/Scripts/Game/Buff/BuffEntity/Attribute/ |
| 条件BUFF | Assets/Scripts/Game/Buff/BuffEntity/Conditional/ |
| 即时BUFF | Assets/Scripts/Game/Buff/BuffEntity/Instant/ |
| 周期性BUFF(无次数) | Assets/Scripts/Game/Buff/BuffEntity/Periodic/ |
| 周期性BUFF(有次数) | Assets/Scripts/Game/Buff/BuffEntity/Pecurrent/ |
| 前置条件基类 | Assets/Scripts/Game/Buff/BuffPre/BuffBasePreEntity.cs |
| 前置条件实现 | Assets/Scripts/Game/Buff/BuffPre/ |
| BUFF数据Bean | Assets/Scripts/Bean/Game/BuffBean.cs(含静态工厂 CreateRandomWithFloor 带下限随机) |
| 稀有度 BUFF 生成工具 | Assets/Scripts/Utils/BuffUtil.cs(CreateRandomRarityBuff 通用 / CreateAscendRarityBuff 进阶 / GetRarityBuffType / GetCreatureAscendBuffChances 进阶概率展示) |
| BUFF运行时实例 | Assets/Scripts/Bean/Game/BuffEntityBean.cs |
| BUFF配置Bean | Assets/Scripts/Bean/MVC/Game/BuffInfoBean.cs |
| BUFF配置扩展 | Assets/Scripts/Bean/MVC/Game/BuffInfoBeanPartial.cs(含 BuffStackMode 枚举) |
| BUFF前置条件配置 | Assets/Scripts/Bean/MVC/Game/BuffPreInfoBean.cs |
| BuffHandler | Assets/Scripts/Component/Handler/BuffHandler.cs |
| BuffManager | Assets/Scripts/Component/Manager/BuffManager.cs |
常见坑
- 新增前置条件忘记重写
GetEventRole()→ 在 Attacker/Attacked 事件中被错误过滤。 - 属性BUFF 试图修改
BuffInfoBean.cs→ 该文件自动生成,扩展请写在BuffInfoBeanPartial.cs。 - 在
ClearData后访问buffEntityData→ 已置 null,请用isValid守卫。 SetData重复调用 → 基类已做保护性Unregister,但仍应避免;池化复用时务必先ClearData。- Stack 模式忘配
stack_max→ 0 表示无上限;属性BUFF用 stack 时确保EmitModifiers已用stackCount。 - 新增事件类型只改
BuffBaseEntity不改BuffEventDispatcher→ 事件订阅不会生效。 - 无 pre_info 的条件 BUFF 直接整体重写
EventForUnderAttack/EventForRegainHP做归属过滤(不调 base) → base 在 pre_info 为空时不做任何过滤,会对全场每次攻击/治疗事件都累积conditionalValue并调HandleForEvent(参考BuffEntityConditionalThorns/BuffEntityConditionalLifeSteal的写法)。 - 时间型 BUFF 到期刷新已自动化:自然到期(
isValid=false)由UpdateForActivieBuffs移除时会触发Buff_FightCreatureChange,事件处理自动RefreshBaseAttribute() + RefreshBodyColor(),BUFF 无需再自理视图刷新(2026-08 修复;此前移除链路不触发事件,减速/变色会永久残留)。但需清理自身运行时旗标的(如BuffEntityConditionalInvincible的isInvincible)仍要在SetData/ClearData/UpdateBuffTime三处自理旗标。