Unity表情聊天(NGUI圖文混排)

圖文混排

從字面意思來理解:就是圖片和文字混合在一起。不知道這樣的的定義是否正確,起碼我是這樣理解的,在遊戲開發過程中,如果單單從業務邏輯去看的話,圖文混排算是比較複雜的。個人感覺也是必須會的技能。

原理

我們期待在文本的合適地方插入我們需要顯示的表情。所以我們要取插入表情的位置,但是一條聊天的信息是一段文本的字符串,是一個整體,每一個字符都不是單個對象,所以要在文本字符串中取合適的位置就不能用平時的方法(ps:UILabe的本質是一張面片),好在NGUI底層給我們提供了頂點信息的集合。

	/// <summary>
	/// Widget's vertices (before they get transformed).
	/// </summary>

	public BetterList<Vector3> verts = new BetterList<Vector3>();

現在我們已經找到突破口,verts保存的是位置信息,那麼只有我們算出表情在第幾個字後面就可以取得位置信息了。具體步驟如下:

  • 1 定義表情的數據格式,
    在這裏插入圖片描述
  • 2 在寫入字符串時候,加上特效的標記,然後用正則表達去匹配和替換字符。
  • 3 遍歷整個文本,把之前標記的位置記錄下來。並且使用空格去替換標記的內容。
  • 4 創建表情對象,把第三步取到的位置信息賦值給剛剛創建的表情對象

核心代碼

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;

public class UIEmojiWrapper
{
    private bool hasAtlas = false;
    public UIAtlas mAtlas;
    private GameObject emojiPrefab;
    private Dictionary<string, string> emojiName = new Dictionary<string, string>();
    private List<Expression> listCfgData = new List<Expression>();
    private Queue<GameObject> freeSprite = new Queue<GameObject>();

    private List<GameObject> usedSprite = new List<GameObject>();

    private Vector3 OutOffScreen = new Vector3(10000f, 10000f, 10000f);

    private static UIEmojiWrapper sInstance;
    public static UIEmojiWrapper Instance
    {
        get
        {
            if (sInstance == null)
            {
                sInstance = new UIEmojiWrapper();
            }

            return sInstance;
        }
    }

    public void Init(UIAtlas atlas)
    {
        if (!hasAtlas)
        {
            if (atlas == null)
            {
                Debug.LogError("[UIEmojiWrapper Atlas is null]");
                return;
            }
            mAtlas = atlas;
            //預分配
            AddEmojiName();
            emojiPrefab = new GameObject();
            emojiPrefab.layer = 8;
            hasAtlas = true;
        }

    }

    public GameObject EmojiPrefab
    {
        get
        {
            if (emojiPrefab == null)
            {
                emojiPrefab = new GameObject();
                emojiPrefab.layer = 8;
            }
            return emojiPrefab;
        }
    }

    //表情字典
    public void AddEmojiName()
    {
        List<Expression> listCfgEmo = SetlistExpression();
        if (listCfgEmo == null)
        {
            return;
        }
        listCfgData.Clear();
        listCfgData = listCfgEmo;
        for (int i = 0; i < listCfgEmo.Count; i++)
        {
            int m_key = (int)listCfgEmo[i].key;
            string name = listCfgEmo[i].value;
            string sprKey = GetConvertedInt32(m_key.ToString());
            emojiName.Add(sprKey, name);
        }
    }

    public string GetConvertedString(string inputString)
    {
        string[] converted = inputString.Split('-');
        for (int j = 0; j < converted.Length; j++)
        {
            int value = Convert.ToInt32(converted[j], 16);
            converted[j] = char.ConvertFromUtf32(value);
        }
        return string.Join(string.Empty, converted);
    }

    public string GetConvertedInt32(string inputString)
    {
        int value = int.Parse(inputString);
        string[] converted = new string[1];
        converted[0] = char.ConvertFromUtf32(value);
        return string.Join(string.Empty, converted);
    }

    //從對象池取對象
    public string GetEmoji(string encode)
    {
        string em;
        if (emojiName.TryGetValue(encode, out em))
        {
            return em;
        }
        return null;
    }

    public bool HasEmoji(string key)
    {
        // 120000 120001 120002
        return emojiName.ContainsKey(key);
    }

    public List<Expression> GetListCfgData()
    {
        return listCfgData;
    }

    // 取表情數據Key
    public int GetEmojiKey(string _value)
    {
        if (listCfgData.Count > 0)
        {
            for (int i = 0; i < listCfgData.Count; i++)
            {
                if (_value == listCfgData[i].name)
                {
                    return (int)listCfgData[i].key;
                }
            }
        }
        return -1;
    }

    public void OnPostFill(UIWidget widget, int bufferOffset, BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols)
    {
        if (widget != null)
        {
            if (!widget.isVisible)
            {
                UISprite spt = widget as UISprite;
                if (spt != null)
                {
                    PushEmoji(spt.gameObject);
                }
            }
        }
    }

