25 KiB
17 · 装备系统(魅力/工具槽)
命名空间
BaseGames.Equipment
所属文档集 ← 返回索引 · 总览
依赖BaseGames.Core.Events·BaseGames.Player(PlayerStats)·BaseGames.UI
目录
- 系统总览
- CharmSO — 魅力数据
- ICharmEffect — 效果接口
- 内置魅力效果实现
- EquipmentManager — 槽位管理
- 槽位系统与笔记(Notch)
- ToolSO — 主动工具
- 装备 UI
- SaveData 集成
- 事件频道
- 编辑器友好设计
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("插槽替换配置: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):
[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 费用递增)
├─ 隐藏收集物 NotchShard(4 个合 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; // 使用消耗 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 状态:
// 在 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 |
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 │
│ ... │
└────────────────────────────────────────────────────────────┘