原創文章如需轉載請注明:轉載自 脫莫柔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次。