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();

        // 动态字体大小赋值(!勿删!)
        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;
    
    /// 
    /// 表情偏移
    /// 
    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 eList = new List();
    List faceSprites = new List();

    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("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次。

你可能感兴趣的:(Unity3D)