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;
}
}
}
}