105 lines
4.5 KiB
C#
105 lines
4.5 KiB
C#
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// 让一个"普通 Transform"(非 RectTransform,如 gameplay 敌人容器 empty/enemyObject)在分辨率变化时
|
|
/// 横向跟随其所属 Canvas 的某一屏幕边缘,保持到该边缘的间距恒定。
|
|
///
|
|
/// 用于敌人这类"用普通 Transform 本地坐标定位、挂在 ScreenSpaceCamera Canvas 下"的对象——
|
|
/// 它们无锚点,UIEdgeFollow(要求 RectTransform)用不了。本组件自测量:运行时读取对象在所属
|
|
/// Canvas 局部空间的 X,算出到选定边缘的间距并保持恒定,与该 Canvas 的精确坐标系无关(健壮)。
|
|
///
|
|
/// 仅改 localPosition.x(表现层),不改 Y/Z/旋转/缩放/引用,不与只改 alpha/scaleY 的入场动画冲突。
|
|
/// 满足三要求:1) 不改业务逻辑/引用;2) 仅横向,首帧于设计分辨率(16:9)测量则位置逐像素不变;
|
|
/// 3) 跟随边缘。
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
public sealed class UITransformEdgeFollow : MonoBehaviour
|
|
{
|
|
public enum Edge { Left, Right }
|
|
|
|
[SerializeField] private Edge edge = Edge.Left;
|
|
|
|
// 设计基准宽度:gap 以 16:9(1920)设计画布为基准计算,与首次附加时的实际分辨率无关,
|
|
// 保证 16:9 逐像素一致、其它分辨率下敌人始终贴同一边缘、露出同样部分。
|
|
private const float DesignWidth = 1920f;
|
|
|
|
private Transform tf;
|
|
private RectTransform rootRt; // 所属 Canvas 的 RectTransform
|
|
private float gap; // 对象中心到选定边缘的间距(设计参考单位,恒定)
|
|
private bool measured;
|
|
private float lastRootW = -1f;
|
|
|
|
private void OnEnable()
|
|
{
|
|
tf = transform;
|
|
ResolveRoot();
|
|
Measure();
|
|
}
|
|
|
|
private void ResolveRoot()
|
|
{
|
|
Canvas c = GetComponentInParent<Canvas>();
|
|
if (c != null)
|
|
{
|
|
Canvas root = c.rootCanvas != null ? c.rootCanvas : c;
|
|
rootRt = root.GetComponent<RectTransform>();
|
|
}
|
|
}
|
|
|
|
private void Measure()
|
|
{
|
|
if (measured || tf == null || rootRt == null) return;
|
|
float rootW = rootRt.rect.width;
|
|
if (rootW <= 0f) return;
|
|
|
|
// 对象在 Canvas 局部空间的 X。该值由固定 localPosition 决定,与分辨率无关(缩放均匀),
|
|
// 即为设计位置。Canvas 关于中心对称(rect 以 0 为中心),故设计左/右缘 = ∓DesignWidth/2。
|
|
float localX = rootRt.InverseTransformPoint(tf.position).x;
|
|
float designLeft = -DesignWidth * 0.5f; // 16:9 设计左缘 = -960
|
|
float designRight = DesignWidth * 0.5f; // 16:9 设计右缘 = +960
|
|
// gap = 对象中心到选定边缘的距离(设计参考单位,恒定)。
|
|
// 敌人 gap 会是负值(设计上就在左缘之外,只露右半)——正是要保持的视觉。
|
|
gap = edge == Edge.Left ? (localX - designLeft) : (designRight - localX);
|
|
lastRootW = rootW;
|
|
measured = true;
|
|
|
|
// 应用一次,使当前分辨率立即到位(不必等分辨率变化)。
|
|
ApplyToEdge();
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (rootRt == null) { ResolveRoot(); return; }
|
|
if (!measured) { Measure(); return; }
|
|
|
|
float rootW = rootRt.rect.width;
|
|
if (Mathf.Abs(rootW - lastRootW) <= 0.5f) return;
|
|
lastRootW = rootW;
|
|
ApplyToEdge();
|
|
}
|
|
|
|
// 让对象中心距当前画布选定边缘保持恒定的 gap(设计参考单位)。
|
|
// 画布关于中心对称,故当前左缘 = -rootW/2、右缘 = +rootW/2。
|
|
private void ApplyToEdge()
|
|
{
|
|
if (tf == null || rootRt == null || !measured) return;
|
|
float rootW = rootRt.rect.width;
|
|
if (rootW <= 0f) return;
|
|
|
|
float curLeft = -rootW * 0.5f;
|
|
float curRight = rootW * 0.5f;
|
|
float targetLocalX = edge == Edge.Left ? (curLeft + gap) : (curRight - gap);
|
|
|
|
float curLocalX = rootRt.InverseTransformPoint(tf.position).x;
|
|
float deltaLocalX = targetLocalX - curLocalX;
|
|
if (Mathf.Abs(deltaLocalX) <= 0.01f) return;
|
|
|
|
// 把 Canvas 局部 X 位移换算到对象自身父空间的 localPosition.x。
|
|
Vector3 worldCur = tf.position;
|
|
Vector3 localCurInCanvas = rootRt.InverseTransformPoint(worldCur);
|
|
Vector3 worldTarget = rootRt.TransformPoint(new Vector3(targetLocalX, localCurInCanvas.y, localCurInCanvas.z));
|
|
Vector3 parentTarget = tf.parent != null ? tf.parent.InverseTransformPoint(worldTarget) : worldTarget;
|
|
tf.localPosition = new Vector3(parentTarget.x, tf.localPosition.y, tf.localPosition.z);
|
|
}
|
|
}
|