Unity TextMeshPro 超链接实现

TMP 的超链接功能本质上是‌在富文本中嵌入可点击的标记区域‌,然后通过代码检测玩家的点击位置,精准识别被点击的链接并执行对应逻辑。下面从零开始,一步步拆解。


一、超链接语法

在 TMP 文本中,超链接使用类似 HTML 的标签语法:

<link="自定义ID">显示文本</link>

比如在 RPG 聊天频道里标记一件装备:

 
获得稀有装备:<link="item_1234">龙息之剑</link>!

为了增强视觉提示,通常会配合下划线或颜色标签一起使用:

 
<u><link="item_1234">龙息之剑</link></u> <color=#FFA500><link="weapon_123">传说之剑</link></color>

‌注意事项:‌

  • 链接 ID 可以是任意字符串,建议用有意义的命名规则(如 item_编号player_名字
  • 标签必须正确闭合,否则会破坏整个文本块的渲染
  • 文本组件的 Rich Text 选项必须勾选

二、点击检测的两种实现方式

方式一:Update 轮询检测(适合简单场景)

在 Update 中检测鼠标点击,然后调用 TMP_TextUtilities.FindIntersectingLink 判断是否点中了链接:

using TMPro;
using UnityEngine;

public class LinkHandler : MonoBehaviour
{
    public TMP_Text textComponent;
    private Camera mainCamera;

    void Start()
    {
        mainCamera = Camera.main;
        textComponent.raycastTarget = true;  // 必须启用射线检测
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            // 关键:查找与鼠标位置相交的链接
            int linkIndex = TMP_TextUtilities.FindIntersectingLink(
                textComponent, 
                Input.mousePosition, 
                mainCamera   // 注意相机参数,见下文详解
            );

            if (linkIndex != -1)
            {
                TMP_LinkInfo linkInfo = textComponent.textInfo.linkInfo[linkIndex];
                string linkId = linkInfo.GetLinkID();      // 拿到 "item_1234"
                string linkText = linkInfo.GetLinkText();  // 拿到 "龙息之剑"
                
                Debug.Log($"点击了链接:ID={linkId},文本={linkText}");
                OnLinkClicked(linkId, linkText);
            }
        }
    }

    void OnLinkClicked(string id, string text)
    {
        // 根据 ID 执行不同逻辑:打开物品面板、跳转网页等
    }
}

 

方式二:IPointerClickHandler 接口(推荐,性能更好)

让脚本继承 IPointerClickHandler 接口,利用 Unity 原生 UI 事件系统,比每帧轮询更高效:

using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;

[RequireComponent(typeof(TMP_Text))]
public class LinkOpener : MonoBehaviour, IPointerClickHandler
{
    public void OnPointerClick(PointerEventData eventData)
    {
        TMP_Text textMeshPro = GetComponent<TMP_Text>();
        
        int linkIndex = TMP_TextUtilities.FindIntersectingLink(
            textMeshPro, 
            eventData.position, 
            eventData.enterEventCamera  // 从事件数据中获取相机
        );

        if (linkIndex != -1)
        {
            TMP_LinkInfo linkInfo = textMeshPro.textInfo.linkInfo[linkIndex];
            string linkId = linkInfo.GetLinkID();
            string linkText = linkInfo.GetLinkText();
            
            // 处理点击逻辑
            HandleLinkClick(linkId, linkText);
        }
    }

    void HandleLinkClick(string id, string text)
    {
        // 例如:打开物品详情面板
        Debug.Log($"点击了:{text}(ID:{id})");
    }
}

‌注意:‌ 脚本必须挂载在 TMP 的 UI 对象上,且该对象需要启用 Raycast Target


三、Canvas 渲染模式与相机参数(最容易踩的坑)

FindIntersectingLink 的第三个参数(相机)传什么,完全取决于 Canvas 的 Render Mode

