天天看點

Unity3D NGUI圖文混排聊天表情

原創文章如需轉載請注明:轉載自 脫莫柔Unity3D學習之旅 Unity3D引擎技術交流QQ群:【119706192】本文連結位址: Unity3D NGUI圖文混排的聊天表情

曆時1年開發的mmorpg遊戲終于進入收尾階段,最後還差些需要提升表現力的小功能沒做。比如圖文混排聊天表情。

網上找了插件對接半天發現插件各種bug,修複實在困難,焦躁之下最終決定自己寫!

最為一個cv戰士,生存的原則就是能ctrl+c ctrl+v 實作的功能,堅決不自己寫,這麼普及的功能,網上肯定有人寫。

然後我就找到了蠻牛的師兄弟寫的這篇文章http://www.manew.com/blog-3649-2595.html

推算不出來他藏起來的NGUIText.CalculatePrintedSize()功能是做了什麼實作,我傳回的寬度結果居然是(0,高度),原文展示的代碼部分思路我也看不明白。

沒辦法,還是自己動手吧。于是自己做了一套擷取每個字元累加寬度的方式實作了一版。核心代碼如下:

void replaceFace(ref string text)
    {
        eList.Clear();

        float lineW = 0;
        float low = 0;
        float fontSize = mLabel.fontSize;

        for (int i = 0; i < text.Length; i++)
        {
            float cWidth = 0;
            int isface = -1;

            Debug.Log(text[i]);
            if (text[i] == '#' && i + 4 < text.Length && text[i + 2] == '_')
            {
                cWidth = faceWidth;
                isface = i;
            }
            else
            {
                int charW;
                if (!GetCharWidth(text[i], out charW))
                {
                    text = text.Remove(isface, faceNamelength);
                    text = text.Insert(isface, spaceChar.ToString());
                }
                cWidth += charW;
            }

            StringBuilder spaceTemp = new StringBuilder();

            Debug.Log("預判" + (lineW + cWidth) + ">" + mLabel.width);
            if (lineW + cWidth > mLabel.width)
            {
                lineW = cWidth;
                low++;
                Debug.Log("換行");

                if (isface > 0)
                {
                    spaceTemp.Append("\n") ;
                    Debug.Log("圖檔換行補換行符");
                    i += 1;
                }
            }
            else
            {
                lineW += cWidth;
            }

            if (isface > 0)
            {
                FacePostion ePos = new FacePostion();
                ePos.facePackID = text[isface + 1];
                ePos.faceID = text.Substring(isface + 3, 2);
                ePos.posX = lineW - faceWidth + m_offsetX;
                ePos.posY = low * -faceWidth + m_offsetY;
                eList.Add(ePos);
                Debug.Log("圖:" + ePos.faceID);

                for (int j = 0; j < spaceCount; j++)
                    spaceTemp.Append(spaceChar);
                text = text.Remove(isface, faceNamelength);
                text = text.Insert(isface, spaceTemp.ToString());
                i += faceNamelength;
            }
        }

        DrawFace();
    }
           

此方式,終于實作了基本的中文字+圖示的混合方式。

但是!

連續的數字、字母寬度,顯示的寬度居然會縮減(平均沒2個字元減少2像素),經細研究發現,NGUI果然對這些字元做了縮減間距的處理。

不開心!

憑什麼UILabel的widget就能擷取正常寬度呢,深入探究NGUI源碼發現,根源就在以上博文中提起的NGUIText.CalculatePrintedSize()方法。寬度擷取失敗的原因居然是我沒對字型指派。我服你!

回頭又重寫,嗯,秉承大神代碼我看不懂的原則,最終我實作了完美解決方案。

再次分享在網上以備其它師兄弟備用。看完覺着

public class UILabelTest : MonoBehaviour 
{
    UILabel mLabel;
    UIAtlas faceAtlas;

    void Awake()
    {
        mLabel = GetComponent<UILabel>();

        // 動态字型大小指派(!勿删!)
        NGUIText.dynamicFont = mLabel.trueTypeFont;//傻逼要先指派
        int charW = (int)(NGUIText.CalculatePrintedSize(spaceChar2.ToString(), true).x);

        spaceCount = faceWH / charW;
        if (faceWH % charW != 0)
           spaceCount += 1;
    }


    #region Debug
    void Start ()
 	{
        string str = mLabel.text;
        replaceFace4(ref str);
        mLabel.text = str;
	}
    #endregion 
 
    const int faceNamelength = 5;
    const char spaceChar2 = ',';
    int faceWH = 22;
    int spaceCount = 6;
    
    /// <summary>
    /// 表情偏移
    /// </summary>
    const float m_offsetX = 1;
    const float m_offsetY = 8;

