using System.Collections; using Event; using TMPro; using UnityEngine; using UnityEngine.UI; using UnityGameFramework.Runtime; namespace UI { public class ChapterTitleForm : UGuiForm { [SerializeField] private CanvasGroup _contentGroup; [SerializeField] private Image _dimImage; [SerializeField] private TMP_Text _titleText; [SerializeField] private TMP_Text _subtitleText; [SerializeField] private float _dimFadeInDuration = 0.2f; [SerializeField] private float _preBlackDuration = 0.25f; [SerializeField] private float _titleFadeInDelay = 0f; [SerializeField] private float _fadeInDuration = 0.3f; [SerializeField] private float _holdDuration = 1.2f; [SerializeField] private float _fadeOutDuration = 0.3f; [SerializeField] private float _dimFadeOutDuration = 0.2f; private Coroutine _playCoroutine; private int _requestId; private int _chapterId; private bool _hasPublishedCompletion; private float _runtimeDimFadeInDuration; private float _runtimePreBlackDuration; private float _runtimeTitleFadeInDelay; private float _runtimeTitleFadeInDuration; private float _runtimeHoldDuration; private float _runtimeTitleFadeOutDuration; private float _runtimeDimFadeOutDuration; protected override void OnOpen(object userData) { base.OnOpen(userData); EnsureView(); ApplyContext(userData as ChapterTitleFormContext); StartPlay(); } protected override void OnClose(bool isShutdown, object userData) { StopPlay(); SetDimAlpha(0f); SetTitleAlpha(0f); if (!isShutdown) { PublishCompletionIfNeeded(); } base.OnClose(isShutdown, userData); } private void EnsureView() { RectTransform rootRect = transform as RectTransform; if (rootRect == null) { return; } if (_contentGroup == null) { _contentGroup = CreateContentGroup(rootRect); } if (_contentGroup != null) { _contentGroup.alpha = 1f; } RectTransform contentRect = _contentGroup != null && _contentGroup.transform is RectTransform contentTransform ? contentTransform : rootRect; if (_dimImage == null) { _dimImage = CreateImage("Dim", contentRect, new Color(0f, 0f, 0f, 0.72f), true); } if (_titleText == null) { _titleText = CreateText("Title", contentRect, 108f, FontStyles.Bold, TextAlignmentOptions.Center); RectTransform titleRect = _titleText.rectTransform; titleRect.anchorMin = new Vector2(0.5f, 0.5f); titleRect.anchorMax = new Vector2(0.5f, 0.5f); titleRect.pivot = new Vector2(0.5f, 0.5f); titleRect.sizeDelta = new Vector2(2200f, 220f); titleRect.anchoredPosition = new Vector2(0f, 40f); } if (_subtitleText == null) { _subtitleText = CreateText("Subtitle", contentRect, 42f, FontStyles.Normal, TextAlignmentOptions.Center); RectTransform subtitleRect = _subtitleText.rectTransform; subtitleRect.anchorMin = new Vector2(0.5f, 0.5f); subtitleRect.anchorMax = new Vector2(0.5f, 0.5f); subtitleRect.pivot = new Vector2(0.5f, 0.5f); subtitleRect.sizeDelta = new Vector2(2200f, 120f); subtitleRect.anchoredPosition = new Vector2(0f, -60f); } if (_dimImage != null) { _dimImage.transform.SetAsFirstSibling(); } if (_subtitleText != null) { _subtitleText.transform.SetAsLastSibling(); } if (_titleText != null) { _titleText.transform.SetAsLastSibling(); } } private void ApplyContext(ChapterTitleFormContext context) { _hasPublishedCompletion = false; ResetRuntimeTimings(context); if (context == null) { _requestId = 0; _chapterId = 0; SetTitleText(string.Empty, string.Empty); return; } _requestId = context.RequestId; _chapterId = context.ChapterId; string title = string.IsNullOrWhiteSpace(context.Title) ? BuildDefaultChapterTitle(_chapterId) : context.Title.Trim(); string subtitle = string.IsNullOrWhiteSpace(context.Subtitle) ? string.Empty : context.Subtitle.Trim(); SetTitleText(title, subtitle); } private void SetTitleText(string title, string subtitle) { if (_titleText != null) { _titleText.text = title; } if (_subtitleText != null) { _subtitleText.text = subtitle; _subtitleText.gameObject.SetActive(!string.IsNullOrEmpty(subtitle)); } } private void StartPlay() { StopPlay(); _playCoroutine = StartCoroutine(PlayRoutine()); } private void StopPlay() { if (_playCoroutine == null) { return; } StopCoroutine(_playCoroutine); _playCoroutine = null; } private IEnumerator PlayRoutine() { SetDimAlpha(0f); SetTitleAlpha(0f); yield return FadeDimAlpha(0f, 1f, _runtimeDimFadeInDuration); yield return WaitUnscaled(_runtimePreBlackDuration); yield return WaitUnscaled(_runtimeTitleFadeInDelay); yield return FadeTitleAlpha(0f, 1f, _runtimeTitleFadeInDuration); yield return WaitUnscaled(_runtimeHoldDuration); yield return FadeTitleAlpha(1f, 0f, _runtimeTitleFadeOutDuration); yield return FadeDimAlpha(1f, 0f, _runtimeDimFadeOutDuration); _playCoroutine = null; PublishCompletionIfNeeded(); if (GameEntry.UI != null && UIForm != null) { GameEntry.UI.CloseUIForm(UIForm); } } private IEnumerator FadeDimAlpha(float from, float to, float duration) { float safeDuration = Mathf.Max(0f, duration); if (safeDuration <= 0f) { SetDimAlpha(to); yield break; } float elapsed = 0f; while (elapsed < safeDuration) { elapsed += Time.unscaledDeltaTime; float t = Mathf.Clamp01(elapsed / safeDuration); SetDimAlpha(Mathf.Lerp(from, to, t)); yield return null; } SetDimAlpha(to); } private IEnumerator FadeTitleAlpha(float from, float to, float duration) { float safeDuration = Mathf.Max(0f, duration); if (safeDuration <= 0f) { SetTitleAlpha(to); yield break; } float elapsed = 0f; while (elapsed < safeDuration) { elapsed += Time.unscaledDeltaTime; float t = Mathf.Clamp01(elapsed / safeDuration); SetTitleAlpha(Mathf.Lerp(from, to, t)); yield return null; } SetTitleAlpha(to); } private static IEnumerator WaitUnscaled(float duration) { float safeDuration = Mathf.Max(0f, duration); if (safeDuration <= 0f) { yield break; } float elapsed = 0f; while (elapsed < safeDuration) { elapsed += Time.unscaledDeltaTime; yield return null; } } private void SetDimAlpha(float alpha) { if (_dimImage == null) { return; } Color color = _dimImage.color; color.a = Mathf.Clamp01(alpha); _dimImage.color = color; } private void SetTitleAlpha(float alpha) { float clampedAlpha = Mathf.Clamp01(alpha); SetTextAlpha(_titleText, clampedAlpha); SetTextAlpha(_subtitleText, clampedAlpha); } private static void SetTextAlpha(TMP_Text text, float alpha) { if (text == null) { return; } Color color = text.color; color.a = alpha; text.color = color; } private void ResetRuntimeTimings(ChapterTitleFormContext context) { _runtimeDimFadeInDuration = ResolveDuration(context != null ? context.DimFadeInDuration : -1f, _dimFadeInDuration); _runtimePreBlackDuration = ResolveDuration(context != null ? context.PreBlackDuration : -1f, _preBlackDuration); _runtimeTitleFadeInDelay = ResolveDuration(context != null ? context.TitleFadeInDelay : -1f, _titleFadeInDelay); _runtimeTitleFadeInDuration = ResolveDuration(context != null ? context.TitleFadeInDuration : -1f, _fadeInDuration); _runtimeHoldDuration = ResolveDuration(context != null ? context.HoldDuration : -1f, _holdDuration); _runtimeTitleFadeOutDuration = ResolveDuration(context != null ? context.TitleFadeOutDuration : -1f, _fadeOutDuration); _runtimeDimFadeOutDuration = ResolveDuration(context != null ? context.DimFadeOutDuration : -1f, _dimFadeOutDuration); } private static float ResolveDuration(float overrideDuration, float fallbackDuration) { return overrideDuration >= 0f ? overrideDuration : Mathf.Max(0f, fallbackDuration); } private void PublishCompletionIfNeeded() { if (_hasPublishedCompletion || _requestId <= 0) { return; } _hasPublishedCompletion = true; GameEntry.Event.Fire(this, ChapterTitleCompletedEventArgs.Create(_chapterId, _requestId)); } private static string BuildDefaultChapterTitle(int chapterId) { return chapterId > 0 ? $"第{chapterId}章" : string.Empty; } private static CanvasGroup CreateContentGroup(RectTransform parent) { GameObject go = new GameObject("ContentRoot", typeof(RectTransform), typeof(CanvasGroup)); RectTransform rect = go.GetComponent(); rect.SetParent(parent, false); rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; return go.GetComponent(); } private static Image CreateImage(string name, RectTransform parent, Color color, bool raycastTarget) { GameObject go = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image)); RectTransform rect = go.GetComponent(); rect.SetParent(parent, false); rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; Image image = go.GetComponent(); image.color = color; image.raycastTarget = raycastTarget; return image; } private static TMP_Text CreateText(string name, RectTransform parent, float fontSize, FontStyles style, TextAlignmentOptions alignment) { GameObject go = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI)); RectTransform rect = go.GetComponent(); rect.SetParent(parent, false); TextMeshProUGUI text = go.GetComponent(); if (TMP_Settings.defaultFontAsset != null) { text.font = TMP_Settings.defaultFontAsset; } text.text = string.Empty; text.fontSize = fontSize; text.fontStyle = style; text.alignment = alignment; text.color = new Color(0.96f, 0.94f, 0.88f, 1f); text.raycastTarget = false; return text; } } }