    public GameObject PopEmoji()
    {
        if (freeSprite.Count <= 0)
        {
            if (emojiPrefab == null)
                return null;

            GameObject tran = GameObject.Instantiate(emojiPrefab) as GameObject;
            freeSprite.Enqueue(tran);
        }

        GameObject sptRet = freeSprite.Dequeue();

        if (sptRet != null)
        {
            usedSprite.Add(sptRet);
        }
        return sptRet;
    }


    public void PushEmoji(GameObject spt)
    {
        //spt.transform.localPosition = OutOffScreen;
        // spt.transform.parent = null;
        freeSprite.Enqueue(spt);
    }

    public void PushEmoji(ref List<GameObject> list)
    {
        for (int i = 0, cnt = list.Count; i < cnt; i++)
        {
            PushEmoji(list[i]);
        }

        list.Clear();
    }

    /// <summary>
    /// 字符串替換方法
    /// </summary>
    /// <param name="myStr">需要替換的字符串</param>
    /// <param name="displaceA">需要替換的字符</param>
    /// <param name="displaceB">將替換爲</param>
    /// <returns></returns>
    public string Displace(string myStr, string displaceA, string displaceB)
    {
        string[] strArrayA = Regex.Split(myStr, displaceA);
        for (int i = 0; i < strArrayA.Length - 1; i++)
        {
            strArrayA[i] += displaceB;
        }
        string returnStr = "";
        foreach (string var in strArrayA)
        {
            returnStr += var;
        }
        return returnStr;
    }

    //建議用json或者其他的數據格式
    public static List<Expression> SetlistExpression()
    {
        List<Expression> data = new List<Expression>();
        data.Add(new Expression(120000, "ziya", 1, "齜牙"));
        data.Add(new Expression(120001, "zhouma", 1, "咒罵"));
        data.Add(new Expression(120002, "youxian", 1, "悠閒"));
        data.Add(new Expression(120003, "weixiao", 1, "微笑"));
        data.Add(new Expression(120004, "qiaoda", 1, "敲打"));
        data.Add(new Expression(120005, "nanguo", 1, "難過"));
        data.Add(new Expression(120006, "daxiao", 1, "大笑"));
        data.Add(new Expression(120008, "deng", 1, "瞪"));
        data.Add(new Expression(120009, "dese", 1, "得瑟"));
        data.Add(new Expression(120010, "dese", 1, "得意"));
        data.Add(new Expression(120011, "lianhong", 1, "臉紅"));
        data.Add(new Expression(120012, "keai", 1, "可愛"));
        data.Add(new Expression(120013, "han", 1, "汗"));
        data.Add(new Expression(120014, "ganga", 1, "尷尬"));
        data.Add(new Expression(120015, "se", 1, "色"));
        data.Add(new Expression(120016, "kun", 1, "困"));
        data.Add(new Expression(120017, "ku", 1, "酷"));
        data.Add(new Expression(120018, "baiyan", 1, "白眼"));
        data.Add(new Expression(120019, "baoya", 1, "齙牙"));
        data.Add(new Expression(120020, "beida", 1, "被打"));

        return data;
    }

}

public class Expression
{
    public uint key;                // 16進制編號
    public string value;                // 前綴
    public uint sin;                // 是否序列幀
    public string name;				// 名稱

