157 lines
4.7 KiB
C#
157 lines
4.7 KiB
C#
using UnityEngine;
|
|||
|
|
using UnityEngine.UI;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 计算 Sprite 的非透明可见区域(基于 sprite.rect),
|
||
|
|
/// 将其中心与 Image 矩形中心对齐,并把偏移应用到「父物体」的 anchoredPosition.y。
|
||
|
|
/// 同时缓存上下左右边界,避免重复扫描,并提供 UI 尺寸下的边界/宽度。
|
||
|
|
/// </summary>
|
||
|
|
[DisallowMultipleComponent]
|
||
|
|
public class ImageAlphaBounds : MonoBehaviour
|
||
|
|
{
|
||
|
|
[Range(0f, 1f)]
|
||
|
|
public float alphaThreshold = 0.1f;
|
||
|
|
|
||
|
|
private Sprite cachedSprite;
|
||
|
|
private int cachedTop;
|
||
|
|
private int cachedBottom;
|
||
|
|
private int cachedLeft;
|
||
|
|
private int cachedRight;
|
||
|
|
private bool cachedValid;
|
||
|
|
|
||
|
|
private void EnsureBounds(Image img)
|
||
|
|
{
|
||
|
|
Sprite sp = img.sprite;
|
||
|
|
if (cachedValid && cachedSprite == sp) return;
|
||
|
|
|
||
|
|
cachedSprite = sp;
|
||
|
|
cachedValid = false;
|
||
|
|
|
||
|
|
if (sp == null) return;
|
||
|
|
|
||
|
|
Texture2D tex = sp.texture;
|
||
|
|
if (tex == null || !tex.isReadable)
|
||
|
|
{
|
||
|
|
Debug.LogWarning($"{name}: Texture not readable. Enable Read/Write in import settings.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Rect r = sp.rect;
|
||
|
|
int sx = Mathf.RoundToInt(r.x);
|
||
|
|
int sy = Mathf.RoundToInt(r.y);
|
||
|
|
int sw = Mathf.RoundToInt(r.width);
|
||
|
|
int sh = Mathf.RoundToInt(r.height);
|
||
|
|
|
||
|
|
Color[] pixels = tex.GetPixels(sx, sy, sw, sh);
|
||
|
|
|
||
|
|
int top = -1, bottom = -1, left = -1, right = -1;
|
||
|
|
|
||
|
|
for (int y = 0; y < sh; y++)
|
||
|
|
{
|
||
|
|
int rowStart = y * sw;
|
||
|
|
for (int x = 0; x < sw; x++)
|
||
|
|
{
|
||
|
|
if (pixels[rowStart + x].a > alphaThreshold)
|
||
|
|
{
|
||
|
|
if (top == -1) top = y;
|
||
|
|
bottom = y;
|
||
|
|
|
||
|
|
if (left == -1 || x < left) left = x;
|
||
|
|
if (right == -1 || x > right) right = x;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (top == -1 || bottom == -1)
|
||
|
|
{
|
||
|
|
cachedTop = cachedBottom = cachedLeft = cachedRight = -1;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
cachedTop = top;
|
||
|
|
cachedBottom = bottom;
|
||
|
|
cachedLeft = left;
|
||
|
|
cachedRight = right;
|
||
|
|
}
|
||
|
|
|
||
|
|
cachedValid = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void AdjustY()
|
||
|
|
{
|
||
|
|
Image img = GetComponent<Image>();
|
||
|
|
if (img == null || img.sprite == null) return;
|
||
|
|
|
||
|
|
EnsureBounds(img);
|
||
|
|
|
||
|
|
if (!cachedValid || cachedBottom < cachedTop) return;
|
||
|
|
|
||
|
|
RectTransform imgRT = img.rectTransform;
|
||
|
|
float fullHeightUI = imgRT.sizeDelta.y;
|
||
|
|
float spriteHeightPx = img.sprite.rect.height;
|
||
|
|
float scaleY = fullHeightUI / spriteHeightPx;
|
||
|
|
|
||
|
|
float centerVisibleFromTopPx = (cachedTop + cachedBottom + 1) * 0.5f;
|
||
|
|
float centerVisibleFromTopUI = centerVisibleFromTopPx * scaleY;
|
||
|
|
float imageCenterFromTopUI = fullHeightUI * 0.5f;
|
||
|
|
|
||
|
|
float deltaY = centerVisibleFromTopUI - imageCenterFromTopUI;
|
||
|
|
|
||
|
|
RectTransform parentRT = img.transform.parent as RectTransform;
|
||
|
|
if (parentRT != null)
|
||
|
|
{
|
||
|
|
Vector2 p = parentRT.anchoredPosition;
|
||
|
|
float parentScaleY = parentRT.localScale.y;
|
||
|
|
parentRT.anchoredPosition = new Vector2(p.x, -deltaY * parentScaleY);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public float GetOpaqueHeight()
|
||
|
|
{
|
||
|
|
if (!cachedValid || cachedBottom < cachedTop) return 0f;
|
||
|
|
return cachedBottom - cachedTop + 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
public float GetOpaqueWidth()
|
||
|
|
{
|
||
|
|
if (!cachedValid || cachedRight < cachedLeft) return 0f;
|
||
|
|
return cachedRight - cachedLeft + 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
public float GetOpaqueLeftUI()
|
||
|
|
{
|
||
|
|
Image img = GetComponent<Image>();
|
||
|
|
if (img == null || !cachedValid || cachedRight < cachedLeft) return 0f;
|
||
|
|
|
||
|
|
RectTransform rt = img.rectTransform;
|
||
|
|
float spriteWidthPx = img.sprite.rect.width;
|
||
|
|
float scaleX = rt.sizeDelta.x / spriteWidthPx;
|
||
|
|
return cachedLeft * scaleX;
|
||
|
|
}
|
||
|
|
|
||
|
|
public float GetOpaqueRightUI()
|
||
|
|
{
|
||
|
|
Image img = GetComponent<Image>();
|
||
|
|
if (img == null || !cachedValid || cachedRight < cachedLeft) return 0f;
|
||
|
|
|
||
|
|
RectTransform rt = img.rectTransform;
|
||
|
|
float spriteWidthPx = img.sprite.rect.width;
|
||
|
|
float scaleX = rt.sizeDelta.x / spriteWidthPx;
|
||
|
|
return cachedRight * scaleX;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 返回不透明宽度的 UI 尺寸(换算自 sprite.rect 像素宽度)
|
||
|
|
/// </summary>
|
||
|
|
public float GetOpaqueWidthUI()
|
||
|
|
{
|
||
|
|
Image img = GetComponent<Image>();
|
||
|
|
if (img == null || !cachedValid || cachedRight < cachedLeft) return 0f;
|
||
|
|
|
||
|
|
RectTransform rt = img.rectTransform;
|
||
|
|
float spriteWidthPx = img.sprite.rect.width;
|
||
|
|
float scaleX = rt.sizeDelta.x / spriteWidthPx;
|
||
|
|
return (cachedRight - cachedLeft + 1) * scaleX;
|
||
|
|
}
|
||
|
|
}
|