Files
zeling_v2/Assets/_Game/Scripts/Enemies/Abilities/MeleeAttackAbility.cs
Joywayer a1b4e629aa feat: Implement Room Streaming System
- Add RoomStreamingManager to manage room loading and unloading based on player proximity.
- Create StreamingBudgetConfigSO for memory and performance budgeting of the streaming system.
- Introduce TransitionDirector to handle seamless and atmospheric fade transitions between rooms.
- Develop WorldGraph to represent room connectivity and facilitate neighbor queries and distance calculations.
- Implement RoomNode and RoomEdge classes to structure room data and connections.
2026-05-23 19:10:29 +08:00

120 lines
3.9 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 System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BaseGames.Combat;
namespace BaseGames.Enemies.Abilities
{
/// <summary>
/// 近战连击能力。
/// 按 EnemyAbilitySO.attackSequence 顺序播放动画段,每段在 hitBoxEnterT~hitBoxExitT
/// 之间激活对应槽位的 HitBox。
///
/// 设计HitBox 通过命名槽位绑定,避免对 EnemyCombat 的硬依赖;同一敌人多个近战
/// 能力可共享同一 HitBox 槽位(如不同段使用同一把武器)。
/// </summary>
public sealed class MeleeAttackAbility : EnemyAbilityBase
{
[System.Serializable]
public struct HitBoxSlot
{
public string slotName;
public HitBox hitBox;
}
[Header("HitBox 槽位(按名字索引)")]
[SerializeField] private HitBoxSlot[] _hitBoxSlots;
[Header("行为")]
[SerializeField] private bool _faceTargetOnStart = true;
private Dictionary<string, HitBox> _slotMap;
protected override void Awake()
{
base.Awake();
_slotMap = new Dictionary<string, HitBox>(_hitBoxSlots?.Length ?? 0);
if (_hitBoxSlots != null)
{
for (int i = 0; i < _hitBoxSlots.Length; i++)
{
var s = _hitBoxSlots[i];
if (s.hitBox != null && !string.IsNullOrEmpty(s.slotName))
_slotMap[s.slotName] = s.hitBox;
}
}
}
protected override IEnumerator ExecuteCoroutine()
{
var seq = _config != null ? _config.attackSequence : null;
if (seq == null || seq.Length == 0) yield break;
if (_faceTargetOnStart && _enemy != null && _enemy.PlayerTransform != null)
FaceTarget(_enemy.PlayerTransform);
for (int i = 0; i < seq.Length; i++)
{
var atk = seq[i];
if (atk == null) continue;
yield return PlayAttackStep(atk);
if (atk.postDelay > 0f)
yield return EnemyAbilityWaits.Get(atk.postDelay);
}
}
private IEnumerator PlayAttackStep(EnemyAttackSO atk)
{
Phase = AbilityRunState.Active;
float duration = atk.fallbackDuration;
if (atk.clip != null && _animancer != null)
{
var state = _animancer.Play(atk.clip);
if (state != null && state.Length > 0f) duration = state.Length;
}
HitBox hb = null;
if (!string.IsNullOrEmpty(atk.hitBoxSlot))
_slotMap.TryGetValue(atk.hitBoxSlot, out hb);
float enterAbs = atk.hitBoxEnterT * duration;
float exitAbs = atk.hitBoxExitT * duration;
float t = 0f;
bool active = false;
while (t < duration)
{
t += Time.deltaTime;
if (hb != null)
{
if (!active && t >= enterAbs)
{
hb.Activate(atk.damageSource, _transform);
active = true;
}
else if (active && t >= exitAbs)
{
hb.Deactivate();
active = false;
}
}
yield return null;
}
if (active && hb != null) hb.Deactivate();
}
protected override void OnInterrupted(InterruptReason reason)
{
// 确保 HitBox 被关闭,防止中断时残留激活
if (_hitBoxSlots == null) return;
for (int i = 0; i < _hitBoxSlots.Length; i++)
{
var hb = _hitBoxSlots[i].hitBox;
if (hb != null && hb.IsActive) hb.Deactivate();
}
}
}
}