好多新内容

This commit is contained in:
FloatGaming
2026-07-22 05:35:53 +08:00
parent b5d1cdcbc5
commit 15649d02f8
128 changed files with 5956 additions and 673 deletions
+114
View File
@@ -0,0 +1,114 @@
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);
}
}