209 lines
7.0 KiB
C#
209 lines
7.0 KiB
C#
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using System.Collections;
|
||
|
||
/// <summary>
|
||
/// 终极等比例版:Image严格按原始比例填满父容器,尺寸正常、无留白、不变形、无报错
|
||
/// </summary>
|
||
[RequireComponent(typeof(Image))]
|
||
[ExecuteInEditMode]
|
||
public class ImageAspectFill : MonoBehaviour
|
||
{
|
||
[Header("基础配置")]
|
||
[Tooltip("Canvas的参考分辨率(必须和CanvasScaler一致,如1920x1080)")]
|
||
public Vector2 canvasReferenceSize = new Vector2(1920, 1080);
|
||
[Tooltip("最大缩放倍数(防止过度放大模糊)")]
|
||
public float maxScale = 10f;
|
||
|
||
[Header("调试开关")]
|
||
public bool showLog = false;
|
||
|
||
private Image targetImage;
|
||
private RectTransform imageRect;
|
||
private RectTransform parentRect;
|
||
private CanvasScaler canvasScaler;
|
||
|
||
private float originalAspectRatio; // Sprite原始宽高比(全程锁定)
|
||
private float canvasScaleFactor; // 统一的Canvas缩放因子(宽高共用)
|
||
private float baseUIWidth; // 基准UI宽度(按比例推导高度)
|
||
private float lastScreenSizeW = 0;
|
||
private float lastScreenSizeH = 0;
|
||
void Start()
|
||
{
|
||
lastScreenSizeW = Screen.width;
|
||
lastScreenSizeH = Screen.height;
|
||
|
||
StartCoroutine(DelayedInit());
|
||
}
|
||
|
||
void OnEnable()
|
||
{
|
||
if (targetImage != null)
|
||
{
|
||
FitToParentProportionally();
|
||
}
|
||
else
|
||
{
|
||
StartCoroutine(DelayedInit());
|
||
}
|
||
}
|
||
|
||
void OnRectTransformDimensionsChange()
|
||
{
|
||
if (targetImage != null)
|
||
{
|
||
FitToParentProportionally();
|
||
}
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
// 检测Game视图窗口尺寸变化(编辑器下)
|
||
if (lastScreenSizeW != Screen.width || lastScreenSizeH != Screen.height)
|
||
{
|
||
lastScreenSizeW = Screen.width;
|
||
lastScreenSizeH = Screen.height;
|
||
if (showLog)
|
||
{
|
||
Debug.Log($"[编辑器窗口变化] 尺寸变为 {lastScreenSizeW}x{lastScreenSizeH},重新适配Image", this);
|
||
}
|
||
FitToParentProportionally();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 延迟初始化:锁定原始比例+统一缩放因子
|
||
/// </summary>
|
||
private IEnumerator DelayedInit()
|
||
{
|
||
yield return null; // 等待Canvas布局完成
|
||
|
||
// 初始化组件
|
||
targetImage = GetComponent<Image>();
|
||
imageRect = GetComponent<RectTransform>();
|
||
parentRect = transform.parent.GetComponent<RectTransform>();
|
||
canvasScaler = GetComponentInParent<CanvasScaler>();
|
||
|
||
// 强制锚点居中(等比例缩放的前提)
|
||
imageRect.anchorMin = imageRect.anchorMax = imageRect.pivot = new Vector2(0.5f, 0.5f);
|
||
//imageRect.anchoredPosition = Vector2.zero;
|
||
|
||
// 1. 锁定Sprite原始宽高比(核心:全程不变)
|
||
if (targetImage.sprite != null && targetImage.sprite.texture != null)
|
||
{
|
||
float spriteW = targetImage.sprite.texture.width;
|
||
float spriteH = targetImage.sprite.texture.height;
|
||
originalAspectRatio = spriteW / spriteH;
|
||
// 基准UI宽度:取Sprite像素转换为UI单位的宽度(统一缩放因子)
|
||
baseUIWidth = spriteW / GetUnifiedCanvasScaleFactor();
|
||
}
|
||
else
|
||
{
|
||
// 无Sprite时用16:9默认比例,基准宽度设为参考分辨率的1/2
|
||
originalAspectRatio = 16f / 9f;
|
||
baseUIWidth = canvasReferenceSize.x / 2f;
|
||
}
|
||
|
||
// 兜底:防止比例异常(避免变成正方形)
|
||
originalAspectRatio = Mathf.Clamp(originalAspectRatio, 0.1f, 10f); // 限制比例在0.1~10之间
|
||
baseUIWidth = Mathf.Max(baseUIWidth, 50f); // 基准宽度最小50UI单位
|
||
|
||
// 2. 初始化Image尺寸(严格按比例)
|
||
float initHeight = baseUIWidth / originalAspectRatio;
|
||
imageRect.sizeDelta = new Vector2(baseUIWidth, initHeight);
|
||
|
||
if (showLog)
|
||
{
|
||
Debug.Log($"[初始化] 原始比例:{originalAspectRatio:0.00} | 初始UI尺寸:{baseUIWidth}x{initHeight}", this);
|
||
}
|
||
|
||
// 执行适配
|
||
FitToParentProportionally();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 核心逻辑:严格按原始比例缩放,填满父容器
|
||
/// </summary>
|
||
private void FitToParentProportionally()
|
||
{
|
||
if (parentRect == null || targetImage == null) return;
|
||
|
||
// 1. 获取父容器的UI尺寸(纯UI单位,无缩放)
|
||
Vector2 parentUISize = parentRect.rect.size;
|
||
float parentW = Mathf.Max(parentUISize.x, 50f);
|
||
float parentH = Mathf.Max(parentUISize.y, 50f);
|
||
|
||
// 2. 计算两个方向的缩放系数(严格按原始比例)
|
||
// 按宽度填满需要的缩放:父宽 / 基准UI宽
|
||
float scaleForWidth = parentW / baseUIWidth;
|
||
// 按高度填满需要的缩放:父高 / (基准UI宽 / 原始比例) → 等价于 父高×原始比例 / 基准UI宽
|
||
float scaleForHeight = (parentH * originalAspectRatio) / baseUIWidth;
|
||
|
||
// 3. 取最大值(填满父容器)+ 限制最大缩放
|
||
float finalScale = Mathf.Min(Mathf.Max(scaleForWidth, scaleForHeight), maxScale);
|
||
|
||
// 安全校验
|
||
if (float.IsInfinity(finalScale) || float.IsNaN(finalScale))
|
||
{
|
||
finalScale = 1f;
|
||
Debug.LogWarning($"缩放系数异常,兜底为1倍", this);
|
||
}
|
||
|
||
// 4. 应用缩放(仅改缩放,尺寸不变,比例绝对锁定)
|
||
imageRect.localScale = new Vector3(finalScale, finalScale, 1f);
|
||
|
||
// 调试:验证最终比例
|
||
float finalW = baseUIWidth * finalScale;
|
||
float finalH = finalW / originalAspectRatio;
|
||
if (showLog)
|
||
{
|
||
Debug.Log($"[适配] 缩放系数:{finalScale:0.00} | 最终尺寸:{finalW:0.00}x{finalH:0.00} | 最终比例:{finalW / finalH:0.00}(原始:{originalAspectRatio:0.00})", this);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取统一的Canvas缩放因子(宽高共用,不破坏比例)
|
||
/// </summary>
|
||
private float GetUnifiedCanvasScaleFactor()
|
||
{
|
||
if (canvasScaler == null) return 1f;
|
||
|
||
// 适配CanvasScaler的三种模式,全程返回单一缩放因子
|
||
switch (canvasScaler.uiScaleMode)
|
||
{
|
||
case CanvasScaler.ScaleMode.ConstantPixelSize:
|
||
return canvasScaler.scaleFactor;
|
||
case CanvasScaler.ScaleMode.ScaleWithScreenSize:
|
||
// 统一缩放因子:按最短边匹配参考分辨率
|
||
float scaleX = (float)Screen.width / canvasReferenceSize.x;
|
||
float scaleY = (float)Screen.height / canvasReferenceSize.y;
|
||
return canvasScaler.screenMatchMode == CanvasScaler.ScreenMatchMode.MatchWidthOrHeight
|
||
? Mathf.Lerp(scaleX, scaleY, canvasScaler.matchWidthOrHeight)
|
||
: Mathf.Min(scaleX, scaleY); // 按最短边缩放,统一因子
|
||
case CanvasScaler.ScaleMode.ConstantPhysicalSize:
|
||
return canvasScaler.scaleFactor;
|
||
default:
|
||
return 1f;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动刷新(更换Sprite后调用)
|
||
/// </summary>
|
||
public void RefreshFit()
|
||
{
|
||
StartCoroutine(DelayedInit());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 编辑器预览:在Scene视图中也能看到效果
|
||
/// </summary>
|
||
private void OnDrawGizmos()
|
||
{
|
||
if (imageRect != null && showLog)
|
||
{
|
||
Gizmos.color = Color.green;
|
||
Gizmos.DrawWireCube(imageRect.position, imageRect.sizeDelta * imageRect.localScale);
|
||
}
|
||
}
|
||
} |