using System.Collections;
using UnityEngine;
using BaseGames.Combat;
using BaseGames.Core;
using BaseGames.Core.Pool;
namespace BaseGames.Enemies.Abilities
{
///
/// 远程射弹能力(扇形 / 弹幕)。
/// 每段攻击按 projectileFireT 触发,从池中生成 projectileCount 个抛射物,
/// 按 spreadAngleDeg 均布角度。
///
public sealed class ProjectileAttackAbility : EnemyAbilityBase
{
[Header("发射点(为空则使用本物体位置)")]
[SerializeField] private Transform _muzzle;
[Header("行为")]
[SerializeField] private bool _faceTargetOnStart = true;
private IObjectPoolService _pool;
protected override void Awake()
{
base.Awake();
if (_muzzle == null) _muzzle = _transform;
}
protected override IEnumerator ExecuteCoroutine()
{
_pool ??= ServiceLocator.GetOrDefault();
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 PlayShot(atk);
if (atk.postDelay > 0f) yield return EnemyAbilityWaits.Get(atk.postDelay);
}
}
private IEnumerator PlayShot(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;
}
float fireAbs = atk.projectileFireT * duration;
float t = 0f;
bool fired = false;
while (t < duration)
{
t += Time.deltaTime;
if (!fired && t >= fireAbs)
{
SpawnVolley(atk);
fired = true;
}
yield return null;
}
if (!fired) SpawnVolley(atk);
}
private void SpawnVolley(EnemyAttackSO atk)
{
if (_pool == null || atk.projectileConfig == null) return;
var cfg = atk.projectileConfig;
if (string.IsNullOrEmpty(cfg.PoolKey)) return;
Vector2 baseDir = GetAimDirection();
int count = Mathf.Max(1, atk.projectileCount);
float spread = atk.spreadAngleDeg;
float startAngle = -spread * 0.5f;
float step = count > 1 ? spread / (count - 1) : 0f;
var src = atk.damageSource != null ? atk.damageSource : cfg.DamageSource;
int layer = gameObject.layer;
for (int i = 0; i < count; i++)
{
float angle = startAngle + step * i;
Vector2 dir = Rotate(baseDir, angle);
var go = _pool.Spawn(cfg.PoolKey, _muzzle.position, Quaternion.identity);
if (go == null)
{
Debug.LogWarning($"[ProjectileAttackAbility] 对象池 '{cfg.PoolKey}' 无法获取弹体,请检查池配置或容量。", this);
continue;
}
var proj = go.GetComponent();
if (proj == null)
{
Debug.LogWarning($"[ProjectileAttackAbility] 弹体 '{go.name}' 缺少 Projectile 组件,请检查 Prefab 配置。", this);
continue;
}
var info = DamageInfo.From(src, dir, _muzzle.position, layer, proj);
proj.Initialize(cfg, info, dir, layer);
}
}
private Vector2 GetAimDirection()
{
if (_enemy != null && _enemy.PlayerTransform != null)
return ((Vector2)_enemy.PlayerTransform.position - (Vector2)_muzzle.position).normalized;
return _transform.localScale.x >= 0f ? Vector2.right : Vector2.left;
}
private static Vector2 Rotate(Vector2 v, float degrees)
{
float rad = degrees * Mathf.Deg2Rad;
float c = Mathf.Cos(rad), s = Mathf.Sin(rad);
return new Vector2(v.x * c - v.y * s, v.x * s + v.y * c);
}
}
}