using System; using System.Collections.Generic; using Cysharp.Threading.Tasks; using DG.Tweening; using PhxhSDK; using UnityEngine; using UnityEngine.UI; /// /// 项目侧自定义UI显隐动画播放器 /// public class UIAnimationPlayer : IUIAnimationPlayer { /// /// 显示动画开播前的预热帧数:给 Canvas/TMP/Layout 一点时间完成 rebuild,避免首开吞动画 /// private const int ShowWarmupFrames = 2; /// /// 缓存背景alpha /// private readonly Dictionary _bgImageOriginAlpha = new(); /// /// 提前初始化动画目标(在 UI Init 时调用,避免首次显示卡顿) /// public void PreInitialize(UIWindow window) { if (window == null) return; var profile = GetAnimProfile(window.AnimPresetType); if (profile == null) return; // 提前查找和缓存所有节点引用,避免首次显示时卡顿 EnsureAnimTargets(window); } /// /// 预设动画初始状态(在 SetActive 前调用,确保第一帧就是正确状态) /// public void PresetInitialState(UIWindow window, bool isShow) { if (window == null || !isShow) return; var profile = GetAnimProfile(window.AnimPresetType); if (profile == null) return; EnsureAnimTargets(window); // 避免截屏未就绪时白屏闪烁:预设阶段就强制 mask 透明(不隐藏节点) EnsureMaskTextureReady(isShow, profile); // 若缺少主体和背景,无需预设 if ((_bgImages == null || _bgImages.Count == 0) && _contentCg == null) return; // 设置动画初始状态(alpha=0, scale等) ResetAnimInitialState(isShow, profile); // 标记已预设,避免 PlayAnim 中重复设置 _isInitialStatePreset = true; } public async UniTask Play(UIWindow window, bool isShow, Action callback) { if (window == null) { callback?.Invoke(); return; } await PlayAnim(window, isShow, callback); } private async UniTask PlayAnim(UIWindow window, bool isShow, Action callBack = null) { // 隐藏动画不播放,直接执行回调 if (!isShow) { callBack?.Invoke(); return; } var profile = GetAnimProfile(window.AnimPresetType); var message = isShow ? "显示" : "隐藏"; if (profile == null) { DebugUtil.LogG($"{window.name} - {window.AnimPresetType} - 使用默认方案 播放{message}动画"); // 使用默认播放器 await PlayAnimationDefault(window, isShow, callBack); return; } DebugUtil.LogG($"{window.name} - {window.AnimPresetType} - 使用新方案 播放{message}动画"); EnsureAnimTargets(window); // 若缺少主体和背景,降级为旧动画 if ((_bgImages == null || _bgImages.Count == 0) && _contentCg == null) { // 使用默认播放器 await PlayAnimationDefault(window, isShow, callBack); return; } window.OpenAnimSeq?.Kill(); window.IsCloseCbCalled = false; window.IsPlayingOpenAnim = true; // ✅ 显示时等待若干帧,让 UI 布局稳定后再开播,避免首开卡顿导致动画跳帧 if (isShow && ShowWarmupFrames > 0) { await UniTask.DelayFrame(ShowWarmupFrames); } // 设置初始状态 // 显示动画:如果 PresetInitialState 已调用,则跳过(避免重复设置) // 隐藏动画:必须在这里设置初始状态 if (!isShow || !_isInitialStatePreset) { ResetAnimInitialState(isShow, profile); } // 重置标记(用于下次动画) _isInitialStatePreset = false; window.OpenAnimSeq = DOTween.Sequence(); // 遮罩:useMask=false 时直接设为最终值,不播放动画 var canPlayMask = EnsureMaskTextureReady(isShow, profile); if (canPlayMask && _maskCg != null && profile.maskFade.Duration > 0f) { var tween = _maskCg.DOFade(isShow ? 1f : 0f, profile.maskFade.Duration) .SetDelay(profile.maskFade.start) .SetEase(profile.alphaCurve); window.OpenAnimSeq.Join(tween); } else if (_maskCg != null) { _maskCg.alpha = isShow ? 1f : 0f; } // 背景透明度 if (profile.bgFade.Duration > 0f && _bgImages.Count > 0) { foreach (var img in _bgImages) { if (img == null) continue; float targetAlpha = 1f; if (_bgImageOriginAlpha.TryGetValue(img, out var originAlpha)) targetAlpha = originAlpha; var tween = img.DOFade(isShow ? targetAlpha : 0f, profile.bgFade.Duration) .SetDelay(profile.bgFade.start) .SetEase(profile.alphaCurve); window.OpenAnimSeq.Join(tween); } } // 背景缩放(作用在 BgRoot),隐藏时走反向(显示为 1.1->1,隐藏为 1->1.1) if (_bgRoot != null && profile.bgScale.Duration > 0f) { var toScale = isShow ? profile.bgScaleTo : profile.bgScaleFrom; var tween = _bgRoot.DOScale(toScale, profile.bgScale.Duration) .SetDelay(profile.bgScale.start) .SetEase(profile.scaleCurve); window.OpenAnimSeq.Join(tween); } // 主体透明度 if (profile.contentFade.Duration > 0f && _contentCg != null) { var tween = _contentCg.DOFade(isShow ? 1f : 0f, profile.contentFade.Duration) .SetDelay(profile.contentFade.start) .SetEase(profile.alphaCurve); window.OpenAnimSeq.Join(tween); } window.OpenAnimSeq.OnComplete(() => { if (window.IsCloseCbCalled) return; window.IsPlayingOpenAnim = false; window.OpenAnimSeq = null; window.IsCloseCbCalled = true; callBack?.Invoke(); }); await UniTask.WaitUntil(() => { if (window.OpenAnimSeq == null) return true; if (!window.OpenAnimSeq.IsActive()) return true; return window.OpenAnimSeq.IsComplete(); }); // 补偿兜底 if (!window.IsCloseCbCalled) { window.IsPlayingOpenAnim = false; window.OpenAnimSeq?.Kill(); window.OpenAnimSeq = null; window.IsCloseCbCalled = true; callBack?.Invoke(); } } /// /// 获取界面对应的配置,若资源未就绪则使用内置预设兜底,避免首次打开无动画 /// /// /// private UIAnimProfile GetAnimProfile(UIAnimPresetType preset) { if (preset == UIAnimPresetType.None) return null; var assetProfile = UIManager.Instance.AnimProfileAsset?.GetProfile(preset); if (assetProfile != null) return assetProfile; return s_defaultProfiles.GetValueOrDefault(preset); } /// /// 高斯模糊节点命名 /// private const string BlurMaskName = "GaussianBlurMask"; /// /// 背景节点命名 /// private const string BgRootName = "BgRoot"; /// /// 界面内容主体节点命名 /// private const string ContentRootName = "MainNode"; /// /// 挂在界面上的动画节点 /// private UIAnimationNode animationNode; // 记录当前缓存的窗口,避免跨窗口复用动画目标 private UIWindow _cachedWindow; private RectTransform _bgRoot; private RectTransform _contentRoot; private CanvasGroup _maskCg; private RawImage _maskRawImage; private CanvasGroup _contentCg; /// /// 背景图列表 /// private readonly List _bgImages = new List(); /// /// 标记初始状态是否已预设(避免重复设置) /// private bool _isInitialStatePreset = false; /// /// 内置预设兜底,资源未加载到时使用 /// private static readonly Dictionary s_defaultProfiles = new() { { UIAnimPresetType.Level1, new UIAnimProfile { totalDuration = 0.3f, maskFade = new UIAnimRange(0f, 0.05f), bgFade = new UIAnimRange(0.02f, 0.2f), bgScale = new UIAnimRange(0.02f, 0.12f), bgScaleFrom = 1.1f, bgScaleTo = 1f, contentFade = new UIAnimRange(0.15f, 0.3f), } }, { UIAnimPresetType.Level2, new UIAnimProfile { totalDuration = 0.3f, maskFade = new UIAnimRange(0f, 0.05f), bgFade = new UIAnimRange(0.02f, 0.2f), bgScale = new UIAnimRange(0.02f, 0.12f), bgScaleFrom = 1.1f, bgScaleTo = 1f, contentFade = new UIAnimRange(0.15f, 0.3f), } }, { UIAnimPresetType.Level3, new UIAnimProfile { totalDuration = 0.15f, maskFade = new UIAnimRange(0f, 0.05f), bgFade = new UIAnimRange(0f, 0.15f), bgScale = new UIAnimRange(0.02f, 0.1f), bgScaleFrom = 1.1f, bgScaleTo = 1f, contentFade = new UIAnimRange(0.11f, 0.15f), } }, }; /// /// 初始化动画需要的节点和组件 /// private void EnsureAnimTargets(UIWindow window) { if (window == null) return; if (_cachedWindow != window) { _cachedWindow = window; animationNode = null; _bgRoot = null; _contentRoot = null; _maskCg = null; _maskRawImage = null; _contentCg = null; _bgImages.Clear(); _bgImageOriginAlpha.Clear(); _isInitialStatePreset = false; } if (animationNode == null) { animationNode = window.transform.GetComponent(); } if (_maskCg == null) { var maskTrans = window.transform.Find(BlurMaskName); if (maskTrans != null) { if (_maskRawImage == null) { _maskRawImage = maskTrans.GetComponent(); } _maskCg = maskTrans.GetComponent(); if (_maskCg == null) { _maskCg = maskTrans.gameObject.AddComponent(); } } } if (_bgRoot == null) { if (animationNode != null && animationNode.BGRoot != null) { _bgRoot = animationNode.BGRoot.transform as RectTransform; } if (_bgRoot == null) { _bgRoot = window.transform.Find(BgRootName) as RectTransform; } } if (_bgImages.Count == 0 && _bgRoot != null) { _bgImages.AddRange(_bgRoot.GetComponentsInChildren(true)); foreach (var img in _bgImages) { if (img == null) continue; _bgImageOriginAlpha[img] = img.color.a; } } if (_contentRoot == null) { if (animationNode != null && animationNode.ContentRoot != null) { _contentRoot = animationNode.ContentRoot.transform as RectTransform; } if (_contentRoot == null) { _contentRoot = window.transform.Find(ContentRootName) as RectTransform; } } if (_contentCg == null && _contentRoot != null) { _contentCg = _contentRoot.GetComponent(); if (_contentCg == null) _contentCg = _contentRoot.gameObject.AddComponent(); } } /// /// 设置初始透明度/缩放 /// private void ResetAnimInitialState(bool isShow, UIAnimProfile profile) { // 高斯模糊背景不修改 /*if (_maskCg != null) { _maskCg.alpha = isShow ? 0f : 1f; }*/ if (_bgImages != null) { foreach (var img in _bgImages) { if (img == null) continue; var c = img.color; // c.a = isShow ? 0f : 1f; if (_bgImageOriginAlpha.TryGetValue(img, out var originAlpha)) { c.a = isShow ? 0f : originAlpha; } else { c.a = isShow ? 0f : img.color.a; } img.color = c; } } if (_bgRoot != null) { _bgRoot.localScale = Vector3.one * (isShow ? profile.bgScaleFrom : profile.bgScaleTo); } if (_contentCg != null) { _contentCg.alpha = isShow ? 0f : 1f; } } /// /// useMask=true 时确保模糊遮罩纹理就绪,未就绪时跳过动画并强制透明(避免 RawImage 默认白屏闪烁) /// /// true=纹理就绪可播放动画; false=纹理未就绪已强制透明,跳过动画 private bool EnsureMaskTextureReady(bool isShow, UIAnimProfile profile) { if (!profile.useMask) return false; if (_maskRawImage == null) return false; // 截屏纹理未就绪:保持节点激活但强制透明(alpha=0),跳过动画,避免 RawImage 显示默认白色 if (_maskRawImage.texture == null) { _maskRawImage.gameObject.SetActive(true); // 保持激活(不隐藏节点) if (_maskCg != null) _maskCg.alpha = 0f; // 强制透明,避免白屏 return false; // 跳过fade动画 } // 纹理就绪:确保节点可见(透明度由 CanvasGroup 动画控制) _maskRawImage.gameObject.SetActive(true); return true; } /// /// 播放窗口动画 默认方法 /// private async UniTask PlayAnimationDefault(UIWindow uiWindow, bool isShow, Action callBack = null) { var canvasGroup = uiWindow.gameObject.GetComponent(); if (!canvasGroup) { canvasGroup = uiWindow.gameObject.AddComponent(); } var time = 0.2f; uiWindow.IsPlayingOpenAnim = true; uiWindow.OpenAnimSeq = DOTween.Sequence(); if (isShow) { // 只有当前透明度较低时才做淡入,避免 active=true 后被强制拉成 0 导致闪烁 if (canvasGroup.alpha < 0.99f) { time = 0.3f; var tw0 = canvasGroup.DOFade(1, time); uiWindow.OpenAnimSeq.Append(tw0); uiWindow.OpenAnimSeq.AppendCallback(() => { uiWindow.IsPlayingOpenAnim = false; uiWindow.OpenAnimSeq.Kill(); uiWindow.OpenAnimSeq = null; callBack?.Invoke(); }); } else { uiWindow.IsPlayingOpenAnim = false; uiWindow.OpenAnimSeq.Kill(); uiWindow.OpenAnimSeq = null; callBack?.Invoke(); } } else { canvasGroup.alpha = 1; var tw1 = canvasGroup.DOFade(0, time); uiWindow.OpenAnimSeq.Append(tw1); uiWindow.OpenAnimSeq.AppendCallback(() => { if (!uiWindow.IsCloseCbCalled) { uiWindow.IsPlayingOpenAnim = false; uiWindow.OpenAnimSeq.Kill(); uiWindow.OpenAnimSeq = null; callBack?.Invoke(); uiWindow.IsCloseCbCalled = true; } }); await UniTask.WaitUntil(() => { if (null == uiWindow.OpenAnimSeq) return true; if (!uiWindow.OpenAnimSeq.IsActive()) return true; return uiWindow.OpenAnimSeq.IsComplete(); }); //保证销毁界面时全都销毁掉 //await UniTask.WaitForSeconds(time); // UniTask.wa((int)time * 1000 + 10); if (!uiWindow.IsCloseCbCalled) { uiWindow.IsPlayingOpenAnim = false; uiWindow.OpenAnimSeq?.Kill(); uiWindow.OpenAnimSeq = null; callBack?.Invoke(); uiWindow.IsCloseCbCalled = true; } } } }