多轮审查和修复
This commit is contained in:
@@ -9,7 +9,12 @@
|
||||
"rootNamespace": "BaseGames.Equipment",
|
||||
"references": [
|
||||
"BaseGames.Core.Events",
|
||||
"BaseGames.Player"
|
||||
"BaseGames.Player",
|
||||
"BaseGames.Player.States",
|
||||
"BaseGames.Combat",
|
||||
"BaseGames.Feedback",
|
||||
"BaseGames.Skills",
|
||||
"BaseGames.Core.Save"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"overrideReferences": false,
|
||||
|
||||
25
Assets/Scripts/Equipment/CharmCatalogSO.cs
Normal file
25
Assets/Scripts/Equipment/CharmCatalogSO.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 护符目录 SO。
|
||||
/// 全局唯一资产(Assets/Data/Equipment/CharmCatalog.asset),
|
||||
/// 通过 charmId 查找 CharmSO 引用。
|
||||
/// 由 EquipmentManager 在 AddToCollection / OnLoad 时查询。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Equipment/CharmCatalog")]
|
||||
public class CharmCatalogSO : ScriptableObject
|
||||
{
|
||||
[SerializeField] private CharmSO[] _charms;
|
||||
|
||||
/// <summary>按 charmId 查找护符,找不到返回 null。</summary>
|
||||
public CharmSO Find(string charmId)
|
||||
{
|
||||
if (_charms == null || string.IsNullOrEmpty(charmId)) return null;
|
||||
foreach (var charm in _charms)
|
||||
if (charm != null && charm.charmId == charmId) return charm;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Equipment/CharmCatalogSO.cs.meta
Normal file
11
Assets/Scripts/Equipment/CharmCatalogSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 277db74c72224f149b8805dc31b2f944
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
42
Assets/Scripts/Equipment/CharmSO.cs
Normal file
42
Assets/Scripts/Equipment/CharmSO.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using BaseGames.Core.Events;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 护符数据 SO(架构 09_ProgressionModule §3)。
|
||||
/// 资产路径: Assets/ScriptableObjects/Equipment/Charms/Charm_{Name}.asset
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Equipment/Charm")]
|
||||
public class CharmSO : ScriptableObject
|
||||
{
|
||||
[Header("Identity")]
|
||||
public string charmId; // 全局唯一 ID,如 "Charm_QuickSlash"
|
||||
public string displayNameKey; // 本地化 Key
|
||||
[TextArea(2, 4)]
|
||||
public string descriptionKey;
|
||||
|
||||
[Header("Visual")]
|
||||
public Sprite icon;
|
||||
public Color glowColor;
|
||||
|
||||
[Header("Slot Cost")]
|
||||
[Range(1, 4)]
|
||||
public int notchCost; // 占用笔记数(1~4)
|
||||
|
||||
[Header("Effects")]
|
||||
[SerializeReference]
|
||||
public List<ICharmEffect> effects = new(); // 多态序列化
|
||||
|
||||
[Header("Lore")]
|
||||
public bool isUnique;
|
||||
public string unlockHint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 护符事件频道(EVT_CharmEquipped / EVT_CharmUnequipped)。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Events/CharmEvent")]
|
||||
public class CharmEventChannelSO : BaseEventChannelSO<CharmSO> { }
|
||||
}
|
||||
11
Assets/Scripts/Equipment/CharmSO.cs.meta
Normal file
11
Assets/Scripts/Equipment/CharmSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 332e2f07412f90d45ac0882e9c0b87d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Equipment/Effects.meta
Normal file
8
Assets/Scripts/Equipment/Effects.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6dec97e009cac9d4b81beb1f78478f73
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
28
Assets/Scripts/Equipment/Effects/AttackSpeedEffect.cs
Normal file
28
Assets/Scripts/Equipment/Effects/AttackSpeedEffect.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 攻击速度加成护符效果(架构 09_ProgressionModule §5)。
|
||||
/// 修改 PlayerStats.AnimatorSpeedMultiplier,动画控制器查询该倒数应用速度。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class AttackSpeedEffect : ICharmEffect
|
||||
{
|
||||
[Range(0.1f, 2.0f)]
|
||||
public float speedMultiplier = 1.2f; // 动画速度倍率(1.2 = 加速 20%)
|
||||
|
||||
public void OnEquip(EquipmentContext ctx)
|
||||
{
|
||||
ctx.Stats?.AddAnimatorSpeedBonus(speedMultiplier - 1f);
|
||||
}
|
||||
|
||||
public void OnUnequip(EquipmentContext ctx)
|
||||
{
|
||||
ctx.Stats?.RemoveAnimatorSpeedBonus(speedMultiplier - 1f);
|
||||
}
|
||||
|
||||
public string GetEffectDescription() => $"攻击速度 +{(speedMultiplier - 1f) * 100:0}%";
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Equipment/Effects/AttackSpeedEffect.cs.meta
Normal file
11
Assets/Scripts/Equipment/Effects/AttackSpeedEffect.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0496fa028b434bb4aa5a8db318949418
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
69
Assets/Scripts/Equipment/Effects/OnHitEffect.cs
Normal file
69
Assets/Scripts/Equipment/Effects/OnHitEffect.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using BaseGames.Combat;
|
||||
using BaseGames.Core.Events;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 命中触发效果类型。
|
||||
/// </summary>
|
||||
public enum OnHitEffectType
|
||||
{
|
||||
ApplyPoison,
|
||||
ApplyFire,
|
||||
KnockbackBoost
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 命中触发护符效果(架构 09_ProgressionModule §5)。
|
||||
/// 订阅 HitConfirmedEventChannelSO("EVT_HitConfirmed") 并按概率对命中目标施加状态效果。
|
||||
/// KnockbackBoost 类型通过 DamageInfo.KnockbackForce 标记,由 HurtBox 流水线读取。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class OnHitEffect : ICharmEffect
|
||||
{
|
||||
public OnHitEffectType effectType;
|
||||
[Range(0f, 1f)]
|
||||
public float chance; // 触发概率(0~1)
|
||||
|
||||
private HitConfirmedEventChannelSO _onHitChannel;
|
||||
private EventSubscription _sub;
|
||||
|
||||
public void OnEquip(EquipmentContext ctx)
|
||||
{
|
||||
_onHitChannel = ctx.Events?.Get<HitConfirmedEventChannelSO>("EVT_HitConfirmed");
|
||||
_sub = _onHitChannel?.Subscribe(HandleHit);
|
||||
}
|
||||
|
||||
public void OnUnequip(EquipmentContext ctx)
|
||||
{
|
||||
_sub?.Dispose();
|
||||
_sub = null;
|
||||
_onHitChannel = null;
|
||||
}
|
||||
|
||||
private void HandleHit(HitInfo info)
|
||||
{
|
||||
if (UnityEngine.Random.value > chance) return;
|
||||
|
||||
switch (effectType)
|
||||
{
|
||||
case OnHitEffectType.ApplyPoison:
|
||||
info.HitTransform?.GetComponentInParent<IStatusEffectable>()
|
||||
?.ApplyStatusEffect(DamageType.Poison);
|
||||
break;
|
||||
case OnHitEffectType.ApplyFire:
|
||||
info.HitTransform?.GetComponentInParent<IStatusEffectable>()
|
||||
?.ApplyStatusEffect(DamageType.Fire);
|
||||
break;
|
||||
case OnHitEffectType.KnockbackBoost:
|
||||
// KnockbackBoost 在 HitBox 构建 DamageInfo 前生效,
|
||||
// 此处为命中后的反馈(可在此播放特效;实际击退已在 HurtBox 流水线处理)。
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetEffectDescription() => $"命中时 {chance * 100:0}% 概率附加 {effectType}";
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Equipment/Effects/OnHitEffect.cs.meta
Normal file
11
Assets/Scripts/Equipment/Effects/OnHitEffect.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e37498b992275324896706fb0237f9ca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using BaseGames.Skills;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 技能数值修改护符效果(架构 09_ProgressionModule §5)。
|
||||
/// 通过 SkillModifierRegistry 对指定技能的数值加成。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SkillNumericModifierEffect : ICharmEffect
|
||||
{
|
||||
public string TargetSkillId;
|
||||
public SkillStat Stat;
|
||||
public float Delta;
|
||||
public bool IsPercent;
|
||||
|
||||
public void OnEquip(EquipmentContext ctx)
|
||||
=> ctx.SkillMods?.Register(TargetSkillId, Stat, Delta, IsPercent);
|
||||
|
||||
public void OnUnequip(EquipmentContext ctx)
|
||||
=> ctx.SkillMods?.Unregister(TargetSkillId, Stat, Delta, IsPercent);
|
||||
|
||||
public string GetEffectDescription()
|
||||
=> $"{TargetSkillId}.{Stat} {(Delta >= 0 ? "+" : "")}{Delta}{(IsPercent ? "%" : "")}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa27d39b594b954449a14cfab572255c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
30
Assets/Scripts/Equipment/Effects/SkillSlotOverrideEffect.cs
Normal file
30
Assets/Scripts/Equipment/Effects/SkillSlotOverrideEffect.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using BaseGames.Skills;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 技能插槽替换护符效果(架构 09_ProgressionModule §5)。
|
||||
/// 将指定形态的某技能槽替换为另一技能。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SkillSlotOverrideEffect : ICharmEffect
|
||||
{
|
||||
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.displayNameKey : "null";
|
||||
return $"{formStr}的 {overrideData.targetSlot} 替换为 [{skillName}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 337f2ae2dd788c5489eb13d19db1bbbc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
36
Assets/Scripts/Equipment/Effects/SoulSpellEffect.cs
Normal file
36
Assets/Scripts/Equipment/Effects/SoulSpellEffect.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 法术资源类型。
|
||||
/// </summary>
|
||||
public enum SpellType
|
||||
{
|
||||
SoulAttack,
|
||||
HealingWave
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 灵魂法术强化护符效果(架构 09_ProgressionModule §5)。
|
||||
/// 装备时减少灵力消耗,卸下时还原。SpellManager 消耗时查询 PlayerStats.SoulCostReduction。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SoulSpellEffect : ICharmEffect
|
||||
{
|
||||
public SpellType spellType;
|
||||
public int soulCostReduction; // 减少消耗的灵力点数
|
||||
|
||||
public void OnEquip(EquipmentContext ctx)
|
||||
{
|
||||
ctx.Stats?.AddSoulCostReduction(soulCostReduction);
|
||||
}
|
||||
|
||||
public void OnUnequip(EquipmentContext ctx)
|
||||
{
|
||||
ctx.Stats?.RemoveSoulCostReduction(soulCostReduction);
|
||||
}
|
||||
|
||||
public string GetEffectDescription() => $"{spellType} 消耗减少 {soulCostReduction} 灵力";
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Equipment/Effects/SoulSpellEffect.cs.meta
Normal file
11
Assets/Scripts/Equipment/Effects/SoulSpellEffect.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d2819daddf61c54890ee8bfd55c0093
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
28
Assets/Scripts/Equipment/Effects/StatModifierEffect.cs
Normal file
28
Assets/Scripts/Equipment/Effects/StatModifierEffect.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 属性加成护符效果(架构 09_ProgressionModule §5)。
|
||||
/// 装备时通过 PlayerStats.AddModifier 叠加固定/百分比属性加成。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class StatModifierEffect : ICharmEffect
|
||||
{
|
||||
public StatType statType;
|
||||
public float flatBonus; // 固定加成(如 +1 HP)
|
||||
public float percentBonus; // 百分比加成(如 +0.2 = +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} +{percentBonus * 100:0}%";
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Equipment/Effects/StatModifierEffect.cs.meta
Normal file
11
Assets/Scripts/Equipment/Effects/StatModifierEffect.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a360d87b54b496f448a78f9abf2ef5c0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
29
Assets/Scripts/Equipment/Effects/WeaponOverrideEffect.cs
Normal file
29
Assets/Scripts/Equipment/Effects/WeaponOverrideEffect.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using BaseGames.Player;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 武器替换护符效果(架构 09_ProgressionModule §5)。
|
||||
/// 通过 WeaponManager 将指定形态的武器替换为另一武器。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class WeaponOverrideEffect : ICharmEffect
|
||||
{
|
||||
public string targetFormId; // 目标形态 ID(留空 = 所有形态)
|
||||
public WeaponSO replacementWeapon; // 替换武器 SO
|
||||
|
||||
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}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f78ad8b20f68db4d9673de1a7cb5a58
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
Assets/Scripts/Equipment/EquipmentConfigSO.cs
Normal file
20
Assets/Scripts/Equipment/EquipmentConfigSO.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 装备系统全局配置 SO(架构 09_ProgressionModule §6)。
|
||||
/// 资产路径: Assets/ScriptableObjects/Equipment/EquipmentConfig.asset
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Equipment/EquipmentConfig")]
|
||||
public class EquipmentConfigSO : ScriptableObject
|
||||
{
|
||||
[Header("Notch 配置")]
|
||||
[Tooltip("初始可用笔记数")]
|
||||
public int initialNotchCount = 3;
|
||||
|
||||
[Header("收集上限")]
|
||||
[Tooltip("护符收藏槽上限(-1 = 无限制)")]
|
||||
public int maxCollectionSize = -1;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Equipment/EquipmentConfigSO.cs.meta
Normal file
11
Assets/Scripts/Equipment/EquipmentConfigSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2ff92bffe90e0f499b70bdb9d045552
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
143
Assets/Scripts/Equipment/EquipmentManager.cs
Normal file
143
Assets/Scripts/Equipment/EquipmentManager.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Player;
|
||||
using BaseGames.Core.Events;
|
||||
using BaseGames.Core.Save;
|
||||
using BaseGames.Skills;
|
||||
using BaseGames.Feedback;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 装备管理器(架构 09_ProgressionModule §6)。
|
||||
/// 挂在 Player 上,管理护符的装备/卸下及 Notch 容量。
|
||||
/// 实现 ISaveable 以持久化装备状态。
|
||||
/// </summary>
|
||||
public class EquipmentManager : MonoBehaviour, ISaveable
|
||||
{
|
||||
[Header("配置")]
|
||||
[SerializeField] private EquipmentConfigSO _config;
|
||||
[SerializeField] private CharmCatalogSO _charmCatalog;
|
||||
|
||||
[Header("Event Channels")]
|
||||
[SerializeField] private CharmEventChannelSO _onCharmEquipped;
|
||||
[SerializeField] private CharmEventChannelSO _onCharmUnequipped;
|
||||
[SerializeField] private VoidEventChannelSO _onEquipmentChanged;
|
||||
|
||||
private readonly List<CharmSO> _equipped = new(4);
|
||||
private readonly List<CharmSO> _collected = new(32);
|
||||
private int _currentNotchCapacity;
|
||||
private int _usedNotches;
|
||||
|
||||
private EquipmentContext _ctx;
|
||||
|
||||
// ── 生命周期 ──────────────────────────────────────────────────────────
|
||||
private void Awake()
|
||||
{
|
||||
_ctx = new EquipmentContext
|
||||
{
|
||||
Stats = GetComponent<PlayerStats>(),
|
||||
Feedback = GetComponent<PlayerFeedback>(),
|
||||
Events = ServiceLocator.GetOrDefault<IEventChannelRegistry>(),
|
||||
SkillMods = GetComponent<SkillModifierRegistry>(),
|
||||
WeaponMgr = GetComponent<WeaponManager>(),
|
||||
};
|
||||
Debug.Assert(_config != null, "[EquipmentManager] _config 未赋值,请在 Inspector 中指定 EquipmentConfigSO。", this);
|
||||
Debug.Assert(_charmCatalog != null, "[EquipmentManager] _charmCatalog 未赋值,请在 Inspector 中指定 CharmCatalogSO。", this);
|
||||
_currentNotchCapacity = _config.initialNotchCount;
|
||||
|
||||
Debug.Assert(_ctx.Stats != null, "[EquipmentManager] 缺少 PlayerStats,护符效果无法修改属性。", this);
|
||||
Debug.Assert(_ctx.Feedback != null, "[EquipmentManager] 缺少 PlayerFeedback,护符效果无法触发反馈。", this);
|
||||
Debug.Assert(_ctx.SkillMods != null, "[EquipmentManager] 缺少 SkillModifierRegistry。", this);
|
||||
}
|
||||
|
||||
// ── 查询属性 ─────────────────────────────────────────────────────────
|
||||
public int UsedNotches => _usedNotches; // 缓存值,避免每次调用 LINQ Sum
|
||||
public int TotalNotches => _currentNotchCapacity;
|
||||
public IReadOnlyList<CharmSO> Equipped => _equipped;
|
||||
public IReadOnlyList<CharmSO> Collected => _collected;
|
||||
|
||||
// ── 装备操作 ─────────────────────────────────────────────────────────
|
||||
/// <summary>
|
||||
/// 装备护符。返回 null 表示成功;返回错误字符串表示失败原因。
|
||||
/// </summary>
|
||||
public string TryEquipCharm(CharmSO charm)
|
||||
{
|
||||
if (charm == null) return "护符不存在";
|
||||
if (_equipped.Contains(charm)) return "已经装备";
|
||||
if (!_collected.Contains(charm)) return "尚未收集此护符";
|
||||
int remaining = _currentNotchCapacity - UsedNotches;
|
||||
if (charm.notchCost > remaining)
|
||||
return $"笔记不足(需要 {charm.notchCost},剩余 {remaining})";
|
||||
|
||||
_equipped.Add(charm);
|
||||
_usedNotches += charm.notchCost;
|
||||
foreach (var fx in charm.effects) fx?.OnEquip(_ctx);
|
||||
_onCharmEquipped?.Raise(charm);
|
||||
_onEquipmentChanged?.Raise();
|
||||
return null;
|
||||
}
|
||||
|
||||
public void UnequipCharm(CharmSO charm)
|
||||
{
|
||||
if (charm == null || !_equipped.Remove(charm)) return;
|
||||
_usedNotches -= charm.notchCost;
|
||||
foreach (var fx in charm.effects) fx?.OnUnequip(_ctx);
|
||||
_onCharmUnequipped?.Raise(charm);
|
||||
_onEquipmentChanged?.Raise();
|
||||
}
|
||||
|
||||
/// <summary>将护符加入收藏(拾取时调用)。</summary>
|
||||
public void AddToCollection(string charmId)
|
||||
{
|
||||
var charm = _charmCatalog.Find(charmId);
|
||||
if (charm == null) { Debug.LogWarning($"[EquipmentManager] 找不到护符: {charmId}"); return; }
|
||||
if (!_collected.Contains(charm))
|
||||
_collected.Add(charm);
|
||||
}
|
||||
|
||||
public void IncreaseNotches(int amount)
|
||||
{
|
||||
_currentNotchCapacity += amount;
|
||||
_onEquipmentChanged?.Raise();
|
||||
}
|
||||
|
||||
// ── ISaveable ────────────────────────────────────────────────────────
|
||||
public void OnSave(SaveData data)
|
||||
{
|
||||
data.Equipment.EquippedCharmIds.Clear();
|
||||
data.Equipment.EquippedCharmIds.AddRange(_equipped.Select(c => c.charmId));
|
||||
data.Equipment.OwnedCharmIds.Clear();
|
||||
data.Equipment.OwnedCharmIds.AddRange(_collected.Select(c => c.charmId));
|
||||
data.Equipment.NotchesUsed = UsedNotches;
|
||||
}
|
||||
|
||||
public void OnLoad(SaveData data)
|
||||
{
|
||||
// 卸下所有当前护符(不触发事件,逆序遍历避免 ToList() GC 分配)
|
||||
for (int i = _equipped.Count - 1; i >= 0; i--)
|
||||
foreach (var fx in _equipped[i].effects) fx?.OnUnequip(_ctx);
|
||||
_equipped.Clear();
|
||||
_usedNotches = 0;
|
||||
|
||||
// 从 CharmCatalog 按 ID 恢复收藏并重新装备
|
||||
_collected.Clear();
|
||||
foreach (var id in data.Equipment.OwnedCharmIds)
|
||||
{
|
||||
var charm = _charmCatalog.Find(id);
|
||||
if (charm != null && !_collected.Contains(charm))
|
||||
_collected.Add(charm);
|
||||
}
|
||||
|
||||
foreach (var id in data.Equipment.EquippedCharmIds)
|
||||
{
|
||||
var charm = _charmCatalog.Find(id);
|
||||
if (charm != null) TryEquipCharm(charm);
|
||||
}
|
||||
|
||||
_onEquipmentChanged?.Raise();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Equipment/EquipmentManager.cs.meta
Normal file
11
Assets/Scripts/Equipment/EquipmentManager.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04d451c14acfa56429344271570fa468
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
32
Assets/Scripts/Equipment/ICharmEffect.cs
Normal file
32
Assets/Scripts/Equipment/ICharmEffect.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using BaseGames.Player;
|
||||
using BaseGames.Combat;
|
||||
using BaseGames.Core.Events;
|
||||
using BaseGames.Skills;
|
||||
using BaseGames.Feedback;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 护符效果接口(架构 09_ProgressionModule §4)。
|
||||
/// 所有护符效果均实现此接口,支持 [SerializeReference] 多态序列化。
|
||||
/// </summary>
|
||||
public interface ICharmEffect
|
||||
{
|
||||
void OnEquip(EquipmentContext ctx);
|
||||
void OnUnequip(EquipmentContext ctx);
|
||||
string GetEffectDescription();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 护符效果上下文:避免接口直接依赖具体 Manager 类(架构 09_ProgressionModule §4)。
|
||||
/// </summary>
|
||||
public struct EquipmentContext
|
||||
{
|
||||
public PlayerStats Stats;
|
||||
public PlayerFeedback Feedback;
|
||||
public IEventChannelRegistry Events; // SO 事件频道注册表
|
||||
public SkillModifierRegistry SkillMods; // 技能修改器注册表
|
||||
public WeaponManager WeaponMgr; // 武器切换管理器
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Equipment/ICharmEffect.cs.meta
Normal file
11
Assets/Scripts/Equipment/ICharmEffect.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a1ea5fc438177044b0e339353c4bc1b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
25
Assets/Scripts/Equipment/ToolCatalogSO.cs
Normal file
25
Assets/Scripts/Equipment/ToolCatalogSO.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 工具目录 SO。
|
||||
/// 全局唯一资产(Assets/Data/Equipment/ToolCatalog.asset),
|
||||
/// 通过 toolId 查找 ToolSO 引用。
|
||||
/// 由 ToolSlotManager 在 OnLoad 时查询以恢复槽位工具引用。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Equipment/ToolCatalog")]
|
||||
public class ToolCatalogSO : ScriptableObject
|
||||
{
|
||||
[SerializeField] private ToolSO[] _tools;
|
||||
|
||||
/// <summary>按 toolId 查找工具,找不到返回 null。</summary>
|
||||
public ToolSO Find(string toolId)
|
||||
{
|
||||
if (_tools == null || string.IsNullOrEmpty(toolId)) return null;
|
||||
foreach (var tool in _tools)
|
||||
if (tool != null && tool.toolId == toolId) return tool;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Equipment/ToolCatalogSO.cs.meta
Normal file
11
Assets/Scripts/Equipment/ToolCatalogSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4522e34b162145c458e23d655e9f30f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
51
Assets/Scripts/Equipment/ToolSO.cs
Normal file
51
Assets/Scripts/Equipment/ToolSO.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using BaseGames.Player.States;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 工具使用效果接口(架构 09_ProgressionModule §7)。
|
||||
/// </summary>
|
||||
public interface IToolEffect
|
||||
{
|
||||
void Use(PlayerController player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带冷却时间的工具接口(可选实现)。
|
||||
/// </summary>
|
||||
public interface IToolCooldown
|
||||
{
|
||||
float CooldownDuration { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 典型实现:治疗药水效果。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class HealToolEffect : IToolEffect
|
||||
{
|
||||
public int HealAmount = 4;
|
||||
|
||||
public void Use(PlayerController player) => player.Stats.HealHP(HealAmount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主动工具数据 SO(架构 09_ProgressionModule §7)。
|
||||
/// 资产路径: Assets/ScriptableObjects/Equipment/Tools/Tool_{Name}.asset
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Equipment/Tool")]
|
||||
public class ToolSO : ScriptableObject
|
||||
{
|
||||
public string toolId;
|
||||
public string displayNameKey;
|
||||
public Sprite icon;
|
||||
|
||||
[Tooltip("-1 = 无限使用次数")]
|
||||
public int maxUses = 1;
|
||||
|
||||
[SerializeReference]
|
||||
public IToolEffect effect; // 工具使用效果(多态)
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Equipment/ToolSO.cs.meta
Normal file
11
Assets/Scripts/Equipment/ToolSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 158ac2f8c32186742b9ed83d2332c646
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
91
Assets/Scripts/Equipment/ToolSlotManager.cs
Normal file
91
Assets/Scripts/Equipment/ToolSlotManager.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.Core.Events;
|
||||
using BaseGames.Core.Save;
|
||||
using BaseGames.Player.States;
|
||||
|
||||
namespace BaseGames.Equipment
|
||||
{
|
||||
/// <summary>
|
||||
/// 工具槽管理器(架构 09_ProgressionModule §7.5)。
|
||||
/// 管理玩家的 2 个工具槽(装备、使用、冷却)。
|
||||
/// 实现 ISaveable 以持久化槽位状态。
|
||||
/// </summary>
|
||||
public class ToolSlotManager : MonoBehaviour, ISaveable
|
||||
{
|
||||
private const int SlotCount = 2;
|
||||
|
||||
[SerializeField] private ToolCatalogSO _toolCatalog;
|
||||
[SerializeField] private ToolSO[] _slots = new ToolSO[SlotCount];
|
||||
[SerializeField] private int[] _remainingUses = new int[SlotCount]; // -1 = 无限
|
||||
[SerializeField] private ToolUsedEventChannelSO _onToolUsed;
|
||||
|
||||
private readonly float[] _cooldowns = new float[SlotCount];
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Debug.Assert(_toolCatalog != null, "[ToolSlotManager] _toolCatalog 未赋值,请在 Inspector 中指定 ToolCatalogSO。", this);
|
||||
}
|
||||
|
||||
// ── 装备 ─────────────────────────────────────────────────────────────
|
||||
public void EquipTool(int slotIndex, ToolSO tool)
|
||||
{
|
||||
if (slotIndex < 0 || slotIndex >= SlotCount) return;
|
||||
_slots[slotIndex] = tool;
|
||||
_remainingUses[slotIndex] = tool != null ? tool.maxUses : 0;
|
||||
_cooldowns[slotIndex] = 0f;
|
||||
}
|
||||
|
||||
// ── 使用 ─────────────────────────────────────────────────────────────
|
||||
public bool TryUseTool(int slotIndex, PlayerController player)
|
||||
{
|
||||
if (slotIndex < 0 || slotIndex >= SlotCount) return false;
|
||||
var tool = _slots[slotIndex];
|
||||
if (tool == null) return false;
|
||||
if (_cooldowns[slotIndex] > 0f) return false;
|
||||
if (_remainingUses[slotIndex] == 0) return false; // 已耗尽(-1 = 无限不触发)
|
||||
|
||||
tool.effect?.Use(player);
|
||||
|
||||
if (_remainingUses[slotIndex] > 0) _remainingUses[slotIndex]--;
|
||||
_cooldowns[slotIndex] = tool is IToolCooldown tc ? tc.CooldownDuration : 0f;
|
||||
|
||||
_onToolUsed?.Raise(new ToolUsedPayload
|
||||
{
|
||||
SlotIndex = slotIndex,
|
||||
ToolId = tool.toolId
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
for (int i = 0; i < SlotCount; i++)
|
||||
if (_cooldowns[i] > 0f) _cooldowns[i] -= Time.deltaTime;
|
||||
}
|
||||
|
||||
// ── 查询 ─────────────────────────────────────────────────────────────
|
||||
public ToolSO GetTool(int slotIndex) => _slots[slotIndex];
|
||||
public int GetRemainingUses(int slotIndex) => _remainingUses[slotIndex];
|
||||
public float GetCooldownRatio(int slotIndex)
|
||||
{
|
||||
if (_slots[slotIndex] is IToolCooldown tc && tc.CooldownDuration > 0f)
|
||||
return _cooldowns[slotIndex] / tc.CooldownDuration;
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// ── ISaveable ────────────────────────────────────────────────────────
|
||||
public void OnSave(SaveData data)
|
||||
{
|
||||
data.Tools.ToolSlot0 = _slots[0]?.toolId;
|
||||
data.Tools.ToolSlot1 = _slots[1]?.toolId;
|
||||
}
|
||||
|
||||
public void OnLoad(SaveData data)
|
||||
{
|
||||
_cooldowns[0] = _cooldowns[1] = 0f;
|
||||
|
||||
EquipTool(0, _toolCatalog.Find(data.Tools.ToolSlot0));
|
||||
EquipTool(1, _toolCatalog.Find(data.Tools.ToolSlot1));
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Equipment/ToolSlotManager.cs.meta
Normal file
11
Assets/Scripts/Equipment/ToolSlotManager.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2efface7a79a85458455ce78bf8e5d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user