Files
zeling_v2/Assets/_Game/Scripts/Enemies/Abilities/EnemyAbilityBase.cs
Joywayer bcd8b0e90b feat: Update enemy AI and movement systems
- 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.
2026-05-29 17:01:59 +08:00

186 lines
7.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 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;
}
}
}