18 KiB
53 · 武器系统
命名空间
BaseGames.Combat
所属文档集 ← 返回索引 · 总览
依赖BaseGames.Player(FormController / PlayerCombat)·BaseGames.Equipment(EquipmentManager)
目录
- 系统总览
- WeaponSO — 武器数据
- WeaponManager — 武器切换管理
- 形态与武器绑定
- PlayerCombat 适配
- 护符武器覆盖(WeaponOverrideEffect)
- 三形态武器规格表
- 武器 VFX 与音效
- SaveData 集成
- 编辑器友好设计
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 — 武器数据
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)。
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:
[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:
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:
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)。
配置示例:
// 护符:【命魂刃灵符】(将命魂形态的武器替换为高速短刃)
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保持一致(天魂青色 / 地魂橙色 / 命魂紫色)- 拖尾在攻击动画帧内激活,
DisableHitBoxAnimationEvent 同时关闭
切换音效
onEquipFeedback:一个FeedbackPresetSO,包含刀鸣/切音 SFX + 短暂 MMF 屏幕冲击- 在
WeaponManager.ApplyWeapon()末尾调用PlayFeedbacks(),切换形态时自动触发
命中特效优先级
vfxConfig.hitFxOverride != null
→ 使用 WeaponSO 指定的 HitFxType(覆盖 DamageSourceSO.HitFxType)
else
→ 使用 DamageSourceSO.HitFxType
HitFxType 由 HitFXSpawner(见 41_VFXArchitecture §3)处理。
9. SaveData 集成
武器系统无需单独存档字段:
- 当前武器由
FormController.CurrentForm+ 装备的护符组合决定 FormController.CurrentForm.formId已写入 SaveData(见 31_SaveDataSchema_Unified §4)- 护符 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 / 暂停时),便于运行时调试武器切换状态。