2025-07-23 13:56:37 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2025-07-24 10:19:23 +08:00
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Text.RegularExpressions;
|
2025-07-23 13:56:37 +08:00
|
|
|
|
using Sirenix.OdinInspector;
|
|
|
|
|
|
using TMPro;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.Events;
|
|
|
|
|
|
using XericLibrary.Runtime.MacroLibrary;
|
|
|
|
|
|
using XericLibrary.Runtime.Type;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Deconstruction.UI.TmpText
|
2025-07-24 10:19:23 +08:00
|
|
|
|
{
|
2025-07-23 13:56:37 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// TMP超链接点击管理组件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class TMPHyperlinkManager : MonoBehaviour
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 画布组件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
#if ODIN_INSPECTOR
|
|
|
|
|
|
[LabelText("画布*")]
|
|
|
|
|
|
#endif
|
|
|
|
|
|
[Tooltip("组件可为空,将自动向上查找")]
|
2025-07-24 10:19:23 +08:00
|
|
|
|
[SerializeField]
|
|
|
|
|
|
public Canvas canvas;
|
2025-07-23 13:56:37 +08:00
|
|
|
|
|
|
|
|
|
|
#if ODIN_INSPECTOR
|
|
|
|
|
|
[LabelText("点击超链接时回调")]
|
|
|
|
|
|
#endif
|
|
|
|
|
|
[SerializeField]
|
2025-07-24 10:19:23 +08:00
|
|
|
|
public UnityEvent<string> onClickLink = new UnityEvent<string>();
|
|
|
|
|
|
|
2025-07-23 13:56:37 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前摄像机
|
|
|
|
|
|
/// </summary>
|
2025-07-24 10:19:23 +08:00
|
|
|
|
private Camera _camera;
|
|
|
|
|
|
|
2025-07-23 13:56:37 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 存储链接ID与对应回调函数的映射
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private Dictionary<string, Action> _linkCallbacks = new Dictionary<string, Action>();
|
|
|
|
|
|
|
|
|
|
|
|
protected void Awake()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (canvas == null)
|
2025-07-24 10:19:23 +08:00
|
|
|
|
canvas = transform.GetParents().Startup(transform).GetComponent<Canvas>();
|
2025-07-23 13:56:37 +08:00
|
|
|
|
if (canvas == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("TMP文本对象必须在Canvas下才能使用超链接功能");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-07-24 10:19:23 +08:00
|
|
|
|
|
2025-07-23 13:56:37 +08:00
|
|
|
|
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
|
|
|
|
|
|
_camera = null;
|
|
|
|
|
|
else
|
|
|
|
|
|
_camera = canvas.worldCamera;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册链接回调函数
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void RegisterLinkCallback(string linkID, Action callback)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_linkCallbacks.ContainsKey(linkID))
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"链接ID '{linkID}' 已存在,将覆盖之前的回调函数");
|
|
|
|
|
|
_linkCallbacks[linkID] = callback;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_linkCallbacks.Add(linkID, callback);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-24 10:19:23 +08:00
|
|
|
|
|
2025-07-23 13:56:37 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注销链接回调函数
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void UnregisterLinkCallback(string linkID)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_linkCallbacks.ContainsKey(linkID))
|
|
|
|
|
|
{
|
|
|
|
|
|
_linkCallbacks.Remove(linkID);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-24 10:19:23 +08:00
|
|
|
|
|
2025-07-23 13:56:37 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 处理链接点击事件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void HandleLinkClick(string linkID)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_linkCallbacks.TryGetValue(linkID, out Action callback))
|
|
|
|
|
|
{
|
|
|
|
|
|
callback?.Invoke();
|
|
|
|
|
|
Debug.Log($"执行链接 '{linkID}' 的回调函数");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"未找到链接ID '{linkID}' 的回调函数");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-24 10:19:23 +08:00
|
|
|
|
|
2025-07-23 13:56:37 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检查坐标下的超链接索引
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="text"></param>
|
|
|
|
|
|
/// <param name="position"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
internal int CheckLinkIndex(TMP_Text text, Vector3 position)
|
|
|
|
|
|
{
|
|
|
|
|
|
return TMP_TextUtilities.FindIntersectingLink(text, position, _camera);
|
|
|
|
|
|
}
|
2025-07-24 10:19:23 +08:00
|
|
|
|
|
2025-07-23 13:56:37 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检查坐标下的超链接id信息
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="text"></param>
|
|
|
|
|
|
/// <param name="position"></param>
|
|
|
|
|
|
/// <param name="linkInfo"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
internal bool CheckLinkInfo(TMP_Text text, Vector3 position, out TMP_LinkInfo linkInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
var index = CheckLinkIndex(text, position);
|
|
|
|
|
|
if (index != -1)
|
|
|
|
|
|
{
|
|
|
|
|
|
linkInfo = text.textInfo.linkInfo[index];
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-07-24 10:19:23 +08:00
|
|
|
|
|
2025-07-23 13:56:37 +08:00
|
|
|
|
linkInfo = default;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-07-24 10:19:23 +08:00
|
|
|
|
|
2025-07-23 13:56:37 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检查坐标下的超链接id
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="text"></param>
|
|
|
|
|
|
/// <param name="position"></param>
|
|
|
|
|
|
/// <param name="linkID"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
internal bool CheckLinkID(TMP_Text text, Vector3 position, out string linkID)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (CheckLinkInfo(text, position, out var info))
|
|
|
|
|
|
{
|
|
|
|
|
|
linkID = info.GetLinkID();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-07-24 10:19:23 +08:00
|
|
|
|
|
2025-07-23 13:56:37 +08:00
|
|
|
|
linkID = string.Empty;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当任意tmp对象超链接点击时调用,从这里发送事件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="linkID"></param>
|
|
|
|
|
|
internal void OnAnyLinkClick(string linkID)
|
|
|
|
|
|
{
|
|
|
|
|
|
onClickLink?.Invoke(linkID);
|
2025-07-24 10:19:23 +08:00
|
|
|
|
// Debug.Log($"click link{linkID}");
|
2025-07-23 13:56:37 +08:00
|
|
|
|
// 这里不需要处理报错,应当由发送消息的成员处理
|
|
|
|
|
|
}
|
2025-07-24 10:19:23 +08:00
|
|
|
|
|
2025-07-23 13:56:37 +08:00
|
|
|
|
// todo 主动查找所有的tmp组件,或是等组件唤醒后区管理它们,用来获得所有的带有标记的超链接组件
|
2025-07-24 10:19:23 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 主动查找所有具有link属性的tmp组件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="requirementComponent">自动查找</param>
|
|
|
|
|
|
/// <param name="forceRefesh"></param>
|
|
|
|
|
|
public void FindAllID(bool requirementComponent = true, bool forceRefesh = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
transform.GetChildrenBFS().GetComponentsOTON<TMPHyperlinkReceiver>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<TMPHyperlinkReceiver> _tempGetChildrenHyperlink = null;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取所有链接子项
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="forceRefesh">强制刷新,如果刷新过一次,之后不论设定如何都将直接从缓存中返回对象,置位将跳过这个缓存</param>
|
|
|
|
|
|
/// <param name="includeRepeatedMarking">包括重复标记(也就是是否包含另一个超链接管理器作用域下的超链接对象)</param>
|
|
|
|
|
|
public List<TMPHyperlinkReceiver> GetChildrenHyperlink(bool forceRefesh = false,
|
|
|
|
|
|
bool includeRepeatedMarking = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!forceRefesh && _tempGetChildrenHyperlink != null && _tempGetChildrenHyperlink.Count > 0)
|
|
|
|
|
|
return _tempGetChildrenHyperlink;
|
|
|
|
|
|
|
|
|
|
|
|
_tempGetChildrenHyperlink = transform.GetChildrenBFS()
|
|
|
|
|
|
.GetComponentsOTON<TMPHyperlinkReceiver>().ToList();
|
|
|
|
|
|
|
|
|
|
|
|
return _tempGetChildrenHyperlink;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<TMPHyperlinkReceiver> _tempGetChildrenTmpText2Hyperlink = null;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取所有tmp_text,然后根据规则返回链接
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="forceRefesh">强制刷新,如果刷新过一次,之后不论设定如何都将直接从缓存中返回对象,置位将跳过这个缓存</param>
|
|
|
|
|
|
/// <param name="requirementHyperlink">强制给带有link标记的对象加上超链接组件</param>
|
|
|
|
|
|
/// <param name="includeRepeatedMarking">包括重复标记(也就是是否包含另一个超链接管理器作用域下的超链接对象)</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public List<TMPHyperlinkReceiver> GetChildrenTmpText2Hyperlink(
|
|
|
|
|
|
bool forceRefesh = false,
|
|
|
|
|
|
bool requirementHyperlink = true,
|
|
|
|
|
|
bool includeRepeatedMarking = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!forceRefesh &&
|
|
|
|
|
|
_tempGetChildrenTmpText2Hyperlink != null &&
|
|
|
|
|
|
_tempGetChildrenTmpText2Hyperlink.Count > 0)
|
|
|
|
|
|
return _tempGetChildrenTmpText2Hyperlink;
|
|
|
|
|
|
|
|
|
|
|
|
var children = includeRepeatedMarking
|
|
|
|
|
|
? transform.GetChildrenBFS()
|
|
|
|
|
|
: transform.GetChildrenBFS<TMPHyperlinkManager>();
|
|
|
|
|
|
|
|
|
|
|
|
var result = new List<TMPHyperlinkReceiver>();
|
|
|
|
|
|
var hyperlinkType = typeof(TMPHyperlinkReceiver);
|
|
|
|
|
|
var tmpTextType = typeof(TMP_Text);
|
|
|
|
|
|
|
|
|
|
|
|
// 预先分配足够的容量,减少扩容
|
|
|
|
|
|
result.Capacity = Mathf.Min(children.Count(), 10);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var child in children)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 尝试从缓存获取组件
|
|
|
|
|
|
var hyperlink = child.GetComponent(hyperlinkType) as TMPHyperlinkReceiver;
|
|
|
|
|
|
if (hyperlink != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
result.Add(hyperlink);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!requirementHyperlink)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试获取TMP_Text组件
|
|
|
|
|
|
var tmp = child.GetComponent(tmpTextType) as TMP_Text;
|
|
|
|
|
|
if (tmp != null && tmp.text.MatchRichTextLinkID())
|
|
|
|
|
|
{
|
|
|
|
|
|
result.Add(child.gameObject.AddComponent<TMPHyperlinkReceiver>());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2025-07-23 13:56:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|