115 lines
5.1 KiB
C#
115 lines
5.1 KiB
C#
using UnityEngine;
|
||
|
||
/// <summary>
|
||
/// 让一个“中心锚定 + 固定横向偏移”的顶层 HUD 容器,在任意宽高比下横向跟随其设计所属的屏幕边缘。
|
||
/// 只处理横向(X),纵向(Y)完全不动;保持 1920 设计宽度下的视觉位置逐像素不变。
|
||
///
|
||
/// 为什么用组件而非直接改业务脚本坐标:许多 HUD 的位置由业务脚本在运行时写死设置
|
||
/// (relative 到这些容器)。本组件只移动“容器”本身到边缘相对位置,容器内部由脚本布置的
|
||
/// 子元素保持相对关系不变——既实现了“跟随边缘”,又不触碰任何业务坐标逻辑。
|
||
///
|
||
/// 满足三要求:1) 不改业务逻辑/引用;2) 仅横向、16:9 逐像素一致、不动 Y 与动画;3) 跟随边缘。
|
||
/// </summary>
|
||
[RequireComponent(typeof(RectTransform))]
|
||
[DisallowMultipleComponent]
|
||
public sealed class UIEdgeFollow : MonoBehaviour
|
||
{
|
||
public enum Edge { Left, Right, Auto }
|
||
|
||
public Edge edge = Edge.Auto;
|
||
|
||
// 设计基准宽度:所有 gap 以此为参考,保证与首次附加时的分辨率无关。
|
||
private const float DesignWidth = 1920f;
|
||
|
||
private RectTransform rt;
|
||
private RectTransform rootRt;
|
||
private float gapX; // 元素中心距目标边缘的间距(设计参考像素,恒定)
|
||
private Edge resolved;
|
||
private bool measured;
|
||
private float lastRootW = -1f;
|
||
|
||
private void OnEnable()
|
||
{
|
||
rt = GetComponent<RectTransform>();
|
||
ResolveRoot();
|
||
Measure();
|
||
ApplyIfNeeded(true);
|
||
}
|
||
|
||
private void ResolveRoot()
|
||
{
|
||
Canvas c = GetComponentInParent<Canvas>();
|
||
if (c != null)
|
||
{
|
||
Canvas root = c.rootCanvas != null ? c.rootCanvas : c;
|
||
rootRt = root.GetComponent<RectTransform>();
|
||
}
|
||
}
|
||
|
||
// 元素在画布局部空间的设计 X(以画布中心为原点)。用世界坐标换算,正确处理嵌套父级
|
||
// (如 bbt 挂在 logo 下,anchoredPosition 相对 logo 而非画布)。
|
||
private float designCanvasX;
|
||
|
||
private void Measure()
|
||
{
|
||
if (measured || rt == null || rootRt == null) return;
|
||
// 仅处理横向未拉伸 + 中心锚定的元素。
|
||
if (Mathf.Abs(rt.anchorMax.x - rt.anchorMin.x) > 0.01f) return;
|
||
if (Mathf.Abs(rt.anchorMin.x - 0.5f) > 0.01f) return;
|
||
|
||
// 用元素世界位置换算到"画布局部空间"的 X(画布中心为原点)。
|
||
// 关键:InverseTransformPoint 已返回画布参考单位(reference pixels),且与分辨率无关
|
||
//(anchoredPosition 固定在参考单位、画布缩放被 Inverse 抵消),直接用即可。
|
||
// 绝不能再除以 rootW 归一——那会在非 16:9(rootW≠1920)时把坐标算错,元素停在中间。
|
||
// 这对直接子级(uid/score)和嵌套子级(bbt 挂在 logo 下)都正确。
|
||
float rootW = rootRt.rect.width;
|
||
if (rootW <= 0f) return;
|
||
designCanvasX = rootRt.InverseTransformPoint(rt.position).x; // 画布中心为原点,参考单位
|
||
|
||
// gap = 元素中心到"设计左/右缘"的距离(参考单位,恒定)。设计画布宽=DesignWidth。
|
||
float distFromDesignLeft = DesignWidth * 0.5f + designCanvasX; // 到左缘距离
|
||
resolved = edge;
|
||
if (resolved == Edge.Auto)
|
||
resolved = designCanvasX < 0f ? Edge.Left : Edge.Right;
|
||
|
||
gapX = resolved == Edge.Left ? distFromDesignLeft : (DesignWidth - distFromDesignLeft);
|
||
measured = true;
|
||
}
|
||
|
||
private void LateUpdate()
|
||
{
|
||
if (rootRt == null) { ResolveRoot(); return; }
|
||
if (!measured) { Measure(); if (!measured) return; }
|
||
float rootW = rootRt.rect.width;
|
||
if (Mathf.Abs(rootW - lastRootW) > 0.5f) ApplyIfNeeded(false);
|
||
}
|
||
|
||
private void ApplyIfNeeded(bool force)
|
||
{
|
||
if (rt == null || rootRt == null) return;
|
||
if (!measured) { Measure(); if (!measured) return; }
|
||
|
||
float rootW = rootRt.rect.width;
|
||
if (rootW <= 0f) return;
|
||
lastRootW = rootW;
|
||
|
||
// 目标画布局部 X(画布中心为原点,参考单位):当前画布半宽=rootW/2。
|
||
// 左贴边:目标 = -rootW/2 + gapX;右贴边:目标 = rootW/2 - gapX。
|
||
float halfW = rootW * 0.5f;
|
||
float targetCanvasLocalX = resolved == Edge.Left ? (-halfW + gapX) : (halfW - gapX);
|
||
|
||
// 当前画布局部 X;若已到位则跳过。
|
||
Vector3 worldCur = rt.position;
|
||
Vector3 canvasCur = rootRt.InverseTransformPoint(worldCur);
|
||
if (Mathf.Abs(targetCanvasLocalX - canvasCur.x) <= 0.01f) return;
|
||
|
||
// 画布局部目标 → 世界目标点 → 自身父空间 anchoredPosition.x(仅横向)。
|
||
Vector3 worldTarget = rootRt.TransformPoint(new Vector3(targetCanvasLocalX, canvasCur.y, canvasCur.z));
|
||
float worldDeltaX = worldTarget.x - worldCur.x;
|
||
Vector3 parentScale = rt.parent != null ? rt.parent.lossyScale : Vector3.one;
|
||
float parentScaleX = Mathf.Abs(parentScale.x) < 1e-6f ? 1f : parentScale.x;
|
||
Vector2 anchoredNow = rt.anchoredPosition;
|
||
rt.anchoredPosition = new Vector2(anchoredNow.x + worldDeltaX / parentScaleX, anchoredNow.y);
|
||
}
|
||
}
|