1.简单粗暴方法:

不使用最上层的canvas进行渲染,每个提示框单独挂载一个canvas渲染就可见了,不会被遮挡和裁剪了。
但为了"防止裁剪"而给每个道具都挂一个 Canvas,相当于"用大炮打蚊子"
问题:
每个道具都是一个独立的 Canvas。UI 系统需要为每一个 进行独立的布局计算、网格重建和批处理准备,无论其是否激活。这会产生大量固定的 CPU 开销 (管理 Canvas)和潜在的 GPU 开销(大量不可合并的小批次),在背包打开、滚动时会造成卡顿。
2.单一顶层Canvas:
- 造成这个问题的核心原因是每个道具item都有自己的子对象,提示框。
- 再滑动列表中会受到影响。

- 那我们就将提示框移出Viewport的遮挡节点。

- 那现在需要解决的问题就是控制提示框的位置定位,和显示隐藏
思路:
-
结构解耦:
-
将提示框预制体从所有道具项中彻底移除。
-
在UI顶层 (例如与背包面板同级,可以更具需要移动)创建一个唯一的、共享的提示框GameObject。它就挂在一个公共的、高级别的 Canvas 下。
-
-
事件驱动控制:
-
编写一个单例或管理器(如
TooltipManager),专门控制这个唯一的提示框。 -
在每个道具的脚本上,继续处理鼠标悬停(
OnPointerEnter)和离开(OnPointerExit)事件。 -
鼠标悬停时 ,道具脚本不自己创建提示框 ,而是向
TooltipManager发送请求:"请显示提示框,内容是我,我的位置是这里"。 -
TooltipManager收到请求后,立刻隐藏当前可能正在显示的提示框,然后用新数据和位置更新那个唯一的提示框实例,再显示它。 -
鼠标离开时,道具脚本通知管理器"隐藏提示框"。管理器可以加一个延时隐藏或判断鼠标是否已移到提示框本身上。
-
这个交互事件就不多说了,可以更具自己的需求实现,不管是点击,滑动还是鼠标悬停等。
简单列举几个接口,滑动,点击的,实现后挂载在ScrollRect上就可以,不能挂载到其子对象上,这些事件是不被ScrollRect继续向下传递的。
cs
public class EventHandler : MonoBehaviourAutoRelease, IPointerUpHandler, IPointerDownHandler, IPointerClickHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
{
public Action<PointerEventData> OnBeginDragAction;
public Action<PointerEventData> OnDragAction;
public Action<PointerEventData> OnEndDragAction;
public Action<PointerEventData> OnPointerClickAction;
public Action<PointerEventData> OnPointerDownAction;
public Action<PointerEventData> OnPointerUpAction;
public void OnBeginDrag(PointerEventData eventData)
{
OnBeginDragAction?.Invoke(eventData);
}
public void OnDrag(PointerEventData eventData)
{
OnDragAction?.Invoke(eventData);
}
public void OnEndDrag(PointerEventData eventData)
{
OnEndDragAction?.Invoke(eventData);
}
public void OnPointerClick(PointerEventData eventData)
{
OnPointerClickAction?.Invoke(eventData);
}
public void OnPointerDown(PointerEventData eventData)
{
OnPointerDownAction?.Invoke(eventData);
}
public void OnPointerUp(PointerEventData eventData)
{
OnPointerUpAction?.Invoke(eventData);
}
}
列举下定位的问题把
-
AdjustTooltipPosition的内容就根据自己的需要去修改了
-
位置实现的更具item相对于viewport的本地坐标的判断,修改了提示框背景板的朝向和位置效果如下图:


-
其中使用到一个重要的API:InverseTransformPoint,将对象的世界坐标转换为相对目标的本地坐标的方法。其锚点是(0.5,0.5)
-
因为我是将提示框放在,backpackScrollRect滚动组件下,所以转换的本地坐标可以直接赋值给提示框,实现定位。
cs
[Header("背包引用")]
public ScrollRect backpackScrollRect;
public RectTransform viewportRect;
public RectTransform contentRect;
public RectTransform ItemTipObj; // 提示框
public void ShowTooltip()
{
RectTransform itemRect = transform.GetComponent<RectTransform>();
if (itemRect == null) return;
Vector2 itemInViewportLocalPos = backpackScrollRect.GetComponent<RectTransform>().InverseTransformPoint(itemRect.position);
float viewportHalfWidth = backpackScrollRect.GetComponent<RectTransform>().rect.width / 2;
float itemRelativeX = Mathf.InverseLerp(-viewportHalfWidth, viewportHalfWidth, itemInViewportLocalPos.x + itemRect.rect.width / 2);
itemRelativeX = Mathf.Clamp01(itemRelativeX);
bool isLeftEdge = itemRelativeX < 0.5;
bool isRightEdge = itemRelativeX > 0.5;
AdjustTooltipPosition(itemRect, isLeftEdge, isRightEdge);
}
public void AdjustTooltipPosition(RectTransform itemRect, bool isLeft, bool isRight)
{
Vector2 itemInViewportLocalPos = backpackScrollRect.GetComponent<RectTransform>()
.InverseTransformPoint(itemRect.position);
float pos = 0;
if (isLeft) pos = 80;
else pos = -80;
ItemTipObj.localPosition = new Vector3(itemInViewportLocalPos.x + pos, itemInViewportLocalPos.y + itemRect.rect.height, 0);
ItemTipObj.Find("Bg_RectTransform").transform.localScale = new Vector3(isLeft ? 1 : -1, 1, 1);
}