news 2026/9/16 17:14:49

ET 技能与 Buff 系统包 cn.etetet.spell 全面解析:配置模型、运行时施法链路与编辑器工具链

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ET 技能与 Buff 系统包 cn.etetet.spell 全面解析:配置模型、运行时施法链路与编辑器工具链

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/ModelScripts/HotfixScripts/HotfixViewScripts/Editor)与配置资产,系统讲解技能/Buff 的数据模型与配置方式、服务端施法与 Buff 生命周期管理、效果节点与行为树驱动的实现原理、客户端表现与消息同步,以及 Spell Editor 编辑器工具的使用与开发约束,最终帮助你在自己的 ET 项目中落地一套可复用的技能系统。

包概览:定位、依赖与目录结构

cn.etetet.spell的定位由 AGENTS.md 概述为:技能与 Buff 系统包,包含四大部分:

  1. Spell/Buff 配置模型——配置数据结构与代码生成;
  2. 运行时施法逻辑——服务端施法、目标选择、消耗与 CD 管理;
  3. 客户端表现组件——技能指示器、Buff 表现物体等;
  4. 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.unitcn.etetet.mapcn.etetet.yooassetscn.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 标注含义
IdReadOnlyBoxGroup("技能信息")技能 ID(主技能 ID 为 10 的整数倍,见下文 ID 分组规则)
DescBoxGroup("技能信息")技能描述
IconNameBoxGroup("技能信息")技能图标资源名
CDBoxGroup("技能信息")技能 CD,单位毫秒(int)
DamageMultiplierBoxGroup("技能信息"),默认 100伤害系数(百分比),默认 100%
CostLabelText("消耗")消耗节点(CostNode),行为树驱动
TargetSelectorLabelText("目标选择")目标选择器(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__.xlsxLuban/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描述
Duration1000000000持续时间(毫秒)。注意默认值极大,等价于“近乎永久”;配置为负数时运行时按“无到期时间”处理(ExpireTime = long.MaxValue
TickTime0Tick 间隔(毫秒),>0 时启用周期效果
MaxStack1最大叠加层数
Stack1初始层数
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眩晕删除
AIRemoveAI 删除

这些 Flag 同时用于BuffComponent.flagBuffs的索引(见下文),实现“按 Flag 批量查/删 Buff”。

效果节点体系:EffectNode

Effects列表中每一项都是抽象类EffectNode的派生类。EffectNode.cs 定义EffectNode : BTRoot——效果节点本身就是一个行为树根节点(BTRoot),这决定了“Buff 效果由行为树驱动”的实现方式。包内预置的效果节点(均在Scripts/Model/Share/下):

节点触发时机
EffectServerBuffAddBuff 创建(InitBuff 时执行)
EffectServerBuffRemoveBuff 移除(RemoveBuff 时执行)
EffectServerBuffTick周期 Tick(BuffTickComponent 每次 OnTick 执行)
EffectServerBuffHitted被击中时执行
EffectClientBuffAdd/EffectClientBuffRemove/EffectClientBuffHitted客户端对应的表现/逻辑效果

服务端在InitBuff/RemoveBuff/OnTick中分别通过GetEffect<EffectServerBuffAdd>()等取出节点,用BTEnv绑定BuffUnitCaster上下文后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:

  • flagBuffskey = BuffFlags,支持按移除条件批量查询(如“移除所有 MoveRemove 的 Buff”);
  • effectBuffskey = EffectNode 的 Type,支持按效果类型查询;
  • configIdBuffskey = 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 时间点;
  • SpellCDDictionary<int, long>,每个技能 ID 的 CD 时间点;
  • SpellModsDictionary<int, Dictionary<int, int>>,技能修改点(按 SpellModType 索引的整型数值累加),用于天赋/Buff 对技能伤害、持续时间、施法时间、范围等的动态修正。

SpellComponentSystem.cs 提供CheckCD(公共 CD 500ms + 技能自身 CD)、UpdateCDGetModAddMod等接口。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(私有核心方法)依次完成:

  1. 打断当前技能:无父 Buff 时,若spellComponent.Current存在则BuffHelper.RemoveBuff(currentBuff, BuffFlags.NewSpellInterrupt),并把新 Buff 设为Current
  2. 目标选择:若 BuffData 上没有预置的SpellTargetComponent,则读取spellConfig.TargetSelector(BTRoot),用BTEnv注入Buff/Caster/OwnerBTHelper.RunTree执行;失败则返回错误码;
  3. 消耗检查与扣除spellConfig.Cost(CostNode)先以Check=true运行做资源检查,再以Check=false运行实际扣减;
  4. 更新 CDspellConfig.CD > 0UpdateCD(unit, spellConfig.Id, ServerNow)
  5. 初始化 BuffBuffHelper.InitBuff(buff)进入 Buff 生命周期;若buff.ExpireTime < ServerNow(无持续时间的即时技能)立即以BuffFlags.NoDurationRemove移除。

UpdateCD在 Unit 为UnitType.Player时还会下发M2C_UpdateCD消息通知客户端刷新 CD 表现。

从网络入口到施法的链路:客户端通过 Spell_C_10200.proto 定义的C2M_SpellCast(字段含SpellConfigIdTargetPositionTargetUnitId)发送请求,服务端 C2M_SpellCastHandler.cs 收到后调用SpellHelper.Cast;客户端侧 SpellHelper.cs 仅负责把C2M_SpellCast通过ClientSenderComponent发往服务端。

Buff 生命周期管理:创建、叠加、Tick、移除

创建与初始化(InitBuff)

BuffHelper.cs 的CreateBuff/CreateBuffWithoutInit/InitBuff构成 Buff 创建链路。CreateBuffWithoutInit只做创建与父子绑定(若父 Buff 带ParentRemoveRemoveFlag,则挂到BuffChildrenComponent下随父删除),CreateBuff额外调用InitBuffInitBuff的完整流程:

  1. 处理叠加规则OverLayRuleType):通过GetByConfigId找到同类旧 Buff——
    • AddStackUpdateStack(oldBuff, oldBuff.Stack + buffConfig.Stack)并返回旧 Buff;
    • AddTimeexpireTime比旧 Buff 更晚才刷新(防止“越叠越短”);
    • Replace:移除同配置旧 Buff(SameConfigIdReplaceRemove);
  2. 刷新 Tick 组件TickTime > 0时挂载BuffTickComponent
  3. 注册到期定时器ExpireTime有效(>0 && < long.MaxValue)时NewOnceTimer(ExpireTime, TimerInvokeType.BuffTimeoutTimer, buff)
  4. 执行 Add 效果GetEffect<EffectServerBuffAdd>()用 BTEnv 执行;
  5. 广播客户端:构造M2C_BuffAdd(携带BuffId / BuffConfigId / CreateTime / TickTime / ExpireTime / Stack / CasterId / SpellTarget)按buffConfig.NoticeType下发。

周期 Tick

BuffTickComponentSystem.cs 实现周期效果:

  • Awake时读取EffectServerBuffTick;若Override == true,则挂载到 Unit 的BTCoroutineComponentAddAI(buff)(即 Tick 期间覆盖该 Unit 的 AI 行为,如吟唱引导型技能),DestroyResumeAI()
  • Start()立即执行一次OnTick(),随后NewRepeatedTimer(tickTime, TimerInvokeType.BuffTickTimer, self)循环;
  • OnTick()再次BTHelper.RunTree(EffectServerBuffTick)执行每次 Tick 的效果(如流血/回蓝)。

移除

RemoveBuff(buff, removeType)的执行序列:构造M2C_BuffRemove→ 挂载/写入BuffRemoveTypeComponent记录移除原因 → 执行EffectServerBuffRemoveBuffComponent.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.TimeoutRemoveBuffTimeoutTimerATimer<Buff>,同样定义在 BuffHelper.cs 中)在到期时自动触发。

