Unity Text 实现图文混排和超链接功能

这篇具有很好参考价值的文章主要介绍了Unity Text 实现图文混排和超链接功能。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

unity 已经在2021版本使用了全面使用了TMP实现图文混排和超链接这两个功能。由于当前项目并没有使用TMP。所以需要基于Text实现这两个功能,但是在2019之后的版本,Text 生成顶点时不再储存富文本信息顶点,所以很多以前的图文混排和超链接的插件都失效了,于是研究了一下结合之前大佬们的写法,实现了适配2019版本及以后的图文混排和超链接功能Text。

GitHub - tianminghui923/LinkSpriteText: Unity2019.1.5f1及更高版本 实现了图文混排和超链接功能的 Text

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace LinkSpriteText
{
    /// <summary>
    /// 文本控件,支持超链接、图片
    /// </summary>
    [AddComponentMenu("UI/LinkImageText", 10)]
    public class LinkSpriteText : Text, IPointerClickHandler, IPointerDownHandler
    {
        [TextArea(3, 10), SerializeField] protected string originText;


        public override string text
        {
            get => _outputText;

            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    if (string.IsNullOrEmpty(text))
                    {
                        return;
                    }

                    originText = String.Empty;
                    _mTextDirty = true;
                    SetVerticesDirty();
                }
                else
                {
                    if (originText == value)
                    {
                        return;
                    }

                    originText = value;
                    _mTextDirty = true;
                    SetVerticesDirty();
                }
            }
        }

        public string customLinkColor = "blue";

        public void SetLinkColor(string c)
        {
            customLinkColor = c;
            _mTextDirty = false;
        }


        /// <summary>
        /// 解析完最终的文本
        /// </summary>
        private string _outputText;


        /// <summary>
        /// 对应的顶点输出的文本
        /// </summary>
        private string _vertexText;

        /// <summary>
        /// 是否需要解析
        /// </summary>
        private bool _mTextDirty = true;


        /// <summary>
        /// 图片池
        /// </summary>
        private readonly List<Image> _mImagesPool = new List<Image>();

        /// <summary>
        /// 图片的最后一个顶点的索引
        /// </summary>
        private readonly List<int> _mImagesVertexIndex = new List<int>();

        /// <summary>
        /// 超链接信息列表
        /// </summary>
        private readonly List<LinkInfo> _mLinkInfos = new List<LinkInfo>();

        [Serializable]
        public class LinkClickEvent : UnityEvent<string>
        {
        }


        /// <summary>
        /// 超链接点击事件
        /// </summary>
        public LinkClickEvent onLinkClick;


        public LinkClickEvent onLinkDown;


        /// <summary>
        /// 正则取出所需要的属性
        /// </summary>
        private static readonly Regex ImageRegex =
            new Regex(@"<quad name=(.+?) size=(\d*\.?\d+%?) width=(\d*\.?\d+%?) />", RegexOptions.Singleline);

        /// <summary>
        /// 超链接正则
        /// </summary>
        private static readonly Regex LinkRegex =
            new Regex(@"<a link=([^>\n\s]+)>(.*?)(</a>)", RegexOptions.Singleline);

        /// <summary>
        /// 加载精灵图片方法
        /// </summary>
        public static Func<string, Sprite> FunLoadSprite => Resources.Load<Sprite>;


        public override void SetVerticesDirty()
        {
            base.SetVerticesDirty();
            _mTextDirty = true;
            UpdateQuadImage();
        }


        private void UpdateQuadImage()
        {
            if (_mTextDirty)
            {
                _outputText = GetOutputText(originText);
            }


            _mImagesVertexIndex.Clear();
            int startSearchIndex = 0;
            var matches = ImageRegex.Matches(originText);
            for (var i = 0; i < matches.Count; i++)
            {
                Match match = matches[i];
                int index = _vertexText.IndexOf('&', startSearchIndex);

                var firstIndex = index * 4;
                startSearchIndex = index + 1;

                _mImagesVertexIndex.Add(firstIndex);

                _mImagesPool.RemoveAll(image => image == null);
                if (_mImagesPool.Count == 0)
                {
                    GetComponentsInChildren(_mImagesPool);
                }

                if (_mImagesVertexIndex.Count > _mImagesPool.Count)
                {
                    var resources = new DefaultControls.Resources();
                    var go = DefaultControls.CreateImage(resources);
                    go.layer = gameObject.layer;
                    var rt = go.transform as RectTransform;
                    if (rt)
                    {
                        rt.SetParent(rectTransform, false);
                        rt.localPosition = Vector3.zero;
                        rt.localRotation = Quaternion.identity;
                        rt.localScale = Vector3.one;
                    }

                    _mImagesPool.Add(go.GetComponent<Image>());
                }

                var spriteName = match.Groups[1].Value;
                var img = _mImagesPool[i];
                if (img.sprite == null || img.sprite.name != spriteName)
                {
                    img.sprite = FunLoadSprite(spriteName);
                }

                var imgRectTransform = img.GetComponent<RectTransform>();
                if (Int32.TryParse(match.Groups[2].Value, out int size))
                {
                    imgRectTransform.sizeDelta = new Vector2(size, size);
                }
                else
                {
                    Debug.LogWarning("无法正常解析大小");
                    imgRectTransform.sizeDelta = new Vector2(16f, 16f);
                }

                img.enabled = true;
            }

            for (var i = _mImagesVertexIndex.Count; i < _mImagesPool.Count; i++)
            {
                if (_mImagesPool[i])
                {
                    _mImagesPool[i].enabled = false;
                }
            }
        }

        protected override void OnPopulateMesh(VertexHelper toFill)
        {
            m_DisableFontTextureRebuiltCallback = true;
            base.OnPopulateMesh(toFill);
            UIVertex vert = new UIVertex();


            for (var i = 0; i < _mImagesVertexIndex.Count; i++)
            {
                var index = _mImagesVertexIndex[i];
                var rt = _mImagesPool[i].rectTransform;
                var size = rt.sizeDelta;
                if (index < toFill.currentVertCount)
                {
                    toFill.PopulateUIVertex(ref vert, index);
                    rt.anchoredPosition = new Vector2(vert.position.x + size.x / 2, vert.position.y - size.y * 0.625f);
                    toFill.PopulateUIVertex(ref vert, index);
                    for (int j = index + 3, m = index; j > m; j--)
                    {
                        toFill.SetUIVertex(vert, j);
                    }
                }
            }


            // 处理超链接包围框
            foreach (var info in _mLinkInfos)
            {
                info.Boxes.Clear();
                if (info.StartIndex >= toFill.currentVertCount)
                {
                    continue;
                }

                // 将超链接里面的文本顶点索引坐标加入到包围框
                toFill.PopulateUIVertex(ref vert, info.StartIndex);
                var pos = vert.position;
                var bounds = new Bounds(pos, Vector3.zero);
                for (int i = info.StartIndex, m = info.EndIndex; i < m; i++)
                {
                    if (i >= toFill.currentVertCount)
                    {
                        break;
                    }

                    toFill.PopulateUIVertex(ref vert, i);
                    pos = vert.position;
                    if (pos.x < bounds.min.x) // 换行重新添加包围框
                    {
                        info.Boxes.Add(new Rect(bounds.min, bounds.size));
                        bounds = new Bounds(pos, Vector3.zero);
                    }
                    else
                    {
                        bounds.Encapsulate(pos); // 扩展包围框
                    }
                }

                info.Boxes.Add(new Rect(bounds.min, bounds.size));
            }

            m_DisableFontTextureRebuiltCallback = false;
        }


        /// <summary>
        /// 获取超链接解析后的最后输出文本 
        /// </summary>
        /// <returns></returns>
        protected virtual string GetOutputText(string outputText)
        {
            _mLinkInfos.Clear();
            if (string.IsNullOrEmpty(outputText))
                return "";
            string tempOutputText = outputText;
            _vertexText = outputText;
            _vertexText = Regex.Replace(_vertexText, "<color.*?>", "");
            _vertexText = Regex.Replace(_vertexText, "</color>", "");
            _vertexText = ImageRegex.Replace(_vertexText, "&");
            _vertexText = _vertexText.Replace("\n", "");
            _vertexText = Regex.Replace(_vertexText, @"(?<!a)\s(?!link)", "");
            foreach (Match match in LinkRegex.Matches(_vertexText))
            {
                var group = match.Groups[1];
                _vertexText = _vertexText.Replace(match.Value, match.Groups[2].Value);
                int startNum = _vertexText.IndexOf(match.Groups[2].Value, match.Index, StringComparison.Ordinal);
                if (startNum < 0)
                {
                    Debug.LogError("超链接顶点解析错误");
                }

                var info = new LinkInfo
                {
                    StartIndex = startNum * 4, // 超链接里的文本起始顶点索引

                    EndIndex = startNum * 4 + (match.Groups[2].Length - 1) * 4 + 3,
                    Name = group.Value
                };
                _mLinkInfos.Add(info);
            }


            foreach (Match match in LinkRegex.Matches(outputText))
            {
                tempOutputText = tempOutputText.Replace(match.Value,
                    $"<color={customLinkColor}>" + match.Groups[2].Value + "</color>");
            }


            _mTextDirty = false;

            return tempOutputText;
        }

        #region 回调事件

        /// <summary>
        /// 点击事件检测是否点击到超链接文本
        /// </summary>
        /// <param name="eventData"></param>
        public void OnPointerClick(PointerEventData eventData)
        {
            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                rectTransform, eventData.position, eventData.pressEventCamera, out var lp);

            foreach (var info in _mLinkInfos)
            {
                var boxes = info.Boxes;
                for (var i = 0; i < boxes.Count; ++i)
                {
                    if (!boxes[i].Contains(lp)) continue;
                    onLinkClick.Invoke(info.Name);
                    return;
                }
            }
        }

        /// <summary>
        /// 点击事件检测是否点击到超链接文本
        /// </summary>
        /// <param name="eventData"></param>
        public void OnPointerDown(PointerEventData eventData)
        {
            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                rectTransform, eventData.position, eventData.pressEventCamera, out var lp);

            foreach (var info in _mLinkInfos)
            {
                var boxes = info.Boxes;
                for (var i = 0; i < boxes.Count; ++i)
                {
                    if (!boxes[i].Contains(lp)) continue;
                    onLinkDown.Invoke(info.Name);
                    return;
                }
            }
        }

        #endregion

        /// <summary>
        /// 超链接信息类
        /// </summary>
        private class LinkInfo
        {
            public int StartIndex;

            public int EndIndex;

            public string Name;

            public readonly List<Rect> Boxes = new List<Rect>();
        }
    }
}

