修复换算摄像机目标,修复ui跟随移动滞后的问题

This commit is contained in:
2026-06-12 10:55:03 +08:00
parent d4b7848dde
commit b2d82a054a
+133 -63
View File
@@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using XericLibrary.Runtime.MacroLibrary;
namespace XericUI.VisualForm namespace XericUI.VisualForm
{ {
@@ -9,6 +10,7 @@ namespace XericUI.VisualForm
/// 负责收集所有世界锚点,每帧将世界坐标转换到屏幕坐标,并管理中间窗口状态。 /// 负责收集所有世界锚点,每帧将世界坐标转换到屏幕坐标,并管理中间窗口状态。
/// </summary> /// </summary>
[AddComponentMenu("Xeric UI Vessel/AnchorWindow/AnchorDock")] [AddComponentMenu("Xeric UI Vessel/AnchorWindow/AnchorDock")]
[DefaultExecutionOrder(31000)]
public class AnchorDock : MonoBehaviour public class AnchorDock : MonoBehaviour
{ {
[Header("配置")] [Header("配置")]
@@ -44,7 +46,8 @@ namespace XericUI.VisualForm
#region 内部状态 #region 内部状态
private Canvas _canvas; private Canvas _canvas;
private Camera _renderCamera; private Camera _worldCamera;
private Camera _uiCamera;
private RectTransform _rectTransform; private RectTransform _rectTransform;
/// <summary> /// <summary>
@@ -93,23 +96,38 @@ namespace XericUI.VisualForm
private bool _cameraDirty; private bool _cameraDirty;
/// <summary> /// <summary>
/// 上帧摄像机世界坐标 /// 上帧世界摄像机坐标
/// </summary> /// </summary>
private Vector3 _lastCameraPosition; private Vector3 _lastWorldCameraPosition;
/// <summary> /// <summary>
/// 上帧摄像机世界旋转 /// 上帧世界摄像机旋转
/// </summary> /// </summary>
private Quaternion _lastCameraRotation; private Quaternion _lastWorldCameraRotation;
/// <summary>
/// 上帧UI摄像机坐标
/// </summary>
private Vector3 _lastUICameraPosition;
/// <summary>
/// 上帧UI摄像机旋转
/// </summary>
private Quaternion _lastUICameraRotation;
#endregion #endregion
#region 公开属性 #region 公开属性
/// <summary> /// <summary>
/// 当前使用的渲染摄像机 /// 世界摄像机(用于 World→Screen 转换,通常是 MainCamera)
/// </summary> /// </summary>
public Camera RenderCamera => _renderCamera; public Camera WorldCamera => _worldCamera;
/// <summary>
/// UI摄像机(用于 Screen→Canvas 转换,Overlay时为null,Camera时为canvas.worldCamera)
/// </summary>
public Camera UICamera => _uiCamera;
/// <summary> /// <summary>
/// Canvas组件引用 /// Canvas组件引用
@@ -173,8 +191,10 @@ namespace XericUI.VisualForm
// 初始化摄像机引用 // 初始化摄像机引用
RefreshCameraReference(); RefreshCameraReference();
_lastCameraPosition = _renderCamera ? _renderCamera.transform.position : Vector3.zero; _lastWorldCameraPosition = _worldCamera ? _worldCamera.transform.position : Vector3.zero;
_lastCameraRotation = _renderCamera ? _renderCamera.transform.rotation : Quaternion.identity; _lastWorldCameraRotation = _worldCamera ? _worldCamera.transform.rotation : Quaternion.identity;
_lastUICameraPosition = _uiCamera ? _uiCamera.transform.position : Vector3.zero;
_lastUICameraRotation = _uiCamera ? _uiCamera.transform.rotation : Quaternion.identity;
// 标记需要重建子对象哈希集 // 标记需要重建子对象哈希集
MarkChildrenSetDirty(); MarkChildrenSetDirty();
@@ -348,54 +368,71 @@ namespace XericUI.VisualForm
#region 坐标更新 #region 坐标更新
/// <summary> /// <summary>
/// 刷新摄像机引用:检查Canvas的renderMode,选择合适的摄像机 /// 刷新摄像机引用:分离世界摄像机(观察3D场景)和UI摄像机(渲染Canvas)
/// </summary> /// </summary>
public void RefreshCameraReference() public void RefreshCameraReference()
{ {
// 世界摄像机:用于 World→Screen 转换
_worldCamera = Camera.main;
// UI摄像机:用于 Screen→Canvas 转换
if (m_OverrideCamera != null) if (m_OverrideCamera != null)
{ {
_renderCamera = m_OverrideCamera; _uiCamera = m_OverrideCamera;
return;
} }
else
if (_canvas == null)
{ {
_canvas = GetComponent<Canvas>();
if (_canvas == null) if (_canvas == null)
_canvas = GetComponentInParent<Canvas>(); {
} _canvas = GetComponent<Canvas>();
if (_canvas == null)
_canvas = GetComponentInParent<Canvas>();
}
if (_canvas != null && _canvas.renderMode == RenderMode.ScreenSpaceCamera) _uiCamera = (_canvas != null && _canvas.renderMode == RenderMode.ScreenSpaceCamera)
{ ? _canvas.worldCamera : null;
_renderCamera = _canvas.worldCamera;
} }
// ScreenSpaceOverlay 或找不到摄像机时使用MainCamera
if (_renderCamera == null)
_renderCamera = Camera.main;
} }
/// <summary> /// <summary>
/// 检查摄像机变换是否发生变化 /// 检查世界摄像机和UI摄像机是否发生变换,任意一个变化即标记脏
/// </summary> /// </summary>
private void CheckCameraDirty() private void CheckCameraDirty()
{ {
if (_renderCamera == null) if (_worldCamera == null)
{ {
RefreshCameraReference(); RefreshCameraReference();
return; return;
} }
var camTransform = _renderCamera.transform; bool dirty = false;
var currentPos = camTransform.position;
var currentRot = camTransform.rotation;
if (currentPos != _lastCameraPosition || currentRot != _lastCameraRotation) // 检查世界摄像机变换
var wcTransform = _worldCamera.transform;
Vector3 wcPos = wcTransform.position;
Quaternion wcRot = wcTransform.rotation;
if (wcPos != _lastWorldCameraPosition || wcRot != _lastWorldCameraRotation)
{ {
_cameraDirty = true; dirty = true;
_lastCameraPosition = currentPos; _lastWorldCameraPosition = wcPos;
_lastCameraRotation = currentRot; _lastWorldCameraRotation = wcRot;
} }
// 检查UI摄像机变换
if (_uiCamera != null && _uiCamera != _worldCamera)
{
var uiTransform = _uiCamera.transform;
Vector3 uiPos = uiTransform.position;
Quaternion uiRot = uiTransform.rotation;
if (uiPos != _lastUICameraPosition || uiRot != _lastUICameraRotation)
{
dirty = true;
_lastUICameraPosition = uiPos;
_lastUICameraRotation = uiRot;
}
}
_cameraDirty = dirty;
} }
/// <summary> /// <summary>
@@ -403,25 +440,22 @@ namespace XericUI.VisualForm
/// </summary> /// </summary>
private void UpdateStatePosition(WindowState state) private void UpdateStatePosition(WindowState state)
{ {
if (state.Anchor == null || _renderCamera == null) return; if (state.Anchor == null || _worldCamera == null || _canvas == null) return;
var target = state.GetTargetTransform(); var target = state.GetTargetTransform();
if (target == null) return; if (target == null) return;
Vector3 worldPos = state.Anchor.CachedTransform.position; Vector3 worldPos = state.Anchor.CachedTransform.position;
Vector2 localPoint = WorldToScreenPoint(worldPos, _renderCamera, RectTransform);
// target是容器的子对象,设置localPosition if (TryWorldToScreenPoint(worldPos, _worldCamera, _uiCamera, _canvas, RectTransform, out Vector2 localPoint))
if (target is RectTransform rectTarget)
{ {
rectTarget.localPosition = new Vector3(localPoint.x, localPoint.y, rectTarget.localPosition.z); if (target is RectTransform rectTarget)
} rectTarget.anchoredPosition = localPoint;
else else
{ target.localPosition = new Vector3(localPoint.x, localPoint.y, target.localPosition.z);
target.localPosition = new Vector3(localPoint.x, localPoint.y, target.localPosition.z);
}
state.CoordinateDirty = false; state.CoordinateDirty = false;
}
} }
#endregion #endregion
@@ -487,32 +521,68 @@ namespace XericUI.VisualForm
#region 静态工具方法 #region 静态工具方法
/// <summary> /// <summary>
/// 将世界空间坐标转换到Canvas容器内的本地坐标 /// 尝试将世界空间坐标转换到目标RectTransform容器内的本地坐标。
/// 世界摄像机负责 World→Screen,UI摄像机负责 Screen→Canvas 本地。
/// </summary> /// </summary>
/// <param name="worldPos">世界空间坐标</param> /// <param name="worldPos">世界坐标</param>
/// <param name="camera">使用的摄像机</param> /// <param name="worldCamera">世界摄像机(用于 WorldToScreenPoint,通常为MainCamera)</param>
/// <param name="container">目标Canvas容器的RectTransform</param> /// <param name="uiCamera">UI摄像机(用于 ScreenPointToLocalPointInRectangle,Overlay时为null)</param>
/// <returns>容器内的本地坐标</returns> /// <param name="canvas">所属Canvas</param>
public static Vector2 WorldToScreenPoint(Vector3 worldPos, Camera camera, RectTransform container) /// <param name="container">目标容器的RectTransform</param>
/// <param name="localPoint">输出的本地坐标</param>
/// <returns>坐标是否有效</returns>
public static bool TryWorldToScreenPoint(Vector3 worldPos, Camera worldCamera, Camera uiCamera,
Canvas canvas, RectTransform container, out Vector2 localPoint)
{ {
if (camera == null || container == null) localPoint = Vector2.zero;
return Vector2.zero;
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(camera, worldPos); if (worldCamera == null || container == null || canvas == null)
return false;
if (container.TryGetComponent<Canvas>(out var canvas)) // 1. 世界坐标 → 屏幕像素(使用世界摄像机)
Vector3 screenPos = worldCamera.WorldToScreenPoint(worldPos);
if (screenPos.IsNaN() || screenPos.IsInfinity() || screenPos.z < 0f)
return false;
// 2. Canvas RectTransform参数
RectTransform canvasRect = (RectTransform)canvas.transform;
Vector2 canvasSize = canvasRect.rect.size;
Vector2 canvasPivot = canvasRect.pivot;
// 3. 屏幕像素 → Canvas本地坐标
Vector2 canvasLocal;
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{ {
if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && camera != null) // Overlay:屏幕即Canvas
{ canvasLocal = new Vector2(
RectTransformUtility.ScreenPointToLocalPointInRectangle( screenPos.x - canvasSize.x * canvasPivot.x,
container, screenPoint, camera, out Vector2 localPoint); screenPos.y - canvasSize.y * canvasPivot.y
return localPoint; );
} }
else
{
// Camera模式:用UI摄像机做 Screen→Canvas 转换
RectTransformUtility.ScreenPointToLocalPointInRectangle(
canvasRect, (Vector2)screenPos, uiCamera, out canvasLocal);
} }
RectTransformUtility.ScreenPointToLocalPointInRectangle( if (canvasLocal.IsNaN() || canvasLocal.IsInfinity())
container, screenPoint, null, out Vector2 overlayPoint); return false;
return overlayPoint;
// 4. 如果container不是Canvas自身,从Canvas空间变换到container本地空间
if (container != canvasRect)
{
Vector3 worldInCanvas = canvasRect.TransformPoint(new Vector3(canvasLocal.x, canvasLocal.y, 0f));
Vector3 localInContainer = container.InverseTransformPoint(worldInCanvas);
localPoint = new Vector2(localInContainer.x, localInContainer.y);
}
else
{
localPoint = canvasLocal;
}
return true;
} }
/// <summary> /// <summary>