ET 技能与 Buff 系统包 cn.etetet.spell 全面解析:配置模型、运行时施法链路与编辑器工具链
【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET
导读
cn.etetet.spell是 ET 框架(Unity3D Client And C# Server Framework)中负责**技能(Spell)与 Buff(增益/减益状态)**的完整功能包,涵盖 Spell/Buff 配置模型、服务端运行时施法逻辑、客户端表现组件,以及配套的 Unity 编辑器工具。本文以Packages/cn.etetet.spell/AGENTS.md为骨架,结合该包源码(Scripts/Model、Scripts/Hotfix、Scripts/HotfixView、Scripts/Editor)与配置资产,系统讲解技能/Buff 的数据模型与配置方式、服务端施法与 Buff 生命周期管理、效果节点与行为树驱动的实现原理、客户端表现与消息同步,以及 Spell Editor 编辑器工具的使用与开发约束,最终帮助你在自己的 ET 项目中落地一套可复用的技能系统。
包概览:定位、依赖与目录结构
cn.etetet.spell的定位由 AGENTS.md 概述为:技能与 Buff 系统包,包含四大部分:
- Spell/Buff 配置模型——配置数据结构与代码生成;
- 运行时施法逻辑——服务端施法、目标选择、消耗与 CD 管理;
- 客户端表现组件——技能指示器、Buff 表现物体等;
- spell 相关 Unity 编辑器工具——Spell Editor 窗口及资产导出链路。
包的版本与依赖定义在 package.json(版本 4.0.0,Unity 2022.3),核心依赖包括:
cn.etetet.core(实体组件基础框架);cn.etetet.config(Luban 配置生成);cn.etetet.proto(协议代码生成);cn.etetet.behaviortree(行为树,用于目标选择/消耗/效果节点);cn.etetet.numeric(数值组件,Buff 修改数值的底层支持);cn.etetet.unit、cn.etetet.map、cn.etetet.yooassets、cn.etetet.yiuiframework等。
从 目录结构 可以看出,包内代码按 ET 标准分层:
Scripts/ ├── Model/ # 纯数据结构:Buff/SpellConfig、EffectNode、CostNode、TargetSelector、各 Component ├── Hotfix/ # 逻辑层 │ ├── Share/ # 双端共享:BuffSystem、BuffComponentSystem、SpellComponentSystem、BuffTickComponentSystem │ ├── Server/ # 服务端:SpellHelper.Cast、BuffHelper、伤害/仇恨/数值记录等 │ ├── Client/ # 客户端:SpellHelper、BuffHelper、消息 Handler(M2C_BuffAdd 等) │ └── Test/ # Fiber 单测:Spell_FiberSingleton_Test ├── HotfixView/ # 客户端表现:BuffGameObjectComponentSystem、SpellIndicatorComponentSystem、TargetSelector ├── ModelView/ # 客户端表现数据结构:BuffScriptableObject、SpellScriptableObject └── Editor/Share/ # SpellEditor 工具(编译进 ET.Editor)Spell 配置模型:SpellConfig 与配置表生成
SpellConfig 字段详解
Spell 的主配置类型为 SpellConfig(Scripts/Model/Share/SpellConfig.cs),它继承Object并使用 Odin 特性组织 Inspector 布局:
| 字段 | Odin 标注 | 含义 |
|---|---|---|
Id | ReadOnly,BoxGroup("技能信息") | 技能 ID(主技能 ID 为 10 的整数倍,见下文 ID 分组规则) |
Desc | BoxGroup("技能信息") | 技能描述 |
IconName | BoxGroup("技能信息") | 技能图标资源名 |
CD | BoxGroup("技能信息") | 技能 CD,单位毫秒(int) |
DamageMultiplier | BoxGroup("技能信息"),默认 100 | 伤害系数(百分比),默认 100% |
Cost | LabelText("消耗") | 消耗节点(CostNode),行为树驱动 |
TargetSelector | LabelText("目标选择") | 目标选择器(TargetSelector),行为树驱动 |
同文件中还定义了CastTimeType枚举,标注三种施法类型:
[LabelText("施法类型")] public enum CastTimeType { [LabelText("普通攻击")] Attack, [LabelText("读条")] Cast, [LabelText("吟唱")] Channeling, }SpellConfigCategory:单例配置表
SpellConfigCategory 是一个Singleton+IConfig的配置表容器,标记[ConfigProcess(ConfigType.Code)],内部用Dictionary<int, SpellConfig> _dataMap按 Id 索引,提供Get / Contain / GetAll / Add接口,并在加载后统一调用ResolveRef()完成引用解析。对应的代码生成产物位于 CodeMode/Config(按 Client / ClientServer / Server 三种 CodeMode 分别生成)。
配置数据源是 Luban 的 Excel 表:Luban/Config/Base/tables.xlsx 等(Luban/Config/Base下还有__beans__.xlsx、__enums__.xlsx,Luban/Config/Datas下有Text.xlsx)。因此修改 Spell/Buff 配置字段后,需要通过 Luban 链路重新生成配置代码与二进制数据。
技能 ID 分组规则与“主/子技能”
SpellEditorConstants.cs 定义了技能与 Buff 资产的核心编号约定:
public const int SpellGroupSize = 10; public static bool IsMainSpell(int id) => id % SpellGroupSize == 0; // 主技能 public static bool IsSubSpell(int id) => id % SpellGroupSize != 0; // 子技能 public static int MainSpellBase(int spellId) => spellId / SpellGroupSize * SpellGroupSize;即:每个主技能占用连续 10 个 ID(如 100100),主技能本身是 10 的整数倍,其余 9 个 ID 留给它的子技能(Buff)。这一规则同时体现在运行时的BuffSystem.GetSpellConfigId()(见下文),也是 SpellEditor 资产命名(s{spellId}/b{buffId})与导出工具解析的基础。
Buff 配置模型:BuffConfig、BuffFlags 与效果节点
BuffConfig 字段详解
Buff 的主配置类型为 BuffConfig(Scripts/Model/Share/BuffConfig.cs),同样通过 Odin 布局:
| 字段 | 默认值 | 含义 |
|---|---|---|
Id | —(ReadOnly) | Buff 配置 ID(通常与所属主技能 ID 同段) |
Desc | — | 描述 |
Duration | 1000000000 | 持续时间(毫秒)。注意默认值极大,等价于“近乎永久”;配置为负数时运行时按“无到期时间”处理(ExpireTime = long.MaxValue) |
TickTime | 0 | Tick 间隔(毫秒),>0 时启用周期效果 |
MaxStack | 1 | 最大叠加层数 |
Stack | 1 | 初始层数 |
OverLayRuleType | — | 叠加规则类型(见下) |
Flags | 空 | 移除条件集合(BuffFlags) |
NoticeType | — | 广播客户端类型(NoticeType) |
Effects | 空列表 | 效果节点列表(EffectNode),可多选 |
其中Effects在 Unity 下用[UnityEngine.SerializeReference]序列化,并维护一个Dictionary<Type, EffectNode> effectDict缓存(OnBeforeSerialize/OnAfterDeserialize时重建),提供GetEffect<T>()按类型快速取效果节点——这是运行时buff.GetConfig().GetEffect<EffectServerBuffAdd>()一类调用的底层支撑。
OverLayRuleType定义四种叠加规则:
public enum OverLayRuleType { [LabelText("无")] None, // 不叠加 [LabelText("层数")] AddStack, // 叠加层数 [LabelText("时间")] AddTime, // 刷新/延长持续时间 [LabelText("替换")] Replace, // 相同配置 ID 直接替换 }BuffFlags:移除条件集合
BuffFlags.cs 定义了 Buff 的 17 种移除条件(含 None=0),是 Buff 生命周期管理的核心语义:
| Flag | 含义 |
|---|---|
TimeoutRemove | 超时删除 |
DeadRemove | 死亡删除 |
InBattleRemove/OutBattleRemove | 进入/离开战斗删除 |
MoveRemove | 移动删除 |
DamageRemove | 受伤删除 |
CurrentSpellRemoveRemove | 当前技能删除时一并删除 |
RideRemove | 骑乘删除 |
StackRemove | 层数归零删除 |
SameConfigIdReplaceRemove | 相同配置 ID 替换 |
NewSpellInterrupt | 新技能打断 |
NoDurationRemove | 无持续时间删除 |
ParentRemoveRemove | 父 Buff 移除时随删(用于技能→子 Buff 的绑定) |
NotFoundTargetRemove | 目标不存在删除 |
StunRemove | 眩晕删除 |
AIRemove | AI 删除 |
这些 Flag 同时用于BuffComponent.flagBuffs的索引(见下文),实现“按 Flag 批量查/删 Buff”。
效果节点体系:EffectNode
Effects列表中每一项都是抽象类EffectNode的派生类。EffectNode.cs 定义EffectNode : BTRoot——效果节点本身就是一个行为树根节点(BTRoot),这决定了“Buff 效果由行为树驱动”的实现方式。包内预置的效果节点(均在Scripts/Model/Share/下):
| 节点 | 触发时机 |
|---|---|
EffectServerBuffAdd | Buff 创建(InitBuff 时执行) |
EffectServerBuffRemove | Buff 移除(RemoveBuff 时执行) |
EffectServerBuffTick | 周期 Tick(BuffTickComponent 每次 OnTick 执行) |
EffectServerBuffHitted | 被击中时执行 |
EffectClientBuffAdd/EffectClientBuffRemove/EffectClientBuffHitted | 客户端对应的表现/逻辑效果 |
服务端在InitBuff/RemoveBuff/OnTick中分别通过GetEffect<EffectServerBuffAdd>()等取出节点,用BTEnv绑定Buff、Unit、Caster上下文后BTHelper.RunTree驱动执行(源码见 BuffHelper.cs 与 BuffTickComponentSystem.cs)。
CostNode:消耗节点
CostNode.cs 定义技能消耗的行为树节点,同样是BTRoot,声明三个BTOutput输出参数:
public class CostNode: BTRoot { [BTOutput(typeof(Buff))] public string Buff = "Buff"; [BTOutput(typeof(Unit))] public string Unit = "Unit"; [BTOutput(typeof(bool))] public string Check = "Check"; }运行时会先以Check = true跑一次做“消耗检查”,通过后再以Check = false跑一次真正扣减(见下文施法流程)。
运行时数据模型:Buff、BuffData 与索引组件
Buff 实体
Buff.cs(Scripts/Model/Share/Buff.cs)是运行时的 Buff 实体,[ChildOf(typeof(BuffComponent))]挂载在 Unit 的BuffComponent下:
[ChildOf(typeof(BuffComponent))] public class Buff: Entity, IAwake<int>, IDestroy { public int ConfigId; // Buff 配置 ID public long Caster; // 施法者 Unit Id public long CreateTime; // 创建时间(ServerNow) public int TickTime; // 实际生效的 Tick 间隔 public long ExpireTime; // 到期时间 public int Stack; // 当前层数 public long TimeoutTimer; // 到期一次性定时器 [BsonIgnore] public EntityRef<BuffData> BuffData; // Buff 上下文 }BuffData:不被销毁的 Buff 上下文
BuffData.cs 注释明确其为“buff上下文,不会被dispose”,用来在 Buff 生命周期之外保存跨阶段数据(如施法目标SpellTargetComponent),ParentData指向父 Buff 的 BuffData,形成父子链:
[ComponentOf(typeof(Buff))] public class BuffData: Entity, IAwake { public EntityRef<BuffData> ParentData; }BuffSystem.GetBuffData()(BuffSystem.cs)在Buff.BuffData为空时自动AddComponent<BuffData>()创建,而移除 Buff 时只RemoveComponent<BuffData>(false)显式保留 BuffData 供数据传递(见BuffComponentSystem.RemoveBuff中注释“BuffData靠GC回收,方便传递数据”)。
BuffComponent:三套索引
BuffComponent.cs 挂载在 Unit 上([ComponentOf(typeof(Unit))]),用三张MultiMapSet索引当前全部 Buff:
flagBuffs:key = BuffFlags,支持按移除条件批量查询(如“移除所有 MoveRemove 的 Buff”);effectBuffs:key = EffectNode 的 Type,支持按效果类型查询;configIdBuffs:key = BuffConfig.Id,支持按配置 ID 查询/叠加判定。
对应查询/删除 API 在 BuffComponentSystem.cs:
HasBuff(configId)、GetByFlag(flag)、GetByConfigId(configId)、GetOneByConfigId(configId)、GetByEffectType<T>();CreateBuff(buffId, buffConfigId, casterId):创建时同步写入三套索引,并按Duration < 0决定ExpireTime = long.MaxValue;RemoveBuff(buff):反向清理索引,RemoveComponent<BuffData>(false)保留上下文后移除子实体。
SpellComponent:CD 与技能修改点
SpellComponent.cs(Scripts/Model/Share/SpellComponent.cs)同样挂载在 Unit 上,维护:
Current:当前正在施放的 Buff(技能本体以 Buff 形式存在);CDTime:公共 CD 时间点;SpellCD:Dictionary<int, long>,每个技能 ID 的 CD 时间点;SpellMods:Dictionary<int, Dictionary<int, int>>,技能修改点(按 SpellModType 索引的整型数值累加),用于天赋/Buff 对技能伤害、持续时间、施法时间、范围等的动态修正。
SpellComponentSystem.cs 提供CheckCD(公共 CD 500ms + 技能自身 CD)、UpdateCD、GetMod、AddMod等接口。SpellModType枚举(同文件)定义了 40 余种修改点,如SPELLMOD_DAMAGE(10)、SPELLMOD_DURATION(11)、SPELLMOD_THREAT(12)、SPELLMOD_RANGE(15)、SPELLMOD_CASTING_TIME(20)、SPELLMOD_COOLDOWN(21)、SPELLMOD_GLOBAL_COOLDOWN(31)等,与经典魔兽式技能模板的修改点模型同构。
服务端施法链路:SpellHelper.Cast 与技能本体即 Buff
服务端施法入口是 SpellHelper.cs(Scripts/Hotfix/Server/SpellHelper.cs),核心思想是**“技能本体以 Buff 形式存在”**:一次施法 = 创建一个带完整上下文的主 Buff,并驱动其生命周期。
public static int Cast(Unit unit, int spellConfigId, Buff parent = null) { SpellConfig spellConfig = unit.Fiber().GetSingleton<SpellConfigCategory>().Get(spellConfigId); SpellComponent spellComponent = unit.GetComponent<SpellComponent>(); // 1. 检查:主技能才检查 CD(子技能/附带技能跳过) if (SpellConfigHelper.IsMainSpell(spellConfigId)) { bool ret = spellComponent.CheckCD(spellConfig); if (!ret) return TextConstDefine.SpellCast_SpellInCD; } // 2. 创建技能本体 Buff Buff buff = BuffHelper.CreateBuffWithoutInit(unit, unit.Id, IdGenerater.Instance.GenerateId(), spellConfig.Id, parent); int castRet = SpellCasting(unit, buff, spellConfig, parent); if (castRet != 0) unit.GetComponent<BuffComponent>().RemoveBuff(buff); // 失败则回收 return castRet; }另有重载Cast(unit, spellConfigId, Unit target, Buff parent = null):在创建 Buff 后向buff.GetBuffData()添加SpellTargetComponent并写入目标 Unit Id,绕过后续的目标选择步骤。
SpellCasting(私有核心方法)依次完成:
- 打断当前技能:无父 Buff 时,若
spellComponent.Current存在则BuffHelper.RemoveBuff(currentBuff, BuffFlags.NewSpellInterrupt),并把新 Buff 设为Current; - 目标选择:若 BuffData 上没有预置的
SpellTargetComponent,则读取spellConfig.TargetSelector(BTRoot),用BTEnv注入Buff/Caster/Owner后BTHelper.RunTree执行;失败则返回错误码; - 消耗检查与扣除:
spellConfig.Cost(CostNode)先以Check=true运行做资源检查,再以Check=false运行实际扣减; - 更新 CD:
spellConfig.CD > 0时UpdateCD(unit, spellConfig.Id, ServerNow); - 初始化 Buff:
BuffHelper.InitBuff(buff)进入 Buff 生命周期;若buff.ExpireTime < ServerNow(无持续时间的即时技能)立即以BuffFlags.NoDurationRemove移除。
UpdateCD在 Unit 为UnitType.Player时还会下发M2C_UpdateCD消息通知客户端刷新 CD 表现。
从网络入口到施法的链路:客户端通过 Spell_C_10200.proto 定义的C2M_SpellCast(字段含SpellConfigId、TargetPosition、TargetUnitId)发送请求,服务端 C2M_SpellCastHandler.cs 收到后调用SpellHelper.Cast;客户端侧 SpellHelper.cs 仅负责把C2M_SpellCast通过ClientSenderComponent发往服务端。
Buff 生命周期管理:创建、叠加、Tick、移除
创建与初始化(InitBuff)
BuffHelper.cs 的CreateBuff/CreateBuffWithoutInit/InitBuff构成 Buff 创建链路。CreateBuffWithoutInit只做创建与父子绑定(若父 Buff 带ParentRemoveRemoveFlag,则挂到BuffChildrenComponent下随父删除),CreateBuff额外调用InitBuff。InitBuff的完整流程:
- 处理叠加规则(
OverLayRuleType):通过GetByConfigId找到同类旧 Buff——AddStack:UpdateStack(oldBuff, oldBuff.Stack + buffConfig.Stack)并返回旧 Buff;AddTime:expireTime比旧 Buff 更晚才刷新(防止“越叠越短”);Replace:移除同配置旧 Buff(SameConfigIdReplaceRemove);
- 刷新 Tick 组件:
TickTime > 0时挂载BuffTickComponent; - 注册到期定时器:
ExpireTime有效(>0 && < long.MaxValue)时NewOnceTimer(ExpireTime, TimerInvokeType.BuffTimeoutTimer, buff); - 执行 Add 效果:
GetEffect<EffectServerBuffAdd>()用 BTEnv 执行; - 广播客户端:构造
M2C_BuffAdd(携带BuffId / BuffConfigId / CreateTime / TickTime / ExpireTime / Stack / CasterId / SpellTarget)按buffConfig.NoticeType下发。
周期 Tick
BuffTickComponentSystem.cs 实现周期效果:
Awake时读取EffectServerBuffTick;若Override == true,则挂载到 Unit 的BTCoroutineComponent并AddAI(buff)(即 Tick 期间覆盖该 Unit 的 AI 行为,如吟唱引导型技能),Destroy时ResumeAI();Start()立即执行一次OnTick(),随后NewRepeatedTimer(tickTime, TimerInvokeType.BuffTickTimer, self)循环;OnTick()再次BTHelper.RunTree(EffectServerBuffTick)执行每次 Tick 的效果(如流血/回蓝)。
移除
RemoveBuff(buff, removeType)的执行序列:构造M2C_BuffRemove→ 挂载/写入BuffRemoveTypeComponent记录移除原因 → 执行EffectServerBuffRemove→BuffComponent.RemoveBuff(buff)清理索引 → 按NoticeType广播客户端。配套的批量移除 API 包括:
RemoveBuff(unit, long id, BuffFlags):按 Buff 实例 ID;RemoveBuffByConfigId(unit, configId, BuffFlags):按配置 ID 批量;RemoveBuffFlag(unit, flag)/RemoveBuffsByFlag(unit, flag, removeType):按 Flag 批量。
BuffFlags.TimeoutRemove由BuffTimeoutTimer(ATimer<Buff>,同样定义在 BuffHelper.cs 中)在到期时自动触发。
动态更新与客户端同步
UpdateStack:限制在MaxStack内,归零时以StackRemove移除;UpdateExpireTime:重建到期定时器,防止过期则立即移除;UpdateTickTime:动态改变 Tick 间隔并刷新 Tick 组件。
三者都会构造M2C_BuffUpdate广播客户端。对应的客户端消息 Handler 位于Scripts/Hotfix/Client/:M2C_BuffAddHandler.cs、M2C_BuffRemoveHandler.cs、M2C_BuffUpdateHandler.cs、M2C_UpdateCDHandler.cs。
Buff 与技能的联动:Buff 修改数值与技能修改点
- BuffChangeNumericRecordComponent.cs 与对应 System 负责记录并回滚 Buff 对 Unit 数值组件(cn.etetet.numeric)的修改,保证 Buff 移除后数值能正确还原;
- BuffSpellModRecordComponent.cs 与对应 System 记录 Buff 对
SpellComponent.SpellMods的修改,移除时回滚技能修改点; - ThreatComponent.cs 与
ThreatInfo.cs提供仇恨累积支持(与SpellModType.SPELLMOD_THREAT对应); - DamageHelper.cs、DamageEvent.cs 提供伤害结算与伤害事件(供
NumericChange_DamageRemoveBuff.cs、ChangePosition_RemoveBuff.cs这类“条件触发移除”使用——前者在数值变化(受击)时移除DamageRemove类 Buff,后者在位移时移除MoveRemove类 Buff)。
这一设计与SpellModType(40+ 种技能修改点)共同支撑起“天赋/Buff 动态改技能、技能释放改数值、受击/位移触发清 Buff”的完整联动闭环。
客户端表现组件
技能指示器与目标选择
- SpellIndicatorComponent.cs 与 SpellIndicatorComponentSystem.cs:技能释放前的范围/方向指示器表现;
- TargetSelector.cs、
TargetSelectorCircle.cs/TargetSelectorPosition.cs(Scripts/Model/Share/Root/)定义目标选择器节点,TargetSelectDispatcher.cs(Scripts/Model/Client/)负责客户端按需分发;HotfixView/Client/TargetSelector/下提供圆范围选择(TargetSelectorCircleHandler)与点位置选择(TargetSelectorPositionHandler)的视图处理。
Buff 表现
- BuffGameObjectComponent.cs 与 BuffGameObjectComponentSystem.cs:将 Buff 绑定到具体 GameObject 表现;
- SpellScriptableObject.cs / BuffScriptableObject.cs:客户端可编辑的技能/Buff ScriptableObject 资产。
Spell Editor 编辑器工具链
入口与主界面
SpellEditor 是包内自带的 Unity 编辑器窗口,入口在 SpellEditorWindow.cs:
[MenuItem(SpellEditorConstants.MenuPath)] // "ET/Spell/Spell Editor" public static void Open() { ... }窗口顶栏提供三个核心按钮:
- 刷新:重建资产索引;
- 保存:
SpellEditorAssetOperations.SaveAssets()保存当前编辑的资产; - 导出配置:
SpellEditorAssetOperations.ExportConfig()走现有的 ScriptableObject 导出链路(对应 AGENTS.md 中“导出 Spell/Buff 配置时复用现有 ScriptableObject 导出链路”的规则)。
主界面分为主技能列表(可调宽度面板)、资产工具栏与技能/ Buff 表格区,支持技能筛选、重命名、复制主技能等功能(见 SpellEditorWindow.cs 字段mainSpellFilter、renameId、copyMainSpellId等)。
资产组织与命名约定
SpellEditorConstants.cs 定义资产根目录与命名规则:
- 资产根目录:
BtAssetRoot = "Packages/cn.etetet.statesync/Assets/BT"(对应 AGENTS.md 中“当前资产来源为Packages/cn.etetet.statesync/Assets/BT/**”); - 命名前缀:技能资产
s{spellId}(兼容旧前缀Spell),Buff 资产b{buffId}(兼容旧前缀Buff); - 提供
TryParseSpellAssetName / TryParseBuffAssetName从资产名反解 ID。
编辑器目录下还包含:
- SpellEditorAssetIndex.cs:资产索引;
- SpellEditorGraphBuilder.cs:效果/消耗/目标选择节点图构建;
- SpellEditorReference.cs 与 SpellEditorReferenceScanner.cs:资产引用扫描;
- SpellEditorValidator.cs:配置校验(产出
SpellEditorIssue问题列表); - SpellEditorModels.cs 与 SpellEditorEffectsPopupContent.cs:界面数据模型与效果节点选择弹窗。
开发规则与工程约束(AGENTS.md 逐条解读)
AGENTS.md 列出的开发规则既是本包作者的工作约定,也是理解工程结构的钥匙:
- 遵守根目录 AGENTS.md 与
Packages/cn.etetet.harness/AGENTS.md:本包开发受 AGENTS.md(仓库根)与 harness 包说明 的全局规范约束; - 所有命令必须使用
pwsh:本仓库的构建/导出/测试脚本(如 Scripts 目录 下的 PowerShell 脚本)均以 PowerShell 为准; - C# 文件默认一类一文件:与
Scripts/Model|Hotfix|ModelView|HotfixView各子目录下文件组织方式一致; - 新增编辑器代码放在
Scripts/Editor/Share/下,编译进ET.Editor:本包的编辑器工具全部位于Scripts/Editor/Share/SpellEditor/; - 不手工创建
.meta(由 Unity 刷新生成)、不手工修改.csproj; - 修改 Spell/Buff 配置源时,当前资产来源为
Packages/cn.etetet.statesync/Assets/BT/**:Spell/Buff 的行为树资产实际存放在 statesync 包的Assets/BT下(对应SpellEditorConstants.BtAssetRoot); - 导出 Spell/Buff 配置时复用现有 ScriptableObject 导出链路:对应 SpellEditor 的“导出配置”按钮实现。
测试验证:Fiber 单测示例
包内自带一个 Fiber 级测试 Spell_FiberSingleton_Test.cs,它验证了 Spell/Buff 配置表在 Fiber 单例中的使用方式,也示范了如何在测试中注入 Mock 配置:
await using TestFiberScope scope = await TestFiberScope.Create(context.Fiber, SceneType.TestCase, nameof(Spell_FiberSingleton_Test)); Fiber robot = await TestHelper.CreateRobot(testFiber, "Client"); Unit unit = TestHelper.GetServerUnit(testFiber, robot); SpellConfig globalSpellConfig = unitFiber.GetSingleton<SpellConfigCategory>().Get(100100); BuffConfig globalBuffConfig = unitFiber.GetSingleton<BuffConfigCategory>().Get(globalSpellConfig.Id); // 构造 Mock 配置并替换 Fiber 单例 unitFiber.AddSingleton(mockSpellCategory); unitFiber.AddSingleton(mockBuffCategory); Buff buff = buffComponent.CreateBuff(IdGenerater.Instance.GenerateId(), mockBuffConfig.Id, unit.Id);要点:主技能100100必然对应同名 Buff 配置(印证“技能本体即 Buff”);通过unitFiber.AddSingleton(...)在测试 Fiber 内替换SpellConfigCategory/BuffConfigCategory单例即可隔离测试数据,无需改动全局配置。
总结:技能系统的整体工作流
把以上各节串起来,一次完整技能交互的链路是:
- 客户端发出
C2M_SpellCast(SpellConfigId + TargetPosition + TargetUnitId),必要时先播放技能指示器(SpellIndicatorComponent); - 服务端
C2M_SpellCastHandler→SpellHelper.Cast:校验 CD → 创建技能本体 Buff → 打断旧技能 → 行为树目标选择(TargetSelector)→ CostNode 检查/扣除 → 更新 CD →InitBuff; - Buff 生命周期:
BuffComponent建索引 → 按OverLayRuleType处理叠加 → 注册到期定时器 → 执行EffectServerBuffAdd→ 按NoticeType下发M2C_BuffAdd; - 运行期:
BuffTickComponent周期执行EffectServerBuffTick(DOT 类效果),EffectServerBuffHitted响应受击,Buff 通过BuffChangeNumericRecordComponent/BuffSpellModRecordComponent修改数值与技能修改点; - 移除:到期(
TimeoutRemove)、受击(DamageRemove)、位移(MoveRemove)、层数归零(StackRemove)、新技能打断(NewSpellInterrupt)、父 Buff 删除(ParentRemoveRemove)等任一 Flag 触发 → 执行EffectServerBuffRemove→ 清理索引与数值回滚 → 下发M2C_BuffRemove; - 客户端通过
M2C_BuffAdd/Update/RemoveHandler 同步表现,BuffGameObjectComponent驱动具体 GameObject 特效。
借助这套“配置(Luban 表 + SpellEditor 资产)+ 行为树效果节点 + 实体组件运行时”的组合,cn.etetet.spell为 ET 项目提供了一套可配置、可扩展、逻辑与表现分离的完整技能/Buff 框架,而Scripts/Hotfix/Test/Spell_FiberSingleton_Test.cs则为后续扩展提供了可直接照搬的验证范式。
【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考