动态更新与客户端同步

  • UpdateStack:限制在MaxStack内,归零时以StackRemove移除;
  • UpdateExpireTime:重建到期定时器,防止过期则立即移除;
  • UpdateTickTime:动态改变 Tick 间隔并刷新 Tick 组件。

三者都会构造M2C_BuffUpdate广播客户端。对应的客户端消息 Handler 位于Scripts/Hotfix/Client/M2C_BuffAddHandler.csM2C_BuffRemoveHandler.csM2C_BuffUpdateHandler.csM2C_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.csChangePosition_RemoveBuff.cs这类“条件触发移除”使用——前者在数值变化(受击)时移除DamageRemove类 Buff,后者在位移时移除MoveRemove类 Buff)。

这一设计与SpellModType(40+ 种技能修改点)共同支撑起“天赋/Buff 动态改技能、技能释放改数值、受击/位移触发清 Buff”的完整联动闭环。

客户端表现组件

技能指示器与目标选择

  • SpellIndicatorComponent.cs 与 SpellIndicatorComponentSystem.cs:技能释放前的范围/方向指示器表现;
  • TargetSelector.cs、TargetSelectorCircle.cs/TargetSelectorPosition.csScripts/Model/Share/Root/)定义目标选择器节点,TargetSelectDispatcher.csScripts/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 字段mainSpellFilterrenameIdcopyMainSpellId等)。

