- Implemented WeaponFeedback class for handling weapon-related feedbacks such as hit effects and attack sounds. - Added meta file for AddressableManagerWindow to manage addressable assets. - Included a new jump.data file for profiler data.
220 lines
9.3 KiB
C#
220 lines
9.3 KiB
C#
using UnityEngine;
|
||
using Animancer;
|
||
using System.Collections.Generic;
|
||
using BaseGames.Player;
|
||
using BaseGames.Input;
|
||
using BaseGames.Combat;
|
||
using BaseGames.Feedback;
|
||
|
||
namespace BaseGames.Skills
|
||
{
|
||
/// <summary>
|
||
/// 技能管理器(架构 09_ProgressionModule §9)。
|
||
/// 挂在 Player 上,订阅输入事件并处理所有技能施放逻辑。
|
||
/// 通过 UpdateSkillSet() 由 FormController 注入当前形态技能。
|
||
/// 直接使用 AnimancerComponent 播放动画,无需 SoulSkillState FSM。
|
||
/// </summary>
|
||
public class SkillManager : MonoBehaviour
|
||
{
|
||
[Header("依赖引用")]
|
||
[SerializeField] private PlayerStats _stats;
|
||
[SerializeField] private AnimancerComponent _animancer;
|
||
[SerializeField] private InputReaderSO _input;
|
||
[SerializeField] private FormController _formController;
|
||
[SerializeField] private SkillModifierRegistry _modifiers;
|
||
|
||
[Header("技能挂载点")]
|
||
[SerializeField] private Transform _skillSocket; // [SkillSocket] 子节点
|
||
|
||
[Header("形态技能映射")]
|
||
[SerializeField] private FormSkillSet[] _formSkillSets; // 按 FormType 配置,与 FormConfigSO.forms[] 对应
|
||
|
||
[System.Serializable]
|
||
private struct FormSkillSet
|
||
{
|
||
public FormType formType;
|
||
public FormSkillSO soulSkill;
|
||
public FormSkillSO spiritSkill1;
|
||
public FormSkillSO spiritSkill2;
|
||
}
|
||
|
||
// 当前形态技能集(绑定到对应输入槽)
|
||
private FormSkillSO _soulSkill;
|
||
private FormSkillSO _spirit1;
|
||
private FormSkillSO _spirit2;
|
||
private IFeedbackPlayer _feedback;
|
||
|
||
// 冷却字典(FormSkillSO → 剩余冷却秒数),UpdateSkillSet 时重建
|
||
private readonly Dictionary<FormSkillSO, float> _cooldowns = new(3);
|
||
// 无分配 Update 遍历用的快照数组
|
||
private FormSkillSO[] _activeSkills = System.Array.Empty<FormSkillSO>();
|
||
// 技能 HitBox 对象池:prefab → 已创建的实例列表,通过 activeSelf 判断是否可复用
|
||
private readonly Dictionary<GameObject, List<SkillHitBoxInstance>> _hitBoxPools = new();
|
||
|
||
// ── 生命周期 ──────────────────────────────────────────────────────────
|
||
private void Awake()
|
||
{
|
||
_feedback = GetComponentInChildren<IFeedbackPlayer>()
|
||
?? GetComponentInParent<IFeedbackPlayer>()
|
||
?? NullFeedbackPlayer.Instance;
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
if (_input != null)
|
||
{
|
||
_input.SoulSkillEvent += TrySoulSkill;
|
||
_input.SpiritSkill1StartedEvent += TrySpiritSkill1;
|
||
_input.SpiritSkill2StartedEvent += TrySpiritSkill2;
|
||
}
|
||
if (_formController != null)
|
||
{
|
||
_formController.OnFormChanged += OnFormChanged;
|
||
// 立即同步当前形态的技能集
|
||
ApplyFormSkills(_formController.CurrentForm);
|
||
}
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
if (_input != null)
|
||
{
|
||
_input.SoulSkillEvent -= TrySoulSkill;
|
||
_input.SpiritSkill1StartedEvent -= TrySpiritSkill1;
|
||
_input.SpiritSkill2StartedEvent -= TrySpiritSkill2;
|
||
}
|
||
if (_formController != null)
|
||
_formController.OnFormChanged -= OnFormChanged;
|
||
}
|
||
|
||
private void OnFormChanged()
|
||
{
|
||
ApplyFormSkills(_formController.CurrentForm);
|
||
}
|
||
|
||
private void ApplyFormSkills(FormSO form)
|
||
{
|
||
if (form == null || _formSkillSets == null) { UpdateSkillSet(null, null, null); return; }
|
||
foreach (var s in _formSkillSets)
|
||
{
|
||
if (s.formType == form.formType)
|
||
{
|
||
UpdateSkillSet(s.soulSkill, s.spiritSkill1, s.spiritSkill2);
|
||
return;
|
||
}
|
||
}
|
||
UpdateSkillSet(null, null, null);
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
for (int i = 0; i < _activeSkills.Length; i++)
|
||
{
|
||
var s = _activeSkills[i];
|
||
if (_cooldowns.TryGetValue(s, out float cd) && cd > 0f)
|
||
_cooldowns[s] = cd - Time.deltaTime;
|
||
}
|
||
}
|
||
|
||
// ── 公共 API ─────────────────────────────────────────────────────────
|
||
/// <summary>切换形态时由 FormController 调用,注入当前形态的三个技能。</summary>
|
||
public void UpdateSkillSet(FormSkillSO soul, FormSkillSO spirit1, FormSkillSO spirit2)
|
||
{
|
||
_soulSkill = soul;
|
||
_spirit1 = spirit1;
|
||
_spirit2 = spirit2;
|
||
|
||
_cooldowns.Clear();
|
||
// 构建无分配遍历快照(固定大小数组,避免 List + ToArray GC)
|
||
int count = (soul != null ? 1 : 0) + (spirit1 != null ? 1 : 0) + (spirit2 != null ? 1 : 0);
|
||
if (_activeSkills.Length != count)
|
||
_activeSkills = count > 0 ? new FormSkillSO[count] : System.Array.Empty<FormSkillSO>();
|
||
int idx = 0;
|
||
if (soul != null) { _cooldowns[soul] = 0f; _activeSkills[idx++] = soul; }
|
||
if (spirit1 != null) { _cooldowns[spirit1] = 0f; _activeSkills[idx++] = spirit1; }
|
||
if (spirit2 != null) { _cooldowns[spirit2] = 0f; _activeSkills[idx] = spirit2; }
|
||
}
|
||
|
||
// ── 内部施放逻辑 ─────────────────────────────────────────────────────
|
||
private void TrySoulSkill() => TryCastSkill(_soulSkill);
|
||
private void TrySpiritSkill1() => TryCastSkill(_spirit1);
|
||
private void TrySpiritSkill2() => TryCastSkill(_spirit2);
|
||
|
||
private void TryCastSkill(FormSkillSO skill)
|
||
{
|
||
if (skill == null) return;
|
||
|
||
var p = _modifiers != null
|
||
? _modifiers.GetEffectiveParams(skill)
|
||
: EffectiveSkillParams.FromBase(skill);
|
||
|
||
if (!_cooldowns.TryGetValue(skill, out float cooldown) || cooldown > 0f) return;
|
||
|
||
// 消耗资源
|
||
bool consumed = skill.resourceType == SkillResourceType.SoulPower
|
||
? _stats.ConsumeSoulPower(p.effectiveCost)
|
||
: _stats.ConsumeSpiritPower(p.effectiveCost);
|
||
if (!consumed) return;
|
||
|
||
_cooldowns[skill] = p.effectiveCooldown;
|
||
|
||
// 施放反馈
|
||
_feedback.TriggerPreset("skill_cast");
|
||
|
||
// 播放动画(优先修改器动画,回退技能默认动画)
|
||
var clip = p.effectiveAnimation.Clip != null
|
||
? p.effectiveAnimation
|
||
: skill.castAnimation;
|
||
if (clip.Clip != null && _animancer != null)
|
||
_animancer.Play(clip);
|
||
|
||
// 生成 HitBox Prefab(近战/爆炸类技能)
|
||
if (skill.SkillHitBoxPrefab != null)
|
||
{
|
||
var socket = _skillSocket != null ? _skillSocket : transform;
|
||
var inst = GetOrCreateHitBox(skill.SkillHitBoxPrefab, socket);
|
||
inst?.Activate(skill.damageSource, transform);
|
||
inst?.AutoReturnAfter(skill.castLockDuration > 0f ? skill.castLockDuration : 0.5f);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从对象池获取或新建 SkillHitBoxInstance。
|
||
/// 扫描该 prefab 已创建的实例列表,找到首个未激活的复用;
|
||
/// 无可用实例时 Instantiate,并追加到列表供下次复用。
|
||
/// </summary>
|
||
private SkillHitBoxInstance GetOrCreateHitBox(GameObject prefab, Transform socket)
|
||
{
|
||
if (!_hitBoxPools.TryGetValue(prefab, out var list))
|
||
_hitBoxPools[prefab] = list = new List<SkillHitBoxInstance>(2);
|
||
|
||
for (int i = 0; i < list.Count; i++)
|
||
{
|
||
var pooled = list[i];
|
||
if (pooled != null && !pooled.gameObject.activeSelf)
|
||
{
|
||
pooled.transform.SetParent(socket);
|
||
pooled.transform.SetPositionAndRotation(socket.position, socket.rotation);
|
||
pooled.gameObject.SetActive(true);
|
||
return pooled;
|
||
}
|
||
}
|
||
|
||
var go = Object.Instantiate(prefab, socket.position, socket.rotation, socket);
|
||
var inst = go.GetComponent<SkillHitBoxInstance>();
|
||
if (inst != null) list.Add(inst);
|
||
return inst;
|
||
}
|
||
|
||
// ── 属性查询 ─────────────────────────────────────────────────────────
|
||
public FormSkillSO SoulSkill => _soulSkill;
|
||
public FormSkillSO Spirit1 => _spirit1;
|
||
public FormSkillSO Spirit2 => _spirit2;
|
||
|
||
public float SoulCooldownRatio =>
|
||
(_soulSkill != null && _soulSkill.cooldown > 0 &&
|
||
_cooldowns.TryGetValue(_soulSkill, out float cd))
|
||
? Mathf.Clamp01(cd / _soulSkill.cooldown) : 0f;
|
||
}
|
||
}
|