恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
ET 框架 Buff 系统实战:用事件驱动与键值数值组件接管技能效果
首页
资讯中心
/
ET 框架 Buff 系统实战:用事件驱动与键值数值组件接管技能效果
ET 框架 Buff 系统实战:用事件驱动与键值数值组件接管技能效果
发布时间:2026/9/17 18:55:13
ET 框架 Buff 系统实战用事件驱动与键值数值组件接管技能效果【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET在 ET 框架里做技能 Buff 系统真正难的从来不是给战士加个持续掉血而是策划第三次改需求时你还敢不敢直接动手。这套体系把 Buff 生命周期交给实体和定时器把属性计算交给键值数值组件把 UI、成就、日志的响应交给事件订阅——新增一个流血 Debuff通常只需要在配置表加一行。游戏战斗场景背景装饰图一个 Buff 引发的三次返工第一版中毒 Buff 我写的是典型的一类一 BuffPoisonBuff类内部 Timer 定时跳伤UI 飘字直接调死亡判定也直接写在里面。第一天跑通 demo第二天策划说中毒要能叠 3 层第三天说中毒期间眩晕要暂停第四天说战士也加个流血和中毒差不多。猜猜改到这时要动几个文件几乎每个碰掉血逻辑的模块都要动一遍。问题根源是Buff 干什么写死在了具体类里叠加、暂停、替换这些规则全靠 if-else 硬编码。ET 框架的思路反过来Buff 只负责记录我是哪个配置、挂在谁身上、什么时候到期、还有几层行为一律去配置表查。行为进配置表Buff 实体只存 6 个状态字段先看 Buff 实体定义服务端所有 Buff 共用这一个实体类字段全是状态[ChildOf(typeof(BuffComponent))] public class Buff: Entity, IAwakeint, IDestroy { public int ConfigId { get; set; } // 行为完全由配置表决定 public long Caster { get; set; } // 施法者 Unit ID public long CreateTime { get; set; } // 创建时间戳 public int TickTime { get; set; } // 周期效果间隔0 表示无 tick public long ExpireTime { get; set; } // 到期时间戳 public int Stack { get; set; } // 当前层数 public long TimeoutTimer; // 超时定时器句柄 }创建时传一个配置 ID具体逻辑来自配置里的 EffectNode行为树根节点EffectServerBuffAdd在挂上时触发一次EffectServerBuffTick每 tick 触发EffectServerBuffRemove移除时触发节点里再组装行为树组件。策划在配置表里填的是 Duration、TickTime、MaxStack、叠加规则和移除条件BuffFlags死亡移除、移动移除、受击移除等十几枚举值。两个实现细节值得注意BuffComponent用 configId、flag、效果类型三套索引同时登记身上所有 Buff移除某类型全部 Buff是 O(1) 查表不用遍历。到期检查不走每帧 Update而是创建时注册一个一次性定时器到点统一走移除流程。⚠️ 这里有个容易忽略的点叠加刷时间时必须先移除旧定时器再注册新的否则会出现两个超时回调竞争。键值对存储一份公式管住所有属性OOP 的属性类方案有个绕不开的死结盗贼不用法力数值类里却得有 MP 字段10 个种族乘以 8 种职业继承关系直接失控每种属性的计算式还得各写一遍。ET 在 数值组件文档 里给出的答案是一个字典 一个枚举关键在键值编码// 键值编码主键 * 10 位序一个属性占 5 个槽位 public enum NumericType { Speed 1000, // 最终值 SpeedBase Speed * 10 1, // 基础值 SpeedAdd Speed * 10 2, // 绝对值加成 SpeedPct Speed * 10 3, // 百分比加成 SpeedFinalPct Speed * 10 5, // 最终倍率 } // 任意属性重算都走这一条公式 long result (long)(((GetByKey(bas) GetByKey(add)) * (100 pct) / 100f GetByKey(finalAdd)) * (100 finalPct) / 100f); Insert(final, result, true); // 写回最终值并抛出变化事件编码方案让任意属性都能拆成 base/add/pct/finalAdd/finalPct 五个位置重算公式全属性共用一个函数。加 10 点绝对速度的 Buff 改SpeedAdd提速 20%改SpeedPct移除时原路减回即可。 这也是配置表里策划只需要填一个 NumericType 的原因——程序按键找值不依赖字段名更不用反射。还有一个细节Insert里走两条路。写子键值大于等于 10000比如 SpeedPct会触发最终值重算直接写最终值比如扣 HP则先做上限钳制再按配置联动影响其它数值MaxHP 掉了 HP 跟着钳。两条路最后都会抛出NumbericChange事件。掉血之后谁该响应watcher 订阅链NumbericChange事件里带 Unit、NumericType、Old、New 四样东西一个事件处理器把它转发给NumericWatcherComponent后者按 NumericType 二次分发到用特性注册的监听器整体事件分发见 事件机制文档// 用特性声明我关心 HP 变化框架负责注册和分发 [NumericWatcher(NumericType.HP)] public class HpWatcher_DamageLog : INumericWatcher { public void Run(Unit unit, NumbericChange args) { long loss args.Old - args.New; if (loss 500) // 单跳掉血超过阈值就记录 { Log.Warning($unit {unit.Id} 掉血 {loss}超过 500); } } }战斗代码一行不用改这个类一编译单跳掉血超 500 就会打日志。想做被流血击杀 100 次的成就、想做客户端伤害飘字都是照这个格式再加一个类——战斗系统从头到尾不知道它们存在这就是只抛事件、不关心谁在听的订阅链。叠加规则的三种落法策划嘴里的叠加其实有三种语义在 BuffHelper 里收敛成一个枚举 switch// 叠加规则同一配置 ID 的 Buff 再次命中时怎么处理 var oldBuffs buffComponent.GetByConfigId(buffConfig.Id); if (oldBuffs ! null oldBuffs.Count 0) { switch (buffConfig.OverLayRuleType) { case OverLayRuleType.AddStack: // 层数累加超 MaxStack 截断0 层自动移除 UpdateStack(oldBuffs.First(), oldBuffs.First().Stack buffConfig.Stack); return oldBuffs.First(); case OverLayRuleType.AddTime: // 到期时间取更晚的那个 UpdateExpireTime(oldBuffs.First(), ServerNow() buffConfig.Duration); return oldBuffs.First(); case OverLayRuleType.Replace: // 旧的走移除流程新的留下 foreach (Buff old in oldBuffs) RemoveBuff(old, BuffFlags.SameConfigIdReplaceRemove); break; } }策划在配置表改一个下拉框就能切换叠加语义。而且 Stack、ExpireTime 每次变化都会通过M2C_BuffUpdate广播到客户端UI 的倒计时和层数图标不需要自己维护定时器跟着消息走就行。周期性伤害靠定时器跳出来每 2 秒跳 10 点由BuffTickComponent处理核心就是一个重复定时器// 周期性效果 一个重复定时器 public static void Start(this BuffTickComponent self) { Buff buff self.GetParentBuff(); int tickTime buff.TickTime; if (tickTime 0) return; // 配置里没填 tick 间隔就不启动 self.OnTick(); // 先立刻跳一次别让玩家等满第一个间隔 self.TimerId buff.Root().TimerComponent .NewRepeatedTimer(tickTime, TimerInvokeType.BuffTickTimer, self); }OnTick并不直接写伤害而是从配置取出EffectServerBuffTick节点当行为树来跑把 Buff、Unit、Caster 注入环境。所以跳伤回血持续击退用的是同一套定时器区别只在挂的行为树。如果节点勾了 Override还会暂停目标 AI 表现被控状态。新加流血 Debuff 的最小改动清单串起来看给战士加流血2 秒一跳、每跳 10 点、持续 8 秒、死亡移除要做的全部事情是在 spell 包 Luban 目录的配置表加一行Duration8000、TickTime2000、FlagsDeadRemove给这行挂一个EffectServerBuffTick节点行为树里接对宿主造成 10 点伤害不需要写任何新的 C# 类想要伤害飘字就在客户端加一个 watcher。对比一类一 Buff写法这是 4 个文件起步的改动。✅ 唯一可能写代码的地方是 watcher而它不在战斗主路径上。接下来可以动手的两件事先照本文的 watcher 写法做一个大额伤害事件统计面板验证整条事件链路再去 Book 目录读行为机那一章把 tick 效果树扩展成可被打断的行为这套 Buff 系统就能接住更复杂的技能设计了。【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考