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(); if (canvas != null) { Canvas root = canvas.rootCanvas != null ? canvas.rootCanvas : canvas; coverTarget = root.GetComponent(); } } private void Fit(bool force) { if (_isFitting) return; if (rt == null) rt = GetComponent(); 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; } } }