示意
文字内容和实际效果
-
测试表情图:[4][5][Laugh]\n[左边三条线]\n生气[愤怒]\n[眼巴巴地看着你]可怜巴巴\n[红脸歪嘴]
文字内容中穿插表情.png
思路
- 用特定的文字内容(用中括号括起来)表示要插入的表情图:[笑脸]
- 运行时用空白字符字符替代前述特定文字内容
- 在各空白字符位置处创建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
网友评论