Files
zeling_v2/Assets/_Game/Scripts/Skills/SkillManager.cs
Joywayer b7baf7ad6a Add WeaponFeedback component and AddressableManagerWindow meta file
- 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.
2026-05-22 22:03:32 +08:00

220 lines
9.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}