UGUI—显示聊天表情
作者:追风剑情 发布于:2020-4-14 14:34 分类:Unity3d
工程截图
UIEmojiTextEditor.cs (将这个脚本放在Editor目录下)
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEditor; namespace UnityEditor.UI { [CustomEditor(typeof(UIEmojiText), true)] [CanEditMultipleObjects] public class UIEmojiTextEditor : GraphicEditor { SerializedProperty m_Text; SerializedProperty m_FontData; SerializedProperty m_EmojiAtlas; protected override void OnEnable() { base.OnEnable(); m_Text = serializedObject.FindProperty("m_Text"); m_FontData = serializedObject.FindProperty("m_FontData"); m_EmojiAtlas = serializedObject.FindProperty("m_EmojiAtlas"); } public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.PropertyField(m_Text); EditorGUILayout.PropertyField(m_EmojiAtlas); EditorGUILayout.PropertyField(m_FontData); AppearanceControlsGUI(); RaycastControlsGUI(); serializedObject.ApplyModifiedProperties(); } } }
UIEmojiText.cs
using System; using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; using UnityEngine.UI; using UnityEngine.Sprites; using UnityEngine.U2D; using UnityEngine.Events; using UnityEngine.EventSystems; using UnityEngine.Serialization; /// <summary> /// 支持 聊天表情&超链接 Text /// </summary> public class UIEmojiText : Text, IPointerClickHandler { [SerializeField] private SpriteAtlas m_EmojiAtlas; private bool m_EmojiDirty = false; private bool m_InsertLine = true; private bool m_OnPopulateMesh = false; private List<EmojiInfo> m_EmojiList = new List<EmojiInfo>(); private List<Image> m_ImageList = new List<Image>(); //超链接列表 private List<HyperLink> m_HyperLinkList = new List<HyperLink>(); //超链接点击事件 public Action<HyperLink> OnClickHyperLinkEvent; private bool m_ParseHyperLink = true; private const string HYPER_LINK_PATTERN = "<a href=\"(?<url>[\\s\\S]*?)\">(?<text>[\\s\\S]*?)</a>"; public struct HyperLink { //区域 public Rect rect; //超链接参数 public string href; //超链接文本 public string text; } private class EmojiInfo { public string name; public Vector3 position; } protected override void Start() { base.Start(); DestroyEmoji(); } public override string text { get { return m_Text; } set { if (String.IsNullOrEmpty(value)) { if (String.IsNullOrEmpty(m_Text)) return; m_Text = ""; SetVerticesDirty(); } else if (m_Text != value) { m_Text = value; SetVerticesDirty(); SetLayoutDirty(); m_InsertLine = true; m_ParseHyperLink = true; } } } protected override void OnPopulateMesh(VertexHelper toFill) { base.OnPopulateMesh(toFill); //不能在OnPopulateMesh方法中创建资源,否则会报以下错: //Trying to add xxx (UnityEngine.UI.Image) for graphic rebuild while we are already inside a graphic rebuild loop. This is not supported. m_EmojiList.Clear(); //解决Text组件在占位符位置显示乱码问题 string s = text; string pattern = "<quad name=(?<name>\\d*?) size=\\d+ width=\\d+/>"; int preIndex = 0; int verIndex = 0; foreach (Match match in Regex.Matches(s, pattern, RegexOptions.Multiline)) { int index = match.Index; string name = match.Groups["name"].Value; //表情名,例如 001 string str = s.Substring(preIndex, index - preIndex); preIndex = index + match.Value.Length; int length = CalculateLength(str); verIndex += length * 4;//占位符起始顶点索引 SetUIVertex(toFill, verIndex); //在占位符处创建表情 EmojiInfo ei = new EmojiInfo(); ei.name = name; ei.position = CalculateEmojiPosition(verIndex); m_EmojiList.Add(ei); m_EmojiDirty = true; //Bug: //自动换行时toFill.currentVertCount值会突然变大很多,导致表情坐标计算错误. //解决方法:在原本自动换行处手动插入\n //Debug.LogFormat("{0} {1} length={2} verIndex={3} total_length={4} toFill.currentVertCount={5}", //name, ei.position.ToString(), length, verIndex, s.Length, toFill.currentVertCount); verIndex += 4; //跳过占位符顶点索引 } m_OnPopulateMesh = true; } private void Update() { if (!m_OnPopulateMesh) return; if (m_InsertLine) { m_Text = InsertLine(m_Text); //Debug.Log(m_Text); m_InsertLine = false; SetVerticesDirty(); } else if (m_EmojiDirty) { ReleaseEmoji(); CreateEmoji(); } else if (m_ParseHyperLink) { ParseHyperLink(); m_ParseHyperLink = false; } } #region 聊天表情处理 private void SetUIVertex(VertexHelper toFill, int index) { if (index + 4 > toFill.currentVertCount) return; //1个字符包含4个顶点 for (int i = index; i < index + 4; i++) { UIVertex vert = new UIVertex(); //通过修改占位符uv坐标来隐藏乱码显示 vert.uv0 = Vector2.zero; vert.uv1 = Vector2.zero; vert.uv2 = Vector2.zero; vert.uv3 = Vector2.zero; toFill.SetUIVertex(vert, i); } } private int CalculateLength(string str) { int length = 0; bool findSymbol = false; for (int i = 0; i < str.Length; i++) { if (str[i] == '\r' || str[i] == '\n' || str[i] == ' ') { continue; } if (str[i] == '<') { findSymbol = true; continue; } if (str[i] == '>') { findSymbol = false; continue; } if (findSymbol) continue; length++; } return length; } // 插入换行符 private string InsertLine(string str) { if (!Application.isPlaying) return str; string s = str; float width = 0, nextWidth; int offset = 0; float rectWidth = rectTransform.sizeDelta.x; TextGenerator textGen = cachedTextGenerator; IList<UICharInfo> infos = textGen.characters; for (int i=0; i<infos.Count; i++) { width += infos[i].charWidth; nextWidth = i + 1 < infos.Count ? infos[i + 1].charWidth : 0; if (width + nextWidth > rectWidth) { //Debug.LogFormat("Insert Line: {0}FL{1}", str[i-1], str[i]); s = s.Insert(i + offset, "\n"); width = 0; offset++; } } //超链接文本中不允许换行,将超链接中的换行符提到文本之前 foreach (Match match in Regex.Matches(s, HYPER_LINK_PATTERN)) { int index = match.Index; string url = match.Groups["url"].Value; string text = match.Groups["text"].Value; int lineIndex = text.IndexOf('\n'); if (lineIndex != -1) { lineIndex = s.IndexOf('\n', index); s = s.Remove(lineIndex, 1); s = s.Insert(index, "\n"); } } return s; } // 计算Emoji坐标 private Vector2 CalculateEmojiPosition(int startIndex) { IList<UIVertex> verts = cachedTextGenerator.verts; Vector3 pos = Vector3.zero; for (int i=startIndex; i<startIndex+4; i++) { if (i >= verts.Count) break; pos += verts[i].position; } pos /= 4; pos /= canvas.scaleFactor; //适应不同分辨率 return pos; } // 创建表情 private void CreateEmoji() { if (m_EmojiDirty) { m_EmojiDirty = false; for (int i = 0; i < m_EmojiList.Count; i++) { EmojiInfo ei = m_EmojiList[i]; Image image = CreateEmoji(ei.name, ei.position); m_ImageList.Add(image); } m_EmojiList.Clear(); Debug.LogFormat("Pool created element count: {0}", EmojiPool.Instance.elementCount); } } // 创建表情 private Image CreateEmoji(string name, Vector2 position) { if (EmojiPool.Instance.emojiTemplet == null) { Transform emoji_templet = this.transform.Find("emoji_templet"); EmojiPool.Instance.emojiTemplet = emoji_templet.GetComponent<Image>(); } Image emoji = EmojiPool.Instance.Get(name); if (emoji == null) return null; CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(emoji); emoji.rectTransform.SetParent(this.transform); RectTransform rt = emoji.rectTransform; rt.anchoredPosition = position; emoji.gameObject.SetActive(true); emoji.enabled = true; return emoji; } // 释放表情 private void ReleaseEmoji() { for (int i=0; i<m_ImageList.Count; i++) { EmojiPool.Instance.Release(m_ImageList[i]); } m_ImageList.Clear(); } // 销毁Emoji对象 private void DestroyEmoji() { Image[] arr = transform.GetComponentsInChildren<Image>(); if (arr == null) return; for (int i=0; i<arr.Length; i++) { if (Application.isPlaying) Destroy(arr[i].gameObject); else DestroyImmediate(arr[i].gameObject); } } // 设置Unity默认字体 private void AssignDefaultFont() { font = Resources.GetBuiltinResource<Font>("Arial.ttf"); } #endregion #region 超链接处理 // 点击事件 public void OnPointerClick(PointerEventData eventData) { Vector2 position = eventData.position; Vector2 rect_position = UGUITool.ScreenPointToLocal(rectTransform, position); HyperLink hyper = GetHyperLinkByPosition(rect_position); if (!string.IsNullOrEmpty(hyper.text)) { Debug.LogFormat("Click: href={0}, text={1}", hyper.href, hyper.text); if (OnClickHyperLinkEvent != null) OnClickHyperLinkEvent(hyper); } } // 通过点击坐标获取超链接 private HyperLink GetHyperLinkByPosition(Vector2 rect_position) { HyperLink hyperLink = default(HyperLink); float x = rect_position.x; float y = rect_position.y; for (int i = 0; i < m_HyperLinkList.Count; i++) { HyperLink hyper = m_HyperLinkList[i]; if (x > hyper.rect.xMin && x < hyper.rect.xMax && y > hyper.rect.yMin && y < hyper.rect.yMax) { hyperLink = hyper; break; } } return hyperLink; } // 解析文本中的超链接 private void ParseHyperLink() { if (!Application.isPlaying) return; m_HyperLinkList.Clear(); string input = m_Text; string nosymbol_text = StripSymbol(input); foreach (Match match in Regex.Matches(input, HYPER_LINK_PATTERN)) { string url = match.Groups["url"].Value; string text = match.Groups["text"].Value; Capture capture = match.Groups["text"]; Debug.LogFormat("HyperLink: url={0}, text={1}", url, text); TextGenerator tg = cachedTextGeneratorForLayout; //UGUI中标记符的字符不占用文本宽度(即,UICharInfo.charWidth=0) UICharInfo[] charInfos = tg.GetCharactersArray();//数组中包含标记符 int start_index = capture.Index; int end_index = capture.Index + capture.Length; float charHeight = 0; float linkWidth = 0; //找字符高度 for (int i = start_index; i < end_index; i++) { UICharInfo info = charInfos[i]; if (info.charWidth > 0) { if (info.charWidth > charHeight) charHeight = info.charWidth; linkWidth += info.charWidth; } } //计算超链接在文本中的x坐标 float linkStartX = 0;//超链接起始X坐标 for (int i = start_index; i >= 0; i--) { char c = input[i]; if (c == '\n') break; UICharInfo info = charInfos[i]; linkStartX += info.charWidth; } float linkEndX = linkStartX + linkWidth;//超链接终止X坐标 UICharInfo start_info = charInfos[start_index]; UICharInfo end_info = charInfos[end_index]; Rect rect = new Rect(); rect.xMin = linkStartX; rect.xMax = linkEndX; //y坐标是负值 rect.yMin = end_info.cursorPos.y - charHeight; rect.yMax = start_info.cursorPos.y; HyperLink hyper = new HyperLink(); hyper.href = url; hyper.text = StripSymbol(text); hyper.rect = rect; m_HyperLinkList.Add(hyper); } } // 返回不含标记符的文本 private string StripSymbol(string text) { if (string.IsNullOrEmpty(text)) return string.Empty; string str = string.Empty; List<char> list = new List<char>(); bool findSymbol = false; for (int i = 0; i < text.Length; i++) { if (text[i] == '<') { findSymbol = true; continue; } if (text[i] == '>') { findSymbol = false; continue; } if (findSymbol) continue; list.Add(text[i]); } str = new string(list.ToArray()); return str; } #endregion } #region Emoji对象池 /// <summary> /// Emoji对象池 /// </summary> [ExecuteInEditMode] public class EmojiPool { private static EmojiPool _instance = null; public static EmojiPool Instance { get { if (_instance == null) _instance = new EmojiPool(); return _instance; } private set { } } public Image emojiTemplet; public int elementCount; private SpriteAtlas atlas; private readonly Dictionary<string, Stack<Image>> dic = new Dictionary<string, Stack<Image>>(); private EmojiPool() { atlas = Resources.Load<SpriteAtlas>("UI/EmojiAtlas"); } public Image Get(string name) { if (atlas == null || emojiTemplet == null) return null; Image image; Sprite sprite; if (dic.ContainsKey(name)) { Stack<Image> stack = dic[name]; if (stack.Count <= 0) { sprite = atlas.GetSprite(name); image = GameObject.Instantiate<Image>(emojiTemplet); image.sprite = sprite; image.name = name; elementCount++; } else image = stack.Pop(); } else { sprite = atlas.GetSprite(name); image = GameObject.Instantiate<Image>(emojiTemplet); image.sprite = sprite; image.name = name; elementCount++; } return image; } public void Release(Image image) { if (image == null) return; string name = image.name; if (!dic.ContainsKey(name)) dic.Add(name, new Stack<Image>()); Stack<Image> stack = dic[name]; stack.Push(image); image.gameObject.SetActive(false); } } #endregion
效果
测试用例
emojiText.text = "<color=red>【系统】</color><quad name=001 size=32 width=1/>恭喜<a href=\"263665629\"><color=green>埃布尔☆亚当</color></a>获得<quad name=084 size=32 width=1/><a href=\"this is mofaxianglian\"><color=yellow>魔法项链</color></a>最高级别防御武器<quad name=004 size=32 width=1/>真是让人羡慕嫉妒恨<quad name=041 size=32 width=1/>~~~~~~~~~~~~~~~~~<a href=\"fangtianhuaji\"><color=yellow>方天化戟</color></a>";
标签: Unity3d
日历
最新文章
随机文章
热门文章
分类
存档
- 2024年11月(3)
- 2024年10月(5)
- 2024年9月(3)
- 2024年8月(3)
- 2024年7月(11)
- 2024年6月(3)
- 2024年5月(9)
- 2024年4月(10)
- 2024年3月(11)
- 2024年2月(24)
- 2024年1月(12)
- 2023年12月(3)
- 2023年11月(9)
- 2023年10月(7)
- 2023年9月(2)
- 2023年8月(7)
- 2023年7月(9)
- 2023年6月(6)
- 2023年5月(7)
- 2023年4月(11)
- 2023年3月(6)
- 2023年2月(11)
- 2023年1月(8)
- 2022年12月(2)
- 2022年11月(4)
- 2022年10月(10)
- 2022年9月(2)
- 2022年8月(13)
- 2022年7月(7)
- 2022年6月(11)
- 2022年5月(18)
- 2022年4月(29)
- 2022年3月(5)
- 2022年2月(6)
- 2022年1月(8)
- 2021年12月(5)
- 2021年11月(3)
- 2021年10月(4)
- 2021年9月(9)
- 2021年8月(14)
- 2021年7月(8)
- 2021年6月(5)
- 2021年5月(2)
- 2021年4月(3)
- 2021年3月(7)
- 2021年2月(2)
- 2021年1月(8)
- 2020年12月(7)
- 2020年11月(2)
- 2020年10月(6)
- 2020年9月(9)
- 2020年8月(10)
- 2020年7月(9)
- 2020年6月(18)
- 2020年5月(4)
- 2020年4月(25)
- 2020年3月(38)
- 2020年1月(21)
- 2019年12月(13)
- 2019年11月(29)
- 2019年10月(44)
- 2019年9月(17)
- 2019年8月(18)
- 2019年7月(25)
- 2019年6月(25)
- 2019年5月(17)
- 2019年4月(10)
- 2019年3月(36)
- 2019年2月(35)
- 2019年1月(28)
- 2018年12月(30)
- 2018年11月(22)
- 2018年10月(4)
- 2018年9月(7)
- 2018年8月(13)
- 2018年7月(13)
- 2018年6月(6)
- 2018年5月(5)
- 2018年4月(13)
- 2018年3月(5)
- 2018年2月(3)
- 2018年1月(8)
- 2017年12月(35)
- 2017年11月(17)
- 2017年10月(16)
- 2017年9月(17)
- 2017年8月(20)
- 2017年7月(34)
- 2017年6月(17)
- 2017年5月(15)
- 2017年4月(32)
- 2017年3月(8)
- 2017年2月(2)
- 2017年1月(5)
- 2016年12月(14)
- 2016年11月(26)
- 2016年10月(12)
- 2016年9月(25)
- 2016年8月(32)
- 2016年7月(14)
- 2016年6月(21)
- 2016年5月(17)
- 2016年4月(13)
- 2016年3月(8)
- 2016年2月(8)
- 2016年1月(18)
- 2015年12月(13)
- 2015年11月(15)
- 2015年10月(12)
- 2015年9月(18)
- 2015年8月(21)
- 2015年7月(35)
- 2015年6月(13)
- 2015年5月(9)
- 2015年4月(4)
- 2015年3月(5)
- 2015年2月(4)
- 2015年1月(13)
- 2014年12月(7)
- 2014年11月(5)
- 2014年10月(4)
- 2014年9月(8)
- 2014年8月(16)
- 2014年7月(26)
- 2014年6月(22)
- 2014年5月(28)
- 2014年4月(15)
友情链接
- Unity官网
- Unity圣典
- Unity在线手册
- Unity中文手册(圣典)
- Unity官方中文论坛
- Unity游戏蛮牛用户文档
- Unity下载存档
- Unity引擎源码下载
- Unity服务
- Unity Ads
- wiki.unity3d
- Visual Studio Code官网
- SenseAR开发文档
- MSDN
- C# 参考
- C# 编程指南
- .NET Framework类库
- .NET 文档
- .NET 开发
- WPF官方文档
- uLua
- xLua
- SharpZipLib
- Protobuf-net
- Protobuf.js
- OpenSSL
- OPEN CASCADE
- JSON
- MessagePack
- C在线工具
- 游戏蛮牛
- GreenVPN
- 聚合数据
- 热云
- 融云
- 腾讯云
- 腾讯开放平台
- 腾讯游戏服务
- 腾讯游戏开发者平台
- 腾讯课堂
- 微信开放平台
- 腾讯实时音视频
- 腾讯即时通信IM
- 微信公众平台技术文档
- 白鹭引擎官网
- 白鹭引擎开放平台
- 白鹭引擎开发文档
- FairyGUI编辑器
- PureMVC-TypeScript
- 讯飞开放平台
- 亲加通讯云
- Cygwin
- Mono开发者联盟
- Scut游戏服务器引擎
- KBEngine游戏服务器引擎
- Photon游戏服务器引擎
- 码云
- SharpSvn
- 腾讯bugly
- 4399原创平台
- 开源中国
- Firebase
- Firebase-Admob-Unity
- google-services-unity
- Firebase SDK for Unity
- Google-Firebase-SDK
- AppsFlyer SDK
- android-repository
- CQASO
- Facebook开发者平台
- gradle下载
- GradleBuildTool下载
- Android Developers
- Google中国开发者
- AndroidDevTools
- Android社区
- Android开发工具
- Google Play Games Services
- Google商店
- Google APIs for Android
- 金钱豹VPN
- TouchSense SDK
- MakeHuman
- Online RSA Key Converter
- Windows UWP应用
- Visual Studio For Unity
- Open CASCADE Technology
- 慕课网
- 阿里云服务器ECS
- 在线免费文字转语音系统
- AI Studio
- 网云穿
- 百度网盘开放平台
- 迅捷画图
- 菜鸟工具
- [CSDN] 程序员研修院
- 华为人脸识别
- 百度AR导航导览SDK
- 海康威视官网
- 海康开放平台
- 海康SDK下载
- git download
交流QQ群
-
Flash游戏设计: 86184192
Unity游戏设计: 171855449
游戏设计订阅号