Part of these game systems:
Tooltip System
Dynamic mouse-following tooltip that auto-positions to stay on screen. Supports rich text and custom layouts.
How to Use
1
Create a tooltip UI panel with TextMeshPro fields (header + body)
2
Add CanvasGroup and ContentSizeFitter for auto-sizing
3
Attach TooltipSystem to the tooltip panel
4
Show: TooltipSystem.Show("Sword", "+10 Damage")
5
Hide: TooltipSystem.Hide()
6
Tooltip auto-flips to stay on screen
7
Use on hover events: OnPointerEnter/Exit to show/hide
Source Code
C#
using UnityEngine;
using TMPro;
/// <summary>
/// Mouse-following tooltip that auto-positions to stay on screen.
/// Call TooltipSystem.Show/Hide from any UI element.
/// </summary>
public class TooltipSystem : MonoBehaviour
{
public static TooltipSystem Instance { get; private set; }
[Header("References")]
[SerializeField] private RectTransform tooltipRect;
[SerializeField] private TextMeshProUGUI headerText;
[SerializeField] private TextMeshProUGUI bodyText;
[SerializeField] private CanvasGroup canvasGroup;
[Header("Settings")]
[SerializeField] private Vector2 offset = new Vector2(16f, -16f);
[SerializeField] private float showDelay = 0.3f;
[SerializeField] private float fadeSpeed = 8f;
[Header("Padding")]
[SerializeField] private float screenPadding = 10f;
private Canvas parentCanvas;
private float showTimer;
private bool isShowing;
private float targetAlpha;
private void Awake()
{
if (Instance == null)
Instance = this;
else
{
Destroy(gameObject);
return;
}
parentCanvas = GetComponentInParent<Canvas>();
if (canvasGroup == null)
canvasGroup = tooltipRect.GetComponent<CanvasGroup>();
if (canvasGroup == null)
canvasGroup = tooltipRect.gameObject.AddComponent<CanvasGroup>();
canvasGroup.alpha = 0f;
canvasGroup.blocksRaycasts = false;
}
private void Update()
{
if (!isShowing) return;
showTimer -= Time.unscaledDeltaTime;
if (showTimer <= 0f)
targetAlpha = 1f;
canvasGroup.alpha = Mathf.Lerp(canvasGroup.alpha, targetAlpha, Time.unscaledDeltaTime * fadeSpeed);
UpdatePosition();
}
private void UpdatePosition()
{
Vector2 mousePos = Input.mousePosition;
Vector2 tooltipPos = mousePos + offset;
// Get tooltip size
float width = tooltipRect.rect.width;
float height = tooltipRect.rect.height;
// Clamp to screen edges
if (tooltipPos.x + width > Screen.width - screenPadding)
tooltipPos.x = mousePos.x - width - offset.x;
if (tooltipPos.y - height < screenPadding)
tooltipPos.y = mousePos.y + height + Mathf.Abs(offset.y);
tooltipPos.x = Mathf.Max(screenPadding, tooltipPos.x);
tooltipPos.y = Mathf.Min(Screen.height - screenPadding, tooltipPos.y);
tooltipRect.position = tooltipPos;
}
/// <summary>
/// Show tooltip with header and body text.
/// </summary>
public static void Show(string header, string body = "")
{
if (Instance == null) return;
Instance.ShowInternal(header, body);
}
/// <summary>
/// Show tooltip with body text only.
/// </summary>
public static void ShowText(string text)
{
if (Instance == null) return;
Instance.ShowInternal("", text);
}
/// <summary>
/// Hide the tooltip.
/// </summary>
public static void Hide()
{
if (Instance == null) return;
Instance.HideInternal();
}
private void ShowInternal(string header, string body)
{
isShowing = true;
showTimer = showDelay;
targetAlpha = 0f;
if (headerText != null)
{
headerText.text = header;
headerText.gameObject.SetActive(!string.IsNullOrEmpty(header));
}
if (bodyText != null)
bodyText.text = body;
// Force layout rebuild for correct sizing
UnityEngine.UI.LayoutRebuilder.ForceRebuildLayoutImmediate(tooltipRect);
UpdatePosition();
}
private void HideInternal()
{
isShowing = false;
targetAlpha = 0f;
canvasGroup.alpha = 0f;
}
}