chore: initial commit

This commit is contained in:
2026-05-08 11:04:00 +08:00
commit f55d2a57c3
6278 changed files with 866081 additions and 0 deletions

View File

@@ -0,0 +1,681 @@
# 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<ICharmEffect> effects; // 魅力效果列表(可叠加多个)
[Header("获得方式")]
public bool isUnique; // 唯一物品,只能携带一个
public string unlockHint; // 提示文本(从何处获得)
}
```
**资产存放路径**`Assets/ScriptableObjects/Equipment/Charms/`
**命名规范**`Charm_{Name}.asset`
---
## 3. ICharmEffect — 效果接口
```csharp
namespace BaseGames.Equipment
{
/// <summary>
/// 魅力效果接口。装备时 OnEquip卸下时 OnUnequip。
/// 所有效果通过修改 PlayerStats 或注册事件监听来生效,不直接引用 PlayerController。
/// </summary>
[Serializable]
public interface ICharmEffect
{
void OnEquip(EquipmentContext ctx);
void OnUnequip(EquipmentContext ctx);
string GetEffectDescription(); // Inspector 与 UI Tooltip 显示
}
/// <summary>
/// 效果上下文(注入依赖,避免全局引用)
/// </summary>
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<DamageInfoEventChannelSO>("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<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.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("插槽替换配置targetFormnull=全部形态)+ 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<CharmSO> _collectedCharms = new(); // 已收集魅力库
readonly List<CharmSO> _equippedCharms = new(); // 当前装备的魅力
int _currentNotchCapacity; // 当前最大 Notch 数(会随升级增加)
int _usedNotches => _equippedCharms.Sum(c => c.notchCost);
EquipmentContext _ctx;
void Awake()
{
var stats = GetComponent<PlayerStats>();
var feedback = GetComponent<PlayerFeedback>();
_ctx = new EquipmentContext {
Stats = stats,
Feedback = feedback,
Events = EventChannelRegistry.Instance,
SkillMods = GetComponent<SkillModifierRegistry>(), // 同一 Player GO
WeaponMgr = GetComponent<WeaponManager>(), // 同一 Player GO
};
}
// ─────────────── 公开 API ───────────────
/// <summary>装备魅力。返回失败原因null = 成功)</summary>
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<CharmSO> EquippedCharms => _equippedCharms;
public IReadOnlyList<CharmSO> 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 升级 ───────────────
/// <summary>由 AbilityUnlock / 剧情事件调用,增加 Notch 容量</summary>
public void AddNotchCapacity(int amount)
{
_currentNotchCapacity += amount;
_onEquipmentChanged.Raise();
}
}
}
```
---
## 6. 槽位系统与笔记Notch
《丝之歌》对标系统:**Notch笔记** 而非固定槽位数量。
```
初始 Notch 容量: 3
可通过以下途径升级:
├─ 购买特殊 NPC 服务: +1 Notch每次 Geo 费用递增)
├─ 隐藏收集物 NotchShard4 个合 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; // 使用消耗 Soul0 = 无消耗)
public float cooldown; // 冷却时间(秒)
[Header("效果")]
[SerializeReference]
public IToolEffect effect; // 工具使用效果
}
```
---
## 8. 装备 UI
### 8.1 装备面板(全屏/暂停中打开)
```
Canvas_Pause
└── EquipmentPanel
├── LeftPanel — 魅力收集库
│ ├── CharmGrid (ScrollView3列)
│ │ └── CharmItem图标 + Notch 数量 + 状态:未收集/已收集/已装备)
│ └── SearchBar可选P2
├── RightPanel — 装备槽
│ ├── NotchBar可视化 Notch 容量条,已用/总量)
│ │ ├── NotchCell × N已用=橙色,剩余=灰色)
│ │ └── CapacityText "4 / 8"
│ ├── EquippedSlots已装备魅力列表图标 + 卸下按钮)
│ └── OvercharmWarning装备超过容量时显示 "过充咒Overcharmed" 警告,玩家受到双倍伤害)
└── BottomBar — 选中魅力详情
├── CharmIcon大图
├── CharmName + NotchCost
├── DescriptionText
├── EffectListICharmEffect.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 │
│ ... │
└────────────────────────────────────────────────────────────┘
```