using System.Collections; using SepCore.Event; using GameFramework.Event; using SepCore.EnemyManager; using TMPro; using UnityEngine; using Unity.Profiling; using UnityEngine.UI; namespace SepCore.UI { public class HudForm : UGuiForm { [SerializeField] private float _sliderFadeDuration = 0.5f; [SerializeField] private Slider _hpSlider; [SerializeField] private TMP_Text _hpText; private float _currentHpPercent = 0; private Coroutine _hpSliderFadeCoroutine; [SerializeField] private Slider _expSlider; private float _currentExpPercent = 0; private Coroutine _expSliderFadeCoroutine; [SerializeField] private TMP_Text _expText; private int _playerCurrentLevel = 1; [SerializeField] private TMP_Text _coinText; private int _currentCoin = 0; [SerializeField] private TMP_Text _levelTimeLeftText; private int _currentTimeLeft = 0; [SerializeField] private TMP_Text _enemyCountText; private EnemyManagerComponent _enemy; [SerializeField] private GameObject _movePromptRoot; [SerializeField] private TMP_Text _movePromptText; [SerializeField] private TMP_Text _cpuTimeCostText; [SerializeField] private float _performanceStatsRefreshInterval = 0.2f; private ProfilerRecorder _cpuTimeRecorder; private float _performanceStatsRefreshTimer; private Coroutine _performanceStatsCoroutine; public void RefreshUI(HudContext hudContext) { RefreshMovePrompt(hudContext?.MovePrompt); } public void RefreshMovePrompt(string prompt) { bool visible = !string.IsNullOrEmpty(prompt); if (_movePromptRoot != null) { _movePromptRoot.SetActive(visible); } if (_movePromptText != null) { _movePromptText.text = prompt; } } #region FSM protected override void OnInit(object userData) { base.OnInit(userData); GameEntry.Event.Subscribe(PlayerHealthChangeEventArgs.EventId, PlayerHpChange); GameEntry.Event.Subscribe(PlayerExpChangeEventArgs.EventId, PlayerExpChange); GameEntry.Event.Subscribe(PlayerCoinChangeEventArgs.EventId, PlayerCoinChange); GameEntry.Event.Subscribe(PlayerLevelUpEventArgs.EventId, PlayerLevelUp); GameEntry.Event.Subscribe(LevelProcessEventArgs.EventId, LevelProcess); _enemy = GameEntry.EnemyManager; } protected override void OnOpen(object userData) { base.OnOpen(userData); StartPerformanceStatsCollection(); } protected override void OnPause() { StopPerformanceStatsCoroutine(); base.OnPause(); } protected override void OnResume() { base.OnResume(); StartPerformanceStatsCoroutine(); } private void StartPerformanceStatsCollection() { DisposePerformanceStatsRecorders(); _cpuTimeRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Internal, "Main Thread", 15); StartPerformanceStatsCoroutine(); } private void StartPerformanceStatsCoroutine() { StopPerformanceStatsCoroutine(); _performanceStatsRefreshTimer = 0f; _performanceStatsCoroutine = StartCoroutine(RefreshPerformanceStatsAtEndOfFrame()); } protected override void OnClose(bool isShutdown, object userData) { GameEntry.Event.Unsubscribe(PlayerLevelUpEventArgs.EventId, PlayerLevelUp); GameEntry.Event.Unsubscribe(PlayerHealthChangeEventArgs.EventId, PlayerHpChange); GameEntry.Event.Unsubscribe(PlayerExpChangeEventArgs.EventId, PlayerExpChange); GameEntry.Event.Unsubscribe(PlayerCoinChangeEventArgs.EventId, PlayerCoinChange); GameEntry.Event.Unsubscribe(PlayerLevelUpEventArgs.EventId, PlayerLevelUp); StopPerformanceStatsCoroutine(); DisposePerformanceStatsRecorders(); base.OnClose(isShutdown, userData); } private void StopPerformanceStatsCoroutine() { if (_performanceStatsCoroutine != null) { StopCoroutine(_performanceStatsCoroutine); _performanceStatsCoroutine = null; } } private void DisposePerformanceStatsRecorders() { if (_cpuTimeRecorder.Valid) { _cpuTimeRecorder.Dispose(); } } protected override void OnUpdate(float elapseSeconds, float realElapseSeconds) { base.OnUpdate(elapseSeconds, realElapseSeconds); if (_enemy == null) { _enemy = GameEntry.EnemyManager; } if (_enemy != null) { _enemyCountText.text = $"Enemy: {_enemy.CurrentEnemyCount}"; } } #endregion private IEnumerator RefreshPerformanceStatsAtEndOfFrame() { WaitForEndOfFrame waitForEndOfFrame = new WaitForEndOfFrame(); while (true) { yield return waitForEndOfFrame; _performanceStatsRefreshTimer -= Time.unscaledDeltaTime; if (_performanceStatsRefreshTimer > 0f) continue; _performanceStatsRefreshTimer = Mathf.Max(0.05f, _performanceStatsRefreshInterval); RefreshPerformanceStats(); } } private void RefreshPerformanceStats() { if (_cpuTimeCostText == null) return; bool hasCpuFrame = TryGetLastValue(_cpuTimeRecorder, out long cpuFrameNanoseconds); if (!hasCpuFrame) { _cpuTimeCostText.text = "CPU Frame: -- ms\nSimulation: -- ms\nJob Wait: -- ms"; return; } float cpuFrameMilliseconds = cpuFrameNanoseconds / 1_000_000f; _cpuTimeCostText.SetText("CPU Frame: {0:2} ms",cpuFrameMilliseconds); } private static bool TryGetLastValue(ProfilerRecorder recorder, out long value) { if (recorder.Valid && recorder.Count > 0) { value = recorder.LastValue; return true; } value = 0; return false; } #region Event Handlers private void PlayerHpChange(object sender, GameEventArgs e) { if (!(e is PlayerHealthChangeEventArgs args)) return; _hpText.text = $"{args.CurrentHealth}/{args.MaxHealth}"; float percent = (float)args.CurrentHealth / args.MaxHealth; if (Mathf.Approximately(_currentHpPercent, percent)) return; _currentHpPercent = percent; if (_hpSliderFadeCoroutine != null) StopCoroutine(_hpSliderFadeCoroutine); _hpSliderFadeCoroutine = StartCoroutine(_hpSlider.SmoothValue(percent, _sliderFadeDuration)); } private void PlayerExpChange(object sender, GameEventArgs e) { if (!(e is PlayerExpChangeEventArgs args)) return; float percent = (float)args.CurrentExp / args.MaxExp; if (Mathf.Approximately(_currentExpPercent, percent)) return; _currentExpPercent = percent; if (_expSliderFadeCoroutine != null) StopCoroutine(_expSliderFadeCoroutine); _expSliderFadeCoroutine = StartCoroutine(_expSlider.SmoothValue(percent, _sliderFadeDuration)); } private void PlayerLevelUp(object sender, GameEventArgs e) { if (!(e is PlayerLevelUpEventArgs)) return; _playerCurrentLevel++; _expText.text = $"LV.{_playerCurrentLevel}"; } private void PlayerCoinChange(object sender, GameEventArgs e) { if (!(e is PlayerCoinChangeEventArgs args)) return; if (_currentCoin == args.CoinCount) return; _coinText.text = $" {args.CoinCount}"; _currentCoin = args.CoinCount; } private void LevelProcess(object sender, GameEventArgs e) { if (!(e is LevelProcessEventArgs args)) return; if (_currentTimeLeft == args.LevelTimeLeft) return; _currentTimeLeft = args.LevelTimeLeft; _levelTimeLeftText.text = _currentTimeLeft.ToString(); } #endregion } }