重构
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using IndianOceanAssets.Engine2_5D;
|
||||
using Architecture.Core;
|
||||
|
||||
namespace GameFramework
|
||||
{
|
||||
@@ -27,15 +28,32 @@ namespace GameFramework
|
||||
private float _currentAlpha;
|
||||
private bool _isFlashing;
|
||||
|
||||
[Header("SO 事件通道(替代 static HealthSystem.onPlayerDamaged 事件)")]
|
||||
[SerializeField] private GameEvent onPlayerDamagedEvent;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (onPlayerDamagedEvent == null)
|
||||
Debug.LogWarning("[DamageFlashOverlay] onPlayerDamagedEvent 未接线!请在场景中预置已接线的 DamageFlashOverlay 实例,否则受击不闪红。", this);
|
||||
else
|
||||
onPlayerDamagedEvent.Register(OnPlayerDamaged);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (onPlayerDamagedEvent != null)
|
||||
onPlayerDamagedEvent.Unregister(OnPlayerDamaged);
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (onPlayerDamagedEvent == null)
|
||||
Debug.LogWarning($"[DamageFlashOverlay] On Player Damaged Event 未接线({gameObject.name})。受击泛红不会出现。", this);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
CreateOverlay();
|
||||
HealthSystem.onPlayerDamaged += OnPlayerDamaged;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
HealthSystem.onPlayerDamaged -= OnPlayerDamaged;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
|
||||
+37
-75
@@ -2,12 +2,14 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
using IndianOceanAssets.Engine2_5D;
|
||||
using Architecture.Core;
|
||||
using Architecture.Variables;
|
||||
|
||||
namespace GameFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏内 HUD —— 魂灵计数 + 生命图标 + 技能CD。
|
||||
///
|
||||
///
|
||||
/// 布局:
|
||||
/// - 左上角:收集到的魂灵(icon + 数字)
|
||||
/// - 左下角:5个生命图标
|
||||
@@ -36,12 +38,14 @@ namespace GameFramework
|
||||
[Header("暂停")]
|
||||
[SerializeField] private Button pauseButton;
|
||||
|
||||
[Header("SO 事件通道 / 变量")]
|
||||
[SerializeField] private IntEvent scoreChangedEvent;
|
||||
[SerializeField] private IntVariable playerHealthVar;
|
||||
|
||||
private HealthSystem _playerHealth;
|
||||
private SpiritLanternSystem _lanternSystem;
|
||||
private EchoSystem _echoSystem;
|
||||
private PlayerController _playerController;
|
||||
private int _maxHealth = 5;
|
||||
private bool _subscribedScoreEvent = false;
|
||||
private int _lastSoulCount = -1;
|
||||
|
||||
void Start()
|
||||
@@ -62,59 +66,34 @@ namespace GameFramework
|
||||
if (soulIcon != null)
|
||||
SoulIconRect = soulIcon.rectTransform;
|
||||
|
||||
// 订阅分数事件(魂灵计数复用分数系统)
|
||||
if (ScoreManager.Instance != null)
|
||||
// 订阅分数事件(魂灵计数复用分数系统):无条件注册,事件资产始终存在;
|
||||
// 初始值若 ScoreManager 已就绪则取实时分,否则先显示 0,首个 ScoreChanged 会刷新。
|
||||
scoreChangedEvent?.Register(UpdateSoulCount);
|
||||
UpdateSoulCount(ScoreManager.Instance != null ? ScoreManager.Instance.CurrentScore : 0);
|
||||
|
||||
// 订阅玩家血量变量(替代反射读取私有字段 + Update 条件 Find 回退)
|
||||
if (playerHealthVar != null)
|
||||
{
|
||||
ScoreManager.onScoreChanged += UpdateSoulCount;
|
||||
_subscribedScoreEvent = true;
|
||||
UpdateSoulCount(ScoreManager.Instance.CurrentScore);
|
||||
playerHealthVar.OnValueChanged += UpdateLifeIcons;
|
||||
UpdateLifeIcons(playerHealthVar.Value);
|
||||
}
|
||||
|
||||
UpdateLifeIcons();
|
||||
|
||||
// 自动创建受击泛红特效(如果场景中没有)
|
||||
// 受击泛红特效:场景需预置已接线的 DamageFlashOverlay 实例(不再自动创建,
|
||||
// 否则会生成一个事件为 null 的实例,既无效果又掩盖「未接线」问题)
|
||||
if (FindObjectOfType<DamageFlashOverlay>() == null)
|
||||
{
|
||||
var overlayObj = new GameObject("DamageFlashOverlay");
|
||||
overlayObj.AddComponent<DamageFlashOverlay>();
|
||||
}
|
||||
Debug.LogWarning("[GameHUD] 场景中未找到 DamageFlashOverlay,受击泛红特效不会显示。请在场景中放置一个、并把它 On Player Damaged Event 字段接上 OnPlayerDamaged 资产。", this);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (_subscribedScoreEvent)
|
||||
{
|
||||
ScoreManager.onScoreChanged -= UpdateSoulCount;
|
||||
_subscribedScoreEvent = false;
|
||||
}
|
||||
scoreChangedEvent?.Unregister(UpdateSoulCount);
|
||||
if (playerHealthVar != null)
|
||||
playerHealthVar.OnValueChanged -= UpdateLifeIcons;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// 延迟订阅 ScoreManager(跨场景后 ScoreManager 可能才创建)
|
||||
if (!_subscribedScoreEvent && ScoreManager.Instance != null)
|
||||
{
|
||||
ScoreManager.onScoreChanged += UpdateSoulCount;
|
||||
_subscribedScoreEvent = true;
|
||||
UpdateSoulCount(ScoreManager.Instance.CurrentScore);
|
||||
Debug.Log("[GameHUD] 延迟订阅 ScoreManager.onScoreChanged 成功");
|
||||
}
|
||||
|
||||
// 延迟查找玩家(GameSpawnManager 可能比 HUD 晚创建玩家)
|
||||
if (_playerHealth == null)
|
||||
{
|
||||
var player = GameObject.FindWithTag("Player");
|
||||
if (player != null)
|
||||
{
|
||||
_playerHealth = player.GetComponent<HealthSystem>();
|
||||
_lanternSystem = player.GetComponent<SpiritLanternSystem>();
|
||||
_echoSystem = player.GetComponent<EchoSystem>();
|
||||
_playerController = player.GetComponent<PlayerController>();
|
||||
Debug.Log("[GameHUD] 延迟找到玩家,已绑定组件");
|
||||
}
|
||||
}
|
||||
|
||||
UpdateLifeIcons();
|
||||
// 事件已在 Start 中无条件订阅,无需逐帧延迟订阅(避免反模式)。
|
||||
UpdateSkillCooldowns();
|
||||
}
|
||||
|
||||
@@ -140,17 +119,14 @@ namespace GameFramework
|
||||
|
||||
/// <summary>
|
||||
/// 更新生命图标(左下角)。
|
||||
/// 根据当前血量显示/隐藏对应图标。
|
||||
/// 直接读取 PlayerHealth 共享变量,由 OnValueChanged 事件驱动,无反射、无逐帧 Find。
|
||||
/// </summary>
|
||||
private void UpdateLifeIcons()
|
||||
private void UpdateLifeIcons(int value)
|
||||
{
|
||||
if (_playerHealth == null || lifeIcons == null || lifeIcons.Length == 0) return;
|
||||
|
||||
// 获取当前血量
|
||||
var healthField = typeof(HealthSystem).GetField("health",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
int currentHealth = healthField != null ? (int)healthField.GetValue(_playerHealth) : _maxHealth;
|
||||
if (lifeIcons == null || lifeIcons.Length == 0) return;
|
||||
if (playerHealthVar == null) return;
|
||||
|
||||
int currentHealth = value;
|
||||
for (int i = 0; i < lifeIcons.Length; i++)
|
||||
{
|
||||
if (lifeIcons[i] != null)
|
||||
@@ -160,43 +136,29 @@ namespace GameFramework
|
||||
|
||||
/// <summary>
|
||||
/// 更新技能CD遮罩(右下角)。
|
||||
/// 使用 Image.fillAmount 实现圆形CD效果。
|
||||
/// 通过各系统的公共只读属性读取冷却状态,替代反射读取私有字段。
|
||||
/// </summary>
|
||||
private void UpdateSkillCooldowns()
|
||||
{
|
||||
// 冲刺 CD(从 PlayerController 读取 rollCooldown + lastRollTime)
|
||||
// 冲刺 CD(从 PlayerController.RollCooldown 读取)
|
||||
if (_playerController != null)
|
||||
{
|
||||
var cdField = typeof(PlayerController).GetField("rollCooldown",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
var lastField = typeof(PlayerController).GetField("lastRollTime",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
float sprintCD = cdField != null ? (float)cdField.GetValue(_playerController) : 1f;
|
||||
float lastRoll = lastField != null ? (float)lastField.GetValue(_playerController) : -999f;
|
||||
float remaining = Mathf.Max(0f, (lastRoll + sprintCD) - Time.time);
|
||||
UpdateSkillCD(skillSprintCD, skillSprintIcon, remaining, sprintCD);
|
||||
var cd = _playerController.RollCooldown;
|
||||
UpdateSkillCD(skillSprintCD, skillSprintIcon, cd.remaining, cd.total);
|
||||
}
|
||||
|
||||
// 摇铃 CD(从 EchoSystem 读取 cooldown + _lastEchoTime)
|
||||
// 摇铃 CD(从 EchoSystem.BellCooldown 读取)
|
||||
if (_echoSystem != null)
|
||||
{
|
||||
var cdField = typeof(EchoSystem).GetField("cooldown",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
var lastField = typeof(EchoSystem).GetField("_lastEchoTime",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
float bellCD = cdField != null ? (float)cdField.GetValue(_echoSystem) : 2f;
|
||||
float lastTime = lastField != null ? (float)lastField.GetValue(_echoSystem) : -999f;
|
||||
float remaining = Mathf.Max(0f, (lastTime + bellCD) - Time.time);
|
||||
UpdateSkillCD(skillBellCD, skillBellIcon, remaining, bellCD);
|
||||
var cd = _echoSystem.BellCooldown;
|
||||
UpdateSkillCD(skillBellCD, skillBellIcon, cd.remaining, cd.total);
|
||||
}
|
||||
|
||||
// 灵灯 CD(从 SpiritLanternSystem 读取)
|
||||
// 灵灯 CD(SpiritLanternSystem 已暴露公共 Cooldown / CooldownRemaining,无反射)
|
||||
if (_lanternSystem != null)
|
||||
{
|
||||
float remaining = _lanternSystem.CooldownRemaining;
|
||||
var cdField = typeof(SpiritLanternSystem).GetField("cooldown",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
float maxCD = cdField != null ? (float)cdField.GetValue(_lanternSystem) : 3f;
|
||||
float maxCD = _lanternSystem.Cooldown;
|
||||
UpdateSkillCD(skillLanternCD, skillLanternIcon, remaining, maxCD);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Architecture.Core;
|
||||
|
||||
namespace GameFramework
|
||||
{
|
||||
@@ -12,14 +13,12 @@ namespace GameFramework
|
||||
/// 1. 在 Gameplay 场景里创建一个空物体,挂上本脚本
|
||||
/// 2. 运行一次游戏(或点 Editor 按钮),脚本会自动构建子 UI
|
||||
/// 3. 停止运行后,子 UI 留在场景中,可自由编辑样式/位置/精灵
|
||||
/// 4. 运行时由 GameManager 调用 GameLostOverlay.Show() 激活
|
||||
/// 4. 运行时订阅 OnGameOver 事件自激活(不再由 GameManager 直接调用)
|
||||
///
|
||||
/// 所有 UI 元素均为 [SerializeField],可在 Inspector 中替换。
|
||||
/// </summary>
|
||||
public class GameLostOverlay : MonoBehaviour
|
||||
{
|
||||
private static GameLostOverlay _instance;
|
||||
|
||||
// ====== Canvas ======
|
||||
[Header("Canvas(留空则自动创建)")]
|
||||
[SerializeField] private Canvas lostCanvas;
|
||||
@@ -43,6 +42,9 @@ namespace GameFramework
|
||||
[Header("音效")]
|
||||
[SerializeField] private AudioData lostSFX;
|
||||
|
||||
[Header("SO 事件通道(替代 GameManager 直接调用 Show,订阅 OnGameOver 自激活)")]
|
||||
[SerializeField] private GameEvent onGameOverEvent;
|
||||
|
||||
[Header("标题渐现动效")]
|
||||
[Tooltip("标题渐现总时长(秒)")]
|
||||
[SerializeField] private float titleFadeDuration = 1.5f;
|
||||
@@ -54,6 +56,9 @@ namespace GameFramework
|
||||
[SerializeField] private float rippleFrequency = 15f;
|
||||
[Tooltip("水波纹材质(留空则自动从 Shader 创建)")]
|
||||
[SerializeField] private Material rippleMaterial;
|
||||
|
||||
[Tooltip("水波纹着色器(留空则尝试 Shader.Find 兜底;建议拖入 Assets/UI/shaders/WaterRippleFade.shader 以避免构建裁剪导致运行时找不到)")]
|
||||
[SerializeField] private Shader waterRippleShader;
|
||||
[Tooltip("主界面按钮延迟出现时间(秒)")]
|
||||
[SerializeField] private float buttonFadeDelay = 2f;
|
||||
[Tooltip("按钮渐现时长(秒)")]
|
||||
@@ -65,46 +70,11 @@ namespace GameFramework
|
||||
private Material _rippleMaterial;
|
||||
|
||||
// ====================================================================
|
||||
// 静态入口
|
||||
// ====================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 显示失败叠加层。优先使用场景中已有的实例,
|
||||
/// 没有则动态创建一个。
|
||||
/// </summary>
|
||||
public static void Show()
|
||||
{
|
||||
// 场景中已有(隐藏的)实例?
|
||||
if (_instance != null)
|
||||
{
|
||||
_instance.Activate();
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试在场景中找到
|
||||
var found = FindObjectOfType<GameLostOverlay>();
|
||||
if (found != null)
|
||||
{
|
||||
_instance = found;
|
||||
found.Activate();
|
||||
return;
|
||||
}
|
||||
|
||||
// 都没有 → 动态创建
|
||||
var obj = new GameObject("GameLostOverlay");
|
||||
var overlay = obj.AddComponent<GameLostOverlay>();
|
||||
overlay.BuildAll();
|
||||
overlay.Activate();
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// 生命周期
|
||||
// 生命周期(激活由 OnGameOver 事件驱动,见 OnEnable)
|
||||
// ====================================================================
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_instance = this;
|
||||
|
||||
// 如果 Canvas 还没赋值,尝试从自身获取
|
||||
if (lostCanvas == null)
|
||||
lostCanvas = GetComponent<Canvas>();
|
||||
@@ -114,6 +84,21 @@ namespace GameFramework
|
||||
lostCanvas.enabled = false;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (onGameOverEvent != null)
|
||||
onGameOverEvent.Register(OnGameOver);
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (onGameOverEvent != null)
|
||||
onGameOverEvent.Unregister(OnGameOver);
|
||||
}
|
||||
|
||||
/// <summary>收到 OnGameOver 事件后激活失败叠加层(替代 GameManager 直接调用 Show)。</summary>
|
||||
private void OnGameOver() => Activate();
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!_isVisible) return;
|
||||
@@ -355,7 +340,7 @@ namespace GameFramework
|
||||
}
|
||||
else
|
||||
{
|
||||
var shader = Shader.Find("GameFramework/UI/WaterRippleFade");
|
||||
var shader = waterRippleShader != null ? waterRippleShader : Shader.Find("GameFramework/UI/WaterRippleFade");
|
||||
if (shader == null)
|
||||
{
|
||||
Debug.LogWarning("[GameLostOverlay] 找不到 WaterRippleFade Shader!请确认 Assets/UI/shaders/WaterRippleFade.shader 存在且无编译错误。回退到抖动动效。");
|
||||
|
||||
@@ -4,6 +4,7 @@ using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using IndianOceanAssets.Engine2_5D;
|
||||
using Architecture.Core;
|
||||
|
||||
namespace GameFramework
|
||||
{
|
||||
@@ -13,11 +14,31 @@ namespace GameFramework
|
||||
/// </summary>
|
||||
public class GameManager : PersistentSingleton<GameManager>
|
||||
{
|
||||
/// <summary>游戏结束(失败)时触发。</summary>
|
||||
public static Action onGameOver;
|
||||
[Header("SO 事件通道(替代 static Action 事件,Inspector 拖入对应资产)")]
|
||||
[SerializeField] private GameEvent onGameOverEvent;
|
||||
[SerializeField] private GameEvent onGameWinEvent;
|
||||
|
||||
/// <summary>游戏胜利时触发。</summary>
|
||||
public static Action onGameWin;
|
||||
[Header("SO 事件通道(玩家死亡事件,替代 HealthSystem 直接调用 GameOver)")]
|
||||
[SerializeField] private GameEvent onPlayerDiedEvent;
|
||||
|
||||
[Header("运行时集合(替代 FindObjectsOfType<EnemyAI>,需敌人 Prefab 挂 RuntimeSetRegistrar 指向该集合)")]
|
||||
[SerializeField] private TransformRuntimeSet enemiesSet;
|
||||
[SerializeField] private EnemyManager enemyManagerRef;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (onPlayerDiedEvent != null)
|
||||
onPlayerDiedEvent.Register(OnPlayerDied);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (onPlayerDiedEvent != null)
|
||||
onPlayerDiedEvent.Unregister(OnPlayerDied);
|
||||
}
|
||||
|
||||
/// <summary>玩家死亡事件回调:触发失败流程(设置状态 + 过场 + 广播 OnGameOver)。</summary>
|
||||
private void OnPlayerDied() => GameOver();
|
||||
|
||||
public static GameState GameState
|
||||
{
|
||||
@@ -49,7 +70,7 @@ namespace GameFramework
|
||||
{
|
||||
if (Instance == null) return;
|
||||
GameState = GameState.GameOver;
|
||||
onGameOver?.Invoke();
|
||||
Instance.onGameOverEvent?.Raise();
|
||||
Instance.StartCoroutine(Instance.DeathTransition());
|
||||
}
|
||||
|
||||
@@ -58,7 +79,7 @@ namespace GameFramework
|
||||
{
|
||||
if (Instance == null) return;
|
||||
GameState = GameState.Victory;
|
||||
onGameWin?.Invoke();
|
||||
Instance.onGameWinEvent?.Raise();
|
||||
Instance.StartCoroutine(Instance.VictoryTransition());
|
||||
}
|
||||
|
||||
@@ -141,17 +162,32 @@ namespace GameFramework
|
||||
/// </summary>
|
||||
private void DisableAllEnemyAI()
|
||||
{
|
||||
// 禁用所有 EnemyAI
|
||||
var enemyAIs = FindObjectsOfType<IndianOceanAssets.Engine2_5D.EnemyAI>();
|
||||
foreach (var ai in enemyAIs)
|
||||
ai.enabled = false;
|
||||
int disabled = 0;
|
||||
|
||||
// 优先用 Enemies 运行时集合(敌人 Prefab 需挂 RuntimeSetRegistrar 并指向该集合,
|
||||
// 在 OnEnable/OnDisable 时自动注册/注销自身 Transform)
|
||||
if (enemiesSet != null)
|
||||
{
|
||||
foreach (var t in enemiesSet.Items)
|
||||
{
|
||||
if (t == null) continue;
|
||||
var ai = t.GetComponent<IndianOceanAssets.Engine2_5D.EnemyAI>();
|
||||
if (ai != null) { ai.enabled = false; disabled++; }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 过渡期兜底:尚未接入 Enemies 集合时回退(Prefab 接入 RuntimeSetRegistrar 后可删除此分支)
|
||||
var enemyAIs = FindObjectsOfType<IndianOceanAssets.Engine2_5D.EnemyAI>();
|
||||
foreach (var ai in enemyAIs) ai.enabled = false;
|
||||
disabled = enemyAIs.Length;
|
||||
}
|
||||
|
||||
// 禁用 EnemyManager(停止生成/管理逻辑)
|
||||
var enemyManager = FindObjectOfType<IndianOceanAssets.Engine2_5D.EnemyManager>();
|
||||
if (enemyManager != null)
|
||||
enemyManager.enabled = false;
|
||||
if (enemyManagerRef != null)
|
||||
enemyManagerRef.enabled = false;
|
||||
|
||||
Debug.Log($"[GameManager] 已禁用 {enemyAIs.Length} 个 EnemyAI + EnemyManager");
|
||||
Debug.Log($"[GameManager] 已禁用 {disabled} 个 EnemyAI + EnemyManager");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -230,8 +266,7 @@ namespace GameFramework
|
||||
|
||||
yield return null;
|
||||
|
||||
// 在全黑之上显示失败叠加层("你已迷失……" + 主界面按钮)
|
||||
GameLostOverlay.Show();
|
||||
// 失败叠加层("你已迷失……")由 GameLostOverlay 订阅 OnGameOver 事件自显示,此处不再直接调用。
|
||||
|
||||
// 等待一帧让 UI 渲染
|
||||
yield return null;
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace GameFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏结束画面。
|
||||
/// 监听 GameManager.onGameOver 事件,显示结束 Canvas 并暂停游戏。
|
||||
/// 玩家按确认键或点击按钮后跳转到排行榜场景。
|
||||
/// </summary>
|
||||
public class GameOverScreen : MonoBehaviour
|
||||
{
|
||||
[Header("Canvas")]
|
||||
[SerializeField] Canvas gameOverCanvas;
|
||||
|
||||
[Header("Button")]
|
||||
[SerializeField] Button confirmButton;
|
||||
|
||||
[Header("Audio")]
|
||||
[SerializeField] AudioData gameOverSFX;
|
||||
|
||||
[Header("Input")]
|
||||
[SerializeField] KeyCode confirmKey = KeyCode.Return;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
GameManager.onGameOver += ShowGameOver;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
GameManager.onGameOver -= ShowGameOver;
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
// 初始隐藏
|
||||
if (gameOverCanvas != null)
|
||||
gameOverCanvas.enabled = false;
|
||||
|
||||
if (confirmButton != null)
|
||||
confirmButton.onClick.AddListener(OnConfirmClick);
|
||||
}
|
||||
|
||||
void ShowGameOver()
|
||||
{
|
||||
// 失败时由 GameLostOverlay 接管 UI,这里不再显示旧 Canvas
|
||||
// 暂停和鼠标由 GameManager + GameLostOverlay 统一处理
|
||||
return;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// 游戏结束画面可见时,按确认键跳转
|
||||
if (gameOverCanvas != null && gameOverCanvas.enabled)
|
||||
{
|
||||
if (Input.GetKeyDown(confirmKey) || Input.GetKeyDown(KeyCode.Space))
|
||||
{
|
||||
OnConfirmClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnConfirmClick()
|
||||
{
|
||||
// 先解除暂停
|
||||
if (TimeController.Instance != null)
|
||||
TimeController.Instance.Unpause();
|
||||
|
||||
// 跳转到排行榜场景
|
||||
if (SceneLoader.Instance != null)
|
||||
SceneLoader.Instance.LoadScoringScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: CClOsC78Vi9/cIog8UeuUzecgGTFlu5AQ5iyvdkeMcyG1n6ns9R5ds8=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,6 +1,7 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Architecture.Core;
|
||||
|
||||
namespace GameFramework
|
||||
{
|
||||
@@ -10,9 +11,9 @@ namespace GameFramework
|
||||
public enum GameResult { Win, Lose }
|
||||
|
||||
/// <summary>
|
||||
/// 游戏结果界面(支持胜利/失败)。
|
||||
/// 监听 GameManager.onGameOver 和 onGameWin 事件,
|
||||
/// 显示对应面板并暂停游戏。确认后跳转到排行榜场景。
|
||||
/// 游戏结果界面(仅负责「胜利」结算)。
|
||||
/// 监听 OnGameWin 事件,显示胜利面板并暂停游戏,确认后跳转到排行榜场景。
|
||||
/// 「失败」由 GameLostOverlay 接管(订阅 OnGameOver),本屏不处理失败,避免空响应。
|
||||
/// </summary>
|
||||
public class GameResultScreen : MonoBehaviour
|
||||
{
|
||||
@@ -38,6 +39,9 @@ namespace GameFramework
|
||||
[Header("Input")]
|
||||
[SerializeField] private KeyCode confirmKey = KeyCode.Return;
|
||||
|
||||
[Header("SO 事件通道(胜利事件;失败由 GameLostOverlay 接管)")]
|
||||
[SerializeField] private GameEvent onGameWinEvent;
|
||||
|
||||
private bool _isVisible;
|
||||
|
||||
/// <summary>
|
||||
@@ -59,20 +63,14 @@ namespace GameFramework
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
GameManager.onGameOver += OnGameOver;
|
||||
GameManager.onGameWin += OnGameWin;
|
||||
onGameWinEvent?.Register(OnGameWin);
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
GameManager.onGameOver -= OnGameOver;
|
||||
GameManager.onGameWin -= OnGameWin;
|
||||
onGameWinEvent?.Unregister(OnGameWin);
|
||||
}
|
||||
|
||||
void OnGameOver()
|
||||
{
|
||||
// 失败时由 GameLostOverlay 接管,不再显示旧失败面板
|
||||
}
|
||||
void OnGameWin() => ShowResult(GameResult.Win);
|
||||
|
||||
void Start()
|
||||
|
||||
@@ -59,10 +59,10 @@ Material:
|
||||
- _DecalMeshDepthBias: 0
|
||||
- _DecalMeshViewBias: 0
|
||||
- _DrawOrder: 0
|
||||
- _FadeAlpha: 0.96674436
|
||||
- _FadeAlpha: 1
|
||||
- _RippleAmplitude: 0.0412
|
||||
- _RippleFrequency: 10.5
|
||||
- _RippleIntensity: 0.64074624
|
||||
- _RippleIntensity: -0
|
||||
- _RippleSpeed: 3
|
||||
- _Shininess: 0.2
|
||||
- _Stencil: 0
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Architecture.Core;
|
||||
|
||||
namespace GameFramework
|
||||
{
|
||||
@@ -13,24 +14,25 @@ namespace GameFramework
|
||||
[SerializeField] Text scoreText;
|
||||
[SerializeField] string format = "{0}";
|
||||
|
||||
[Header("SO 事件通道(替代 static ScoreManager.onScoreChanged 事件)")]
|
||||
[SerializeField] private IntEvent scoreChangedEvent;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
ScoreManager.onScoreChanged += UpdateText;
|
||||
scoreChangedEvent?.Register(UpdateText);
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
ScoreManager.onScoreChanged -= UpdateText;
|
||||
scoreChangedEvent?.Unregister(UpdateText);
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (scoreText == null)
|
||||
scoreText = GetComponent<Text>();
|
||||
if (ScoreManager.Instance != null)
|
||||
UpdateText(ScoreManager.Instance.CurrentScore);
|
||||
else
|
||||
UpdateText(0);
|
||||
// 初始值走事件:订阅后首个 ScoreChanged 会刷新显示;分数初始即为 0,先显示 0。
|
||||
UpdateText(0);
|
||||
}
|
||||
|
||||
void UpdateText(int score)
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Architecture.Core;
|
||||
|
||||
namespace GameFramework
|
||||
{
|
||||
@@ -32,7 +33,7 @@ namespace GameFramework
|
||||
|
||||
/// <summary>
|
||||
/// 得分管理器(持久单例)。
|
||||
/// 维护当前局得分,通过事件通知 UI,并管理 Top-10 排行榜存档。
|
||||
/// 维护当前局得分,通过 SO 事件通道通知 UI,并管理 Top-10 排行榜存档。
|
||||
/// </summary>
|
||||
public class ScoreManager : PersistentSingleton<ScoreManager>
|
||||
{
|
||||
@@ -42,10 +43,9 @@ namespace GameFramework
|
||||
// 当前局得分
|
||||
int currentScore = 0;
|
||||
|
||||
// 当得分变化时触发,参数为最新得分(动画过程中的中间值也会触发)
|
||||
public static event Action<int> onScoreChanged;
|
||||
// 当得分完成最终增加时触发,参数为最终得分
|
||||
public static event Action<int> onScoreSettled;
|
||||
[Header("SO 事件通道(替代 static onScoreChanged / onScoreSettled 事件,Inspector 拖入对应资产)")]
|
||||
[SerializeField] private IntEvent scoreChangedEvent;
|
||||
[SerializeField] private IntEvent scoreSettledEvent;
|
||||
|
||||
/// <summary>当前得分(只读)。</summary>
|
||||
public int CurrentScore => currentScore;
|
||||
@@ -75,8 +75,8 @@ namespace GameFramework
|
||||
public void SetScore(int value)
|
||||
{
|
||||
currentScore = Mathf.Max(0, value);
|
||||
onScoreChanged?.Invoke(currentScore);
|
||||
onScoreSettled?.Invoke(currentScore);
|
||||
scoreChangedEvent?.Raise(currentScore);
|
||||
scoreSettledEvent?.Raise(currentScore);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -85,8 +85,8 @@ namespace GameFramework
|
||||
public void ResetScore()
|
||||
{
|
||||
currentScore = 0;
|
||||
onScoreChanged?.Invoke(0);
|
||||
onScoreSettled?.Invoke(0);
|
||||
scoreChangedEvent?.Raise(0);
|
||||
scoreSettledEvent?.Raise(0);
|
||||
}
|
||||
|
||||
IEnumerator ScoreCountUpCoroutine(int from, int to)
|
||||
@@ -100,12 +100,12 @@ namespace GameFramework
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
int display = Mathf.RoundToInt(Mathf.Lerp(from, to, t));
|
||||
onScoreChanged?.Invoke(display);
|
||||
scoreChangedEvent?.Raise(display);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
onScoreChanged?.Invoke(to);
|
||||
onScoreSettled?.Invoke(to);
|
||||
scoreChangedEvent?.Raise(to);
|
||||
scoreSettledEvent?.Raise(to);
|
||||
}
|
||||
|
||||
#region 排行榜
|
||||
|
||||
Reference in New Issue
Block a user