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(); _uiCamera = _rootCanvas.worldCamera; // 兜底找 Text if (_text == null) _text = GetComponentInChildren(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(); RectTransformUtility.ScreenPointToLocalPointInRectangle( canvasRt, pivotScreenPos, _uiCamera, // 即使_uiCamera为null (Overlay模式),此方法依然有效 out Vector2 localPos); // 7. 设置最终位置 _selfRt.localPosition = localPos; _selfRt.SetAsLastSibling(); } }