using System.Collections; using System.Collections.Generic; using Definition.Enum; using UnityEngine; using UnityEngine.UI; using UnityGameFramework.Runtime; namespace UI { public class MainOverlayForm : UGuiForm { [SerializeField] private Image _cueImage; [SerializeField] private List _cueBindings = new List(); [SerializeField] private float _showDuration = 0.2f; [SerializeField] private float _hideDuration = 0.2f; private static int _dispatchCount; private Coroutine _transitionCoroutine; private bool _isClosingByCue; private Vector3 _baseScale = Vector3.one; private bool _baseScaleInitialized; public static void DispatchCue(EmphasisType emphasis) { if (GameEntry.UI == null) { return; } UGuiForm existingForm = GameEntry.UI.GetUIForm(UIFormId.MainOverlayForm); if (emphasis == EmphasisType.EntityCueClose) { if (existingForm is MainOverlayForm overlay) { overlay.PlayHideAndClose(); } return; } if (emphasis != EmphasisType.EntityCue) { return; } if (existingForm is MainOverlayForm overlayForm) { overlayForm.ShowNextCue(); return; } int? formSerialId = GameEntry.UI.OpenUIForm(UIFormId.MainOverlayForm); if (!formSerialId.HasValue) { Log.Warning("MainOverlayForm open failed. Check UIForm.txt config and prefab asset."); } } protected override void OnOpen(object userData) { base.OnOpen(userData); EnsureBaseScaleInitialized(); ShowNextCue(); } protected override void OnClose(bool isShutdown, object userData) { StopTransition(); HideCueImage(clearSprite: true); base.OnClose(isShutdown, userData); } private void ShowNextCue() { if (_cueImage == null) { Log.Warning("MainOverlayForm cue image is missing."); return; } if (_cueBindings == null || _cueBindings.Count == 0) { HideCueImage(clearSprite: false); Log.Warning("MainOverlayForm cue bindings are empty."); return; } int index = _dispatchCount % _cueBindings.Count; _dispatchCount++; Sprite sprite = _cueBindings[index]; if (sprite == null) { HideCueImage(clearSprite: false); return; } EnsureBaseScaleInitialized(); StopTransition(); _transitionCoroutine = StartCoroutine(PlayShowRoutine(sprite)); } private void PlayHideAndClose() { if (_isClosingByCue) { return; } if (_cueImage == null) { if (GameEntry.UI != null && UIForm != null) { GameEntry.UI.CloseUIForm(UIForm); } return; } StopTransition(); _transitionCoroutine = StartCoroutine(PlayHideAndCloseRoutine()); } private IEnumerator PlayShowRoutine(Sprite sprite) { _cueImage.sprite = sprite; _cueImage.SetNativeSize(); _cueImage.gameObject.SetActive(true); SetCueVisual(0f, 0f); yield return PlayLerp(0f, 1f, 0f, 0.6f, _showDuration); _transitionCoroutine = null; } private IEnumerator PlayHideAndCloseRoutine() { _isClosingByCue = true; float fromAlpha = _cueImage.color.a; float fromScale = GetCurrentScale01(); yield return PlayLerp(fromAlpha, 0f, fromScale, 0f, _hideDuration); HideCueImage(clearSprite: true); _isClosingByCue = false; _transitionCoroutine = null; if (GameEntry.UI != null && UIForm != null) { GameEntry.UI.CloseUIForm(UIForm); } } private IEnumerator PlayLerp(float fromAlpha, float toAlpha, float fromScale, float toScale, float duration) { if (_cueImage == null) { yield break; } float safeDuration = Mathf.Max(0f, duration); if (safeDuration <= 0f) { SetCueVisual(toAlpha, toScale); yield break; } float elapsed = 0f; while (elapsed < safeDuration) { elapsed += Time.unscaledDeltaTime; float t = Mathf.Clamp01(elapsed / safeDuration); float alpha = Mathf.Lerp(fromAlpha, toAlpha, t); float scale = Mathf.Lerp(fromScale, toScale, t); SetCueVisual(alpha, scale); yield return null; } SetCueVisual(toAlpha, toScale); } private void SetCueVisual(float alpha, float scale01) { if (_cueImage == null) { return; } Color color = _cueImage.color; color.a = Mathf.Clamp01(alpha); _cueImage.color = color; RectTransform rectTransform = _cueImage.rectTransform; if (rectTransform == null) { return; } float safeScale = Mathf.Clamp01(scale01); rectTransform.localScale = _baseScale * safeScale; } private float GetCurrentScale01() { if (_cueImage == null) { return 1f; } EnsureBaseScaleInitialized(); RectTransform rectTransform = _cueImage.rectTransform; if (rectTransform == null) { return 1f; } float ratioX = Mathf.Abs(_baseScale.x) > 0.0001f ? rectTransform.localScale.x / _baseScale.x : 1f; float ratioY = Mathf.Abs(_baseScale.y) > 0.0001f ? rectTransform.localScale.y / _baseScale.y : 1f; return Mathf.Clamp01((ratioX + ratioY) * 0.5f); } private void EnsureBaseScaleInitialized() { if (_baseScaleInitialized) { return; } if (_cueImage == null || _cueImage.rectTransform == null) { _baseScale = Vector3.one; _baseScaleInitialized = true; return; } _baseScale = _cueImage.rectTransform.localScale; if (_baseScale == Vector3.zero) { _baseScale = Vector3.one; } _baseScaleInitialized = true; } private void StopTransition() { if (_transitionCoroutine != null) { StopCoroutine(_transitionCoroutine); _transitionCoroutine = null; } _isClosingByCue = false; } private void HideCueImage(bool clearSprite) { if (_cueImage == null) { return; } _cueImage.gameObject.SetActive(false); SetCueVisual(0f, 0f); if (clearSprite) { _cueImage.sprite = null; } } } }