# 17 · 装备系统(魅力/工具槽) > **命名空间** `BaseGames.Equipment` > **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md) > **依赖** `BaseGames.Core.Events` · `BaseGames.Player`(PlayerStats)· `BaseGames.UI` --- ## 目录 1. [系统总览](#1-系统总览) 2. [CharmSO — 魅力数据](#2-charmso--魅力数据) 3. [ICharmEffect — 效果接口](#3-icharmeffect--效果接口) 4. [内置魅力效果实现](#4-内置魅力效果实现) - [4.1 StatModifierEffect](#41-statmodifiereffect--属性加成) - [4.2 AttackSpeedEffect](#42-attackspeedeffect--攻击速度加成) - [4.3 OnHitEffect](#43-onhiteffect--命中触发效果) - [4.4 SoulSpellEffect](#44-soulspelleffect--灵魂法术强化) - [4.5 SkillNumericModifierEffect](#45-skillnumericmodifiereffect--技能数值修改) - [4.6 SkillSlotOverrideEffect](#46-skillslotoverrideeffect--技能插槽替换) - [4.7 WeaponOverrideEffect](#47-weaponoverrideeffect--武器替换) 5. [EquipmentManager — 槽位管理](#5-equipmentmanager--槽位管理) 6. [槽位系统与笔记(Notch)](#6-槽位系统与笔记notch) 7. [ToolSO — 主动工具](#7-toolso--主动工具) 8. [装备 UI](#8-装备-ui) 9. [SaveData 集成](#9-savedata-集成) 10. [事件频道](#10-事件频道) 11. [编辑器友好设计](#11-编辑器友好设计) --- ## 1. 系统总览 装备系统(Charm/Tool 系统)是银河恶魔城游戏元进度的核心:玩家在世界中收集魅力,在存档点装备,通过组合不同魅力改变战斗风格。对标游戏《丝之歌》中的 Threads 系统。 ``` 装备系统职责: ├─ CharmSO → 魅力数据 SO(效果、槽位占用、图标) ├─ ICharmEffect → 效果注入接口(无脚本级耦合,通过接口多态) ├─ EquipmentManager → 装备/卸下逻辑、槽位管理、装备状态查询 ├─ ToolSO → 主动工具(有限使用次数的消耗品/无限技能) └─ EquipmentUI → 装备槽 HUD、装备面板 UI、收集列表 ``` **零耦合原则**:`EquipmentManager` 通过 SO 事件频道发布装备变更通知,`PlayerStats` 等订阅方自行响应;不持有 `PlayerController` 直接引用。 --- ## 2. CharmSO — 魅力数据 ```csharp [CreateAssetMenu(menuName = "Equipment/Charm")] public class CharmSO : ScriptableObject { [Header("基础信息")] public string charmId; // 全局唯一 ID,如 "Charm_QuickSlash" public string displayName; // 显示名称(本地化 key 或直接填写) [TextArea(3, 6)] public string description; // 描述文本 [Header("外观")] public Sprite icon; // 魅力图标 public Color glowColor; // 魅力光晕颜色 [Header("槽位占用")] [Range(1, 4)] public int notchCost; // 占用的 Notch 数量(1~4) [Header("效果")] [SerializeReference] // 支持多态序列化 public List effects; // 魅力效果列表(可叠加多个) [Header("获得方式")] public bool isUnique; // 唯一物品,只能携带一个 public string unlockHint; // 提示文本(从何处获得) } ``` **资产存放路径**:`Assets/ScriptableObjects/Equipment/Charms/` **命名规范**:`Charm_{Name}.asset` --- ## 3. ICharmEffect — 效果接口 ```csharp namespace BaseGames.Equipment { /// /// 魅力效果接口。装备时 OnEquip,卸下时 OnUnequip。 /// 所有效果通过修改 PlayerStats 或注册事件监听来生效,不直接引用 PlayerController。 /// [Serializable] public interface ICharmEffect { void OnEquip(EquipmentContext ctx); void OnUnequip(EquipmentContext ctx); string GetEffectDescription(); // Inspector 与 UI Tooltip 显示 } /// /// 效果上下文(注入依赖,避免全局引用) /// public struct EquipmentContext { public PlayerStats Stats; public PlayerFeedback Feedback; public EventChannelRegistry Events; // SO 事件频道注册表 public SkillModifierRegistry SkillMods; // 技能修改器注册表(见 21_SpellSystem §5) public WeaponManager WeaponMgr; // 武器切换管理器(见 53_WeaponSystem §3) } } ``` --- ## 4. 内置魅力效果实现 ### 4.1 StatModifierEffect — 属性加成 ```csharp [Serializable] public class StatModifierEffect : ICharmEffect { public StatType statType; // MaxHP / AttackDamage / MoveSpeed / JumpHeight / SoulGain public float flatBonus; // 固定加成(如 +1 HP) public float percentBonus;// 百分比加成(如 +20%) public void OnEquip(EquipmentContext ctx) => ctx.Stats.AddModifier(statType, flatBonus, percentBonus); public void OnUnequip(EquipmentContext ctx) => ctx.Stats.RemoveModifier(statType, flatBonus, percentBonus); public string GetEffectDescription() => $"{statType}: {(flatBonus > 0 ? $"+{flatBonus}" : "")}" + $"{(percentBonus > 0 ? $" +{percentBonus*100:0}%" : "")}"; } ``` **可加成属性列表**: | StatType | 说明 | 示例魅力 | |---------|------|---------| | `MaxHP` | 最大生命值 | 强心魅(+2 HP)| | `AttackDamage` | 攻击伤害 | 利刃魅(+15% 攻击)| | `MoveSpeed` | 移动速度 | 疾步魅(+0.5 m/s)| | `JumpHeight` | 跳跃高度 | 轻跃魅(+20%)| | `SoulGain` | 每次命中获得 Soul | 汲魂魅(+5 Soul/hit)| | `Defense` | 减少受到的伤害 | 护甲魅(+1 防御)| ### 4.2 AttackSpeedEffect — 攻击速度加成 ```csharp [Serializable] public class AttackSpeedEffect : ICharmEffect { [Range(0.1f, 1.0f)] public float speedMultiplier; // 动画速度倍率(如 1.3 = 加速 30%) public void OnEquip(EquipmentContext ctx) => ctx.Stats.AnimatorSpeedMultiplier += (speedMultiplier - 1f); public void OnUnequip(EquipmentContext ctx) => ctx.Stats.AnimatorSpeedMultiplier -= (speedMultiplier - 1f); public string GetEffectDescription() => $"攻击速度 +{(speedMultiplier - 1) * 100:0}%"; } ``` ### 4.3 OnHitEffect — 命中触发效果 ```csharp [Serializable] public class OnHitEffect : ICharmEffect { public OnHitEffectType effectType; // ApplyPoison / ApplyFire / KnockbackBoost [Range(0f, 1f)] public float chance; // 触发概率(0~1) DamageInfoEventChannelSO _onHitChannel; public void OnEquip(EquipmentContext ctx) { _onHitChannel = ctx.Events.Get("OnHitConfirmed"); _onHitChannel.OnEventRaised += HandleHit; } public void OnUnequip(EquipmentContext ctx) => _onHitChannel.OnEventRaised -= HandleHit; void HandleHit(DamageInfo info) { if (Random.value > chance) return; // 触发对应效果(由 StatusEffectManager 处理,见 04_CombatSystem §12) } public string GetEffectDescription() => $"命中时 {chance*100:0}% 概率附加 {effectType}"; } ``` ### 4.4 SoulSpellEffect — 灵魂法术强化 ```csharp [Serializable] public class SoulSpellEffect : ICharmEffect { public SpellType spellType; // SoulAttack / HealingWave(具体法术) public int soulCostReduction; // 减少消耗 Soul 点数 public void OnEquip(EquipmentContext ctx) => ctx.Stats.RegisterSpellModifier(spellType, soulCostReduction, 0f); public void OnUnequip(EquipmentContext ctx) => ctx.Stats.UnregisterSpellModifier(spellType, soulCostReduction, 0f); public string GetEffectDescription() => $"{spellType} 消耗减少 {soulCostReduction} Soul"; } ``` ### 4.5 SkillNumericModifierEffect — 技能数值修改 修改现有技能的伤害、范围、消耗、冷却和特效(不替换技能本身): ```csharp [Serializable] public class SkillNumericModifierEffect : ICharmEffect { [Tooltip("数值修改配置,可指定目标技能 ID / 资源类型 / 各倍率 / 特效覆盖")] 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 System.Collections.Generic.List(); 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.targetSkillId; return $"{target}:{string.Join(" / ", parts)}"; } } ``` **配置示例**: ``` // 范例护符:【裂空掌强化符】(+50% 伤害 / +30% 范围,仅对裂空掌) modifier.targetSkillId = "sky_soul_skill" modifier.damageMult = 1.5 modifier.rangeMult = 1.3 modifier.costMult = 1.0 modifier.cooldownMult = 1.0 // 范例护符:【魄元节笔】(所有魄技能消耗 -25%) modifier.targetSkillId = "" // 留空 = 匹配所有 modifier.resourceFilter = SpiritOnly modifier.costMult = 0.75 // 范例护符:【异形灵踪】(替换灵踪弹特效为火焰弹碗) modifier.targetSkillId = "sky_spirit_skill2" modifier.vfxOverride = FeedbackPreset_FireballVFX // 刹射物设计不变,只改特效 ``` ### 4.6 SkillSlotOverrideEffect — 技能插槽替换 将某形态某槽位的技能 SO **整体替换**为另一个 FormSkillSO: ```csharp [Serializable] public class SkillSlotOverrideEffect : ICharmEffect { [Tooltip("插槽替换配置:targetForm(null=全部形态)+ targetSlot + replacementSkill + priority")] 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}]"; } } ``` **配置示例**: ``` // 范例护符:【混元引导符】(天魂形态的魂技能替换为地行术) overrideData.targetForm = FormSO_SkyForm overrideData.targetSlot = SoulSkill overrideData.replacementSkill = FormSkillSO_DiXingShu overrideData.priority = 0 // 范例护符:【夜行者印记】(任意形态的魄技能2 替换为残阴术) overrideData.targetForm = null // 全部形态均生效 overrideData.targetSlot = SpiritSkill2 overrideData.replacementSkill = FormSkillSO_CanYinShu overrideData.priority = 1 // 高于其他护符,确保生效 ``` **冲突解决规则**: | 场景 | 结果 | |------|---------| | 两个护符替换同一槽位,`priority` 不同 | 高 `priority` 者生效 | | 两个护符替换同一槽位,`priority` 相同 | 后装备的护符生效 | | 一个替换全部形态,一个替换特定形态 | 特定形态的护符在该形态中优先(targetForm != null 得到额外 +1 优先加成,见 SkillModifierRegistry) | --- ### 4.7 WeaponOverrideEffect — 武器替换 将某形态(或所有形态)的默认武器整体替换为另一个 `WeaponSO`(见 [53_WeaponSystem §6](./53_WeaponSystem.md#6-护符武器覆盖weaponoverrideeffect)): ```csharp [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}]"; } } ``` **配置示例**: ``` // 护符:【命魂刃灵符】(命魂形态改用高速短刃,4 Notch) targetFormId = "LifeForm" replacementWeapon = Weapon_ShadowDagger // 护符:【混沌铸兵符】(所有形态均使用地魂锤,3 Notch) targetFormId = "" // 空 = 全部形态 replacementWeapon = Weapon_EarthHammer ``` **冲突规则**:若多个护符同时覆盖同一形态武器,**后装备的护符生效**(`Dictionary` 键覆盖语义)。 --- ## 5. EquipmentManager — 槽位管理 `EquipmentManager` 常驻 Player GameObject,管理装备状态。 ```csharp namespace BaseGames.Equipment { public class EquipmentManager : MonoBehaviour { [Header("配置")] [SerializeField] EquipmentConfigSO _config; // 初始 Notch 数量等 [Header("事件频道")] [SerializeField] CharmEventChannelSO _onCharmEquipped; [SerializeField] CharmEventChannelSO _onCharmUnequipped; [SerializeField] VoidEventChannelSO _onEquipmentChanged; // 总变更通知 // 运行时状态 readonly List _collectedCharms = new(); // 已收集魅力库 readonly List _equippedCharms = new(); // 当前装备的魅力 int _currentNotchCapacity; // 当前最大 Notch 数(会随升级增加) int _usedNotches => _equippedCharms.Sum(c => c.notchCost); EquipmentContext _ctx; void Awake() { var stats = GetComponent(); var feedback = GetComponent(); _ctx = new EquipmentContext { Stats = stats, Feedback = feedback, Events = EventChannelRegistry.Instance, SkillMods = GetComponent(), // 同一 Player GO WeaponMgr = GetComponent(), // 同一 Player GO }; } // ─────────────── 公开 API ─────────────── /// 装备魅力。返回失败原因(null = 成功) public string TryEquipCharm(CharmSO charm) { if (_equippedCharms.Contains(charm)) return "已经装备"; if (!_collectedCharms.Contains(charm)) return "尚未收集此魅力"; int newUsed = _usedNotches + charm.notchCost; if (newUsed > _currentNotchCapacity) return $"笔记不足(需要 {charm.notchCost},剩余 {_currentNotchCapacity - _usedNotches})"; _equippedCharms.Add(charm); foreach (var effect in charm.effects) effect.OnEquip(_ctx); _onCharmEquipped.Raise(charm); _onEquipmentChanged.Raise(); return null; } public void UnequipCharm(CharmSO charm) { if (!_equippedCharms.Remove(charm)) return; foreach (var effect in charm.effects) effect.OnUnequip(_ctx); _onCharmUnequipped.Raise(charm); _onEquipmentChanged.Raise(); } public bool IsEquipped(CharmSO charm) => _equippedCharms.Contains(charm); public bool IsCollected(CharmSO charm) => _collectedCharms.Contains(charm); public int UsedNotches => _usedNotches; public int NotchCapacity => _currentNotchCapacity; public IReadOnlyList EquippedCharms => _equippedCharms; public IReadOnlyList CollectedCharms => _collectedCharms; // ─────────────── 存档集成 ─────────────── public void LoadFromSaveData(SaveData data) { _collectedCharms.Clear(); _equippedCharms.Clear(); // 卸下所有已装备效果(防止重复加载) foreach (var c in _equippedCharms) foreach (var e in c.effects) e.OnUnequip(_ctx); _currentNotchCapacity = data.equipment.notchCapacity; foreach (var id in data.equipment.collectedCharms) { var charm = CharmDatabase.Instance.GetCharm(id); if (charm != null) _collectedCharms.Add(charm); } foreach (var id in data.equipment.equippedCharms) { var charm = CharmDatabase.Instance.GetCharm(id); if (charm != null) TryEquipCharm(charm); } } public void WriteToSaveData(SaveData data) { data.equipment.notchCapacity = _currentNotchCapacity; data.equipment.collectedCharms = _collectedCharms.Select(c => c.charmId).ToList(); data.equipment.equippedCharms = _equippedCharms.Select(c => c.charmId).ToList(); } // ─────────────── Notch 升级 ─────────────── /// 由 AbilityUnlock / 剧情事件调用,增加 Notch 容量 public void AddNotchCapacity(int amount) { _currentNotchCapacity += amount; _onEquipmentChanged.Raise(); } } } ``` --- ## 6. 槽位系统与笔记(Notch) 《丝之歌》对标系统:**Notch(笔记)** 而非固定槽位数量。 ``` 初始 Notch 容量: 3 可通过以下途径升级: ├─ 购买特殊 NPC 服务: +1 Notch(每次 Geo 费用递增) ├─ 隐藏收集物 NotchShard(4 个合 1): +1 Notch └─ 最大 Notch 容量: 11(可配置) ``` 魅力的 Notch 消耗参考: | 消耗 | 魅力类型示例 | |------|------------| | 1 Notch | 轻微属性加成(+MoveSpeed、+SoulGain)| | 2 Notch | 中等效果(+攻击、命中触发)| | 3 Notch | 强力被动(+2 HP、攻速大幅提升)| | 4 Notch | 顶级魅力(改变游戏机制)| --- ## 7. ToolSO — 主动工具 主动工具(Tool)是有限使用次数的消耗品或无限施放的技能。 ```csharp [CreateAssetMenu(menuName = "Equipment/Tool")] public class ToolSO : ScriptableObject { [Header("基础信息")] public string toolId; public string displayName; [TextArea(2, 4)] public string description; public Sprite icon; [Header("使用参数")] public ToolType toolType; // Consumable / Skill / Throwable public int maxCount; // 最大持有数量(-1 = 无限) public int soulCost; // 使用消耗 Soul(0 = 无消耗) public float cooldown; // 冷却时间(秒) [Header("效果")] [SerializeReference] public IToolEffect effect; // 工具使用效果 } ``` --- ## 8. 装备 UI ### 8.1 装备面板(全屏/暂停中打开) ``` Canvas_Pause └── EquipmentPanel ├── LeftPanel — 魅力收集库 │ ├── CharmGrid (ScrollView,3列) │ │ └── CharmItem(图标 + Notch 数量 + 状态:未收集/已收集/已装备) │ └── SearchBar(可选,P2) ├── RightPanel — 装备槽 │ ├── NotchBar(可视化 Notch 容量条,已用/总量) │ │ ├── NotchCell × N(已用=橙色,剩余=灰色) │ │ └── CapacityText "4 / 8" │ ├── EquippedSlots(已装备魅力列表,图标 + 卸下按钮) │ └── OvercharmWarning(装备超过容量时显示 "过充咒(Overcharmed)" 警告,玩家受到双倍伤害) └── BottomBar — 选中魅力详情 ├── CharmIcon(大图) ├── CharmName + NotchCost ├── DescriptionText ├── EffectList(ICharmEffect.GetEffectDescription() 列表) └── ActionButton(装备 / 卸下) ``` ### 8.2 装备 HUD(游戏中快捷栏) ``` Canvas_HUD └── ToolQuickbar (底部中央) ├── ToolSlot_1 (QuickSlot 1) — 绑定工具1图标 + 剩余数量 └── ToolSlot_2 (QuickSlot 2) — 绑定工具2图标 + 剩余数量 ``` 工具快捷栏在装备面板中可拖拽分配;快捷键:手柄 L1/R1,键盘 Q/E。 ### 8.3 过充咒(Overcharmed)机制 参考《空洞骑士》Overcharmed:玩家强行装备超出 Notch 容量的魅力组合时,进入 Overcharmed 状态: ```csharp // 在 EquipmentManager 中检测 bool IsOvercharmed => _usedNotches > _currentNotchCapacity; // PlayerStats 订阅 _onEquipmentChanged void HandleEquipmentChanged() { bool overcharmed = _equipmentManager.IsOvercharmed; _damageTakenMultiplier = overcharmed ? 2.0f : 1.0f; _overcharmedIndicator.SetActive(overcharmed); } ``` --- ## 9. SaveData 集成 `SaveData` 新增 `equipment` 字段: ```json { "equipment": { "notchCapacity": 5, "collectedCharms": ["Charm_QuickSlash", "Charm_LifeBlood", "Charm_LongNail"], "equippedCharms": ["Charm_QuickSlash", "Charm_LongNail"], "quickTools": [ { "toolId": "Tool_HealSeed", "count": 3 }, { "toolId": null, "count": 0 } ], "notchShardsCollected": 2 } } ``` | 字段 | 说明 | |------|------| | `notchCapacity` | 当前 Notch 总容量 | | `collectedCharms` | 已收集魅力 ID 列表 | | `equippedCharms` | 当前装备魅力 ID 列表 | | `quickTools` | 两个快捷工具槽(toolId + count)| | `notchShardsCollected` | 已收集 Notch 碎片数量 | --- ## 10. 事件频道 | 频道资产 | 类型 | 发布方 | 主要订阅方 | |---------|------|--------|----------| | `OnCharmCollected.asset` | `CharmEventChannelSO` | `AbilityUnlock / Collectible` | `EquipmentManager`(添加到库)、`EquipmentUI`(刷新)| | `OnCharmEquipped.asset` | `CharmEventChannelSO` | `EquipmentManager` | `PlayerStats`(通过效果已注入)、`HUD` | | `OnCharmUnequipped.asset` | `CharmEventChannelSO` | `EquipmentManager` | `PlayerStats`、`HUD` | | `OnEquipmentChanged.asset` | `VoidEventChannelSO` | `EquipmentManager` | `EquipmentUI`(刷新)、`PlayerStats`(Overcharmed 检测)| | `OnNotchCapacityChanged.asset` | `IntEventChannelSO` | `EquipmentManager` | `EquipmentUI`(刷新 Notch 条)| | `OnToolUsed.asset` | `ToolEventChannelSO` | `PlayerController` | `EquipmentManager`(扣减数量)、`HUD`(更新计数)| --- ## 11. 编辑器友好设计 ### CharmSO 自定义 Inspector ``` ┌─ Charm_QuickSlash ─────────────────────────────────────────┐ │ [图标预览] Quick Slash │ │ ID: Charm_QuickSlash Notch Cost: ██░░ 2 │ │ 描述: 大幅提升攻击速度 │ │ ───────────────────────────────────── │ │ 效果列表: │ │ [+] AttackSpeedEffect │ │ speedMultiplier: 1.35 → "攻击速度 +35%" │ │ ───────────────────────────────────── │ │ [+ 添加效果] [在游戏中预览] │ └────────────────────────────────────────────────────────────┘ ``` - `[SerializeReference]` 使效果列表在 Inspector 中以多态方式折叠展示 - 每个 `ICharmEffect` 实现类打上 `[Serializable]` 标签,自动出现在添加菜单 ### EquipmentManager Scene Gizmo - 在 Scene 视图中,玩家头顶显示已装备魅力的小图标(仅 Edit Mode 或暂停时显示),便于调试 ### CharmDatabase EditorWindow `Tools → Zeling → Charm Database`: ``` ┌─ Charm Database ───────────────────────────────────────────┐ │ [自动扫描所有 CharmSO] 搜索: [____________] │ │ │ │ ID | Notch | 收集方式 | 效果数 │ │ Charm_QuickSlash | 2 | Forest_Cave_Boss | 1 │ │ Charm_LifeBlood | 2 | Purchase_Shop01 | 1 │ │ ... │ └────────────────────────────────────────────────────────────┘ ```