您好,登錄后才能下訂單哦!
本身ngui是自帶圖文混排的,這個可以在ngui的Example里找到。但是為什么不能用網上已經說得很清楚,比如雨松momo的http://www.xuanyusong.com/archives/2908
最重要的一點就是我們肯定不會選擇一個完整的中文字庫,動態字體無辦法使用ngui的圖文混排
所以還是需要自己寫一個圖文混排。
首先圖文混排的基本邏輯是:
1.定義固定字符串格式作為圖片信息。
2.找到文字中的圖片信息的字符串提取并換成空格
3.根據圖片信息生成uisprite,并放在適當的position
4.輸出文字和圖片
圖文混排有幾個重點是必須解決的:
1.找到圖片應該放的position
2.如果圖片在文字末尾判斷是否放得下是否會被遮擋,是的話要把圖片放到下一行的開頭
3.按照圖片的高度判斷這一行的開頭需要多少個換行符
4.如果一排有多個圖片且尺寸不一,這一排的圖片需要統一高度,不然會出現下面的情況
(如果圖片格式統一的話3,4倒是可以用湊合的辦法省略,但是我們想做一個適用各種大小圖片,每行可能有幾張圖片,適合各種情況的圖文混排)
接下來就是實現。
我的思路是:
有一大段文字且里面有許多圖片信息的前提下
1.首先把所有文字輸入都某個函數,識別出第一個圖片信息的字符串,把這個包含圖片信息的字符串以及前面的文字裁剪下來,和裁剪以后的文字形成兩部分。
2.把裁剪的前面部分(包含圖片信息)分析出圖片信息,各種計算,最后得到圖片的position,生成gameObject并擺放好。保存各種信息。圖片部分用空格留出位置,形成新的字符串,和裁剪的第二部分的文字組合成新文字。
3.輸入到1里的那個函數。遞歸。
4.最終一次過輸出所有文字。
代碼直接寫到UILabel.cs里,也可以寫一個UIEmotionLabel.cs繼承UILabel.cs。
接下來看代碼:(最后會貼出所有代碼)
/// <summary>
/// label中有表情在顯示前調用進行轉換
/// </summary>
public void ShowEmotionLabel()
{
m_newEmotionText = "";
string originalText = MyLabel.text;
//遞歸找表情并生成文字
CutAndShowEmotionLabel(originalText);
//輸出文字
MyLabel.text = m_newEmotionText;
MyLabel.UpdateNGUIText();
//每一行的表情重新排序對其
SortAllSprite();
}
這個是唯一外部調用接口,當要顯示圖片的時候調用這個函數。
通過注釋就可以看懂里面的邏輯,最后的SortAllSprite()最后會再解釋一下。
所以先看CutAndShowEmotionLabel(string str)這個函數。
void CutAndShowEmotionLabel(string str)
{
EmotionData emoData = GetEmotionData(str);//解析str中的第一個表情字符串
if (emoData != null)
{
m_spriteList.Add(emoData);
//把str按第一個表情字符串的最后一個字母分成兩部分
string trimString = str.Substring(0, emoData.end_index);
string trimLeftString = str.Substring(emoData.end_index);
//生成表情和表情前面的文字部分
GenEmotionLabel(emoData, trimString);
m_newEmotionText = m_newEmotionText + trimLeftString;
//遞歸繼續找表情
CutAndShowEmotionLabel(m_newEmotionText);
}
else
{
//找不到表情返回,最后確定文字輸出
m_newEmotionText =str;
return;
}
}
第一行就是用自己的方法解析。
上面的邏輯就是按思路寫的
唯一有點不一樣的就是多了一個m_spriteList.Add(emoData);
因為最后需要把所有圖片按每行輸出時可能要對其高度,所以都要先保存下來。
這里面最重要的是GenEmotionLabel(emoData, trimString);這個函數
void GenEmotionLabel(EmotionData emoData, string tramString)
{
//生成gameobject
GameObject go = CreateEmotionSprite(emoData);
float spriteWidth = NGUIMath.CalculateRelativeWidgetBounds(gameobject.transform, go.transform, true).size.x / go.transform.localScale.x;
float spriteHeight = NGUIMath.CalculateRelativeWidgetBounds(gameobject.transform, go.transform, true).size.y / go.transform.localScale.y;
//計算出圖片的位置,判斷文字的轉換和空格
Vector3 position = CalcuEmotionSpritePosition(tramString, emoData.start_index, spriteWidth, spriteHeight);
//擺放圖片位置
PlaceEmotionSprite(go, position);
m_spriteList[m_spriteList.Count - 1].go = go;
}
CreateEmotionSprite()就是根據分析出來的圖片信息實例化一個GameObject,但是這時候position位置還是不能確定。
在算出圖片的寬高后。把這些數據都輸入到CalcuEmotionSpritePosition();這個函數里算出最后的position。
獲得position數據在PlaceEmotionSprite()函數正確的擺放
所以這里最關鍵的還是CalcuEmotionSpritePosition()。
Vector3 CalcuEmotionSpritePosition(string str, int startIndex, float spriteWidth, float spriteHeight)
{
Vector3 position = GenBlankString(str, startIndex, spriteWidth, spriteHeight);
return position;
}
這里看GenBlankString()函數。
Vector3 GenBlankString(string str, int startIndex, float spriteWidth, float spriteHeight)
{
int finalIndex = startIndex;
BetterList<Vector3> tempVerts = new BetterList<Vector3>();
BetterList<int> tempIndices = new BetterList<int>();
//1.把圖片信息換成空格
string emontionText = str.Substring(startIndex);
int blankNeedCount = CaculateBlankNeed(spriteWidth);
str = str.Replace(emontionText, GenBlank(blankNeedCount));
//把換好的文字放回label再計算sprite應該放的坐標,
UpdateCharacterPosition(str,out tempVerts,out tempIndices);
//2.如果在label末尾且圖片放不下,判斷是否換行
bool needWrap = NeedWrap(tempVerts, tempIndices, startIndex, startIndex + blankNeedCount);
if (needWrap)
{
str = str.Insert(startIndex, "\n");
finalIndex +=1;
//重新計算當前所有字符的位置
UpdateCharacterPosition(str, out tempVerts, out tempIndices);
}
//3.按圖片的高,生成回車(換行)
int returnCount = GenCarriageReturn(tempVerts, tempIndices, ref str, finalIndex, spriteHeight, needWrap);
finalIndex += returnCount;
//4.重新賦值要輸出的str
m_newEmotionText = str;
//重新計算當前所有字符的位置
UpdateCharacterPosition(str, out tempVerts, out tempIndices);
//保存行數,最后重新排放每行的圖片使用
m_spriteList[m_spriteList.Count - 1].line_index = CalcuLineIndex(tempVerts, tempIndices, startIndex) - lastScale;
//最終計算圖片該放的位置
Vector3 position = new Vector3();
if (needWrap)
{
position = new Vector3(tempVerts[0].x, tempVerts[GetIndexFormIndices(finalIndex, tempIndices)].y, tempVerts[0].z);
}
else
{
position = tempVerts[GetIndexFormIndices(finalIndex, tempIndices)];
}
return position;
}
先介紹一下NGUI提供的計算每個字符在字符串中位置的函數。
NGUIText.PrintCharacterPositions(str, tempVerts, tempIndices);
輸入str,輸出tempVerts,tempIndices。通過這兩個變量獲取每個字符的position信息
這里我封裝了個函數通過字符在字符串中的index來獲取在tempVerts中index_v,繼而通過tempVerts[index_v]獲取vecter3
int GetIndexFormIndices(int index, BetterList<int> list)
{
for (int i = 0; i < list.size; i++)
if (list[i] == index)
return i;
return 0;
}
我把NGUIText.PrintCharacterPositions(str, tempVerts, tempIndices)的用法寫成一個接口。
void UpdateCharacterPosition(string str,out BetterList<Vector3> verts,out BetterList<int> indices)
{
//把換好的文字放回label再計算sprite應該放的坐標,
//計算當前所有字符的位置
MyLabel.text = str;
MyLabel.UpdateNGUIText();
BetterList<Vector3> tempVerts = new BetterList<Vector3>();
BetterList<int> tempIndices = new BetterList<int>();
NGUIText.PrintCharacterPositions(str, tempVerts, tempIndices);
verts = tempVerts;
indices = tempIndices;
}
這個接口的意思就是把str放到label里,讓NGUI重新擺放一下文字,之后調用PrintCharacterPositions,返回這兩個變量,就更新了位置信息。這時候就可以取得每個字符的位置信息,也就是圖片將要擺放的位置。(在每次改變文字后都要重新調用才能確定位置準確)
回到上面的GenBlankString().
1.首先根據圖片寬度計算需要多少個空格來預留出位置。調用UpdateCharacterPosition()更新,重新獲得位置信息(這部分我暫時是估算哈,比如5像素1空格)
2.判斷是否需要換行。調用UpdateCharacterPosition()更新,重新獲得位置信息(判斷圖片信息字符串(已換成空格)的第一個字符和最后一個字符是否在同一行,如果不同行證明要換行)
3.按圖片的高,生成換行符。調用UpdateCharacterPosition()更新,重新獲得位置信息
4.這時文字已經確定不會再添加任何符號,所以重新復制最終要輸出的文字m_newEmotionText = str;
步驟3需要特別講一下:
int lastScale = 1;
int lastIndex = 0;
int GenCarriageReturn(BetterList<Vector3> vectList, BetterList<int> indexList, ref string str, int startIndex, float spriteHeight, bool isWrap)
{
float fontSize = MyLabel.fontSize * gameobject.transform.localScale.x;
int scale = Mathf.CeilToInt(spriteHeight / fontSize) - 1;
if (CheckIfSameLine(vectList, indexList, startIndex, lastIndex))
{
if (lastScale < scale)
{
scale = scale - lastScale;
lastScale = scale + lastScale;
}
else
{
scale = 0;
}
}
else
{
lastScale = scale;
}
lastIndex = startIndex;
string CarriageReturn = "";
for (int i = 0; i < scale; i++)
{
CarriageReturn = CarriageReturn + '\n';
lastIndex += 1;
}
//if(CheckIfIsLineFirstCharacter(vectList, indexList, startIndex))
//{
// CarriageReturn = CarriageReturn + '\n';
// scale += 1;
//}
if (!isWrap && scale > 0)
{
CarriageReturn = CarriageReturn + '\n';
scale += 1;
lastIndex += 1;
lastScale += 1;
}
str = str.Insert(FindLineFirstIndex(vectList, indexList, startIndex) - 1, CarriageReturn);
return scale;
}
可以看到在scale就是我需要多少個換行符。
接著下面的邏輯是如果這次判斷的startIndex(這個圖片的第一個字符)和上次lastIndex(上一個圖片的第一個字符)如果是同一行的話,需要判斷后面的圖片有沒有比前面的更大,如果更大需要判斷大多少,還需要多少個回車。
因為如果同一行內多個圖片的大小不一,只取最大的圖片的大小生成換行符。
再后面是判斷,有種情況是本身文字放到label剛好處于文字末尾(就是本身就需要一個換行符),所以如果是這種情況需要再插入一個換行符。
接著就把換行符插入到這一行的第一個字符前(還是通過位置信息去判斷這行的第一個字符)
這個就是判斷圖片位置的邏輯,然后就一遍遍的遞歸把所有圖片找出來放置好。
最后還需要把每一行的圖片檢索一下,同一行有多個圖片時,所有圖片的y軸都跟最后一個對齊(因為最后一個的y軸肯定是最低的,要跟最低的對齊)
void SortAllSprite()
{
for (int i = m_spriteList.Count - 1; i > 0; i--)
{
if (m_spriteList[i].line_index == m_spriteList[i - 1].line_index)
{
m_spriteList[i - 1].pos.y = m_spriteList[i].pos.y;
m_spriteList[i - 1].go.transform.localPosition = m_spriteList[i - 1].pos;
}
}
}
這樣就完成了圖文混排。
下面是所有代碼(掛在UILabel.cs上, UILabel的代碼不顯示)
string m_newEmotionText = "";
List<EmotionData> m_spriteList = new List<EmotionData>();
/// <summary>
/// label中有表情在顯示前調用進行轉換
/// </summary>
public void ShowEmotionLabel()
{
m_newEmotionText = "";
string originalText = MyLabel.text;
//遞歸找表情并生成文字
CutAndShowEmotionLabel(originalText);
//輸出文字
MyLabel.text = m_newEmotionText;
MyLabel.UpdateNGUIText();
//每一行的表情重新排序對其
SortAllSprite();
}
#region 圖文混排輔助函數
void CutAndShowEmotionLabel(string str)
{
EmotionData emoData = GetEmotionData(str);//解析str中的第一個表情字符串
if (emoData != null)
{
m_spriteList.Add(emoData);
//把str按第一個表情字符串的最后一個字母分成兩部分
string trimString = str.Substring(0, emoData.end_index);
string trimLeftString = str.Substring(emoData.end_index);
//生成表情和表情前面的文字部分
GenEmotionLabel(emoData, trimString);
m_newEmotionText = m_newEmotionText + trimLeftString;
//遞歸繼續找表情
CutAndShowEmotionLabel(m_newEmotionText);
}
else
{
//找不到表情返回,最后確定文字輸出
m_newEmotionText =str;
return;
}
}
void GenEmotionLabel(EmotionData emoData, string tramString)
{
//生成gameobject
GameObject go = CreateEmotionSprite(emoData);
float spriteWidth = NGUIMath.CalculateRelativeWidgetBounds(gameobject.transform, go.transform, true).size.x / go.transform.localScale.x;
float spriteHeight = NGUIMath.CalculateRelativeWidgetBounds(gameobject.transform, go.transform, true).size.y / go.transform.localScale.y;
//計算出圖片的位置,判斷文字的轉換和空格
Vector3 position = CalcuEmotionSpritePosition(tramString, emoData.start_index, spriteWidth, spriteHeight);
//擺放圖片位置
PlaceEmotionSprite(go, position);
m_spriteList[m_spriteList.Count - 1].go = go;
}
int lastScale = 1;
int lastIndex = 0;
int GenCarriageReturn(BetterList<Vector3> vectList, BetterList<int> indexList, ref string str, int startIndex, float spriteHeight, bool isWrap)
{
float fontSize = MyLabel.fontSize * gameobject.transform.localScale.x;
int scale = Mathf.CeilToInt(spriteHeight / fontSize) - 1;
if (CheckIfSameLine(vectList, indexList, startIndex, lastIndex))
{
if (lastScale < scale)
{
scale = scale - lastScale;
lastScale = scale + lastScale;
}
else
{
scale = 0;
}
}
else
{
lastScale = scale;
}
lastIndex = startIndex;
string CarriageReturn = "";
for (int i = 0; i < scale; i++)
{
CarriageReturn = CarriageReturn + '\n';
lastIndex += 1;
}
//if(CheckIfIsLineFirstCharacter(vectList, indexList, startIndex))
//{
// CarriageReturn = CarriageReturn + '\n';
// scale += 1;
//}
if (!isWrap && scale > 0)
{
CarriageReturn = CarriageReturn + '\n';
scale += 1;
lastIndex += 1;
lastScale += 1;
}
str = str.Insert(FindLineFirstIndex(vectList, indexList, startIndex) - 1, CarriageReturn);
return scale;
}
Vector3 CalcuEmotionSpritePosition(string str, int startIndex, float spriteWidth, float spriteHeight)
{
Vector3 position = GenBlankString(str, startIndex, spriteWidth, spriteHeight);
return position;
}
Vector3 GenBlankString(string str, int startIndex, float spriteWidth, float spriteHeight)
{
int finalIndex = startIndex;
BetterList<Vector3> tempVerts = new BetterList<Vector3>();
BetterList<int> tempIndices = new BetterList<int>();
//1.把圖片信息換成空格
string emontionText = str.Substring(startIndex);
int blankNeedCount = CaculateBlankNeed(spriteWidth);
str = str.Replace(emontionText, GenBlank(blankNeedCount));
//把換好的文字放回label再計算sprite應該放的坐標,
UpdateCharacterPosition(str,out tempVerts,out tempIndices);
//2.如果在label末尾且圖片放不下,判斷是否換行
bool needWrap = NeedWrap(tempVerts, tempIndices, startIndex, startIndex + blankNeedCount);
if (needWrap)
{
str = str.Insert(startIndex, "\n");
finalIndex +=1;
//重新計算當前所有字符的位置
UpdateCharacterPosition(str, out tempVerts, out tempIndices);
}
//3.按圖片的高,生成回車(換行)
int returnCount = GenCarriageReturn(tempVerts, tempIndices, ref str, finalIndex, spriteHeight, needWrap);
finalIndex += returnCount;
//4.重新賦值要輸出的str
m_newEmotionText = str;
//重新計算當前所有字符的位置
UpdateCharacterPosition(str, out tempVerts, out tempIndices);
//保存行數,最后重新排放每行的圖片使用
m_spriteList[m_spriteList.Count - 1].line_index = CalcuLineIndex(tempVerts, tempIndices, startIndex) - lastScale;
//最終計算圖片該放的位置
Vector3 position = new Vector3();
if (needWrap)
{
position = new Vector3(tempVerts[0].x, tempVerts[GetIndexFormIndices(finalIndex, tempIndices)].y, tempVerts[0].z);
}
else
{
position = tempVerts[GetIndexFormIndices(finalIndex, tempIndices)];
}
return position;
}
GameObject CreateEmotionSprite(EmotionData data)
{
GameObject go = new GameObject("(clone)emotion_sprite");
go.transform.parent = gameobject.transform;
UISprite sprite = go.AddComponent<UISprite>();
sprite.atlas = CResourceManager.Instance.GetAtlas(data.atlas_name);
sprite.spriteName = data.sprite_name;
sprite.MakePixelPerfect();
sprite.pivot = UIWidget.Pivot.BottomLeft;
float scaleFactor = 1 / gameobject.transform.localScale.x;
go.transform.localScale = new Vector3(scaleFactor, scaleFactor, scaleFactor);//字體可能縮小了0.5,所以掛在字體下要放大2倍
go.transform.localPosition = new Vector3(5000, 5000, 0);//先把它放到看不見的地方
return go;
}
void PlaceEmotionSprite(GameObject go, Vector3 position)
{
float fontSize = MyLabel.fontSize * gameobject.transform.localScale.x;
float div = fontSize * go.transform.localScale.x / 2;
Vector3 newPosition = new Vector3(position.x, position.y - div, position.z);
//Vector3 newPosition = position;
go.transform.localPosition = newPosition;
m_spriteList[m_spriteList.Count - 1].pos = newPosition;
}
EmotionData GetEmotionData(string text)
{
EmotionData tempData = null;
int index = text.IndexOf("%p");
if (index != -1)
{
tempData = new EmotionData();
tempData.start_index = index;
int altasEndIndex = text.IndexOf("$", index);
tempData.atlas_name = text.Substring(index + 2, altasEndIndex - (index + 2));
int spriteEndIndex = text.IndexOf("$", altasEndIndex + 1);
tempData.sprite_name = text.Substring(altasEndIndex + 1, spriteEndIndex - (altasEndIndex + 1));
tempData.end_index = spriteEndIndex + 1;
}
return tempData;
}
int GetIndexFormIndices(int index, BetterList<int> list)
{
for (int i = 0; i < list.size; i++)
if (list[i] == index)
return i;
return 0;
}
int CaculateBlankNeed(float spriteWidth)
{
int count = Mathf.CeilToInt(spriteWidth / (float)6);
return count;
}
string GenBlank(int count)
{
string blank = "";
for (int i = 0; i < count; i++)
{
blank = blank + " ";
}
return blank;
}
bool NeedWrap(BetterList<Vector3> vecList, BetterList<int> indicList, int startIndex, int endIndex)
{
int startIndic = GetIndexFormIndices(startIndex, indicList);
int endIndic = GetIndexFormIndices(endIndex, indicList);
if (vecList[startIndic].y == vecList[endIndic].y)
return false;
else
return true;
}
bool CheckIfSameLine(BetterList<Vector3> vecList, BetterList<int> indicList, int firstIndex, int SecondIndex)
{
int firstIndic = GetIndexFormIndices(firstIndex, indicList);
int secondIndic = GetIndexFormIndices(SecondIndex, indicList);
if (vecList[firstIndic].y == vecList[secondIndic].y)
return true;
else
return false;
}
int FindLineFirstIndex(BetterList<Vector3> vecList, BetterList<int> indicList, int index)
{
int startIndic = GetIndexFormIndices(index, indicList);
if (startIndic > 1)
{
if (vecList[startIndic].y == vecList[startIndic - 1].y)
index = FindLineFirstIndex(vecList, indicList, index - 1);
else
return index;
}
else
{
return 1;
}
return index;
}
int CalcuLineIndex(BetterList<Vector3> vecList, BetterList<int> indicList, int index)
{
int startIndic = GetIndexFormIndices(index, indicList);
int count = 0;
float lastVecY = 0;
for (int i = 0; i < vecList.size; i++)
//for (int i =0;i< startIndic; i++)
{
if (lastVecY != vecList[i].y)
{
count++;
lastVecY = vecList[i].y;
}
}
return count;
}
bool CheckIfIsLineFirstCharacter(BetterList<Vector3> vecList, BetterList<int> indicList, int index)
{
int startIndic = GetIndexFormIndices(index, indicList);
if (startIndic > 1)
{
if (vecList[startIndic].y == vecList[startIndic - 1].y)
return false;
else
return true;
}
else
{
return false;
}
}
void SortAllSprite()
{
for (int i = m_spriteList.Count - 1; i > 0; i--)
{
if (m_spriteList[i].line_index == m_spriteList[i - 1].line_index)
{
m_spriteList[i - 1].pos.y = m_spriteList[i].pos.y;
m_spriteList[i - 1].go.transform.localPosition = m_spriteList[i - 1].pos;
}
}
}
void UpdateCharacterPosition(string str,out BetterList<Vector3> verts,out BetterList<int> indices)
{
//把換好的文字放回label再計算sprite應該放的坐標,
//計算當前所有字符的位置
MyLabel.text = str;
MyLabel.UpdateNGUIText();
BetterList<Vector3> tempVerts = new BetterList<Vector3>();
BetterList<int> tempIndices = new BetterList<int>();
NGUIText.PrintCharacterPositions(str, tempVerts, tempIndices);
verts = tempVerts;
indices = tempIndices;
}
#endregion
補上EmotionData類
public class EmotionData
{
public int start_index;
public int end_index;
public string atlas_name;
public string sprite_name;
public float sprite_width;
public int line_index;
public Vector3 pos;
public GameObject go;
}
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。