表格
Canvas 渲染模式传入的相机参数说明
‌Screen Space - Overlay‌ null UI 渲染在最上层,不需要相机
‌Screen Space - Camera‌ Canvas 绑定的 World Camera 必须传入对应相机
‌World Space‌ 场景中的主相机或指定相机 UI 存在于 3D 世界中

‌传错相机是最常见的 Bug 来源‌——如果参数不对,FindIntersectingLink 永远返回 -1,点击怎么都没反应。建议测试阶段先用 Overlay 模式,相机参数直接传 null


四、动态生成超链接文本

实际项目中,文本往往是动态拼接的。推荐用 StringBuilder 批量构建,避免频繁字符串拼接带来的 GC 开销:

using System.Text;
using TMPro;
using UnityEngine;

public class ChatMessageBuilder : MonoBehaviour
{
    public TMP_Text chatText;
    private StringBuilder linkBuilder = new StringBuilder();

    public void AddItemLink(string itemName, string itemId)
    {
        linkBuilder.AppendLine($"<u><link=\"{itemId}\">{itemName}</link></u>");
    }

    public void ApplyText()
    {
        chatText.text = linkBuilder.ToString();
        linkBuilder.Clear();
    }
}

 


五、实战:物品点击查看详情完整示例

using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;

[RequireComponent(typeof(TMP_Text))]
public class ItemLinkHandler : MonoBehaviour, IPointerClickHandler
{
    private TMP_Text tmpText;

    void Awake()
    {
        tmpText = GetComponent<TMP_Text>();
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        int linkIndex = TMP_TextUtilities.FindIntersectingLink(
            tmpText, 
            eventData.position, 
            eventData.enterEventCamera
        );

        if (linkIndex == -1) return;

        TMP_LinkInfo linkInfo = tmpText.textInfo.linkInfo[linkIndex];
        string linkId = linkInfo.GetLinkID();

        // 根据 ID 前缀分发不同逻辑
        if (linkId.StartsWith("item_"))
        {
            OpenItemDetailPanel(linkId);
        }
        else if (linkId.StartsWith("player_"))
        {
            OpenPlayerProfile(linkId);
        }
    }

    void OpenItemDetailPanel(string itemId)
    {
        Debug.Log($"打开物品详情:{itemId}");
        // 实际项目中:UIManager.Instance.ShowItemDetail(itemId);
    }

    void OpenPlayerProfile(string playerId)
    {
        Debug.Log($"打开玩家资料:{playerId}");
    }
}

 

文本内容示例:

 
玩家[<link="player_007">张三</link>]分享了装备 <color=#FF6600><link="item_2048">暗影之刃</link></color>, 点击名称即可查看详情。

六、进阶:超链接 + 图文混排

TMP 的超链接可以和 Sprite 标签结合,实现图标 + 可点击文字的混排效果:

 
点击查看 <sprite name="sword_icon"> <link="item_2048">暗影之刃</link>

调试技巧:

  • 用 <size=150%> 临时放大图标检查对齐
  • 用 <voffset=0.5em> 微调图文垂直对齐
  • 在编辑器中通过 TMP Sprite Asset Creator 批量处理图标

七、常见问题排查

表格
问题可能原因解决方法
点击没反应 相机参数传错了 检查 Canvas 渲染模式,调整第三个参数
点击没反应 Raycast Target 未启用 勾选 TMP 组件上的 Raycast Target
点击没反应 Rich Text 未启用 勾选 TMP 组件上的 Rich Text
链接不显示下划线 没有包裹 <u> 标签 在 <link> 外层加 <u>...</u>
多个链接点击混乱 文本未正确闭合标签 检查每个 <link> 是否有对应的 </link>

掌握了这些,你就能在聊天系统、任务描述、物品说明等任何需要文本交互的场景里,轻松实现类似网页超链接.

posted on 2026-05-15 11:45  -冷夜-  阅读(51)  评论(0)    收藏  举报

导航