# 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 { /// /// 武器数据 SO。封装一种攻击模组的全部属性。 /// 每种形态默认绑定一把武器;护符可在运行时替换。 /// [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 配置")] /// 零向量 = 使用 PlayerCombat 中的默认尺寸 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; /// 当前激活的武器 SO。PlayerCombat 从这里读取攻击数据。 public WeaponSO ActiveWeapon { get; private set; } /// 武器切换时广播(PlayerCombat / VFX 监听) public event Action OnWeaponChanged; // 护符注入的武器覆盖:Key = FormSO.formId,Value = 替换武器 readonly Dictionary _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 调用)─────────────── /// 为指定形态设置武器覆盖。若 formId 为空,覆盖所有形态。 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); } /// 移除指定形态的武器覆盖,恢复默认武器。 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 / 暂停时),便于运行时调试武器切换状态。