using UnityEngine; using UnityEngine.UI; using TMPro; using IndianOceanAssets.Engine2_5D; namespace GameFramework { /// /// 游戏内 HUD —— 魂灵计数 + 生命图标 + 技能CD。 /// /// 布局: /// - 左上角:收集到的魂灵(icon + 数字) /// - 左下角:5个生命图标 /// - 右下角:3个技能图标(冲刺/摇铃/灵灯)+ CD遮罩 /// public class GameHUD : MonoBehaviour { /// 魂灵图标的 RectTransform(供魂灵掉落物飞行定位) public static RectTransform SoulIconRect { get; private set; } [Header("魂灵计数(左上角)")] [SerializeField] private Image soulIcon; [SerializeField] private TextMeshProUGUI soulCountText; [Header("生命图标(左下角)")] [SerializeField] private Image[] lifeIcons; // 5个生命图标 [Header("技能图标(右下角)")] [SerializeField] private Image skillSprintIcon; [SerializeField] private Image skillSprintCD; // CD 遮罩(Image.fillAmount) [SerializeField] private Image skillBellIcon; [SerializeField] private Image skillBellCD; [SerializeField] private Image skillLanternIcon; [SerializeField] private Image skillLanternCD; [Header("暂停")] [SerializeField] private Button pauseButton; 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() { var player = GameObject.FindWithTag("Player"); if (player != null) { _playerHealth = player.GetComponent(); _lanternSystem = player.GetComponent(); _echoSystem = player.GetComponent(); _playerController = player.GetComponent(); } if (pauseButton != null) pauseButton.onClick.AddListener(OnPauseClick); // 暴露魂灵图标 RectTransform(供魂灵掉落物飞行定位) if (soulIcon != null) SoulIconRect = soulIcon.rectTransform; // 订阅分数事件(魂灵计数复用分数系统) if (ScoreManager.Instance != null) { ScoreManager.onScoreChanged += UpdateSoulCount; _subscribedScoreEvent = true; UpdateSoulCount(ScoreManager.Instance.CurrentScore); } UpdateLifeIcons(); // 自动创建受击泛红特效(如果场景中没有) if (FindObjectOfType() == null) { var overlayObj = new GameObject("DamageFlashOverlay"); overlayObj.AddComponent(); } } void OnDestroy() { if (_subscribedScoreEvent) { ScoreManager.onScoreChanged -= UpdateSoulCount; _subscribedScoreEvent = false; } } void Update() { // 延迟订阅 ScoreManager(跨场景后 ScoreManager 可能才创建) if (!_subscribedScoreEvent && ScoreManager.Instance != null) { ScoreManager.onScoreChanged += UpdateSoulCount; _subscribedScoreEvent = true; UpdateSoulCount(ScoreManager.Instance.CurrentScore); Debug.Log("[GameHUD] 延迟订阅 ScoreManager.onScoreChanged 成功"); } UpdateLifeIcons(); UpdateSkillCooldowns(); } /// /// 更新魂灵计数(左上角)。 /// private void UpdateSoulCount(int count) { if (soulCountText != null) { soulCountText.text = count.ToString(); if (count != _lastSoulCount) { Debug.Log($"[GameHUD] UpdateSoulCount: {count}"); _lastSoulCount = count; } } else { Debug.LogWarning("[GameHUD] soulCountText 未赋值!"); } } /// /// 更新生命图标(左下角)。 /// 根据当前血量显示/隐藏对应图标。 /// private void UpdateLifeIcons() { 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; for (int i = 0; i < lifeIcons.Length; i++) { if (lifeIcons[i] != null) lifeIcons[i].enabled = (i < currentHealth); } } /// /// 更新技能CD遮罩(右下角)。 /// 使用 Image.fillAmount 实现圆形CD效果。 /// private void UpdateSkillCooldowns() { // 冲刺 CD(从 PlayerController 读取 rollCooldown + lastRollTime) 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, remaining, sprintCD); } // 摇铃 CD(从 EchoSystem 读取 cooldown + _lastEchoTime) 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, remaining, bellCD); } // 灵灯 CD(从 SpiritLanternSystem 读取) 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; UpdateSkillCD(skillLanternCD, remaining, maxCD); } } /// /// 更新单个技能CD遮罩。 /// fillAmount = 1 表示CD中(完全遮挡),0 表示就绪(无遮挡)。 /// private void UpdateSkillCD(Image cdImage, float remaining, float total) { if (cdImage == null) return; if (total <= 0f || remaining <= 0f) { cdImage.fillAmount = 0f; cdImage.gameObject.SetActive(false); } else { cdImage.gameObject.SetActive(true); cdImage.fillAmount = remaining / total; } } private void OnPauseClick() { if (TimeController.Instance != null) TimeController.Instance.Pause(); } } }