Unity3D 基于UGUI的图文混排组件

起因:在使用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;
    }

接下来我们要在LateUpdate或者Update函数里把我们的表情图片放进去了。

为什么要在这些函数里而不在上面直接放进去呢,这是因为如果增加图片会改变他原来的结构,就会被自动调用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 ();
            }
        }

    }

我添加表情的方式是通过在文本下面放入创建的图片对象,有多少个表情就增加多少个,那是因为我的图片表情是散开的,如果是把全部打包到一张纹理集里,那只要添加一个图片对象,读取相应的uv坐标,再画上去就行,这里就不弄了。

还有上面用到的一个类EmojiImage的代码:

public class EmojiImage : RawImage
{

    protected override void OnBeforeTransformParentChanged()
    {
        // print("OnBeforeTransformParentChanged");
    }

    protected override void OnTransformParentChanged()
    {
        // print ("OnTransformParentChanged");
    }

}

这里要继承RawImage的原因记不太清了,大概是防止添加的时候会让父对象也就是我们的文本对象又进行刷新。



最后:第一篇博客,说得乱七八糟,随便喷吧,不对的我有空就改一下。估计没啥人看所以也只是为了记录一下。

你可能感兴趣的:(unity3d,表情,UGUI,图片混排)