UI 系统

This commit is contained in:
2026-06-08 16:05:00 +08:00
parent b582317692
commit 247de307c6
150 changed files with 10945 additions and 158 deletions

View File

@@ -37,6 +37,12 @@ namespace BaseGames.Enemies.Boss
[SerializeField] private Transform _tornadoMuzzle;
[SerializeField] private Transform _windStoneMuzzle;
[Header("弹体配置ProjectileConfigSOPoolKey 须与弹体地址一致)")]
[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()

View File

@@ -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()
{