    public Expression(uint key, string value, uint sin, string name)
    {
        this.key = key;
        this.value = value;
        this.sin = sin;
        this.name = name;
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;

public class UIEmojiLabel : UILabel
{
    private char emSpace = '\u2001';
    private List<GameObject> mEmojiList = new List<GameObject>();

    public UIAtlas emAtlas = null;

    protected override void Awake()
    {
        base.Awake();
        UIEmojiWrapper.Instance.Init(emAtlas);
    }

    protected override void OnStart()
    {
        base.OnStart();
     
        //表情統一管理器,只初始化一次
    }

    //自動轉碼
    public override void OnFill(BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols)
    {
        base.OnFill(verts, uvs, cols);
       // StartCoroutine(SetUITextThatHasEmoji(text));
    }

    public void ShowEmojiText(string strText)
    {
        //strText = "圖形測試 (ziya) 呵呵 (youxian)"
        //找到表情特殊標記()
        Regex reg = new Regex(@"(?is)(?<=\()[^\)]+(?=\))");
        MatchCollection mc = reg.Matches(strText);
        if (mc != null)
        {
            for (int i = 0; i < mc.Count; i++)
            {
                string oldStr = string.Format("({0})", mc[i].Value);   //舊字符
                int key = UIEmojiWrapper.Instance.GetEmojiKey(mc[i].Value);
                if (key > 0)
                {
                    string newStr = UIEmojiWrapper.Instance.GetConvertedInt32(key.ToString()); //薪字符
                    strText = strText.Replace(oldStr, newStr);
                }
            }
        }
        text = strText;
        StartCoroutine(SetUITextThatHasEmoji(strText));
    }

    private struct PosStringTuple
    {
        public int pos;
        public string emoji;

        public PosStringTuple(int p, string s)
        {
            this.pos = p;
            this.emoji = s;
        }
    }

    private void DestroyEmojiSpr()
    {
        if (this.mEmojiList.Count > 0)
        {
            for (int i = 0; i < this.mEmojiList.Count; i++)
            {
                Destroy(this.mEmojiList[i]);
            }
            this.mEmojiList.Clear();
        }
    }

     public IEnumerator SetUITextThatHasEmoji(string inputString)
    {
        inputString = inputString.Replace("  ", "&&");
        inputString = inputString.Replace(" ", "&");

        List<PosStringTuple> emojiReplacements = new List<PosStringTuple>();
        StringBuilder sb = new StringBuilder();

        //先回收
        DestroyEmojiSpr();
        int i = 0;
        while (i < inputString.Length)
        {
            string singleChar = inputString.Substring(i, 1);
            string doubleChar = "";
            string fourChar = "";

            if (i < (inputString.Length - 1))
            {
                doubleChar = inputString.Substring(i, 2);
            }

            if (i < (inputString.Length - 3))
            {
                fourChar = inputString.Substring(i, 4);
            }
            if (UIEmojiWrapper.Instance.HasEmoji(fourChar))
            {
                // Check 64 bit emojis first
                sb.Append(emSpace);
                int blankSpaceConut = GetBlankSpaceConut(sb);
                emojiReplacements.Add(new PosStringTuple(sb.Length - 1, fourChar));
                i += 4;
            }
            else if (UIEmojiWrapper.Instance.HasEmoji(doubleChar))
            {
                sb.Append(emSpace);
                emojiReplacements.Add(new PosStringTuple(sb.Length - 1, doubleChar));
                i += 2;
            }
            else if (UIEmojiWrapper.Instance.HasEmoji(singleChar))
            {
                sb.Append(emSpace);
                emojiReplacements.Add(new PosStringTuple(sb.Length - 1, singleChar));
                i++;
            }
            else
            {
                sb.Append(inputString[i]);
                i++;
            }
        }
        this.text = sb.ToString();
        yield return null;
        for (int j = 0; j < emojiReplacements.Count; j++)
        {
            int emojiIndex = emojiReplacements[j].pos;
            //表情替換,計算位置,大小
            GameObject go = GameObject.Instantiate(UIEmojiWrapper.Instance.EmojiPrefab);
            mEmojiList.Add(go);
            if (go != null)
            {
                UISprite spt = go.AddComponent<UISprite>();
                string emoji = UIEmojiWrapper.Instance.GetEmoji(emojiReplacements[j].emoji);
                if (!string.IsNullOrEmpty(emoji))
                {
                    spt.atlas = UIEmojiWrapper.Instance.mAtlas;
                    spt.name = emoji;
                    spt.spriteName = emoji + "_1";

                    spt.width = this.ChatPrintedSize;
                    spt.height = this.ChatPrintedSize;
                    spt.depth = this.depth + 10;
                    spt.transform.parent = this.transform;
                    spt.transform.localScale = Vector3.one;
                    Vector3 pos = new Vector3();
                    try
                    {
                        pos = new Vector3(this.geometry.verts[emojiIndex * 4].x + spt.width / 2, (this.geometry.verts[emojiIndex * 4].y + spt.height / 2) - 8);
                        spt.transform.localPosition = pos;
                        UISpriteAnimation sprAni = go.AddComponent<UISpriteAnimation>();
                        sprAni.namePrefix = emoji + "_";
                        sprAni.framesPerSecond = 3;
                        sprAni.Snap = false;
                        sprAni.Play();
                        isVertsBuff = false;
                    }
                    catch
                    {
                        Debug.LogError("geometry.verts == null");
                        isVertsBuff = true;
                        ShowEmojiText(oldText);
                    }
                }
            }
        }
        if (!isVertsBuff)
        {
            this.text = text.Replace("&&", "  ");
            this.text = text.Replace("&", " ");
        }
    }
}

#if !UNITY_3_5 && !UNITY_FLASH
#define DYNAMIC_FONT
#endif

using UnityEngine;
using UnityEditor;

/// <summary>
/// Inspector class used to edit UILabels.
/// </summary>

[CanEditMultipleObjects]
#if UNITY_3_5
[CustomEditor(typeof(UILabel))]
#else
[CustomEditor(typeof(UILabel), true)]
#endif
public class UIEmojiLabelInspector : UILabelInspector
{


    /// <summary>
    /// Draw the label's properties.
    /// </summary>

    protected override bool ShouldDrawProperties()
    {
        bool isValid = base.ShouldDrawProperties();

        EditorGUI.BeginDisabledGroup(!isValid);
        NGUIEditorTools.DrawProperty("Atlas", serializedObject, "emAtlas");//表情所在的圖集Atlas
        EditorGUI.EndDisabledGroup();
        return isValid;
    }
}

主要事項

1 上面的代碼改了一下部分NGUI的接口,直接拷貝會報錯,你可以自己手動加,都是很簡單的接口。我也會上傳一份dome
2 在Lua層調C#的時候,有機會出現第一次會報verts爲空,我還沒有找到原因,歡迎大神指點,我現在的解決方法是延遲執行。
如果大佬們有好的方法,也求推薦。
3 看看 最終效果是帶動態的哦!!
在這裏插入圖片描述
表情聊天— 圖文混排下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章