- 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.
96 lines
4.0 KiB
C#
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 轴
|
|
}
|
|
}
|
|
}
|