Unity中,在Text中插入表情图

2024-02-27  本文已影响0人  全新的饭

示意

文字内容和实际效果

思路

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

使用步骤

创建表情图配置资源:

通过EmojiText显示:

具体代码

表情图配置资源: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
上一篇 下一篇

猜你喜欢

热点阅读