    void replaceFace4(ref string text)
    {
        if (!string.IsNullOrEmpty(text) && ContainsFace(text))
        {
            mLabel.spacingY = faceWH - mLabel.fontSize;
            NGUIText.dynamicFont = mLabel.trueTypeFont;//傻逼要先指派

            eList.Clear();
            int maxWidth = mLabel.width;

            float lineW = 0;
            float lowCount = 0;

            for (int i = 0; i < text.Length; i++)
            {
                if (isFaceLabel(text, i))
                {
                    StringBuilder spaceTemp = new StringBuilder();
                    int isface = i;

                    Vector2 textV = NGUIText.CalculatePrintedSize(text.Substring(0, i),maxWidth, mLabel.fontSize);
                    if (textV.x + faceWH > maxWidth)
                    {
                        //直接下一行
                        spaceTemp.Append("\n");
                        i += 1;
                        lineW = 0;
                        lowCount = textV.y + 1;
                    }
                    else
                    {
                        //不換不換就不換
                        lineW = textV.x;
                        lowCount = textV.y;
                    }

                    FacePostion ePos = new FacePostion();
                    ePos.facePackID = text[isface + 1];
                    ePos.faceID = text.Substring(isface + 3, 2);
                    ePos.posX = lineW + m_offsetX;
                    ePos.posY = -lowCount * faceWH + m_offsetY;
                    eList.Add(ePos);

                    spaceTemp.Append("[ffffff00]");
                    for (int j = 0; j < spaceCount; j++)
                    {
                            spaceTemp.Append(spaceChar2);
                    }
                    spaceTemp.Append("[-]");

                    text = text.Remove(isface, faceNamelength);
                    text = text.Insert(isface, spaceTemp.ToString());
                    i += faceNamelength;
                }
            }
            DrawFace();
        }
    }

    #region 畫表情
    List<FacePostion> eList = new List<FacePostion>();
    List<UISprite> faceSprites = new List<UISprite>();

    bool ContainsFace(string text)
    {
        return text.Contains("#0_");
    }

    bool isFaceLabel(string text,int index)
    {
        return (text[index] == '#' && index + 4 < text.Length && text[index + 2] == '_');
    }

    void DrawFace()
    {
        for (int i = 0; i < eList.Count; i++)
        {
            UISprite sp = GetSprite(i);
            sp.gameObject.SetActive(true);
            sp.spriteName = "#" + eList[i].facePackID + "_" + eList[i].faceID;
            sp.transform.localPosition = new Vector3(eList[i].posX, eList[i].posY);
        }

        for (int i = eList.Count; i < faceSprites.Count; i++)
        {
            faceSprites[i].gameObject.SetActive(false);
        }
    }

    UISprite GetSprite(int index)
    {
        if (faceSprites.Count >= index)
        {
            if (faceAtlas == null)
                faceAtlas = Resources.Load<UIAtlas>("AtlasImotion/Imotions");

            UISprite newsp = NGUITools.AddSprite(this.gameObject, faceAtlas, "", mLabel.depth + 100);
            newsp.MakePixelPerfect();
            newsp.pivot = UIWidget.Pivot.BottomLeft;
            faceSprites.Add(newsp);
            return newsp;
        }
        else
            return faceSprites[index];
    }

    class FacePostion
    {
        public float posX;
        public float posY;
        public char facePackID;
        public string faceID;
    }
    #endregion 
}
           

然後就是NGUIText.CalculatePrintedSize的核心代碼,加個重載實作。

public static Vector2 CalculatePrintedSize(string text,int maxW,int fontSize)
    {
        Vector2 v = new Vector2(0, 1);

        if (!string.IsNullOrEmpty(text))
        {
            if (encoding) text = StripSymbols(text);

            Prepare(text);

            float x = 0f, y = 0f, maxX = 0f;
            int textLength = text.Length, ch = 0, prev = 0;

            regionWidth = maxW;
            finalSize = fontSize;
            for (int i = 0; i < textLength; ++i)
            {
                ch = text[i];

                if (ch == '\n')
                {
                    if (x > maxX) maxX = x;
                    x = 0f;
                    y += 1;
                    continue;
                }

                if (ch < ' ') continue;

                {
                    float w = GetGlyphWidth(ch, prev);
                    if (w != 0f)
                    {
                        w += finalSpacingX;

                        if (Mathf.RoundToInt(x + w) > regionWidth)
                        {
                            if (x > maxX) maxX = x - finalSpacingX;
                            x = w;
                            y += 1;
                        }
                        else x += w;

                        prev = ch;
                    }
                }
            }
            
            v.x = x;
            v.y = y + 1;
        }
        return v;
    }
           

需要注意的地方:

1.uilabel.text必須先指派,然後再替換才能正确計算字型大小和行大小,是以有表情符号時要指派2次。

繼續閱讀