资产组织与命名约定

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 列出的开发规则既是本包作者的工作约定,也是理解工程结构的钥匙:

  1. 遵守根目录 AGENTS.md 与Packages/cn.etetet.harness/AGENTS.md:本包开发受 AGENTS.md(仓库根)与 harness 包说明 的全局规范约束;
  2. 所有命令必须使用pwsh:本仓库的构建/导出/测试脚本(如 Scripts 目录 下的 PowerShell 脚本)均以 PowerShell 为准;
  3. C# 文件默认一类一文件:与Scripts/Model|Hotfix|ModelView|HotfixView各子目录下文件组织方式一致;
  4. 新增编辑器代码放在Scripts/Editor/Share/下,编译进ET.Editor:本包的编辑器工具全部位于Scripts/Editor/Share/SpellEditor/
  5. 不手工创建.meta(由 Unity 刷新生成)、不手工修改.csproj
  6. 修改 Spell/Buff 配置源时,当前资产来源为Packages/cn.etetet.statesync/Assets/BT/**:Spell/Buff 的行为树资产实际存放在 statesync 包的Assets/BT下(对应SpellEditorConstants.BtAssetRoot);
  7. 导出 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单例即可隔离测试数据,无需改动全局配置。

总结:技能系统的整体工作流

把以上各节串起来,一次完整技能交互的链路是:

  1. 客户端发出C2M_SpellCast(SpellConfigId + TargetPosition + TargetUnitId),必要时先播放技能指示器(SpellIndicatorComponent);
  2. 服务端C2M_SpellCastHandlerSpellHelper.Cast:校验 CD → 创建技能本体 Buff → 打断旧技能 → 行为树目标选择(TargetSelector)→ CostNode 检查/扣除 → 更新 CD →InitBuff
  3. Buff 生命周期BuffComponent建索引 → 按OverLayRuleType处理叠加 → 注册到期定时器 → 执行EffectServerBuffAdd→ 按NoticeType下发M2C_BuffAdd
  4. 运行期BuffTickComponent周期执行EffectServerBuffTick(DOT 类效果),EffectServerBuffHitted响应受击,Buff 通过BuffChangeNumericRecordComponent/BuffSpellModRecordComponent修改数值与技能修改点;
  5. 移除:到期(TimeoutRemove)、受击(DamageRemove)、位移(MoveRemove)、层数归零(StackRemove)、新技能打断(NewSpellInterrupt)、父 Buff 删除(ParentRemoveRemove)等任一 Flag 触发 → 执行EffectServerBuffRemove→ 清理索引与数值回滚 → 下发M2C_BuffRemove
  6. 客户端通过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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 17:14:04

如何精简 Windows 11 官方镜像:tiny11builder 实操

如何精简 Windows 11 官方镜像&#xff1a;tiny11builder 实操 【免费下载链接】tiny11builder Scripts to build a trimmed-down Windows 11 image. 项目地址: https://gitcode.com/GitHub_Trending/ti/tiny11builder tiny11builder 是一套 PowerShell 脚本集&#xff…

作者头像 李华
网站建设 2026/9/16 17:12:57

Django视频点播网站搭建:从ORM建模到HLS播放与Nginx部署

简介&#xff1a;一套基于Django框架开发的视频点播网站完整源码&#xff0c;面向计算机、数学、电子信息等专业学生&#xff0c;适合作为课程设计、期末大作业或毕业设计参考项目。项目已实现视频播放、收藏、后台管理等功能模块&#xff0c;代码结构清晰&#xff0c;可直接下…

作者头像 李华
网站建设 2026/9/16 17:11:00

微信快递小程序源码全解析:ThinkPHP后端与部署实战

简介&#xff1a;2024最新版快递小程序源码是一套完整可运营的微信快递服务项目&#xff0c;后端基于ThinkPHP&#xff08;PHP&#xff09;框架构建&#xff0c;前端为微信小程序&#xff0c;覆盖查件、寄件下单、物流跟踪等功能&#xff0c;并兼顾数据加密与隐私保护&#xff…

作者头像 李华
网站建设 2026/9/16 17:08:56

Gumroad 本地开发环境用户与认证(Users Authentication)完全指南

Gumroad 本地开发环境用户与认证&#xff08;Users & Authentication&#xff09;完全指南 【免费下载链接】gumroad See what sticks 项目地址: https://gitcode.com/GitHub_Trending/gumr/gumroad 本文围绕 Gumroad 开源仓库的 docs/users.md 展开&#xff0c;系统…

作者头像 李华