chore: initial commit
This commit is contained in:
469
Docs/Design/53_WeaponSystem.md
Normal file
469
Docs/Design/53_WeaponSystem.md
Normal file
@@ -0,0 +1,469 @@
|
||||
# 53 · 武器系统
|
||||
|
||||
> **命名空间** `BaseGames.Combat`
|
||||
> **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md)
|
||||
> **依赖** `BaseGames.Player`(FormController / PlayerCombat)· `BaseGames.Equipment`(EquipmentManager)
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [系统总览](#1-系统总览)
|
||||
2. [WeaponSO — 武器数据](#2-weaponso--武器数据)
|
||||
3. [WeaponManager — 武器切换管理](#3-weaponmanager--武器切换管理)
|
||||
4. [形态与武器绑定](#4-形态与武器绑定)
|
||||
5. [PlayerCombat 适配](#5-playercombat-适配)
|
||||
6. [护符武器覆盖(WeaponOverrideEffect)](#6-护符武器覆盖weaponoverrideeffect)
|
||||
7. [三形态武器规格表](#7-三形态武器规格表)
|
||||
8. [武器 VFX 与音效](#8-武器-vfx-与音效)
|
||||
9. [SaveData 集成](#9-savedata-集成)
|
||||
10. [编辑器友好设计](#10-编辑器友好设计)
|
||||
|
||||
---
|
||||
|
||||
## 1. 系统总览
|
||||
|
||||
**武器模组(Weapon)** 封装了角色在某一形态下的全部近战攻击数据:连击动画片段、每段伤害来源 SO、HitBox 尺寸偏移、武器特效配置。
|
||||
|
||||
三种形态(天魂 / 地魂 / 命魂)的攻击手感、动作组、HitBox 形状完全不同,因此武器以 **ScriptableObject** 形式独立于形态,通过 `FormSO.defaultWeapon` 绑定,并由 `WeaponManager` 在形态切换时自动激活。
|
||||
|
||||
```
|
||||
武器系统职责:
|
||||
├─ WeaponSO → 武器数据 SO(动画片段 + 伤害来源 + HitBox + VFX)
|
||||
├─ WeaponManager → 运行时武器激活、形态联动切换、护符覆盖管理
|
||||
├─ FormSO → defaultWeapon 字段(每形态默认武器,替代旧 FormAttackConfig)
|
||||
└─ WeaponOverrideEffect → ICharmEffect 实现,允许护符替换特定形态的武器
|
||||
```
|
||||
|
||||
**与旧版 FormAttackConfig 的关系**:
|
||||
`FormAttackConfig` 内联结构体已**废弃**,其所有字段(动画片段 + hitBoxSizeOverride)迁移进 `WeaponSO`,并新增每段伤害来源引用与武器特效配置。
|
||||
|
||||
---
|
||||
|
||||
## 2. WeaponSO — 武器数据
|
||||
|
||||
```csharp
|
||||
namespace BaseGames.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// 武器数据 SO。封装一种攻击模组的全部属性。
|
||||
/// 每种形态默认绑定一把武器;护符可在运行时替换。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Combat/Weapon")]
|
||||
public class WeaponSO : ScriptableObject
|
||||
{
|
||||
[Header("基础信息")]
|
||||
public string weaponId; // 全局唯一 ID,如 "Weapon_SkyBlade"
|
||||
public string displayName; // "天裂刃"
|
||||
public Sprite icon; // 武器图标(装备 UI 可选展示)
|
||||
public WeaponType weaponType; // 武器类型(见枚举)
|
||||
|
||||
[Header("连击动画")]
|
||||
public ClipTransition attack1Clip;
|
||||
public ClipTransition attack2Clip;
|
||||
public ClipTransition attack3Clip;
|
||||
public ClipTransition airAttackClip;
|
||||
public ClipTransition upAttackClip;
|
||||
public ClipTransition downAttackClip;
|
||||
|
||||
[Header("伤害来源(每段独立)")]
|
||||
public DamageSourceSO attack1Source;
|
||||
public DamageSourceSO attack2Source;
|
||||
public DamageSourceSO attack3Source;
|
||||
public DamageSourceSO airAttackSource;
|
||||
public DamageSourceSO upAttackSource;
|
||||
public DamageSourceSO downAttackSource;
|
||||
|
||||
[Header("HitBox 配置")]
|
||||
/// <summary>零向量 = 使用 PlayerCombat 中的默认尺寸</summary>
|
||||
public Vector2 hitBoxSizeOverride;
|
||||
public Vector2 hitBoxOffsetOverride;
|
||||
|
||||
[Header("武器特效")]
|
||||
public WeaponVFXConfig vfxConfig;
|
||||
}
|
||||
|
||||
public enum WeaponType
|
||||
{
|
||||
SkyBlade, // 天魂:裂空刃(高频轻击)
|
||||
EarthHammer, // 地魂:地震锤(低频重击,范围大)
|
||||
LifeScythe, // 命魂:命镰(穿透、直线斩)
|
||||
Custom, // 护符替换或未来扩展武器
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class WeaponVFXConfig
|
||||
{
|
||||
[Tooltip("切换到此武器时播放的特效(形态切换音效/粒子)")]
|
||||
public FeedbackPresetSO onEquipFeedback;
|
||||
|
||||
[Tooltip("武器挥斩拖尾 Prefab(无 = 不显示拖尾)")]
|
||||
public GameObject weaponTrailPrefab;
|
||||
|
||||
public Color trailColor = Color.white;
|
||||
|
||||
[Tooltip("攻击命中时的特效类型(覆盖 DamageSourceSO.HitFxType,null = 不覆盖)")]
|
||||
public HitFxType? hitFxOverride;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**资产存放路径**:`Assets/ScriptableObjects/Combat/Weapons/`
|
||||
**命名规范**:`Weapon_{Name}.asset`,如 `Weapon_SkyBlade.asset`
|
||||
|
||||
---
|
||||
|
||||
## 3. WeaponManager — 武器切换管理
|
||||
|
||||
`WeaponManager` 常驻 Player GameObject,监听 `FormController.OnFormChanged` 并自动切换活动武器。支持护符 override(见 §6)。
|
||||
|
||||
```csharp
|
||||
namespace BaseGames.Combat
|
||||
{
|
||||
public class WeaponManager : MonoBehaviour
|
||||
{
|
||||
[SerializeField] FormController _formController;
|
||||
|
||||
/// <summary>当前激活的武器 SO。PlayerCombat 从这里读取攻击数据。</summary>
|
||||
public WeaponSO ActiveWeapon { get; private set; }
|
||||
|
||||
/// <summary>武器切换时广播(PlayerCombat / VFX 监听)</summary>
|
||||
public event Action<WeaponSO> OnWeaponChanged;
|
||||
|
||||
// 护符注入的武器覆盖:Key = FormSO.formId,Value = 替换武器
|
||||
readonly Dictionary<string, WeaponSO> _overrides = new();
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// 初始化:激活第一个形态对应的武器
|
||||
if (_formController.CurrentForm != null)
|
||||
ApplyWeapon(_formController.CurrentForm);
|
||||
}
|
||||
|
||||
void OnEnable() => _formController.OnFormChanged += HandleFormChanged;
|
||||
void OnDisable() => _formController.OnFormChanged -= HandleFormChanged;
|
||||
|
||||
void HandleFormChanged() => ApplyWeapon(_formController.CurrentForm);
|
||||
|
||||
void ApplyWeapon(FormSO form)
|
||||
{
|
||||
// 护符 override 优先;若无 override,使用形态默认武器
|
||||
WeaponSO next = _overrides.TryGetValue(form.formId, out var ov)
|
||||
? ov
|
||||
: form.defaultWeapon;
|
||||
|
||||
if (next == ActiveWeapon) return;
|
||||
ActiveWeapon = next;
|
||||
OnWeaponChanged?.Invoke(next);
|
||||
next?.vfxConfig.onEquipFeedback?.PlayFeedbacks(gameObject);
|
||||
}
|
||||
|
||||
// ─────────────── 护符 Override API(由 WeaponOverrideEffect 调用)───────────────
|
||||
|
||||
/// <summary>为指定形态设置武器覆盖。若 formId 为空,覆盖所有形态。</summary>
|
||||
public void SetOverride(string formId, WeaponSO weapon)
|
||||
{
|
||||
if (string.IsNullOrEmpty(formId))
|
||||
{
|
||||
// 覆盖所有形态
|
||||
foreach (var form in _formController.AllForms)
|
||||
_overrides[form.formId] = weapon;
|
||||
}
|
||||
else
|
||||
{
|
||||
_overrides[formId] = weapon;
|
||||
}
|
||||
// 若当前形态受影响,立即刷新
|
||||
ApplyWeapon(_formController.CurrentForm);
|
||||
}
|
||||
|
||||
/// <summary>移除指定形态的武器覆盖,恢复默认武器。</summary>
|
||||
public void ClearOverride(string formId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(formId))
|
||||
{
|
||||
foreach (var form in _formController.AllForms)
|
||||
_overrides.Remove(form.formId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_overrides.Remove(formId);
|
||||
}
|
||||
ApplyWeapon(_formController.CurrentForm);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> `FormController.AllForms` 需新增只读属性(返回 `_config.forms`),供 `WeaponManager` 在全形态 override 时枚举。
|
||||
|
||||
---
|
||||
|
||||
## 4. 形态与武器绑定
|
||||
|
||||
`FormSO` 的 `attackConfig: FormAttackConfig`(旧内联结构体)**已替换为** `defaultWeapon: WeaponSO`:
|
||||
|
||||
```csharp
|
||||
[CreateAssetMenu(menuName = "Player/Form")]
|
||||
public class FormSO : ScriptableObject
|
||||
{
|
||||
[Header("形态标识")]
|
||||
public string formId;
|
||||
public string displayName;
|
||||
public Sprite formIcon;
|
||||
public Color formAccentColor;
|
||||
|
||||
[Header("默认武器")]
|
||||
public WeaponSO defaultWeapon; // ← 替代旧 FormAttackConfig
|
||||
|
||||
[Header("技能配置")]
|
||||
public FormSkillSO soulSkill;
|
||||
public FormSkillSO spiritSkill1;
|
||||
public FormSkillSO spiritSkill2;
|
||||
}
|
||||
```
|
||||
|
||||
形态切换流程(完整时序):
|
||||
|
||||
```
|
||||
玩家按形态切换键
|
||||
│
|
||||
▼
|
||||
FormController.SwitchForm(FormSO newForm)
|
||||
│ ① CurrentForm = newForm
|
||||
│ ② _onFormChanged.Raise(newForm)
|
||||
│ ③ OnFormChanged?.Invoke()
|
||||
│
|
||||
├──► WeaponManager.HandleFormChanged()
|
||||
│ → ApplyWeapon(newForm)
|
||||
│ → ActiveWeapon = overrides[formId] ?? newForm.defaultWeapon
|
||||
│ → OnWeaponChanged?.Invoke(newWeapon)
|
||||
│
|
||||
├──► PlayerCombat.OnWeaponChanged(newWeapon)
|
||||
│ → 刷新动画片段引用
|
||||
│ → 刷新 HitBox DamageSource 引用
|
||||
│ → ResetCombo()
|
||||
│
|
||||
└──► VFXManager(拖尾颜色换色等)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. PlayerCombat 适配
|
||||
|
||||
`PlayerCombat` 从 `WeaponManager.ActiveWeapon` 读取当前武器数据,替代旧的 `FormController.CurrentForm.AttackConfig`:
|
||||
|
||||
```csharp
|
||||
public class PlayerCombat : MonoBehaviour
|
||||
{
|
||||
[SerializeField] WeaponManager _weaponManager;
|
||||
// ... 其余字段不变 ...
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
_weaponManager.OnWeaponChanged += RefreshWeaponData;
|
||||
RefreshWeaponData(_weaponManager.ActiveWeapon);
|
||||
}
|
||||
|
||||
void OnDisable() => _weaponManager.OnWeaponChanged -= RefreshWeaponData;
|
||||
|
||||
void RefreshWeaponData(WeaponSO weapon)
|
||||
{
|
||||
if (weapon == null) return;
|
||||
// 将连击动画片段缓存到本地
|
||||
_attack1Clip = weapon.attack1Clip;
|
||||
_attack2Clip = weapon.attack2Clip;
|
||||
_attack3Clip = weapon.attack3Clip;
|
||||
_airAttackClip = weapon.airAttackClip;
|
||||
_upAttackClip = weapon.upAttackClip;
|
||||
_downAttackClip = weapon.downAttackClip;
|
||||
// 刷新 HitBox DamageSource
|
||||
_hitBoxGround.SetDamageSource(weapon.attack1Source); // 初始段;各段在 AttackState 切换时更新
|
||||
// 刷新 HitBox 尺寸
|
||||
if (weapon.hitBoxSizeOverride != Vector2.zero)
|
||||
_hitBoxGround.SetSizeOverride(weapon.hitBoxSizeOverride, weapon.hitBoxOffsetOverride);
|
||||
else
|
||||
_hitBoxGround.ClearSizeOverride();
|
||||
// 重置连击
|
||||
ResetCombo();
|
||||
}
|
||||
|
||||
// AttackState 每段攻击开始时调用,切换当前段 DamageSource
|
||||
public void SetComboSegmentSource(ComboState segment)
|
||||
{
|
||||
WeaponSO w = _weaponManager.ActiveWeapon;
|
||||
DamageSourceSO src = segment switch
|
||||
{
|
||||
ComboState.Attack1 => w.attack1Source,
|
||||
ComboState.Attack2 => w.attack2Source,
|
||||
ComboState.Attack3 => w.attack3Source,
|
||||
ComboState.AirAttack => w.airAttackSource,
|
||||
ComboState.UpAttack => w.upAttackSource,
|
||||
ComboState.DownAttack => w.downAttackSource,
|
||||
_ => null,
|
||||
};
|
||||
if (src != null) _hitBoxGround.SetDamageSource(src);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 护符武器覆盖(WeaponOverrideEffect)
|
||||
|
||||
护符可通过 `WeaponOverrideEffect` 将某形态的武器整体替换为另一把 `WeaponSO`:
|
||||
|
||||
```csharp
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
[Serializable]
|
||||
public class WeaponOverrideEffect : ICharmEffect
|
||||
{
|
||||
[Tooltip("目标形态 ID(留空 = 所有形态)")]
|
||||
public string targetFormId;
|
||||
|
||||
[Tooltip("替换武器 SO")]
|
||||
public WeaponSO replacementWeapon;
|
||||
|
||||
public void OnEquip(EquipmentContext ctx)
|
||||
=> ctx.WeaponMgr.SetOverride(targetFormId, replacementWeapon);
|
||||
|
||||
public void OnUnequip(EquipmentContext ctx)
|
||||
=> ctx.WeaponMgr.ClearOverride(targetFormId);
|
||||
|
||||
public string GetEffectDescription()
|
||||
{
|
||||
string formStr = string.IsNullOrEmpty(targetFormId) ? "所有形态" : targetFormId;
|
||||
string wName = replacementWeapon != null ? replacementWeapon.displayName : "null";
|
||||
return $"{formStr}的武器替换为 [{wName}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`EquipmentContext` 需新增 `WeaponManager WeaponMgr` 字段,`EquipmentManager.Awake()` 中同步初始化(见 [17_EquipmentSystem §3](./17_EquipmentSystem.md#3-icharmeffect--效果接口))。
|
||||
|
||||
**配置示例**:
|
||||
|
||||
```
|
||||
// 护符:【命魂刃灵符】(将命魂形态的武器替换为高速短刃)
|
||||
targetFormId = "LifeForm"
|
||||
replacementWeapon = Weapon_ShadowDagger
|
||||
|
||||
// 护符:【混沌铸兵符】(所有形态均使用地魂锤)
|
||||
targetFormId = "" // 空 = 全部形态
|
||||
replacementWeapon = Weapon_EarthHammer
|
||||
```
|
||||
|
||||
**冲突规则**:若多个护符同时对同一形态设置武器覆盖,**后装备的护符生效**(`Dictionary` 键覆盖语义)。与 `SkillSlotOverride` 的优先级机制独立,互不影响。
|
||||
|
||||
---
|
||||
|
||||
## 7. 三形态武器规格表
|
||||
|
||||
| 形态 | 武器 ID | WeaponType | 动作风格 | Attack3 特征 | HitBox 大小 |
|
||||
|------|---------|-----------|--------|------------|-----------|
|
||||
| 天魂 | `Weapon_SkyBlade` | `SkyBlade` | 高频轻击,水平连斩 | 前冲斩击(带位移) | 标准(无覆盖)|
|
||||
| 地魂 | `Weapon_EarthHammer` | `EarthHammer` | 低频重击,下砸范围 | 地震踩击(AOE) | 宽 ×1.4,高 ×0.8 |
|
||||
| 命魂 | `Weapon_LifeScythe` | `LifeScythe` | 穿透型直线斩 | 长距离水平穿刺 | 长 ×1.8,高 ×0.6 |
|
||||
|
||||
### 伤害来源资产路径
|
||||
|
||||
```
|
||||
Assets/ScriptableObjects/Combat/DamageSources/Player/
|
||||
├─ SkyBlade/
|
||||
│ ├─ DS_Sky_Attack1.asset (BaseDamage 4, ×1.0)
|
||||
│ ├─ DS_Sky_Attack2.asset (BaseDamage 4, ×1.0)
|
||||
│ ├─ DS_Sky_Attack3.asset (BaseDamage 6, ×1.5, KnockbackForce 10)
|
||||
│ ├─ DS_Sky_Air.asset (BaseDamage 4, ×1.0)
|
||||
│ ├─ DS_Sky_Up.asset (BaseDamage 5, ×1.0)
|
||||
│ └─ DS_Sky_Down.asset (BaseDamage 5, ×1.0, CanPogo)
|
||||
├─ EarthHammer/
|
||||
│ ├─ DS_Earth_Attack1.asset (BaseDamage 7, ×1.0, HitStun 0.4s)
|
||||
│ ├─ DS_Earth_Attack2.asset (BaseDamage 7, ×1.0)
|
||||
│ ├─ DS_Earth_Attack3.asset (BaseDamage 12, ×2.0, AOE, HitStun 0.6s)
|
||||
│ ├─ DS_Earth_Air.asset (BaseDamage 7, ×1.0)
|
||||
│ ├─ DS_Earth_Up.asset (BaseDamage 8, ×1.0)
|
||||
│ └─ DS_Earth_Down.asset (BaseDamage 8, ×1.0, CanPogo)
|
||||
└─ LifeScythe/
|
||||
├─ DS_Life_Attack1.asset (BaseDamage 5, ×1.0, IsProjectile-like 穿透)
|
||||
├─ DS_Life_Attack2.asset (BaseDamage 5, ×1.0)
|
||||
├─ DS_Life_Attack3.asset (BaseDamage 9, ×1.8, 长范围)
|
||||
├─ DS_Life_Air.asset (BaseDamage 5, ×1.0)
|
||||
├─ DS_Life_Up.asset (BaseDamage 6, ×1.0)
|
||||
└─ DS_Life_Down.asset (BaseDamage 6, ×1.0, CanPogo)
|
||||
```
|
||||
|
||||
> **平衡参考**:地魂单击伤害最高但攻速最慢;天魂伤害最低但攻速最快;命魂居中,胜在范围。三形态 DPS(攻速×伤害)接近。
|
||||
|
||||
---
|
||||
|
||||
## 8. 武器 VFX 与音效
|
||||
|
||||
### 武器拖尾
|
||||
|
||||
- `WeaponVFXConfig.weaponTrailPrefab`:挂在武器锚点(`WeaponTrailAnchor` 子节点)下,形态切换时重新实例化
|
||||
- `trailColor`:与 `FormSO.formAccentColor` 保持一致(天魂青色 / 地魂橙色 / 命魂紫色)
|
||||
- 拖尾在攻击动画帧内激活,`DisableHitBox` AnimationEvent 同时关闭
|
||||
|
||||
### 切换音效
|
||||
|
||||
- `onEquipFeedback`:一个 `FeedbackPresetSO`,包含刀鸣/切音 SFX + 短暂 MMF 屏幕冲击
|
||||
- 在 `WeaponManager.ApplyWeapon()` 末尾调用 `PlayFeedbacks()`,切换形态时自动触发
|
||||
|
||||
### 命中特效优先级
|
||||
|
||||
```
|
||||
vfxConfig.hitFxOverride != null
|
||||
→ 使用 WeaponSO 指定的 HitFxType(覆盖 DamageSourceSO.HitFxType)
|
||||
else
|
||||
→ 使用 DamageSourceSO.HitFxType
|
||||
```
|
||||
|
||||
HitFxType 由 `HitFXSpawner`(见 [41_VFXArchitecture §3](./41_VFXArchitecture.md))处理。
|
||||
|
||||
---
|
||||
|
||||
## 9. SaveData 集成
|
||||
|
||||
武器系统**无需单独存档字段**:
|
||||
|
||||
- 当前武器由 `FormController.CurrentForm` + 装备的护符组合决定
|
||||
- `FormController.CurrentForm.formId` 已写入 SaveData(见 [31_SaveDataSchema_Unified §4](./31_SaveDataSchema_Unified.md))
|
||||
- 护符 override 在存档加载时随护符 `OnEquip` 回调自动重建
|
||||
- `WeaponManager` 在 `Awake` 时按当前形态初始化活动武器,SavePoint 恢复后无需额外逻辑
|
||||
|
||||
---
|
||||
|
||||
## 10. 编辑器友好设计
|
||||
|
||||
### WeaponSO 自定义 Inspector
|
||||
|
||||
```
|
||||
┌─ Weapon_SkyBlade ─────────────────────────────────────────────┐
|
||||
│ [图标] 天裂刃 Type: SkyBlade │
|
||||
│ ────────────────── 连击动画 ──────────────────────── │
|
||||
│ Attack1: [Sky_A1] Attack2: [Sky_A2] Attack3: [Sky_A3] │
|
||||
│ Air: [Sky_Air] Up: [Sky_Up] Down: [Sky_Dn] │
|
||||
│ ────────────────── 伤害来源 ──────────────────────── │
|
||||
│ A1: DS_Sky_Attack1 A2: DS_Sky_Attack2 A3: DS_Sky_Attack3 │
|
||||
│ Air: DS_Sky_Air Up: DS_Sky_Up Down: DS_Sky_Down │
|
||||
│ ────────────────── HitBox ────────────────────────── │
|
||||
│ Size Override: (0, 0) ← 零向量 = 使用默认尺寸 │
|
||||
│ ────────────────── VFX ───────────────────────────── │
|
||||
│ OnEquip Feedback: [FB_SkyEquip] │
|
||||
│ Trail Prefab: [SkyBlade_Trail] Trail Color: ████ (青色) │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Weapon-Form 绑定验证
|
||||
|
||||
`FormConfigSO` Custom Inspector 在发现 `FormSO.defaultWeapon == null` 时显示红色警告:
|
||||
|
||||
```
|
||||
⚠ FormSO "SkyForm" defaultWeapon 未绑定!攻击无法正常工作。
|
||||
```
|
||||
|
||||
### WeaponManager Scene Gizmo
|
||||
|
||||
在 Scene 视图的玩家头顶显示 `ActiveWeapon.weaponId`(仅 Play Mode / 暂停时),便于运行时调试武器切换状态。
|
||||
Reference in New Issue
Block a user