using UnityEngine;
///
/// 全屏背景/遮罩自适应(Cover):保持原始宽高比,等比放大到覆盖整个根 Canvas,超出部分裁切。
/// 过覆盖 => 任意宽高比都无缝铺满、且 sizeDelta 永不为 0;对纯色遮罩与艺术背景都正确、不失真。
/// 仅调整 sizeDelta(表现层),不改锚点/位置/pivot,不影响居中布局与位移动画。
/// 16:9 屏下结果等于设计尺寸,与原始逐像素一致。由 UIResolutionAutoFitter 自动附加。
///
[RequireComponent(typeof(RectTransform))]
[DisallowMultipleComponent]
public class UIBackgroundCover : MonoBehaviour
{
public float sourceAspect = 16f / 9f;
private RectTransform rt;
private RectTransform coverTarget;
private float lastW = -1f, lastH = -1f;
private bool aspectResolved;
// 重入锁:设置 sizeDelta 会同步触发 OnRectTransformDimensionsChange,
// 若不拦截会无限递归导致 StackOverflow 崩溃。
private bool _isFitting;
private void OnEnable()
{
rt = GetComponent();
ResolveAspectFromInitialSize();
ResolveTarget();
Fit(true);
}
private void LateUpdate()
{
// 每帧检测 coverTarget 实际尺寸变化(而非只看 Screen):Unity 布局延迟更新,
// Screen 变化后 CanvasScaler 可能过几帧才把新尺寸写入 rootCanvas.rect。
// Fit(false) 内部按 lastW/lastH 判重,尺寸未变时零开销,无需额外节流。
if (coverTarget == null) ResolveTarget();
if (coverTarget != null)
{
float pw = coverTarget.rect.width;
float ph = coverTarget.rect.height;
if (!Mathf.Approximately(pw, lastW) || !Mathf.Approximately(ph, lastH))
{
Fit(false);
}
}
}
/// 用元素初始 sizeDelta 推断原始宽高比,避免写死。
private void ResolveAspectFromInitialSize()
{
if (aspectResolved) return;
if (rt == null) rt = GetComponent();
Vector2 s = rt.sizeDelta;
if (s.x > 1f && s.y > 1f) sourceAspect = s.x / s.y;
aspectResolved = true;
}
private void OnRectTransformDimensionsChange()
{
if (_isFitting) return; // 拦截自身写入触发的递归回调
Fit(false);
}
private void ResolveTarget()
{
Canvas canvas = GetComponentInParent