Files
zeling_v2/Docs/Design/21_SpellSystem.md
2026-05-08 11:04:00 +08:00

29 KiB
Raw Permalink Blame History

21 · 形态技能系统Form Skill System

命名空间 BaseGames.Skills
所属文档集 ← 返回索引 · 总览
依赖 BaseGames.Combat · BaseGames.Player · BaseGames.Equipment · BaseGames.Core.Events · BaseGames.Animation


目录

  1. 系统总览
  2. FormSkillSO — 技能数据
  3. SkillManager — 技能执行器
  4. 九种形态技能实现
  5. 技能修改系统(装备系统联动)
  6. 技能初始化与 FormSO 绑定
  7. SaveData 扩展
  8. UI 集成
  9. 事件频道
  10. 编辑器工具

1. 系统总览

技能系统管理形态专属主动技能。每种形态(天魂/地魂/命魂)各拥有 3 个技能槽,消耗独立资源触发:

技能类型 Input Action 消耗资源 说明
魂技能SoulSkill SoulSkill 灵力SoulPower 每种形态各 1 个,高爆发或特殊形态能力
魄技能 1SpiritSkill1 SpiritSkill1 魄元SpiritPower 每种形态各 1 个,自动恢复资源驱动
魄技能 2SpiritSkill2 SpiritSkill2 魄元SpiritPower 每种形态各 1 个,自动恢复资源驱动
技能系统职责:
  ├─ FormSkillSO           → 技能数据(消耗/动画/效果/VFX/SFX
  ├─ SkillManager          → 玩家上的技能执行器(校验资源、执行效果)
  ├─ FormSO                → 持有 3 个技能 SO 引用(见 03_PlayerSystem §6
  ├─ SkillModifierSnapshot → 装备魅力修改器快照
  └─ SkillHUD              → 技能图标 + 资源条(灵力/魄元)

与资源系统的关系

  • 魂技能通过 PlayerStats.ConsumeSoulPower(cost) 扣除灵力,不自动恢复
  • 魄技能通过 PlayerStats.ConsumeSpiritPower(cost) 扣除魄元,魄元随时间自动恢复

2. FormSkillSO — 技能数据

[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 同一 GameObjectFormController.CurrentForm 读取当前形态的技能 SO

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<string, float> _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);
            // 特效预设:护符可替换为不同 VFXnull 则回退到原始预设
            (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 — 技能槽枚举

public enum SkillSlotType
{
    SoulSkill,     // 魂技能(灵力)
    SpiritSkill1,  // 魄技能 1
    SpiritSkill2,  // 魄技能 2
}

EffectiveSkillParams — 运行时最终参数

所有数值修改器叠加后生成的快照,由 SkillModifierRegistry.GetEffectiveParams 计算,传入 CastRoutineExecuteEffect

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/OnUnequipSkillModifierRegistry 注册修改器,SkillManager.TryCast 在每次施放前实时查询:

修改路径 效果 数据结构
数值修改 改变技能的伤害/范围/消耗/冷却/VFX/动画 SkillNumericModifier
插槽替换 将某形态某槽位的技能 SO 整体替换为另一个 SkillSlotOverride

5.2 SkillNumericModifier — 数值修改器

[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;  // 替换施法动画

    /// <summary>此修改器是否适用于给定技能</summary>
    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 — 插槽替换

[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 GameObjectEquipmentManager.Awake 通过 GetComponent 获取并注入 EquipmentContext

namespace BaseGames.Skills
{
    public class SkillModifierRegistry : MonoBehaviour
    {
        readonly List<SkillNumericModifier> _numericMods   = new();
        readonly List<SkillSlotOverride>    _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);

        /// <summary>
        /// 查询某形态某槽位的有效技能 SO。
        /// 优先级规则priority 高者胜;相同 priority 取列表最后注册;
        /// 精确匹配 targetForm 比 targetForm=null通配额外 +1 隐式优先级。
        /// </summary>
        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;
        }

        /// <summary>
        /// 计算技能 SO 经所有数值修改器叠加后的最终参数。
        /// 倍率字段乘法叠加;资产替换字段取最后一个非 null 覆盖。
        /// </summary>
        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 中声明,此处给出完整实现参考:

// ── 数值修改护符 ──
[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<string>();
        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 应用规则

SkillEffectTyperangeMult 含义:

effectType rangeMult 作用对象
MeleeAoE AoE 碰撞体宽度 / 高度
BarrierAura 护体漩涡半径
DelayedExplosion explosionRadius
Projectile 弹射物存活距离上限
GroundDive 地下移动速度(间接扩大覆盖范围)
ShadowDecoy 灵体感应触发半径
DragonKick / WraithDash 位移距离(dashForce

执行函数中应用方式示例MeleeAoE

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<HurtBox>(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 固定绑定,无需存储解锁状态(无解锁机制)。仅持久化魄元当前值(灵力战斗结束归零,不持久化):

"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 SoulPowerBarUI 更新)
OnSpiritPowerChanged.asset IntEventChannelSO PlayerStats SpiritPowerBarUI 更新)

10. 编辑器工具

FormSkillSO 预览 Inspector

  • 技能 Inspector 底部显示模拟执行区域
  • 下拉选择形态 → 自动显示关联 FormSO 的 3 个技能槽
  • 填入当前资源值 → 点击 [模拟施放] → 控制台输出技能效果描述和资源扣除
  • 修改器预览:拖入 SkillModifierSnapshot 资产 → 实时预览最终消耗/伤害参数