- Enhanced Physics2D layer collision report with new interactions between Player and Enemy layers. - Refactored BD_InvestigateLastKnown to streamline animation handling and improve readability. - Simplified BD_MaintainCombatDistance by consolidating movement stop logic. - Updated BD_MoveToPlayer to set AI phase on start. - Improved BD_Patrol logic with better handling of stuck states and path failures. - Enhanced BD_PatrolWaypoints to manage stuck conditions and retry logic more effectively. - Refined BD_ReturnToHome to remove unnecessary animation calls. - Updated BD_WalkRandom to ensure AI phase is set correctly on start. - Improved EnemyAbilityBase to delegate target facing to the movement system. - Enhanced EnemyBase with new movement methods for better control. - Refactored EnemyMovement to introduce a new input system for handling movement and facing. - Added EnemyMoveInput struct to encapsulate movement intentions. - Updated Physics2DSettings to reflect new layer collision matrix. - Introduced RTK CLI instructions for optimized command usage.
186 lines
7.9 KiB
C#
186 lines
7.9 KiB
C#
using System.Collections;
|
||
using UnityEngine;
|
||
using Animancer;
|
||
using BaseGames.Core;
|
||
using BaseGames.Core.Pool;
|
||
|
||
namespace BaseGames.Enemies.Abilities
|
||
{
|
||
/// <summary>
|
||
/// 敌人能力抽象基类(架构 07_EnemyModule §8.4)。
|
||
/// 责任:生命周期管理、冷却计时、中断分发。子类只实现 <see cref="ExecuteCoroutine"/>。
|
||
///
|
||
/// 设计要点:
|
||
/// - 单一执行实例:同一能力同时只有一个协程在跑。
|
||
/// - 协程内 yield WaitForSeconds 复用 <see cref="EnemyAbilityWaits"/>(无 GC)。
|
||
/// - 受击/死亡时由 <see cref="EnemyBase"/> 调用 <see cref="Interrupt"/>。
|
||
/// </summary>
|
||
[DisallowMultipleComponent]
|
||
public abstract class EnemyAbilityBase : MonoBehaviour
|
||
{
|
||
[Header("配置 SO")]
|
||
[SerializeField] protected EnemyAbilitySO _config;
|
||
|
||
// 缓存依赖(Awake 填入,热路径无 GetComponent)
|
||
protected EnemyBase _enemy;
|
||
protected AnimancerComponent _animancer;
|
||
protected Transform _transform;
|
||
|
||
private Coroutine _runner;
|
||
private float _cooldownEndTime = -1f;
|
||
private bool _isRunning;
|
||
|
||
// ── 公共状态 ─────────────────────────────────────────────────────
|
||
public EnemyAbilitySO Config => _config;
|
||
public bool IsRunning => _isRunning;
|
||
public AbilityRunState Phase { get; protected set; } = AbilityRunState.Idle;
|
||
public float CooldownRemaining => Mathf.Max(0f, _cooldownEndTime - Time.time);
|
||
public bool IsOnCooldown => CooldownRemaining > 0f;
|
||
|
||
/// <summary>能力被外部中断时触发(BD Task / 状态机订阅用)。</summary>
|
||
public event System.Action<InterruptReason> Interrupted;
|
||
|
||
/// <summary>BD 任务统一查询入口:当前是否可用(冷却完毕且未执行中)。</summary>
|
||
public virtual bool CanUse => !_isRunning && !IsOnCooldown && _enemy != null && _enemy.IsAlive;
|
||
|
||
protected virtual void Awake()
|
||
{
|
||
_enemy = GetComponentInParent<EnemyBase>();
|
||
_animancer = _enemy != null ? _enemy.Animancer : GetComponentInParent<AnimancerComponent>();
|
||
_transform = transform;
|
||
if (_enemy == null)
|
||
Debug.LogError($"[EnemyAbilityBase] {GetType().Name} 找不到 EnemyBase。", this);
|
||
if (_animancer == null)
|
||
Debug.LogWarning($"[EnemyAbilityBase] {GetType().Name} 找不到 AnimancerComponent,动画能力将无法播放动画。", this);
|
||
}
|
||
|
||
protected virtual void OnDisable()
|
||
{
|
||
if (_isRunning) Interrupt(InterruptReason.ExternalRequest);
|
||
}
|
||
|
||
// ── 执行 ─────────────────────────────────────────────────────────
|
||
/// <summary>
|
||
/// 启动能力。重复调用、冷却中或已运行将返回 false。
|
||
/// 若 <see cref="EnemyAbilitySO.exclusionGroup"/> 非空,会先中断同组其他能力(互斥)。
|
||
/// </summary>
|
||
public bool Execute()
|
||
{
|
||
if (!CanUse) return false;
|
||
|
||
// 互斥组:启动前中断同组正在运行的其他能力
|
||
if (_config != null && !string.IsNullOrEmpty(_config.exclusionGroup))
|
||
_enemy?.Abilities.InterruptGroup(_config.exclusionGroup, InterruptReason.ExternalRequest);
|
||
|
||
_runner = StartCoroutine(RunInternal());
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 强制启动能力,忽略冷却检查(连段语义:外部组合技调用子能力时使用)。
|
||
/// 若能力正在运行则先中断再重启。
|
||
/// </summary>
|
||
public bool ForceExecute()
|
||
{
|
||
if (_enemy == null || !_enemy.IsAlive) return false;
|
||
if (_isRunning) Interrupt(InterruptReason.ExternalRequest);
|
||
_runner = StartCoroutine(RunInternal());
|
||
return true;
|
||
}
|
||
|
||
private IEnumerator RunInternal()
|
||
{
|
||
_isRunning = true;
|
||
Phase = AbilityRunState.Telegraph;
|
||
try
|
||
{
|
||
if (_config != null && _config.telegraphDuration > 0f)
|
||
yield return TelegraphRoutine();
|
||
|
||
Phase = AbilityRunState.Windup;
|
||
yield return ExecuteCoroutine();
|
||
Phase = AbilityRunState.Recovery;
|
||
}
|
||
finally
|
||
{
|
||
_isRunning = false;
|
||
_runner = null;
|
||
_cooldownEndTime = Time.time + (_config != null ? _config.cooldown : 0f);
|
||
if (Phase != AbilityRunState.Interrupted) Phase = AbilityRunState.Idle;
|
||
OnAbilityEnded();
|
||
}
|
||
}
|
||
|
||
/// <summary>子类实现:能力主体。可分多段、含 HitBox 激活/弹幕生成/物理推进等。</summary>
|
||
protected abstract IEnumerator ExecuteCoroutine();
|
||
|
||
/// <summary>预警阶段(默认生成 VFX 后等待 telegraphDuration)。子类可重写。</summary>
|
||
protected virtual IEnumerator TelegraphRoutine()
|
||
{
|
||
if (!string.IsNullOrEmpty(_config.telegraphVfxKey))
|
||
{
|
||
var pool = ServiceLocator.GetOrDefault<IObjectPoolService>();
|
||
pool?.Spawn(_config.telegraphVfxKey, _transform.position, Quaternion.identity);
|
||
}
|
||
yield return EnemyAbilityWaits.Get(_config.telegraphDuration);
|
||
}
|
||
|
||
/// <summary>能力结束钩子(被中断或正常结束都会调用)。</summary>
|
||
protected virtual void OnAbilityEnded() { }
|
||
|
||
/// <summary>中断当前执行。冷却仍会按配置计入。</summary>
|
||
public void Interrupt(InterruptReason reason)
|
||
{
|
||
if (!_isRunning) return;
|
||
if (_config != null)
|
||
{
|
||
if (reason == InterruptReason.Hurt && !_config.interruptOnHurt) return;
|
||
if (reason == InterruptReason.Stagger && !_config.interruptOnStagger) return;
|
||
}
|
||
if (_runner != null) StopCoroutine(_runner);
|
||
_runner = null;
|
||
_isRunning = false;
|
||
Phase = AbilityRunState.Interrupted;
|
||
OnInterrupted(reason);
|
||
Interrupted?.Invoke(reason);
|
||
OnAbilityEnded();
|
||
_cooldownEndTime = Time.time + (_config != null ? _config.cooldown * 0.5f : 0f);
|
||
}
|
||
|
||
protected virtual void OnInterrupted(InterruptReason reason) { }
|
||
|
||
/// <summary>子类辅助:朝向目标。委托给 EnemyMovement.FaceTarget 以保持转身动画系统一致。</summary>
|
||
protected void FaceTarget(Transform target)
|
||
{
|
||
if (target == null || _enemy?.Movement == null) return;
|
||
_enemy.Movement.FaceTarget(target.position);
|
||
}
|
||
}
|
||
|
||
/// <summary>WaitForSeconds 池(架构 §10 GC 优化)。能力协程统一通过此获取等待指令。</summary>
|
||
internal static class EnemyAbilityWaits
|
||
{
|
||
private const int MaxCacheSize = 64;
|
||
|
||
private static readonly System.Collections.Generic.Dictionary<float, WaitForSeconds> _cache
|
||
= new System.Collections.Generic.Dictionary<float, WaitForSeconds>(32);
|
||
public static WaitForSeconds Get(float seconds)
|
||
{
|
||
if (seconds <= 0f) return null;
|
||
if (!_cache.TryGetValue(seconds, out var w))
|
||
{
|
||
if (_cache.Count < MaxCacheSize)
|
||
{
|
||
w = new WaitForSeconds(seconds);
|
||
_cache[seconds] = w;
|
||
}
|
||
else
|
||
{
|
||
return new WaitForSeconds(seconds);
|
||
}
|
||
}
|
||
return w;
|
||
}
|
||
}
|
||
}
|