using System.Collections; using Animancer; using BaseGames.Combat; using BaseGames.Core; using BaseGames.Core.Pool; using UnityEngine; using UnityEngine.Events; namespace BaseGames.Enemies.Boss { /// /// 嘲风 Boss 主脚本。 /// /// Phase 0(地面):4 技能加权随机(回旋扇/扇形连击/小龙卷/大龙卷)。 /// Phase 1(空中):风石技能 + 击落计数机制。 /// /// 阶段过渡流程: /// 1. BossBase.BeginPhaseTransition → OnBeginPhaseTransition(1) 立即播放过渡动画 + 开始浮空 /// 2. 无敌期结束(≥ _riseDuration+buffer)→ BossBase.EnterPhase(1) 广播阶段切换事件 /// /// ⚠️ 动画由本脚本通过 Animancer.Play() 完整控制,不在 BD 中调用 BD_PlayAnimation。 /// public class ChaoFengBoss : BossBase { [Header("浮空 / 击落")] [SerializeField] private ChaoFengFloatController _floatController; [SerializeField] private ChaoFengKnockdownCounter _knockdownCounter; [Header("阶段过渡动画")] [SerializeField] private ClipTransition _phaseTransitionClip; [Header("回旋扇收招动画")] [SerializeField] private ClipTransition _boomerangEndClip; [Header("弹体发射点")] [SerializeField] private Transform _boomerangMuzzle; [SerializeField] private Transform _tornadoMuzzle; [SerializeField] private Transform _windStoneMuzzle; [Header("弹体配置(ProjectileConfigSO,PoolKey 须与弹体地址一致)")] [SerializeField] private ProjectileConfigSO _boomerangConfig; [SerializeField] private ProjectileConfigSO _tornadoSmallConfig; [SerializeField] private ProjectileConfigSO _tornadoLargeConfig; [SerializeField] private ProjectileConfigSO _windStoneConfig; [Header("击败演出动画")] [SerializeField] private ClipTransition _defeatStruggleClip; [Tooltip("倒地喘气(循环);与 ChaoFengKnockdownCounter._staggerClip 共用同一 Clip")] [SerializeField] private ClipTransition _defeatPantClip; [SerializeField] private ClipTransition _defeatStandUpClip; [SerializeField] private float _defeatPantDuration = 3f; [Header("白屏回调(可接 CameraManager / VFX Event)")] [SerializeField] private UnityEngine.Events.UnityEvent _onDefeatWhiteFlash; // ── 阶段过渡钩子 ───────────────────────────────────────────────────── /// /// 阶段过渡开始时立即播放过渡动画并启动浮空协程。 /// invincibleDuration 需在 BD_BossPhaseTransition 中配置为 ≥ _riseDuration + 缓冲(约 2.0s)。 /// protected override void OnBeginPhaseTransition(int targetPhase) { if (targetPhase == 1) { if (_phaseTransitionClip.Clip != null) Animancer.Play(_phaseTransitionClip); StartCoroutine(_floatController.FloatUp()); } } // ── 受击转发 ───────────────────────────────────────────────────────── /// /// 转发受击事件至击落计数器。 /// ⚠️ HurtBox 无公开 OnDamageTaken 事件,必须通过此虚方法转发。 /// protected override void OnDamageTaken(DamageInfo info) { base.OnDamageTaken(info); _knockdownCounter?.OnBossHit(info); } // ── 动画事件 ───────────────────────────────────────────────────────── /// /// 回旋扇返回时由 ReturnProjectile 调用,触发收扇动画。 /// public void OnBoomerangReturned() { if (_boomerangEndClip.Clip != null) Animancer.Play(_boomerangEndClip); } // ── 弹体生成 ───────────────────────────────────────────────────────── /// /// 由技能动画 AnimationEvent 触发,生成对应弹体。 /// payload: "boomerang" / "tornado_small" / "tornado_large" / "wind_stone" /// 生成后通过 ProjectileConfigSO 调用 Projectile.Initialize 注入速度/重力/伤害源与方向。 /// public override void SpawnProjectile(string payload) { var pool = ServiceLocator.GetOrDefault(); if (pool == null) return; switch (payload) { case "boomerang": SpawnConfiguredAt(pool, _boomerangConfig, "PROJ_Boomerang", MuzzlePos(_boomerangMuzzle), AimDir(_boomerangMuzzle), proj => (proj as ReturnProjectile)?.SetOwner(transform)); break; case "tornado_small": SpawnConfiguredAt(pool, _tornadoSmallConfig, "PROJ_TornadoSmall", MuzzlePos(_tornadoMuzzle), AimDir(_tornadoMuzzle), null); break; case "tornado_large": // 定点召唤于玩家当前位置(落点锁定) if (PlayerTransform != null) SpawnConfiguredAt(pool, _tornadoLargeConfig, "PROJ_TornadoLarge", PlayerTransform.position, Vector2.down, null); break; case "wind_stone": SpawnConfiguredAt(pool, _windStoneConfig, "PROJ_WindStone", MuzzlePos(_windStoneMuzzle), Vector2.down, null); break; } } private Vector2 MuzzlePos(Transform muzzle) => muzzle != null ? (Vector2)muzzle.position : (Vector2)transform.position; /// 朝向玩家的发射方向;无玩家时退回当前朝向(localScale.x)。 private Vector2 AimDir(Transform muzzle) { Vector2 origin = MuzzlePos(muzzle); if (PlayerTransform != null) return ((Vector2)PlayerTransform.position - origin).normalized; return transform.localScale.x >= 0f ? Vector2.right : Vector2.left; } /// /// 从池取出弹体并用 config 初始化(注入速度/重力/伤害源/方向)。 /// config 为空时仅生成不初始化并告警(需在 Inspector 指定对应 ProjectileConfigSO)。 /// private void SpawnConfiguredAt(IObjectPoolService pool, ProjectileConfigSO config, string fallbackKey, Vector2 pos, Vector2 dir, System.Action onSpawned) { string key = config != null && !string.IsNullOrEmpty(config.PoolKey) ? config.PoolKey : fallbackKey; var go = pool.Spawn(key, pos, Quaternion.identity); if (go == null) return; var proj = go.GetComponent(); if (proj == null) { Debug.LogWarning($"[ChaoFengBoss] 弹体 '{key}' 缺少 Projectile 组件。", this); return; } onSpawned?.Invoke(proj); if (config == null) { Debug.LogWarning($"[ChaoFengBoss] 弹体 '{key}' 未指定 ProjectileConfigSO,无法初始化速度/伤害。", this); return; } var src = config.DamageSource; var info = src != null ? DamageInfo.From(src, dir, pos, gameObject.layer, proj) : default; proj.Initialize(config, info, dir, gameObject.layer); } // ── 击败演出 ───────────────────────────────────────────────────────── protected override void Die() { StartCoroutine(DefeatSequence()); } private IEnumerator DefeatSequence() { StopBehaviorTree(); _knockdownCounter?.ForceEnd(); // Phase 2(空中)先落地 if (CurrentPhase >= 1 && _floatController != null) yield return _floatController.FallDown(); // 空中挣扎(Defeat_Struggle) if (_defeatStruggleClip.Clip != null) { Animancer.Play(_defeatStruggleClip); yield return new WaitForSeconds(_defeatStruggleClip.Clip.length); } // 白屏效果 _onDefeatWhiteFlash?.Invoke(); // 倒地喘气(Defeat_Pant 循环) if (_defeatPantClip.Clip != null) Animancer.Play(_defeatPantClip); yield return new WaitForSeconds(_defeatPantDuration); // 站起(Defeat_StandUp 单次) if (_defeatStandUpClip.Clip != null) { Animancer.Play(_defeatStandUpClip); yield return new WaitForSeconds(_defeatStandUpClip.Clip.length); } // 广播战斗结束、触发结算过场 base.Die(); } } }