1. 程式人生 > >Unity3D 基於UGUI的圖文混排元件

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的原因記不太清了,大概是防止新增的時候會讓父物件也就是我們的文字物件又進行重新整理。

最後:第一篇部落格,說得亂七八糟,隨便噴吧,不對的我有空就改一下。估計沒啥人看所以也只是為了記錄一下。