Files
zeling_v2/Docs/Design/17_EquipmentSystem.md
2026-05-08 11:04:00 +08:00

25 KiB
Raw Permalink Blame History

17 · 装备系统(魅力/工具槽)

命名空间 BaseGames.Equipment
所属文档集 ← 返回索引 · 总览
依赖 BaseGames.Core.Events · BaseGames.PlayerPlayerStats· BaseGames.UI


目录

  1. 系统总览
  2. CharmSO — 魅力数据
  3. ICharmEffect — 效果接口
  4. 内置魅力效果实现
  5. EquipmentManager — 槽位管理
  6. 槽位系统与笔记Notch
  7. ToolSO — 主动工具
  8. 装备 UI
  9. SaveData 集成
  10. 事件频道
  11. 编辑器友好设计

1. 系统总览

装备系统Charm/Tool 系统)是银河恶魔城游戏元进度的核心:玩家在世界中收集魅力,在存档点装备,通过组合不同魅力改变战斗风格。对标游戏《丝之歌》中的 Threads 系统。

装备系统职责:
  ├─ CharmSO            → 魅力数据 SO效果、槽位占用、图标
  ├─ ICharmEffect       → 效果注入接口(无脚本级耦合,通过接口多态)
  ├─ EquipmentManager   → 装备/卸下逻辑、槽位管理、装备状态查询
  ├─ ToolSO             → 主动工具(有限使用次数的消耗品/无限技能)
  └─ EquipmentUI        → 装备槽 HUD、装备面板 UI、收集列表

零耦合原则EquipmentManager 通过 SO 事件频道发布装备变更通知,PlayerStats 等订阅方自行响应;不持有 PlayerController 直接引用。


2. CharmSO — 魅力数据

[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 — 效果接口

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 — 属性加成

[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 — 攻击速度加成

[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 — 命中触发效果

[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 — 灵魂法术强化

[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 — 技能数值修改

修改现有技能的伤害、范围、消耗、冷却和特效(不替换技能本身):

[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

[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

[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管理装备状态。

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是有限使用次数的消耗品或无限施放的技能。

[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 状态:

// 在 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 字段:

{
  "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 PlayerStatsHUD
OnEquipmentChanged.asset VoidEventChannelSO EquipmentManager EquipmentUI(刷新)、PlayerStatsOvercharmed 检测)
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   │
│  ...                                                       │
└────────────────────────────────────────────────────────────┘