Files
zeling_v2/Assets/_Game/Scripts/Enemies/Abilities/ChargeAbility.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

96 lines
4.0 KiB
C#

using System.Collections;
using UnityEngine;
using BaseGames.Combat;
namespace BaseGames.Enemies.Abilities
{
/// <summary>
/// 直线冲锋能力:朝目标方向高速直线冲锋,撞墙时进入硬直恢复。
/// 流程:朝向目标 → 蓄力 → 高速直线冲锋直到撞墙或超距 → 恢复。
/// 冲锋期间 HitBox 持续激活;可选撞墙时硬直。
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
public sealed class ChargeAbility : EnemyAbilityBase
{
[Header("冲锋参数")]
[SerializeField] [Min(0.1f)] private float _chargeSpeed = 14f;
[SerializeField] [Min(0.1f)] private float _maxDistance = 12f;
[SerializeField] private float _windupTime = 0.4f;
[SerializeField] private float _recoveryTime = 0.6f;
[SerializeField] private float _wallCheckDist = 0.4f;
[SerializeField] private LayerMask _wallMask;
[SerializeField] private bool _stunOnWallHit = true;
[SerializeField] private float _wallStunTime = 0.8f;
[Header("HitBox")]
[SerializeField] private HitBox _chargeHitBox;
[Header("动画 Key")]
[SerializeField] private string _windupAnimSlot = "";
[SerializeField] private string _chargeAnimSlot = "";
private Rigidbody2D _rb;
private float _direction;
protected override void Awake()
{
base.Awake();
_rb = GetComponentInParent<Rigidbody2D>();
}
protected override IEnumerator ExecuteCoroutine()
{
if (_rb == null) yield break;
FaceTarget(_enemy != null ? _enemy.PlayerTransform : null);
_direction = _transform.localScale.x >= 0f ? 1f : -1f;
var seq = _config != null ? _config.attackSequence : null;
var windupAtk = (seq != null && seq.Length > 0) ? seq[0] : null;
var chargeAtk = (seq != null && seq.Length > 1) ? seq[1] : windupAtk;
// Windup
if (windupAtk != null && windupAtk.clip != null && _animancer != null)
_animancer.Play(windupAtk.clip);
_rb.velocity = new Vector2(0f, _rb.velocity.y);
yield return EnemyAbilityWaits.Get(_windupTime);
// Charge
Phase = AbilityRunState.Active;
if (chargeAtk != null && chargeAtk.clip != null && _animancer != null)
_animancer.Play(chargeAtk.clip);
if (_chargeHitBox != null)
_chargeHitBox.Activate(chargeAtk != null ? chargeAtk.damageSource : null, _transform);
Vector2 start = _rb.position;
bool wallHit = false;
float maxTime = (_maxDistance / _chargeSpeed) * 3f; // 3 倍容差兜底,防止极端物理情况下死循环
float elapsed = 0f;
while (true)
{
_rb.velocity = new Vector2(_chargeSpeed * _direction, _rb.velocity.y);
yield return new WaitForFixedUpdate();
elapsed += Time.fixedDeltaTime;
if (elapsed >= maxTime) break; // 超时保护
float traveled = Mathf.Abs(_rb.position.x - start.x);
if (traveled >= _maxDistance) break;
var hit = Physics2D.Raycast(_rb.position, new Vector2(_direction, 0f), _wallCheckDist, _wallMask);
if (hit.collider != null) { wallHit = true; break; }
}
_rb.velocity = new Vector2(0f, _rb.velocity.y);
if (_chargeHitBox != null && _chargeHitBox.IsActive) _chargeHitBox.Deactivate();
// Recovery
float recover = wallHit && _stunOnWallHit ? _wallStunTime : _recoveryTime;
yield return EnemyAbilityWaits.Get(recover);
}
protected override void OnInterrupted(InterruptReason reason)
{
if (_chargeHitBox != null && _chargeHitBox.IsActive) _chargeHitBox.Deactivate();
if (_rb != null) _rb.velocity = Vector2.zero; // 完整清零速度,包含 y 轴
}
}
}