美文网首页
Unity中,在Text中插入表情图

Unity中,在Text中插入表情图

作者: 全新的饭 | 来源:发表于2024-02-27 10:17 被阅读0次

    示意

    文字内容和实际效果

    • 测试表情图:[4][5][Laugh]\n[左边三条线]\n生气[愤怒]\n[眼巴巴地看着你]可怜巴巴\n[红脸歪嘴]


      文字内容中穿插表情.png

    思路

    1. 用特定的文字内容(用中括号括起来)表示要插入的表情图:[笑脸]
    2. 运行时用空白字符字符替代前述特定文字内容
    3. 在各空白字符位置处创建Image,显示相应的表情图

    使用步骤

    创建表情图配置资源:

    • 在Project视图中,选中所有表情图,右键创建EmojiCfg。
      所有表情图都应为正方形。


      创建cfg.png
      创建完成.png

    通过EmojiText显示:

    • 将前一步创建的Cfg拖入该Text的相应字段。


      设置Cfg.png
    • 通过调用SetText(带有表情图文字标记的完整文字内容)进行显示。


      在代码中设置文字内容.png

    具体代码

    表情图配置资源:FanEmojiCfg.cs

    using System.Collections.Generic;
    using UnityEngine;
    
    [CreateAssetMenu(fileName = "FanEmojiCfg", menuName = "FanEmojiCfg", order = 0)]
    public class FanEmojiCfg : ScriptableObject
    {
        [SerializeField]
        private FanEmojiCfgItem[] _items;
        public FanEmojiCfgItem[] Items => _items;
        public void SetItems(Dictionary<string, Sprite> itemDict)
        {
            var items = new List<FanEmojiCfgItem>();
            foreach (var i in itemDict.Keys)
            {
                items.Add(new FanEmojiCfgItem() { Key = i, Sprite = itemDict[i] });
            }
            _items = items.ToArray();
        }
    
        public Sprite GetSprite(string key)
        {
            Sprite sprite = null;
            foreach (var i in Items)
            {
                if (i.Key == key)
                {
                    sprite = i.Sprite;
                    break;
                }
            }
    
            if (sprite == null)
            {
                Debug.Log($"{nameof(FanEmojiCfg)}中不存在名为 {key} 的图,请确认!");
                sprite = Items[0].Sprite;
            }
            return sprite;
        }
    }
    [System.Serializable]
    public class FanEmojiCfgItem
    {
        public string Key;
        public Sprite Sprite;
    }
    

    创建表情图配置资源(需放在Editor下):FanEmojiCfgBuilder.cs

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEditor;
    using System.Linq;
    
    public class FanEmojiCfgBuilder : Editor
    {
        [MenuItem(@"Assets/Fan/CreateFanEmojiCfg")]
        public static void CreateFanEmojiCfg()
        {
            var objs = Selection.objects;
            Dictionary<string, Sprite> dict = new Dictionary<string, Sprite>();
            foreach (var obj in objs)
            {
                if (obj is Texture2D tex)
                {
                    var sprite = Texture2DToSprite(tex);
                    dict.TryAdd(sprite.name, sprite);
                }
            }
    
            if (dict.Count > 0)
            {
                var path = AssetDatabase.GetAssetPath(dict.Values.ToArray()[0]);
                path = System.IO.Path.Join(System.IO.Path.GetDirectoryName(path), nameof(FanEmojiCfg) + ".asset");
                var cfg = ScriptableObject.CreateInstance<FanEmojiCfg>();
                cfg.SetItems(dict);
                AssetDatabase.CreateAsset(cfg, path);
                AssetDatabase.SaveAssets();
                AssetDatabase.Refresh();
            }
    
    
            Sprite Texture2DToSprite(Texture2D tex)
            {
                return AssetDatabase.LoadAssetAtPath<Sprite>(AssetDatabase.GetAssetPath(tex));
            }
        }
    }
    

    改造后的Text:FanEmojiText.cs

    using System.Collections;
    using System.Collections.Generic;
    using System.Text.RegularExpressions;
    using UnityEngine;
    using UnityEngine.UI;
    
    [RequireComponent(typeof(Text))]
    public class FanEmojiText : MonoBehaviour
    {
        private string StrPattern => "\\[[\u4e00-\u9fa5_a-zA-Z0-9]+\\]";
        private string EmptyStr => "饭";
        private string ActualEmptyStr => "ㅤ";
    
        [SerializeField]
        private Text _text;
        [SerializeField]
        private FanEmojiCfg _emojiCfg;
        private List<GameObject> _spriteGos;
        public void SetText(string content)
        {
            StartCoroutine(SetTextCoroutine(content));
        }
    
        private IEnumerator SetTextCoroutine(string content)
        {
            DestroySpriteGos();
            // 提取其中的图片标签:若干个图片key-localPos
            MatchCollection matches = Regex.Matches(content, StrPattern);
            List<Sprite> sprites = new List<Sprite>();
            for (int i = 0; i < matches.Count; i++)
            {
                var str = matches[i].Value;
                var key = str.Replace("[", string.Empty).Replace("]", string.Empty);
                sprites.Add(_emojiCfg.GetSprite(key));
            }
            // 将前述所有图片标签的文字进行替换
            var actualContent = content;
            content = Regex.Replace(content, StrPattern, EmptyStr);
            actualContent = Regex.Replace(actualContent, StrPattern, ActualEmptyStr);
            // 显示文字
            content = content.Replace("\\n", "\n");
            actualContent = actualContent.Replace("\\n", "\n");
            _text.text = content;
    
            // LayoutRebuilder.ForceRebuildLayoutImmediate(_text.rectTransform);
            yield return new WaitForEndOfFrame();
    
            // 获取所有空白字符的局部坐标(按顺序)
            List<Vector3> spritesLocalPos = new List<Vector3>();
            int index = 0;
            for (int i = 0; i < content.Length; i++)
            {
                if (content[i].ToString() == EmptyStr)
                {
                    spritesLocalPos.Add(CalculateCharLocalPos(index));
                }
                if (content[i].ToString() != "\n")
                {
                    index++;
                }
            }
            // 显示真实文字内容
            _text.text = actualContent;
            // 创建所有图片并设置到相应位置
            CreateSpriteGos(sprites, spritesLocalPos);
        }
    
        private void DestroySpriteGos()
        {
            if (_spriteGos != null)
            {
                for (int i = 0; i < _spriteGos.Count; i++)
                {
                    GameObject.Destroy(_spriteGos[i]);
                }
                _spriteGos = null;
            }
        }
        private void CreateSpriteGos(List<Sprite> sprites, List<Vector3> spritesLocalPos)
        {
            _spriteGos = new List<GameObject>();
            for (int i = 0; i < sprites.Count; i++)
            {
                _spriteGos.Add(CreateSpriteGo(sprites[i], spritesLocalPos[i]));
            }
    
            GameObject CreateSpriteGo(Sprite sprite, Vector3 localPos)
            {
                var go = new GameObject(sprite.name);
                var rectTrans = go.AddComponent<RectTransform>();
                rectTrans.sizeDelta = Vector2.one * _text.fontSize;
                var img = go.AddComponent<Image>();
                img.sprite = sprite;
                go.transform.SetParent(transform);
                go.transform.localPosition = localPos;
                go.transform.localScale = Vector3.one;
                return go;
            }
        }
    
    
    
        private void Reset()
        {
            _text = GetComponent<Text>();
        }
    
        private Vector3 CalculateCharLocalPos(int charIndex)
        {
            return CalculateCharLocalPos(_text.canvas, _text, charIndex);
        }
        /// <summary>
        /// 计算字符坐标
        /// </summary>
        /// <param name="canvas"> Text所在的Canvas </param>
        /// <param name="text"> 要计算字符坐在的Text </param>
        /// <param name="charIndex"> 要计算字符在字符串的下标 </param>
        private Vector3 CalculateCharLocalPos(Canvas targetCanvas, Text targetText, int targetCharIndex)
        {
            Vector3 avgPos = Vector3.zero;
            string calculateStr = targetText.text;
    
            TextGenerator textGen = new TextGenerator(calculateStr.Length);
            Vector2 extents = targetText.GetComponent<RectTransform>().rect.size;
            textGen.Populate(calculateStr, targetText.GetGenerationSettings(extents));
            /*
            int newLine = calculateStr.Substring(0, targetCharIndex).Split('\n').Length - 1;
            int whiteSpace = calculateStr.Substring(0, targetCharIndex).Split(' ').Length - 1;
            */
            int indexOfTextQuad = targetCharIndex * 4;
            /*
            Vector3 charPos1 = textGen.verts[indexOfTextQuad].position / targetCanvas.scaleFactor;
            Vector3 charPos2 = textGen.verts[indexOfTextQuad + 1].position / targetCanvas.scaleFactor;
            Vector3 charPos3 = textGen.verts[indexOfTextQuad + 2].position / targetCanvas.scaleFactor;
            Vector3 charPos4 = textGen.verts[indexOfTextQuad + 3].position / targetCanvas.scaleFactor;
            */
    
            if (indexOfTextQuad < textGen.vertexCount)
            {
                avgPos = (textGen.verts[indexOfTextQuad].position +
                    textGen.verts[indexOfTextQuad + 1].position +
                    textGen.verts[indexOfTextQuad + 2].position +
                    textGen.verts[indexOfTextQuad + 3].position) / 4f;
            }
            // 分辨率适配
            avgPos /= targetCanvas.scaleFactor;
    
            // 转为世界坐标
            // avgPos = targetText.transform.TransformPoint(avgPos);
    
            return avgPos;
        }
    }
    

    用于测试功能的代码:FanTest.cs

    using UnityEngine;
    using UnityEngine.UI;
    
    public class FanTest : MonoBehaviour
    {
        [SerializeField]
        private FanEmojiText _text;
        [SerializeField]
        [TextArea(3, 10)]
        private string _content;
        private void Update()
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                _text.SetText(_content);
            }
        }
    }
    

    额外奖励:为文字内容添加背景图,背景图的大小随文字内容变化而变化

    如图,需将Text设置为背景图的子节点。
    背景图需添加2个组件:ContentSize Fitter、Horizontal Layout Group
    为了让文字内容变多时,整体的下方位置保持不变 -> 背景图的RectTransform的Pivot.Y设置为0。


    文字添加背景图.png

    相关文章

      网友评论

          本文标题:Unity中,在Text中插入表情图

          本文链接:https://www.haomeiwen.com/subject/agptzdtx.html