86 lines
3.4 KiB
C#
86 lines
3.4 KiB
C#
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using TMPro;
|
||
|
||
[RequireComponent(typeof(RectTransform))]
|
||
public class HintWindowController : MonoBehaviour
|
||
{
|
||
[SerializeField] private TextMeshProUGUI _text;
|
||
[SerializeField] private Vector2 _padding = new Vector2(16, 10);
|
||
[SerializeField] private Vector2 _anchorOffset = new Vector2(16, 16);
|
||
|
||
private RectTransform _selfRt;
|
||
private Canvas _rootCanvas;
|
||
private Camera _uiCamera;
|
||
|
||
private void Awake()
|
||
{
|
||
_selfRt = (RectTransform)transform;
|
||
_rootCanvas = GetComponentInParent<Canvas>();
|
||
_uiCamera = _rootCanvas.worldCamera;
|
||
|
||
// 兜底找 Text
|
||
if (_text == null)
|
||
_text = GetComponentInChildren<TextMeshProUGUI>(true);
|
||
|
||
if (_text == null)
|
||
Debug.LogError("[HintWindowController] 找不到 TextMeshProUGUI!", this);
|
||
}
|
||
|
||
public void Present(Vector2 mouseScreenPos)
|
||
{
|
||
if (_text == null) return;
|
||
|
||
RefreshSize();
|
||
ClampToScreen(mouseScreenPos);
|
||
}
|
||
|
||
public void RefreshSize()
|
||
{
|
||
_text.ForceMeshUpdate(false, false);
|
||
LayoutRebuilder.ForceRebuildLayoutImmediate(_text.rectTransform);
|
||
|
||
float w = _text.preferredWidth + _padding.x * 2f;
|
||
float h = _text.preferredHeight + _padding.y * 2f;
|
||
|
||
_selfRt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, w);
|
||
_selfRt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, h);
|
||
|
||
Canvas.ForceUpdateCanvases();
|
||
}
|
||
|
||
private void ClampToScreen(Vector2 mouseScreenPos)
|
||
{
|
||
// 1. 获取 Canvas 的缩放因子,这是解决打包后问题的关键
|
||
float scaleFactor = _rootCanvas.scaleFactor;
|
||
if (scaleFactor <= 0) scaleFactor = 1f; // 极端情况下的保护
|
||
// 2. 计算提示窗口在屏幕上的实际像素尺寸
|
||
float windowW = _selfRt.rect.width * scaleFactor;
|
||
float windowH = _selfRt.rect.height * scaleFactor;
|
||
// 3. 计算窗口左上角假想的屏幕坐标 (这里逻辑保持不变)
|
||
Vector2 desiredTopLeft = mouseScreenPos + _anchorOffset;
|
||
// 4. 修正:将假想的左上角位置限制在屏幕范围内
|
||
// 使用 Screen.width 和 Screen.height 更直接
|
||
float clampedX = Mathf.Clamp(desiredTopLeft.x, 0, Screen.width - windowW);
|
||
float clampedY = Mathf.Clamp(desiredTopLeft.y, windowH, Screen.height); // Y坐标从下到上,所以下边界是windowH
|
||
// 5. 关键修正:根据Pivot计算最终实际要设置的屏幕坐标点
|
||
// 我们已经得到了窗口内容左上角的正确位置(clampedX, clampedY)。
|
||
// 现在需要反推出在这种情况下,窗口的Pivot应该在屏幕的哪个坐标。
|
||
Vector2 pivot = _selfRt.pivot;
|
||
Vector2 pivotScreenPos = new Vector2(
|
||
clampedX + pivot.x * windowW,
|
||
clampedY - (1 - pivot.y) * windowH
|
||
);
|
||
// 6. 将计算出的Pivot屏幕坐标转换为Canvas的局部坐标
|
||
RectTransform canvasRt = _rootCanvas.GetComponent<RectTransform>();
|
||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||
canvasRt,
|
||
pivotScreenPos,
|
||
_uiCamera, // 即使_uiCamera为null (Overlay模式),此方法依然有效
|
||
out Vector2 localPos);
|
||
// 7. 设置最终位置
|
||
_selfRt.localPosition = localPos;
|
||
_selfRt.SetAsLastSibling();
|
||
}
|
||
}
|