chore: initial commit
This commit is contained in:
764
Docs/Design/21_SpellSystem.md
Normal file
764
Docs/Design/21_SpellSystem.md
Normal file
@@ -0,0 +1,764 @@
|
||||
# 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<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);
|
||||
// 特效预设:护符可替换为不同 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; // 替换施法动画
|
||||
|
||||
/// <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 — 插槽替换
|
||||
|
||||
```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<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` 中声明,此处给出完整实现参考:
|
||||
|
||||
```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<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 应用规则
|
||||
|
||||
各 `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<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` 固定绑定,无需存储解锁状态(无解锁机制)。仅持久化魄元当前值(灵力战斗结束归零,不持久化):
|
||||
|
||||
```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 资产 → 实时预览最终消耗/伤害参数
|
||||
Reference in New Issue
Block a user