101 lines
3.5 KiB
C#
101 lines
3.5 KiB
C#
using UnityEngine;
|
||
|
||
/// <summary>
|
||
/// 全屏背景/遮罩自适应(Cover):保持原始宽高比,等比放大到覆盖整个根 Canvas,超出部分裁切。
|
||
/// 过覆盖 => 任意宽高比都无缝铺满、且 sizeDelta 永不为 0;对纯色遮罩与艺术背景都正确、不失真。
|
||
/// 仅调整 sizeDelta(表现层),不改锚点/位置/pivot,不影响居中布局与位移动画。
|
||
/// 16:9 屏下结果等于设计尺寸,与原始逐像素一致。由 UIResolutionAutoFitter 自动附加。
|
||
/// </summary>
|
||
[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<RectTransform>();
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>用元素初始 sizeDelta 推断原始宽高比,避免写死。</summary>
|
||
private void ResolveAspectFromInitialSize()
|
||
{
|
||
if (aspectResolved) return;
|
||
if (rt == null) rt = GetComponent<RectTransform>();
|
||
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<Canvas>();
|
||
if (canvas != null)
|
||
{
|
||
Canvas root = canvas.rootCanvas != null ? canvas.rootCanvas : canvas;
|
||
coverTarget = root.GetComponent<RectTransform>();
|
||
}
|
||
}
|
||
|
||
private void Fit(bool force)
|
||
{
|
||
if (_isFitting) return;
|
||
if (rt == null) rt = GetComponent<RectTransform>();
|
||
if (coverTarget == null) ResolveTarget();
|
||
if (coverTarget == null || sourceAspect <= 0f) return;
|
||
|
||
float pw = coverTarget.rect.width;
|
||
float ph = coverTarget.rect.height;
|
||
if (pw <= 0f || ph <= 0f) return;
|
||
if (!force && Mathf.Approximately(pw, lastW) && Mathf.Approximately(ph, lastH)) return;
|
||
lastW = pw; lastH = ph;
|
||
|
||
float targetAspect = pw / ph;
|
||
float w, h;
|
||
if (targetAspect > sourceAspect) { w = pw; h = pw / sourceAspect; }
|
||
else { h = ph; w = ph * sourceAspect; }
|
||
|
||
Vector2 newSize = new Vector2(w, h);
|
||
if (rt.sizeDelta != newSize)
|
||
{
|
||
_isFitting = true;
|
||
rt.sizeDelta = newSize;
|
||
_isFitting = false;
|
||
}
|
||
}
|
||
}
|