Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -214,6 +214,7 @@ namespace BaseGames.Enemies
|
||||
private IEnumerator PhaseTransitionCoroutine(int targetPhase, float duration)
|
||||
{
|
||||
IsPhaseTransitioning = true;
|
||||
OnBeginPhaseTransition(targetPhase);
|
||||
|
||||
// 打断技能 + 停止移动
|
||||
_skillExecutor?.InterruptCurrentSkill();
|
||||
@@ -232,8 +233,7 @@ namespace BaseGames.Enemies
|
||||
_phaseTransitionCoroutine = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 立即终止阶段过渡协程并清除标志位。
|
||||
/// <summary>立即终止阶段过渡协程并清除标志位。
|
||||
/// 死亡时调用,防止 IsPhaseTransitioning 永久为 true 影响对象池复用。
|
||||
/// </summary>
|
||||
private void AbortPhaseTransition()
|
||||
@@ -246,6 +246,12 @@ namespace BaseGames.Enemies
|
||||
IsPhaseTransitioning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 阶段过渡开始时回调(子类可重写以触发演出动画或特殊逻辑)。
|
||||
/// 在无敌帧等待之前调用。
|
||||
/// </summary>
|
||||
protected virtual void OnBeginPhaseTransition(int targetPhase) { }
|
||||
|
||||
/// <summary>检查当前 HP 是否低于指定百分比(0~1)。</summary>
|
||||
public bool IsHPBelow(float ratio)
|
||||
{
|
||||
|
||||
171
Assets/_Game/Scripts/Enemies/Boss/ChaoFengBoss.cs
Normal file
171
Assets/_Game/Scripts/Enemies/Boss/ChaoFengBoss.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System.Collections;
|
||||
using Animancer;
|
||||
using BaseGames.Combat;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Pool;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace BaseGames.Enemies.Boss
|
||||
{
|
||||
/// <summary>
|
||||
/// 嘲风 Boss 主脚本。
|
||||
///
|
||||
/// Phase 0(地面):4 技能加权随机(回旋扇/扇形连击/小龙卷/大龙卷)。
|
||||
/// Phase 1(空中):风石技能 + 击落计数机制。
|
||||
///
|
||||
/// 阶段过渡流程:
|
||||
/// 1. BossBase.BeginPhaseTransition → OnBeginPhaseTransition(1) 立即播放过渡动画 + 开始浮空
|
||||
/// 2. 无敌期结束(≥ _riseDuration+buffer)→ BossBase.EnterPhase(1) 广播阶段切换事件
|
||||
///
|
||||
/// ⚠️ 动画由本脚本通过 Animancer.Play() 完整控制,不在 BD 中调用 BD_PlayAnimation。
|
||||
/// </summary>
|
||||
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("击败演出动画")]
|
||||
[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;
|
||||
|
||||
// ── 阶段过渡钩子 ─────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 阶段过渡开始时立即播放过渡动画并启动浮空协程。
|
||||
/// invincibleDuration 需在 BD_BossPhaseTransition 中配置为 ≥ _riseDuration + 缓冲(约 2.0s)。
|
||||
/// </summary>
|
||||
protected override void OnBeginPhaseTransition(int targetPhase)
|
||||
{
|
||||
if (targetPhase == 1)
|
||||
{
|
||||
if (_phaseTransitionClip.Clip != null)
|
||||
Animancer.Play(_phaseTransitionClip);
|
||||
StartCoroutine(_floatController.FloatUp());
|
||||
}
|
||||
}
|
||||
|
||||
// ── 受击转发 ─────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 转发受击事件至击落计数器。
|
||||
/// ⚠️ HurtBox 无公开 OnDamageTaken 事件,必须通过此虚方法转发。
|
||||
/// </summary>
|
||||
protected override void OnDamageTaken(DamageInfo info)
|
||||
{
|
||||
base.OnDamageTaken(info);
|
||||
_knockdownCounter?.OnBossHit(info);
|
||||
}
|
||||
|
||||
// ── 动画事件 ─────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 回旋扇返回时由 ReturnProjectile 调用,触发收扇动画。
|
||||
/// </summary>
|
||||
public void OnBoomerangReturned()
|
||||
{
|
||||
if (_boomerangEndClip.Clip != null)
|
||||
Animancer.Play(_boomerangEndClip);
|
||||
}
|
||||
|
||||
// ── 弹体生成 ─────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 由技能动画 AnimationEvent 触发,生成对应弹体。
|
||||
/// payload: "boomerang" / "tornado_small" / "tornado_large" / "wind_stone"
|
||||
/// </summary>
|
||||
public override void SpawnProjectile(string payload)
|
||||
{
|
||||
var pool = ServiceLocator.GetOrDefault<IObjectPoolService>();
|
||||
if (pool == null) return;
|
||||
|
||||
switch (payload)
|
||||
{
|
||||
case "boomerang":
|
||||
{
|
||||
var go = pool.Spawn("PROJ_Boomerang",
|
||||
_boomerangMuzzle != null ? _boomerangMuzzle.position : transform.position,
|
||||
Quaternion.identity);
|
||||
go?.GetComponent<ReturnProjectile>()?.SetOwner(transform);
|
||||
break;
|
||||
}
|
||||
case "tornado_small":
|
||||
pool.Spawn("PROJ_TornadoSmall",
|
||||
_tornadoMuzzle != null ? _tornadoMuzzle.position : transform.position,
|
||||
Quaternion.identity);
|
||||
break;
|
||||
|
||||
case "tornado_large":
|
||||
if (PlayerTransform != null)
|
||||
pool.Spawn("PROJ_TornadoLarge", PlayerTransform.position, Quaternion.identity);
|
||||
break;
|
||||
|
||||
case "wind_stone":
|
||||
pool.Spawn("PROJ_WindStone",
|
||||
_windStoneMuzzle != null ? _windStoneMuzzle.position : transform.position,
|
||||
Quaternion.identity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 击败演出 ─────────────────────────────────────────────────────────
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Enemies/Boss/ChaoFengBoss.cs.meta
Normal file
11
Assets/_Game/Scripts/Enemies/Boss/ChaoFengBoss.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7eec9978ded028a409537e37d029c8cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
46
Assets/_Game/Scripts/Enemies/Boss/ChaoFengFloatController.cs
Normal file
46
Assets/_Game/Scripts/Enemies/Boss/ChaoFengFloatController.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies.Boss
|
||||
{
|
||||
/// <summary>
|
||||
/// 嘲风漂浮控制器:负责 Phase 2 进入时上浮、击落时下落的平滑位移。
|
||||
/// 使用 Rigidbody2D.MovePosition 做帧级 Lerp 插值,避免物理穿墙。
|
||||
/// </summary>
|
||||
public class ChaoFengFloatController : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private float _floatHeight = 5f;
|
||||
[SerializeField] private float _riseDuration = 1.5f;
|
||||
[SerializeField] private float _fallDuration = 0.8f;
|
||||
[SerializeField] private Rigidbody2D _rb;
|
||||
|
||||
private float _groundY;
|
||||
|
||||
private void Start() => _groundY = transform.position.y;
|
||||
|
||||
/// <summary>上浮至悬空高度(切换为 Kinematic 后执行)。</summary>
|
||||
public IEnumerator FloatUp()
|
||||
{
|
||||
_rb.bodyType = RigidbodyType2D.Kinematic;
|
||||
_rb.velocity = Vector2.zero;
|
||||
yield return TweenY(transform.position.y, _groundY + _floatHeight, _riseDuration);
|
||||
}
|
||||
|
||||
/// <summary>下落回地面(落地后恢复 Dynamic)。</summary>
|
||||
public IEnumerator FallDown()
|
||||
{
|
||||
yield return TweenY(transform.position.y, _groundY, _fallDuration);
|
||||
_rb.bodyType = RigidbodyType2D.Dynamic;
|
||||
}
|
||||
|
||||
private IEnumerator TweenY(float from, float to, float duration)
|
||||
{
|
||||
for (float t = 0f; t < duration; t += Time.deltaTime)
|
||||
{
|
||||
_rb.MovePosition(new Vector2(transform.position.x, Mathf.Lerp(from, to, t / duration)));
|
||||
yield return null;
|
||||
}
|
||||
_rb.MovePosition(new Vector2(transform.position.x, to));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29caa23156f932e43b02015182e66819
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Collections;
|
||||
using Animancer;
|
||||
using BaseGames.Boss;
|
||||
using BaseGames.Combat;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies.Boss
|
||||
{
|
||||
/// <summary>
|
||||
/// 嘲风击落计数器(Phase 2)。
|
||||
/// 由 <see cref="ChaoFengBoss.OnDamageTaken"/> 直接调用 <see cref="OnBossHit"/>,
|
||||
/// 累计命中达到阈值后触发击落序列(下落 → 硬直 → 复位浮空)。
|
||||
///
|
||||
/// ⚠️ HurtBox 无公开 OnDamageTaken 事件,必须通过 EnemyBase.OnDamageTaken 虚方法转发。
|
||||
/// </summary>
|
||||
public class ChaoFengKnockdownCounter : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private int _threshold = 8;
|
||||
|
||||
[Header("依赖引用")]
|
||||
[SerializeField] private ChaoFengBoss _boss;
|
||||
[SerializeField] private ChaoFengFloatController _floatCtrl;
|
||||
|
||||
[Header("击落动画")]
|
||||
[SerializeField] private ClipTransition _knockdownHitClip;
|
||||
[Tooltip("击落后硬直动画(复用 Defeat_Pant Clip)")]
|
||||
[SerializeField] private ClipTransition _staggerClip;
|
||||
|
||||
[SerializeField] private float _staggerDuration = 3f;
|
||||
|
||||
private int _count;
|
||||
private bool _inKnockdown;
|
||||
|
||||
/// <summary>
|
||||
/// 由 <see cref="ChaoFengBoss.OnDamageTaken"/> 调用,累计受击并在达到阈值时触发击落。
|
||||
/// </summary>
|
||||
public void OnBossHit(DamageInfo info)
|
||||
{
|
||||
if (_inKnockdown || _boss == null || _boss.CurrentPhase != 1) return;
|
||||
|
||||
_count++;
|
||||
if (_count >= _threshold)
|
||||
{
|
||||
_count = 0;
|
||||
StartCoroutine(KnockdownSequence());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>强制结束正在进行中的击落序列(由 ChaoFengBoss.DefeatSequence 调用)。</summary>
|
||||
public void ForceEnd()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
_inKnockdown = false;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
private IEnumerator KnockdownSequence()
|
||||
{
|
||||
_inKnockdown = true;
|
||||
|
||||
_boss.GetComponentInChildren<BossSkillExecutor>()?.InterruptCurrentSkill();
|
||||
|
||||
if (_knockdownHitClip.Clip != null)
|
||||
_boss.Animancer.Play(_knockdownHitClip);
|
||||
|
||||
yield return _floatCtrl.FallDown();
|
||||
|
||||
if (_staggerClip.Clip != null)
|
||||
_boss.Animancer.Play(_staggerClip);
|
||||
|
||||
yield return new WaitForSeconds(_staggerDuration);
|
||||
|
||||
yield return _floatCtrl.FloatUp();
|
||||
|
||||
_inKnockdown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e0b39336c1ec5740abb2754b75bde04
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
61
Assets/_Game/Scripts/Enemies/Boss/ReturnProjectile.cs
Normal file
61
Assets/_Game/Scripts/Enemies/Boss/ReturnProjectile.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.Combat;
|
||||
|
||||
namespace BaseGames.Enemies.Boss
|
||||
{
|
||||
/// <summary>
|
||||
/// 嘲风回旋扇弹体:向前飞行至最大射程后自动追踪 Boss 返回。
|
||||
/// 命中目标或返回到 Boss 身边时归还对象池,并通知 Boss 触发收扇动画。
|
||||
/// </summary>
|
||||
public class ReturnProjectile : Projectile
|
||||
{
|
||||
private enum Stage { Forward, Returning }
|
||||
|
||||
[SerializeField] private float _maxRange = 8f;
|
||||
[SerializeField] private float _returnSpeed = 6f;
|
||||
|
||||
private Stage _stage;
|
||||
private Transform _ownerTransform;
|
||||
private Vector2 _startPos;
|
||||
|
||||
/// <summary>设置持有者 Transform(发射时由 ChaoFengBoss.SpawnProjectile 调用)。</summary>
|
||||
public void SetOwner(Transform owner) => _ownerTransform = owner;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_stage = Stage.Forward;
|
||||
_startPos = transform.position;
|
||||
_rb.velocity = Direction * _config.Speed;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
// 不使用基类的 Lifetime 自毁;由射程 / 返回逻辑控制生命周期
|
||||
if (_stage == Stage.Forward)
|
||||
{
|
||||
if (Vector2.Distance(transform.position, _startPos) >= _maxRange)
|
||||
{
|
||||
_stage = Stage.Returning;
|
||||
_rb.velocity = Vector2.zero;
|
||||
}
|
||||
}
|
||||
else // Returning
|
||||
{
|
||||
if (_ownerTransform == null)
|
||||
{
|
||||
base.ReturnToPool();
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 dir = ((Vector2)_ownerTransform.position - (Vector2)transform.position).normalized;
|
||||
_rb.velocity = dir * _returnSpeed;
|
||||
|
||||
if (Vector2.Distance(transform.position, _ownerTransform.position) < 0.5f)
|
||||
{
|
||||
_ownerTransform.GetComponentInParent<ChaoFengBoss>()?.OnBoomerangReturned();
|
||||
base.ReturnToPool();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Enemies/Boss/ReturnProjectile.cs.meta
Normal file
11
Assets/_Game/Scripts/Enemies/Boss/ReturnProjectile.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b3290cb8ac8a2f42a10c7c0c845c8ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user