NLDClient-yudde/ProjectNLD/Assets/Code/Scripts/Framework/UI/UIAnimationPlayer.cs

490 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using PhxhSDK;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 项目侧自定义UI显隐动画播放器
/// </summary>
public class UIAnimationPlayer : IUIAnimationPlayer
{
/// <summary>
/// 显示动画开播前的预热帧数:给 Canvas/TMP/Layout 一点时间完成 rebuild避免首开吞动画
/// </summary>
private const int ShowWarmupFrames = 2;
/// <summary>
/// 提前初始化动画目标(在 UI Init 时调用,避免首次显示卡顿)
/// </summary>
public void PreInitialize(UIWindow window)
{
if (window == null)
return;
var profile = GetAnimProfile(window.AnimPresetType);
if (profile == null)
return;
// 提前查找和缓存所有节点引用,避免首次显示时卡顿
EnsureAnimTargets(window);
}
/// <summary>
/// 预设动画初始状态(在 SetActive 前调用,确保第一帧就是正确状态)
/// </summary>
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)
{
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;
var tween = img.DOFade(isShow ? 1f : 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();
}
}
/// <summary>
/// 获取界面对应的配置,若资源未就绪则使用内置预设兜底,避免首次打开无动画
/// </summary>
/// <param name="preset"></param>
/// <returns></returns>
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);
}
/// <summary>
/// 高斯模糊节点命名
/// </summary>
private const string BlurMaskName = "GaussianBlurMask";
/// <summary>
/// 背景节点命名
/// </summary>
private const string BgRootName = "BgRoot";
/// <summary>
/// 界面内容主体节点命名
/// </summary>
private const string ContentRootName = "MainNode";
/// <summary>
/// 挂在界面上的动画节点
/// </summary>
private UIAnimationNode animationNode;
private RectTransform _bgRoot;
private RectTransform _contentRoot;
private CanvasGroup _maskCg;
private RawImage _maskRawImage;
private CanvasGroup _contentCg;
/// <summary>
/// 背景图列表
/// </summary>
private readonly List<Image> _bgImages = new List<Image>();
/// <summary>
/// 标记初始状态是否已预设(避免重复设置)
/// </summary>
private bool _isInitialStatePreset = false;
/// <summary>
/// 内置预设兜底,资源未加载到时使用
/// </summary>
private static readonly Dictionary<UIAnimPresetType, UIAnimProfile> 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),
}
},
};
/// <summary>
/// 初始化动画需要的节点和组件
/// </summary>
private void EnsureAnimTargets(UIWindow window)
{
if (animationNode == null)
{
animationNode = window.transform.GetComponent<UIAnimationNode>();
}
if (_maskCg == null)
{
var maskTrans = window.transform.Find(BlurMaskName);
if (maskTrans != null)
{
if (_maskRawImage == null)
{
_maskRawImage = maskTrans.GetComponent<RawImage>();
}
_maskCg = maskTrans.GetComponent<CanvasGroup>();
if (_maskCg == null)
{
_maskCg = maskTrans.gameObject.AddComponent<CanvasGroup>();
}
}
}
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<Image>(true));
}
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<CanvasGroup>();
if (_contentCg == null)
_contentCg = _contentRoot.gameObject.AddComponent<CanvasGroup>();
}
}
/// <summary>
/// 设置初始透明度/缩放
/// </summary>
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;
img.color = c;
}
}
if (_bgRoot != null)
{
_bgRoot.localScale = Vector3.one * (isShow ? profile.bgScaleFrom : profile.bgScaleTo);
}
if (_contentCg != null)
{
_contentCg.alpha = isShow ? 0f : 1f;
}
}
/// <summary>
/// useMask=true 时确保模糊遮罩纹理就绪,未就绪时跳过动画并强制透明(避免 RawImage 默认白屏闪烁)
/// </summary>
/// <returns>true=纹理就绪可播放动画; false=纹理未就绪已强制透明,跳过动画</returns>
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;
}
/// <summary>
/// 播放窗口动画 默认方法
/// </summary>
private async UniTask PlayAnimationDefault(UIWindow uiWindow, bool isShow, Action callBack = null)
{
var canvasGroup = uiWindow.gameObject.GetComponent<CanvasGroup>();
if (!canvasGroup)
{
canvasGroup = uiWindow.gameObject.AddComponent<CanvasGroup>();
}
var time = 0.2f;
uiWindow.IsPlayingOpenAnim = true;
uiWindow.OpenAnimSeq = DOTween.Sequence();
if (isShow)
{
canvasGroup.alpha = 0;
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
{
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;
}
}
}
}