using System; using System.Collections; using AibisDream.Framework; using AibisDream.Kit; using AibisDream.SaveSystem; using AibisDream.Utility; using DG.Tweening; using RainbowArt.CleanFlatUI; using UnityEngine; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.UI; namespace AibisDream.UI { /// /// 主要用来放置图片展示、窗口和时间流转等演出工具 /// public class PlayToolPanel : MonoBehaviour, IUIPanel { public bool IsOpen => gameObject.activeSelf; public bool IsCloseable => false; [Header("Obj尺寸参数")] public float objWidth; public float objHeight; /// Addressable Key 模板,与 Obj 精灵 Address 一致。 private const string ObjSpritePath = "Sprite/Obj/{0}.png"; #region 索引 private GameObject _showObjPanel; private Image _showObjImage; private Image _fullScreenImage; private Image _redFlash; private ModalWindowProgressBar _window; private ModalWindow _warningWindow; private SignBoard _signBoard; private TimeTurner _timeTurner; private int _timeFlowsVersion; private int _objOperationVersion; private int _fullScreenOperationVersion; private string _currentObjPicName; private string _currentFullScreenPicName; private bool _isObjVisible; private bool _isFullScreenVisible; #endregion private void Awake() { InitRefs(); } private void InitRefs() { _showObjPanel = transform.Find("Show Obj").gameObject; _showObjImage = _showObjPanel.transform.Find("Obj").GetComponent(); _fullScreenImage = transform.Find("Full Screen Image").GetComponent(); _window = transform.Find("进度条窗口").GetComponent(); _redFlash = transform.Find("Red Flash").GetComponent(); _signBoard = transform.GetComponentInChildren(); _timeTurner = transform.GetComponentInChildren(); } private static string NormalizeObjPicName(string picName) { if (string.IsNullOrEmpty(picName)) return picName; return picName.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ? picName.Substring(0, picName.Length - 4) : picName; } private IEnumerator LoadObjSprite( string picName, Action completed, Action warning = null) { var normalizedName = NormalizeObjPicName(picName); var key = string.Format(ObjSpritePath, normalizedName); var handle = ResourceSystem.LoadAsync(key); yield return handle; if (handle.Status == AsyncOperationStatus.Succeeded && handle.Result != null) { completed?.Invoke(handle.Result); yield break; } var message = $"[PlayToolPanel] Failed to load sprite: {key}"; Debug.LogError(message); warning?.Invoke(message); completed?.Invoke(null); } private void ApplyObjSprite(Sprite sprite) { var imageAspect = (float)sprite.texture.width / sprite.texture.height; if (imageAspect > 1f) { _showObjImage.rectTransform.sizeDelta = new Vector2(objWidth, objWidth / imageAspect); } else { _showObjImage.rectTransform.sizeDelta = new Vector2(objHeight * imageAspect, objHeight); } _showObjImage.sprite = sprite; } private void ClearObjImmediate() { _objOperationVersion++; _showObjImage.DOKill(); _showObjImage.SetAlpha(0f); _showObjPanel.SetActive(false); _currentObjPicName = null; _isObjVisible = false; } private void ClearFullScreenImmediate() { _fullScreenOperationVersion++; _fullScreenImage.DOKill(); _fullScreenImage.SetAlpha(0f); _fullScreenImage.gameObject.SetActive(false); _currentFullScreenPicName = null; _isFullScreenVisible = false; } /// /// 显示物体 /// /// 协程 public IEnumerator ShowObj(string picName, float duration = 1) { var version = ++_objOperationVersion; _showObjImage.DOKill(); _showObjPanel.gameObject.SetActive(true); Sprite newSprite = null; yield return LoadObjSprite(picName, value => newSprite = value); if (version != _objOperationVersion) { yield break; } if (newSprite == null) { ClearObjImmediate(); yield break; } ApplyObjSprite(newSprite); yield return _showObjImage.FadeInAsync(duration); if (version != _objOperationVersion) yield break; _currentObjPicName = NormalizeObjPicName(picName); _isObjVisible = true; } public IEnumerator HideObj(float duration = 1) { var version = ++_objOperationVersion; _currentObjPicName = null; _isObjVisible = false; _showObjImage.DOKill(); yield return _showObjImage.FadeOutAsync(duration); if (version != _objOperationVersion) yield break; _showObjPanel.SetActive(false); } public IEnumerator ShowFullScreen(string picName, float duration = 1) { var version = ++_fullScreenOperationVersion; _fullScreenImage.DOKill(); _fullScreenImage.gameObject.SetActive(true); Sprite newSprite = null; yield return LoadObjSprite(picName, value => newSprite = value); if (version != _fullScreenOperationVersion) { yield break; } if (newSprite == null) { ClearFullScreenImmediate(); yield break; } _fullScreenImage.sprite = newSprite; yield return _fullScreenImage.FadeInAsync(duration); if (version != _fullScreenOperationVersion) yield break; _currentFullScreenPicName = NormalizeObjPicName(picName); _isFullScreenVisible = true; } public IEnumerator HideFullScreen(float duration = 1) { var version = ++_fullScreenOperationVersion; _currentFullScreenPicName = null; _isFullScreenVisible = false; _fullScreenImage.DOKill(); yield return _fullScreenImage.FadeOutAsync(duration); if (version != _fullScreenOperationVersion) yield break; _fullScreenImage.gameObject.SetActive(false); } public PlayToolSnapshotDto CaptureSnapshot() { return new PlayToolSnapshotDto { isObjVisible = _isObjVisible, objPicName = _currentObjPicName, isFullScreenVisible = _isFullScreenVisible, fullScreenPicName = _currentFullScreenPicName }; } public IEnumerator RestoreSnapshotAsync(PlayToolSnapshotDto snapshot, Action warning = null) { ClearObjImmediate(); ClearFullScreenImmediate(); if (snapshot == null) yield break; if (snapshot.isObjVisible && !string.IsNullOrWhiteSpace(snapshot.objPicName)) { var version = ++_objOperationVersion; Sprite sprite = null; yield return LoadObjSprite(snapshot.objPicName, value => sprite = value, warning); if (version == _objOperationVersion && sprite != null) { ApplyObjSprite(sprite); _showObjPanel.SetActive(true); _showObjImage.SetAlpha(1f); _currentObjPicName = NormalizeObjPicName(snapshot.objPicName); _isObjVisible = true; } } else if (snapshot.isObjVisible) { warning?.Invoke("[PlayToolPanel] Obj snapshot is missing picName."); } if (snapshot.isFullScreenVisible && !string.IsNullOrWhiteSpace(snapshot.fullScreenPicName)) { var version = ++_fullScreenOperationVersion; Sprite sprite = null; yield return LoadObjSprite(snapshot.fullScreenPicName, value => sprite = value, warning); if (version == _fullScreenOperationVersion && sprite != null) { _fullScreenImage.sprite = sprite; _fullScreenImage.gameObject.SetActive(true); _fullScreenImage.SetAlpha(1f); _currentFullScreenPicName = NormalizeObjPicName(snapshot.fullScreenPicName); _isFullScreenVisible = true; } } else if (snapshot.isFullScreenVisible) { warning?.Invoke("[PlayToolPanel] Full-screen snapshot is missing picName."); } } public IEnumerator OpenProgressWindow(string title, string description, float duration) { AudioManager.Instance.PlaySfx("event:/Scriptal/Ui"); _window.TitleValue = title; _window.DescriptionValue = description; float currentProgress = 0; DOTween.To( () => currentProgress, value => { currentProgress = value; _window.SetProgress(currentProgress); // 更新进度 }, 100, duration ); _window.ShowModalWindow(); // 获取 moveText 的 RectTransform RectTransform moveTextRect = _window.transform.Find("View/mask/moveText")?.GetComponent(); if (moveTextRect != null) { moveTextRect.DOAnchorPosY(0f, duration).SetEase(Ease.Linear); } // 等待用户确认 yield return new WaitUntil(() => currentProgress >= 100); _window.HideModalWindow(); moveTextRect.DOAnchorPosY(1326f, 0.1f).SetEase(Ease.Linear); } // 红色闪烁效果 public IEnumerator FlashRed(float duration) { _redFlash.gameObject.SetActive(true); yield return _redFlash.FadeInAsync(duration / 2); yield return _redFlash.FadeOutAsync(duration / 2); _redFlash.gameObject.SetActive(false); } public void ShowSignBoard() { _signBoard.ShowSignBoard(); } public void EnableSignBoard() { _signBoard.EnableSignBoard(); } public void CloseSignBoard() { _signBoard.CloseSignBoard(); } /// /// 清理演出层内容,但不影响全局转场面板(用于返回主界面时在遮罩下重置状态)。 /// public void ClearPresentationEffects() { _timeFlowsVersion++; // SceneLoader unloads scenes with DOTween.KillAll(). Do not rely on a // tween completion callback to deactivate these raycast targets: if // that tween is killed, an invisible full-screen image blocks the UI. ClearFullScreenImmediate(); ClearObjImmediate(); _timeTurner.ForceStop(); } // ------------------------------- 时间流转 ------------------------------- public IEnumerator TimeFlowsOn(string originState, string targetState) { var version = ++_timeFlowsVersion; var transitionPanel = UIManager.Instance.GetPanel(); // 黑屏淡入 if (transitionPanel != null) { yield return transitionPanel.FadeInAsync(0.5f); } if (version != _timeFlowsVersion) yield break; // 时间流转 yield return _timeTurner.TurnTimeTo(originState, targetState); if (version != _timeFlowsVersion) yield break; // 黑屏淡出 if (transitionPanel != null) { yield return transitionPanel.FadeOutAsync(0.5f); } } #region 感觉这个面板应该常驻 public void Show() { gameObject.SetActive(true); } public void Hide() { gameObject.SetActive(false); } #endregion /// /// 打开警告窗口 /// /// 警告描述文本 /// 等待玩家确认的协程 public IEnumerator OpenWarningWindow(string description) { AudioManager.Instance.PlaySfx("event:/Scriptal/Ui"); _warningWindow.DescriptionValue = description; _warningWindow.ShowModalWindow(); // 等待玩家点击确认按钮 yield return new WaitUntil(() => !_warningWindow.gameObject.activeSelf); } } }