UI 系统
This commit is contained in:
@@ -37,6 +37,12 @@ namespace BaseGames.Enemies.Boss
|
||||
[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")]
|
||||
@@ -91,6 +97,7 @@ namespace BaseGames.Enemies.Boss
|
||||
/// <summary>
|
||||
/// 由技能动画 AnimationEvent 触发,生成对应弹体。
|
||||
/// payload: "boomerang" / "tornado_small" / "tornado_large" / "wind_stone"
|
||||
/// 生成后通过 ProjectileConfigSO 调用 Projectile.Initialize 注入速度/重力/伤害源与方向。
|
||||
/// </summary>
|
||||
public override void SpawnProjectile(string payload)
|
||||
{
|
||||
@@ -100,32 +107,75 @@ namespace BaseGames.Enemies.Boss
|
||||
switch (payload)
|
||||
{
|
||||
case "boomerang":
|
||||
{
|
||||
var go = pool.Spawn("PROJ_Boomerang",
|
||||
_boomerangMuzzle != null ? _boomerangMuzzle.position : transform.position,
|
||||
Quaternion.identity);
|
||||
go?.GetComponent<ReturnProjectile>()?.SetOwner(transform);
|
||||
SpawnConfiguredAt(pool, _boomerangConfig, "PROJ_Boomerang",
|
||||
MuzzlePos(_boomerangMuzzle), AimDir(_boomerangMuzzle),
|
||||
proj => (proj as ReturnProjectile)?.SetOwner(transform));
|
||||
break;
|
||||
}
|
||||
|
||||
case "tornado_small":
|
||||
pool.Spawn("PROJ_TornadoSmall",
|
||||
_tornadoMuzzle != null ? _tornadoMuzzle.position : transform.position,
|
||||
Quaternion.identity);
|
||||
SpawnConfiguredAt(pool, _tornadoSmallConfig, "PROJ_TornadoSmall",
|
||||
MuzzlePos(_tornadoMuzzle), AimDir(_tornadoMuzzle), null);
|
||||
break;
|
||||
|
||||
case "tornado_large":
|
||||
// 定点召唤于玩家当前位置(落点锁定)
|
||||
if (PlayerTransform != null)
|
||||
pool.Spawn("PROJ_TornadoLarge", PlayerTransform.position, Quaternion.identity);
|
||||
SpawnConfiguredAt(pool, _tornadoLargeConfig, "PROJ_TornadoLarge",
|
||||
PlayerTransform.position, Vector2.down, null);
|
||||
break;
|
||||
|
||||
case "wind_stone":
|
||||
pool.Spawn("PROJ_WindStone",
|
||||
_windStoneMuzzle != null ? _windStoneMuzzle.position : transform.position,
|
||||
Quaternion.identity);
|
||||
SpawnConfiguredAt(pool, _windStoneConfig, "PROJ_WindStone",
|
||||
MuzzlePos(_windStoneMuzzle), Vector2.down, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 MuzzlePos(Transform muzzle)
|
||||
=> muzzle != null ? (Vector2)muzzle.position : (Vector2)transform.position;
|
||||
|
||||
/// <summary>朝向玩家的发射方向;无玩家时退回当前朝向(localScale.x)。</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从池取出弹体并用 config 初始化(注入速度/重力/伤害源/方向)。
|
||||
/// config 为空时仅生成不初始化并告警(需在 Inspector 指定对应 ProjectileConfigSO)。
|
||||
/// </summary>
|
||||
private void SpawnConfiguredAt(IObjectPoolService pool, ProjectileConfigSO config, string fallbackKey,
|
||||
Vector2 pos, Vector2 dir, System.Action<Projectile> 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<Projectile>();
|
||||
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()
|
||||
|
||||
@@ -30,13 +30,17 @@ namespace BaseGames.Enemies.Boss
|
||||
|
||||
private int _count;
|
||||
private bool _inKnockdown;
|
||||
private BaseGames.Core.IGroundedActor _playerGround;
|
||||
private Transform _cachedPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// 由 <see cref="ChaoFengBoss.OnDamageTaken"/> 调用,累计受击并在达到阈值时触发击落。
|
||||
/// 仅统计「玩家处于空中」时的命中(设计:空中攻击命中嘲风才计入击落)。
|
||||
/// </summary>
|
||||
public void OnBossHit(DamageInfo info)
|
||||
{
|
||||
if (_inKnockdown || _boss == null || _boss.CurrentPhase != 1) return;
|
||||
if (!IsPlayerAirborne()) return; // Q6:仅玩家在空中的命中计数
|
||||
|
||||
_count++;
|
||||
if (_count >= _threshold)
|
||||
@@ -46,6 +50,23 @@ namespace BaseGames.Enemies.Boss
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过 Boss 缓存的 PlayerTransform 取玩家的 <see cref="BaseGames.Core.IGroundedActor"/>(PlayerMovement 实现),
|
||||
/// 判定其是否离地。取不到接口时按「空中」处理(保守计数,避免机制失效)。
|
||||
/// </summary>
|
||||
private bool IsPlayerAirborne()
|
||||
{
|
||||
var player = _boss.PlayerTransform;
|
||||
if (player == null) return false;
|
||||
if (!ReferenceEquals(player, _cachedPlayer))
|
||||
{
|
||||
_cachedPlayer = player;
|
||||
_playerGround = player.GetComponentInParent<BaseGames.Core.IGroundedActor>()
|
||||
?? player.GetComponentInChildren<BaseGames.Core.IGroundedActor>();
|
||||
}
|
||||
return _playerGround == null || !_playerGround.IsGrounded;
|
||||
}
|
||||
|
||||
/// <summary>强制结束正在进行中的击落序列(由 ChaoFengBoss.DefeatSequence 调用)。</summary>
|
||||
public void ForceEnd()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user