# 21 · 形态技能系统(Form Skill System) > **命名空间** `BaseGames.Skills` > **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md) > **依赖** `BaseGames.Combat` · `BaseGames.Player` · `BaseGames.Equipment` · `BaseGames.Core.Events` · `BaseGames.Animation` --- ## 目录 1. [系统总览](#1-系统总览) 2. [FormSkillSO — 技能数据](#2-formskillso--技能数据) 3. [SkillManager — 技能执行器](#3-skillmanager--技能执行器) 4. [九种形态技能实现](#4-九种形态技能实现) 5. [技能修改系统(装备系统联动)](#5-技能修改系统装备系统联动) 6. [技能初始化与 FormSO 绑定](#6-技能初始化与-formso-绑定) 7. [SaveData 扩展](#7-savedata-扩展) 8. [UI 集成](#8-ui-集成) 9. [事件频道](#9-事件频道) 10. [编辑器工具](#10-编辑器工具) --- ## 1. 系统总览 技能系统管理**形态专属主动技能**。每种形态(天魂/地魂/命魂)各拥有 3 个技能槽,消耗独立资源触发: | 技能类型 | Input Action | 消耗资源 | 说明 | |---------|------------|---------|------| | 魂技能(SoulSkill) | `SoulSkill` | 灵力(SoulPower) | 每种形态各 1 个,高爆发或特殊形态能力 | | 魄技能 1(SpiritSkill1) | `SpiritSkill1` | 魄元(SpiritPower) | 每种形态各 1 个,自动恢复资源驱动 | | 魄技能 2(SpiritSkill2) | `SpiritSkill2` | 魄元(SpiritPower) | 每种形态各 1 个,自动恢复资源驱动 | ``` 技能系统职责: ├─ FormSkillSO → 技能数据(消耗/动画/效果/VFX/SFX) ├─ SkillManager → 玩家上的技能执行器(校验资源、执行效果) ├─ FormSO → 持有 3 个技能 SO 引用(见 03_PlayerSystem §6) ├─ SkillModifierSnapshot → 装备魅力修改器快照 └─ SkillHUD → 技能图标 + 资源条(灵力/魄元) ``` **与资源系统的关系**: - 魂技能通过 `PlayerStats.ConsumeSoulPower(cost)` 扣除灵力,**不自动恢复** - 魄技能通过 `PlayerStats.ConsumeSpiritPower(cost)` 扣除魄元,魄元随时间自动恢复 --- ## 2. FormSkillSO — 技能数据 ```csharp [CreateAssetMenu(menuName = "Skills/FormSkill")] public class FormSkillSO : ScriptableObject { [Header("基础信息")] public string skillId; // 唯一 ID(如 "sky_soul_skill") public string displayName; // 如 "裂空掌" [TextArea(1, 3)] public string description; public Sprite icon; [Header("资源消耗")] public SkillResourceType resourceType; // SoulPower / SpiritPower public int baseCost; // 基础消耗量(装备魅力可降低) public float cooldown; // 技能独立冷却(秒,0 = 无冷却) [Header("动画")] public ClipTransition castAnimation; // 施法动画(Layer[1] 上半身) public float castLockDuration; // 前摇期间输入锁定时长(秒) [Header("效果类型")] public SkillEffectType effectType; [Header("伤害配置(MeleeAoE / Projectile)")] public DamageSourceSO damageSource; public float knockbackForce; // 击退力(effectType = MeleeAoE 时) [Header("弹射物配置(Projectile)")] public ProjectileConfigSO projectilePrefab; public bool isHoming; // 是否追踪(灵踪弹) public bool holdForContinuous; // 长按连射(灵踪弹) [Header("位移配置(GroundDive / WraithDash / DragonKick)")] public float dashForce; public float dashDuration; public bool isInvincibleDuringDash; public bool canPassMagicWalls; // 太虚斩穿越魔法障壁 [Header("延迟爆炸配置(DelayedExplosion)")] public float explosionDelay; public float explosionRadius; public float explosionExpandDuration; [Header("VFX/SFX")] public FeedbackPresetSO castFeedback; [Header("霸体配置")] [Tooltip("技能施放期间的霸体窗口(None = 可被任何攻击打断),见 54_PoiseSystem §7.2")] public PoiseWindowConfig poiseWindow; // 见 54_PoiseSystem §7.2 } public enum SkillResourceType { SoulPower, // 灵力(近战积累,不自动恢复) SpiritPower, // 魄元(时间自动恢复) } public enum SkillEffectType { MeleeAoE, // 近身范围伤害 + 击退(裂空掌、霸山拳) Projectile, // 发射弹射物(灵踪弹) BarrierAura, // 护体漩涡 + 阻弹(漩灵击) GroundDive, // 遁入地面无敌移动(地行术) DragonKick, // 垂直升龙踢 + 无敌(登龙蹴) WraithDash, // 灵体冲刺 + 二次瞬移(太虚斩) ShadowDecoy, // 留灵体残影模仿攻击(残阴术) DelayedExplosion, // 留魂元球延迟爆炸(魂元爆) } ``` --- ## 3. SkillManager — 技能执行器 `SkillManager` 挂载于 `PlayerController` 同一 GameObject,从 `FormController.CurrentForm` 读取当前形态的技能 SO: ```csharp namespace BaseGames.Skills { public class SkillManager : MonoBehaviour { [Header("依赖(同 Prefab 内)")] [SerializeField] PlayerStats _stats; [SerializeField] FormController _formController; [SerializeField] AnimancerComponent _animancer; [SerializeField] InputReaderSO _input; [SerializeField] SkillModifierRegistry _modRegistry; // 见 §5 [Header("事件频道")] [SerializeField] VoidEventChannelSO _onSkillCast; [SerializeField] VoidEventChannelSO _onSkillFailed; // 各技能独立冷却计时器(key = skillId) readonly Dictionary _cooldowns = new(); bool _isCasting; void OnEnable() { _input.SoulSkillEvent += CastSoulSkill; _input.SpiritSkill1StartedEvent += CastSpiritSkill1; _input.SpiritSkill2StartedEvent += CastSpiritSkill2; } void OnDisable() { _input.SoulSkillEvent -= CastSoulSkill; _input.SpiritSkill1StartedEvent -= CastSpiritSkill1; _input.SpiritSkill2StartedEvent -= CastSpiritSkill2; } public void CastSoulSkill() => TryCast(_formController.CurrentForm.soulSkill, SkillSlotType.SoulSkill); public void CastSpiritSkill1() => TryCast(_formController.CurrentForm.spiritSkill1, SkillSlotType.SpiritSkill1); public void CastSpiritSkill2() => TryCast(_formController.CurrentForm.spiritSkill2, SkillSlotType.SpiritSkill2); void TryCast(FormSkillSO baseSkill, SkillSlotType slot) { if (baseSkill == null || _isCasting) return; // 1. 查询插槽替换(护符可能完全替换此槽位的技能) FormSkillSO skill = _modRegistry.GetEffectiveSkill( _formController.CurrentForm, slot, baseSkill); // 2. 计算数值修改器(伤害/范围/消耗/冷却/特效) EffectiveSkillParams p = _modRegistry.GetEffectiveParams(skill); if (IsOnCooldown(skill)) return; bool consumed = skill.resourceType switch { SkillResourceType.SoulPower => _stats.ConsumeSoulPower(p.effectiveCost), SkillResourceType.SpiritPower => _stats.ConsumeSpiritPower(p.effectiveCost), _ => false, }; if (!consumed) { _onSkillFailed.Raise(); return; } StartCoroutine(CastRoutine(skill, p)); } IEnumerator CastRoutine(FormSkillSO skill, EffectiveSkillParams p) { _isCasting = true; SetCooldown(skill, p.effectiveCooldown); // 前摇动画(Layer[1] 上半身叠加层);护符可替换为其他施法动画 _animancer.Layers[1].Play(p.effectiveAnimation ?? skill.castAnimation); yield return new WaitForSeconds(skill.castLockDuration); ExecuteEffect(skill, p); // 特效预设:护符可替换为不同 VFX;null 则回退到原始预设 (p.effectiveFeedback ?? skill.castFeedback)?.PlayFeedbacks(gameObject); _onSkillCast.Raise(); _isCasting = false; } // p.damageMult 和 p.rangeMult 传入各效果执行函数,用于缩放伤害和范围 void ExecuteEffect(FormSkillSO skill, EffectiveSkillParams p) { switch (skill.effectType) { case SkillEffectType.MeleeAoE: ExecuteMeleeAoE(skill, p); break; case SkillEffectType.Projectile: FireProjectile(skill, p); break; case SkillEffectType.BarrierAura: ExecuteBarrierAura(skill, p); break; case SkillEffectType.GroundDive: ExecuteGroundDive(skill, p); break; case SkillEffectType.DragonKick: ExecuteDragonKick(skill, p); break; case SkillEffectType.WraithDash: ExecuteWraithDash(skill, p); break; case SkillEffectType.ShadowDecoy: ExecuteShadowDecoy(skill, p); break; case SkillEffectType.DelayedExplosion: ExecuteDelayedExplosion(skill, p); break; } } bool IsOnCooldown(FormSkillSO skill) => _cooldowns.TryGetValue(skill.skillId, out float t) && t > 0f; // overrideDuration < 0 = 使用 SO 内置冷却;>= 0 = 使用修改后冷却(EffectiveSkillParams 传入) void SetCooldown(FormSkillSO skill, float overrideDuration = -1f) { float cd = overrideDuration >= 0f ? overrideDuration : skill.cooldown; if (cd > 0f) _cooldowns[skill.skillId] = cd; } void Update() { foreach (var key in _cooldowns.Keys.ToList()) _cooldowns[key] = Mathf.Max(0f, _cooldowns[key] - Time.deltaTime); } } } ``` ### SkillSlotType — 技能槽枚举 ```csharp public enum SkillSlotType { SoulSkill, // 魂技能(灵力) SpiritSkill1, // 魄技能 1 SpiritSkill2, // 魄技能 2 } ``` ### EffectiveSkillParams — 运行时最终参数 所有数值修改器叠加后生成的快照,由 `SkillModifierRegistry.GetEffectiveParams` 计算,传入 `CastRoutine` 和 `ExecuteEffect`: ```csharp public struct EffectiveSkillParams { public FormSkillSO baseSkill; // 原始 SO 引用(不变,供判断 effectType) public int effectiveCost; // 修改后消耗量 public float effectiveCooldown; // 修改后冷却(秒) public float damageMult; // 伤害倍率(1.0 = 无增益) public float rangeMult; // 范围倍率(AoE 半径 / 爆炸半径 / 障壁半径等) public FeedbackPresetSO effectiveFeedback; // 最终特效预设(可被护符替换,null = 回退原始) public ClipTransition effectiveAnimation; // 最终施法动画(可被护符替换,null = 回退原始) public static EffectiveSkillParams FromBase(FormSkillSO skill) => new() { baseSkill = skill, effectiveCost = skill.baseCost, effectiveCooldown = skill.cooldown, damageMult = 1f, rangeMult = 1f, effectiveFeedback = null, // null = 使用 skill.castFeedback effectiveAnimation = null, // null = 使用 skill.castAnimation }; } ``` --- ## 4. 九种形态技能实现 ### 天魂形态(Sky Form) #### 4.1 裂空掌(SoulSkill,消耗灵力) ``` skillId: "sky_soul_skill" displayName: "裂空掌" resourceType: SoulPower baseCost: 40 cooldown: 0s(受灵力储量天然限频) effectType: MeleeAoE castLockDuration: 0.2s 特效: 角色前方中距离气波(宽约 1.5u,高约 2u) 击退: Medium(水平方向) 说明: 地面空中均可,气波沿面向方向水平发射 ``` #### 4.2 漩灵击(SpiritSkill1,消耗魄元) ``` skillId: "sky_spirit_skill1" displayName: "漩灵击" resourceType: SpiritPower baseCost: 30 cooldown: 6s effectType: BarrierAura castLockDuration: 0.15s 持续时间: 2.5s 特效: 角色周身旋转灵气漩涡,可阻挡弹道并反弹 空中效果: 持续期间重力减半(缓落) 说明: 护体漩涡激活期间可被攻击中断(扣血不中断护体) ``` #### 4.3 灵踪弹(SpiritSkill2,消耗魄元) ``` skillId: "sky_spirit_skill2" displayName: "灵踪弹" resourceType: SpiritPower baseCost: 20(按下一次消耗,长按连射每颗独立消耗) cooldown: 0.5s(每颗之间) effectType: Projectile isHoming: true(灵球锁定最近敌人,有转弯半径上限) holdForContinuous: true(长按持续发射) 每次发射数量: 2 castLockDuration: 0.1s ``` --- ### 地魂形态(Earth Form) #### 4.4 地行术(SoulSkill,消耗灵力) ``` skillId: "earth_soul_skill" displayName: "地行术" resourceType: SoulPower baseCost: 50 cooldown: 0s effectType: GroundDive castLockDuration: 0.3s(进入地面前摇) 移动速度: 4 units/s(地下) 无敌: 全程无敌 说明: 仅限地面发动;遁入地面后可用 Move 水平移动; 松开 SoulSkill 或碰触障碍物时自动浮出 ``` #### 4.5 霸山拳(SpiritSkill1,消耗魄元) ``` skillId: "earth_spirit_skill1" displayName: "霸山拳" resourceType: SpiritPower baseCost: 35 cooldown: 8s effectType: MeleeAoE castLockDuration: 0.25s 范围: 以角色为中心 2u 半径圆形冲击 附加效果: 震起 1 块悬浮灵石(FloatingStonePrefab),灵石可挡弹道; 被裂空掌击中后飞出造成追加伤害 说明: 仅限地面 ``` #### 4.6 登龙蹴(SpiritSkill2,消耗魄元) ``` skillId: "earth_spirit_skill2" displayName: "登龙蹴" resourceType: SpiritPower baseCost: 40 cooldown: 5s effectType: DragonKick castLockDuration: 0.1s dashForce: 22(垂直向上) dashDuration: 0.55s isInvincibleDuringDash: true 说明: 类升龙踢,上升期间无敌;位移约等于 3 段跳高度,可突破障碍物上方平台 ``` --- ### 命魂形态(Death Form) #### 4.7 太虚斩(SoulSkill,消耗灵力) ``` skillId: "death_soul_skill" displayName: "太虚斩" resourceType: SoulPower baseCost: 45 cooldown: 0s effectType: WraithDash castLockDuration: 0.05s dashForce: 20 dashDuration: 0.35s isInvincibleDuringDash: true canPassMagicWalls: true(穿越带 MagicWall 标签的特定障壁) 二次触发: 冲刺期间再按 SoulSkill → 瞬移至灵体当前终点位置 说明: 冲刺路径上持续有斩击判定盒;可穿越带 MagicWall 标签的特定障碍物 ``` #### 4.8 残阴术(SpiritSkill1,消耗魄元) ``` skillId: "death_spirit_skill1" displayName: "残阴术" resourceType: SpiritPower baseCost: 35 cooldown: 10s effectType: ShadowDecoy castLockDuration: 0.2s(后撤位移) 后撤距离: 3u(反方向 Dash,无敌) 灵体持续: 4s 或受到 3 次攻击后消失 灵体行为: 模仿角色最近一次攻击/技能动作(伤害减半) 机关触发: 灵体可触碰延迟机关(压板/感应器等) 说明: 灵体留于原地,角色后撤;灵体消失前可再按 SpiritSkill1 提前销毁 ``` #### 4.9 魂元爆(SpiritSkill2,消耗魄元) ``` skillId: "death_spirit_skill2" displayName: "魂元爆" resourceType: SpiritPower baseCost: 50 cooldown: 8s effectType: DelayedExplosion castLockDuration: 0.15s explosionDelay: 1.2s(布置后延迟) explosionRadius: 4u(最终半径) explosionExpandDuration: 0.6s(冲击波从 0 扩散到 explosionRadius 的时长) 伤害: 扩散范围内所有敌人,范围越大伤害越低(衰减曲线) 说明: 布置时在角色脚下生成魂元球;爆炸冲击波持续扩散,可引爆其他魂元爆 ``` --- ## 5. 技能修改系统(装备系统联动) ### 5.1 两种修改路径 护符通过 `ICharmEffect.OnEquip/OnUnequip` 向 `SkillModifierRegistry` 注册修改器,`SkillManager.TryCast` 在每次施放前实时查询: | 修改路径 | 效果 | 数据结构 | |---------|------|----------| | **数值修改** | 改变技能的伤害/范围/消耗/冷却/VFX/动画 | `SkillNumericModifier` | | **插槽替换** | 将某形态某槽位的技能 SO 整体替换为另一个 | `SkillSlotOverride` | ### 5.2 SkillNumericModifier — 数值修改器 ```csharp [Serializable] public struct SkillNumericModifier { [Header("目标过滤(留空/Any = 对所有技能生效)")] [Tooltip("指定 skillId 则仅修改该技能;留空 = 匹配所有")] public string targetSkillId; // 如 "sky_soul_skill";留空 = 全部 public SkillResourceFilter resourceFilter; // Any / SoulOnly / SpiritOnly [Header("数值倍率(1.0 = 不变)")] public float damageMult; // 伤害倍率 public float rangeMult; // 范围倍率(AoE 半径 / 爆炸半径 / 障壁半径) public float costMult; // 消耗倍率(0.8 = 降低 20% 消耗) public float cooldownMult; // 冷却倍率(0.8 = 降低 20% 冷却) [Header("资产替换(null = 保留原始)")] public FeedbackPresetSO vfxOverride; // 替换特效预设 public ClipTransition animOverride; // 替换施法动画 /// 此修改器是否适用于给定技能 public bool Matches(FormSkillSO skill) { if (!string.IsNullOrEmpty(targetSkillId) && skill.skillId != targetSkillId) return false; if (resourceFilter == SkillResourceFilter.SoulOnly && skill.resourceType != SkillResourceType.SoulPower) return false; if (resourceFilter == SkillResourceFilter.SpiritOnly && skill.resourceType != SkillResourceType.SpiritPower) return false; return true; } public static SkillNumericModifier Identity => new() { damageMult = 1f, rangeMult = 1f, costMult = 1f, cooldownMult = 1f, }; } public enum SkillResourceFilter { Any, SoulOnly, SpiritOnly } ``` ### 5.3 SkillSlotOverride — 插槽替换 ```csharp [Serializable] public struct SkillSlotOverride { [Header("目标槽位")] [Tooltip("null = 对所有形态的该槽位生效")] public FormSO targetForm; // 指定形态;null = 所有形态 public SkillSlotType targetSlot; // Soul / Spirit1 / Spirit2 [Header("替换技能")] public FormSkillSO replacementSkill; // 替换后使用的技能 SO [Header("优先级")] [Tooltip("多个护符同时替换同一槽位时,priority 最高者胜出;相同则后装备的优先")] public int priority; // 默认 0;特殊护符可设置更高值 } ``` ### 5.4 SkillModifierRegistry — 聚合器(组件) 挂载于 Player GameObject,`EquipmentManager.Awake` 通过 `GetComponent` 获取并注入 `EquipmentContext`: ```csharp namespace BaseGames.Skills { public class SkillModifierRegistry : MonoBehaviour { readonly List _numericMods = new(); readonly List _slotOverrides = new(); // ── 注册 / 注销 ── public void AddNumericMod(SkillNumericModifier mod) => _numericMods.Add(mod); public void RemoveNumericMod(SkillNumericModifier mod) => _numericMods.Remove(mod); public void AddSlotOverride(SkillSlotOverride ov) => _slotOverrides.Add(ov); public void RemoveSlotOverride(SkillSlotOverride ov) => _slotOverrides.Remove(ov); /// /// 查询某形态某槽位的有效技能 SO。 /// 优先级规则:priority 高者胜;相同 priority 取列表最后注册; /// 精确匹配 targetForm 比 targetForm=null(通配)额外 +1 隐式优先级。 /// public FormSkillSO GetEffectiveSkill(FormSO form, SkillSlotType slot, FormSkillSO defaultSkill) { FormSkillSO result = defaultSkill; int bestScore = int.MinValue; // score = priority*2 + (精确匹配?1:0) foreach (var ov in _slotOverrides) { if (ov.targetSlot != slot) continue; if (ov.targetForm != null && ov.targetForm != form) continue; if (ov.replacementSkill == null) continue; // 精确指定 targetForm 比 null 通配多 +1 分 int score = ov.priority * 2 + (ov.targetForm != null ? 1 : 0); if (score >= bestScore) { bestScore = score; result = ov.replacementSkill; } } return result; } /// /// 计算技能 SO 经所有数值修改器叠加后的最终参数。 /// 倍率字段乘法叠加;资产替换字段取最后一个非 null 覆盖。 /// public EffectiveSkillParams GetEffectiveParams(FormSkillSO skill) { var p = EffectiveSkillParams.FromBase(skill); foreach (var mod in _numericMods) { if (!mod.Matches(skill)) continue; p.damageMult *= mod.damageMult; p.rangeMult *= mod.rangeMult; p.effectiveCost = Mathf.Max(1, Mathf.RoundToInt(p.effectiveCost * mod.costMult)); p.effectiveCooldown *= mod.cooldownMult; if (mod.vfxOverride != null) p.effectiveFeedback = mod.vfxOverride; if (mod.animOverride != null) p.effectiveAnimation = mod.animOverride; } return p; } } } ``` ### 5.5 ICharmEffect 实现 以下两个效果类在 `17_EquipmentSystem.md §4` 中声明,此处给出完整实现参考: ```csharp // ── 数值修改护符 ── [Serializable] public class SkillNumericModifierEffect : ICharmEffect { [Tooltip("数值修改配置(目标技能 + 各倍率 + 可选资产替换)")] public SkillNumericModifier modifier; public void OnEquip(EquipmentContext ctx) => ctx.SkillMods.AddNumericMod(modifier); public void OnUnequip(EquipmentContext ctx) => ctx.SkillMods.RemoveNumericMod(modifier); public string GetEffectDescription() { var parts = new List(); if (!Mathf.Approximately(modifier.damageMult, 1f)) parts.Add($"伤害 ×{modifier.damageMult:0.##}"); if (!Mathf.Approximately(modifier.rangeMult, 1f)) parts.Add($"范围 ×{modifier.rangeMult:0.##}"); if (!Mathf.Approximately(modifier.costMult, 1f)) parts.Add($"消耗 ×{modifier.costMult:0.##}"); if (!Mathf.Approximately(modifier.cooldownMult, 1f)) parts.Add($"冷却 ×{modifier.cooldownMult:0.##}"); if (modifier.vfxOverride != null) parts.Add("特效替换"); if (modifier.animOverride != null) parts.Add("动画替换"); string target = string.IsNullOrEmpty(modifier.targetSkillId) ? $"所有{modifier.resourceFilter}技能" : modifier.targetSkillId; return $"{target}:{string.Join(" / ", parts)}"; } } // ── 插槽替换护符 ── [Serializable] public class SkillSlotOverrideEffect : ICharmEffect { [Tooltip("插槽替换配置(目标形态 + 槽位 + 替换技能 + 优先级)")] public SkillSlotOverride overrideData; public void OnEquip(EquipmentContext ctx) => ctx.SkillMods.AddSlotOverride(overrideData); public void OnUnequip(EquipmentContext ctx) => ctx.SkillMods.RemoveSlotOverride(overrideData); public string GetEffectDescription() { string formStr = overrideData.targetForm != null ? overrideData.targetForm.name : "所有形态"; string skillName = overrideData.replacementSkill != null ? overrideData.replacementSkill.displayName : "null"; return $"{formStr} {overrideData.targetSlot} → [{skillName}]"; } } ``` ### 5.6 rangeMult 应用规则 各 `SkillEffectType` 的 `rangeMult` 含义: | effectType | rangeMult 作用对象 | |-----------|-------------------| | `MeleeAoE` | AoE 碰撞体宽度 / 高度 | | `BarrierAura` | 护体漩涡半径 | | `DelayedExplosion` | `explosionRadius` | | `Projectile` | 弹射物存活距离上限 | | `GroundDive` | 地下移动速度(间接扩大覆盖范围)| | `ShadowDecoy` | 灵体感应触发半径 | | `DragonKick` / `WraithDash` | 位移距离(`dashForce`)| 执行函数中应用方式示例(MeleeAoE): ```csharp void ExecuteMeleeAoE(FormSkillSO skill, EffectiveSkillParams p) { // 基础 AoE box size 由 skill 的 collider 配置定义,rangeMult 等比缩放 Vector2 baseSize = skill.aoeBoxSize; // (width, height) Vector2 finalSize = baseSize * p.rangeMult; // DamageSourceSO 伤害乘以 damageMult var dmgInfo = skill.damageSource.BuildDamageInfo(p.damageMult); // 物理查询 + 施加伤害 var hits = Physics2D.OverlapBoxAll(aoeCenter, finalSize, 0f, enemyLayer); foreach (var h in hits) if (h.TryGetComponent(out var hb)) hb.ReceiveDamage(dmgInfo); } ``` --- ## 6. 技能初始化与 FormSO 绑定 技能 SO 通过 `FormSO` 持有,在 Inspector 中配置(见 `03_PlayerSystem §6`)。`SkillManager` 不持有技能 SO 引用,始终从 `FormController.CurrentForm` 动态读取,形态切换后自动生效: ``` FormController.SwitchForm(newForm) → FormController.CurrentForm = newForm → SkillManager 下一次技能输入时,自动读取 newForm.soulSkill / spiritSkill1 / spiritSkill2 → 无需主动通知 SkillManager ``` **各形态技能 SO 资产路径**(建议目录结构): ``` Assets/ └── Skills/ ├── SkyForm/ │ ├── Sky_SoulSkill_LieKongZhang.asset │ ├── Sky_SpiritSkill1_XuanLingJi.asset │ └── Sky_SpiritSkill2_LingZongDan.asset ├── EarthForm/ │ ├── Earth_SoulSkill_DiXingShu.asset │ ├── Earth_SpiritSkill1_BaShanQuan.asset │ └── Earth_SpiritSkill2_DengLongCu.asset └── DeathForm/ ├── Death_SoulSkill_TaiXuZhan.asset ├── Death_SpiritSkill1_CanYinShu.asset └── Death_SpiritSkill2_HunYuanBao.asset ``` --- ## 7. SaveData 扩展 技能由 `FormSO` 固定绑定,无需存储解锁状态(无解锁机制)。仅持久化魄元当前值(灵力战斗结束归零,不持久化): ```json "skills": { "currentSpiritPower": 75, "currentSpringCharges": 3 } ``` --- ## 8. UI 集成 ### 技能与资源 HUD ``` Canvas_HUD └── ResourceBar(左下角或角色头顶) ├── SoulPowerBar 灵力条(近战命中积累,橙色格子) ├── SpiritPowerBar 魄元条(自动恢复,蓝色渐变 + 恢复动画) └── FormSkillIcons 当前形态技能图标(右侧) ├── SoulSkillIcon + CooldownRing(冷却为 0 时不显示) ├── SpiritSkill1Icon + CooldownRing └── SpiritSkill2Icon + CooldownRing ``` - `FormController.OnFormChanged` → 切换图标组(天魂/地魂/命魂各自有独立图标集) - `PlayerStats.OnSoulPowerChanged` → 更新 `SoulPowerBar.fillAmount` - `PlayerStats.OnSpiritPowerChanged` → 更新 `SpiritPowerBar.fillAmount` --- ## 9. 事件频道 | 频道资产 | 类型 | 发布方 | 订阅方 | |---------|------|--------|--------| | `OnSkillCast.asset` | `VoidEventChannelSO` | `SkillManager` | `PlayerFeedback`(技能音效)| | `OnSkillFailed.asset` | `VoidEventChannelSO` | `SkillManager` | `PlayerFeedback`(资源不足提示音)| | `OnFormChanged.asset` | `FormEventChannelSO` | `FormController` | `SkillHUD`(切换图标)、`PlayerCombat`(重置连击)| | `OnSoulPowerChanged.asset` | `IntEventChannelSO` | `PlayerStats` | `SoulPowerBar`(UI 更新)| | `OnSpiritPowerChanged.asset` | `IntEventChannelSO` | `PlayerStats` | `SpiritPowerBar`(UI 更新)| --- ## 10. 编辑器工具 ### FormSkillSO 预览 Inspector - 技能 Inspector 底部显示**模拟执行**区域 - 下拉选择形态 → 自动显示关联 FormSO 的 3 个技能槽 - 填入当前资源值 → 点击 \[模拟施放\] → 控制台输出技能效果描述和资源扣除 - 修改器预览:拖入 SkillModifierSnapshot 资产 → 实时预览最终消耗/伤害参数