编辑器代码文章来源地址https://www.toymoban.com/news/detail-556573.html

using UnityEditor;
using TextEditor = UnityEditor.UI.TextEditor;
namespace LinkSpriteText.Scripts.Editor
{
    [CanEditMultipleObjects, CustomEditor(typeof(LinkSpriteText), false)]
    public class LinkSpriteTextEditor : TextEditor
    {
        private SerializedProperty _mOriginText;
        private SerializedProperty _mFontData;

        protected override void OnEnable()
        {
            base.OnEnable();

            _mOriginText = serializedObject.FindProperty("originText");
            _mFontData = serializedObject.FindProperty("m_FontData");
        }

        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            EditorGUILayout.PropertyField(_mOriginText);
            EditorGUILayout.PropertyField(_mFontData);


            AppearanceControlsGUI();
            serializedObject.ApplyModifiedProperties();
        }
    }
}

到了这里,关于Unity Text 实现图文混排和超链接功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【100个 Unity实用技能】☀️ | UGUI Text中加入超链接文本,可直接点击跳转

    老规矩,先介绍一下 Unity 的科普小知识: Unity 是 实时3D互动内容创作和运营平台 。 包括 游戏开发 、 美术 、 建筑 、 汽车设计 、 影视 在内的所有创作者,借助 Unity 将创意变成现实。 Unity 平台提供一整套完善的软件解决方案,可用于创作、运营和变现任何实时互动的2D和

    2024年02月08日
    浏览(37)
  • 【Unity3D小功能】Unity3D中实现Text显示版本功能

    推荐阅读 CSDN主页 GitHub开源地址 Unity3D插件分享 简书地址 我的个人博客 大家好,我是佛系工程师 ☆恬静的小魔龙☆ ,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 在项目开发中,会遇到要控制版本的情况,比如说对比版本号,版本不对再更新版本的功能,这些就是

    2024年02月05日
    浏览(49)
  • Unity 制作登录功能02-创建和链接数据库(SQlite)

    在Unity开发游戏时使用SQLite有多种原因,以下是其中一些主要原因: 嵌入式数据库:SQLite是一个嵌入式数据库引擎,这意味着它不需要单独的服务器进程。这使得使用SQLite非常方便,并且可以轻松地在不同的平台和操作系统上使用。 易于使用:SQLite易于学习和使用,具有简洁

    2024年02月04日
    浏览(39)
  • 计算机视觉与深度学习 | Visual ChatGPT:微软开源视觉(图文)聊天系统——图像生成、迁移学习、边缘检测、颜色渲染等多功能(附代码下载链接)

    ===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 =====================================================

    2024年02月06日
    浏览(50)
  • Unity实现Text内容逐个出现效果

    效果展示: 要实现功能,首先在画布上新建一个Text对象 步骤一:修改Text的锚点,如下图所示即可 步骤二:为Text添加一个Context Size Fitter组件 步骤三:新建脚本TextEffects,并且把脚本添加在Text对象上面,代码如下: 效果完成。

    2024年02月11日
    浏览(29)
  • Unity3D 连接 SQLite 作为数据库基础功能【详细图文教程】

    轻量级: SQLite是一个嵌入式数据库引擎,它的库文件非常小巧,没有独立的服务器进程,适用于嵌入到其他应用程序中,对于轻量级的项目或移动应用程序非常适用。 零配置: 使用SQLite不需要进行复杂的配置或管理,你只需要简单地创建一个文件即可开始使用,减少了额外的

    2024年02月10日
    浏览(28)
  • Unity的Text组件里面如何实现首行缩进

    一般而言,MyText.text里面要输入首行空格,那是不可能的。 譬如输入如下的内容: 那用什么方法呢?当然是用【全角空格\\\"u3000\\\"】。 在这个代码中,u3000 表示全角空格,通过 content += “u3000” + “u3000” + str + “n”; 这一行代码,实现了在当前段落的每行文本前添加两个全

    2024年02月04日
    浏览(31)
  • Unity Text文本实现打字机(一个一个出来)的效果

    Unity Text文本要实现打字机,即一个个文字出来的效果,可以通过代码把text文本字符串拆成一个个字符然后添加到文本中。 具体实现: 新建一个控制脚本:TypewriteController.cs,并编写以下代码: 此控制脚本先把脚本文本获取后赋给一个字符串变量,然后置空文本内容,再通过

    2024年01月25日
    浏览(36)
  • Unity3D中Text实现首行缩进的办法

    我最近在我的Unity3D软件中出现个莫名其妙的问题, 我解决问题之后,觉得对各位有帮助,方便Unity3D的初学者。UGUI的Text首行缩进方法 1.复制代码color=#FFFFFF00XXX/color  #FFFFFF00 表示输出的文字为透明颜色  2.粘贴代码到文本编辑的Text控件,需要缩进的首行地方。  3.查看效果  

    2024年02月12日
    浏览(30)
  • Unity通过改变文本Rect长宽以及缩放来改善Text(Legacy)的清晰度思路,操作以及代码实现

    前情:在最近做的一个比较大的项目中,客户要求导入各种图片以及文字。在1920X1080的情况下是采用了42号字体,提供项目后得到的反馈却是字体太糊,经询问得知1920X1080分辨率并不是使用在电脑上,而是在屏幕特别大的仿真机上运行,贴近看确实很糊,但是这个项目使用的Text(L

    2024年01月25日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包