using UnityEngine; using UnityEngine.UI; /// /// 计算 Sprite 的非透明可见区域(基于 sprite.rect), /// 将其中心与 Image 矩形中心对齐,并把偏移应用到「父物体」的 anchoredPosition.y。 /// 同时缓存上下左右边界,避免重复扫描,并提供 UI 尺寸下的边界/宽度。 /// [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(); 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(); 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(); 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; } /// /// 返回不透明宽度的 UI 尺寸(换算自 sprite.rect 像素宽度) /// public float GetOpaqueWidthUI() { Image img = GetComponent(); 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; } }