起因:在使用UGUI的时候,在网上找了好久也没找到合适的图文混排组件,但是有看到一篇文章给了我思路。甩个链接:点击打开链接
思路:是通过继承Text组件,然后在Mesh生成的函数里做手脚。但是那篇文章好像有些bug,而且UGUI的代码也是开源的,所以就自己动手了。就是把文本解析之后,把表情所在位置的文本的mesh给隐藏,最后在相应的位置放入我们的图片表情。
开始:新建一个类继承于Text,我命名为ImageText
首先是解析文本,这个很简单,用#表情id#这样的字符串来代表相应的表情图片,然后再用正则匹配出来。如下:
private static readonly Regex m_emojiTagRegex = new Regex(@"#(\d+)#", RegexOptions.Singleline);
接下来就是在重写的OnPopulateMesh函数里用正则提取字符,记录对应的下标,把表情字符替换为中文的“一”字,因为中文字是固定宽度,适合用来放固定宽度的表情图片。
然后调用父类的OnPopulateMesh函数生成相应的mesh,把表情所在字符mesh的顶点uv设置为0,这样就让上面被替换后的“一”字消失了。代码如下:
public struct EmojiInfo { public string name; public Vector3 position; public Vector2 size; } private Transform m_EmojiImage; private List<EmojiInfo> m_EmojiInfos; private bool m_isEmojiDirty = false; protected override void OnPopulateMesh(VertexHelper toFill) { List<int> emojiIndexes = new List<int> (); List<string> emojiNames = new List<string> (); //将表情所在字符提取出来,并用一个“一”字替换, //用什么替换都没关系(不过要用中文,宽度才能跟表情图片的宽度一致),因为接下来会其消失 int offset = 0;//字符被替换,长度会变化,这个变量用来调整变化 foreach (Match match in m_emojiTagRegex.Matches(m_Text)) { emojiIndexes.Add (match.Index - offset); emojiNames.Add (match.Groups [1].Value); offset += match.Length - 1; } string transformedText = m_emojiTagRegex.Replace (m_Text, "一"); string originText = m_Text; m_Text = transformedText; base.OnPopulateMesh(toFill); m_Text = originText; List<UIVertex> listUIVertex = new List<UIVertex>(); toFill.GetUIVertexStream(listUIVertex); //将表情所在字符的顶点UV设置0,让其原本内容消失,后面再用图片表情覆盖 if (m_EmojiInfos == null) m_EmojiInfos = new List<EmojiInfo>(); m_EmojiInfos.Clear(); for (int i = 0; i < emojiIndexes.Count; i++) { int textIdx = emojiIndexes[i]; string emojiName = emojiNames[i]; int startIdx = textIdx * 6; if (startIdx >= listUIVertex.Count) break; UIVertex newV0 = listUIVertex [startIdx]; UIVertex newV1 = listUIVertex [startIdx + 1]; UIVertex newV2 = listUIVertex [startIdx + 2]; UIVertex newV3 = listUIVertex [startIdx + 4]; newV0.uv0 = Vector3.zero; newV1.uv0 = Vector3.zero; newV2.uv0 = Vector3.zero; newV3.uv0 = Vector3.zero; toFill.SetUIVertex (newV0, 4 * textIdx); toFill.SetUIVertex (newV1, 4 * textIdx + 1); toFill.SetUIVertex (newV2, 4 * textIdx + 2); toFill.SetUIVertex (newV3, 4 * textIdx + 3); EmojiInfo info = new EmojiInfo (); info.name = emojiName; info.position = (newV0.position + newV1.position + newV2.position + newV3.position) / 4f; info.size = new Vector2 (this.fontSize, this.fontSize); m_EmojiInfos.Add (info); } m_isEmojiDirty = true; }
为什么要在这些函数里而不在上面直接放进去呢,这是因为如果增加图片会改变他原来的结构,就会被自动调用SetAllDirty,那又会立刻刷新,重新生成面片,一直死循环,所以unity是禁止我们在OnPopulateMesh函数里刷新的。那我们只好在下一次的Update里进行表情图片的创建了,为了防止每次Update都会刷新,我们引进一个m_isEmojiDirty变量,每次设置文本的时候才会刷新。代码如下:
void LateUpdate() { if (m_isEmojiDirty) { m_isEmojiDirty = false; if (m_EmojiInfos != null) { if (m_EmojiImage == null) { m_EmojiImage = this.transform.Find ("OriginImage"); m_EmojiImage.gameObject.SetActive (false); } foreach (EmojiImage child in this.transform.GetComponentsInChildren<EmojiImage>()) { if (child.name != "OriginImage") GameObject.DestroyImmediate (child.gameObject); } foreach (EmojiInfo info in m_EmojiInfos) { Transform emoji = Instantiate(m_EmojiImage); emoji.SetParent (this.transform); emoji.localScale = Vector3.one; emoji.localPosition = info.position; emoji.name = EmojiReanNames [int.Parse (info.name) - 1]; emoji.gameObject.SetActive (true); GameObject go = Resources.Load<GameObject>(imagePath + EmojiReanNames[int.Parse(info.name) - 1]); emoji.GetComponent<RawImage> ().texture = go.GetComponent<SpriteRenderer>().sprite.texture; (emoji as RectTransform).sizeDelta = new Vector2 (this.fontSize, this.fontSize); } m_EmojiInfos.Clear (); } } }
还有上面用到的一个类EmojiImage的代码:
public class EmojiImage : RawImage { protected override void OnBeforeTransformParentChanged() { // print("OnBeforeTransformParentChanged"); } protected override void OnTransformParentChanged() { // print ("OnTransformParentChanged"); } }
最后:第一篇博客,说得乱七八糟,随便喷吧,不对的我有空就改一下。估计没啥人看所以也只是为了记录一下。