最近幫一個同事解決圖文混排的問題角雷,發(fā)現(xiàn)了一種犀利的UGUI表情系統(tǒng)的解決方案
https://blog.uwa4d.com/archives/Sparkle_UGUI.html
使用重新生成UGUI文字Mesh的方式來支持表情圖片。在Shader中判斷是否有第二個套UV傳入來渲染表情乎串,動態(tài)表情也在GPU端計算~ 可以合并DrawCall,支持UGUI的遮罩橡伞、自適應(yīng)等等
我在原作者的基礎(chǔ)上擴展了一些改進逗物,使其能支持超鏈接
超鏈接的思路是先計算出超鏈接的頂點包圍盒跌前,監(jiān)聽到點擊事件的時候钮糖,跟超鏈接包圍和進行碰撞檢測來判斷是否點擊到了某個連接。如果一個超鏈接跨行的時候枢析,就需要創(chuàng)建多個包圍盒來處理玉掸。
如何解析超鏈接標簽?
<a href='xx'>點擊加入隊伍</a>
在Text發(fā)生變化的時候醒叁,UGUI會調(diào)用SetVerticesDirty函數(shù)把組件注冊到ReBuilder隊列里面司浪,等待下一幀重繪。所以我們在SetVerticesDirty函數(shù)中寫上解析a標簽的代碼
/// <summary>
/// 獲取超鏈接解析后的最后輸出文本
/// </summary>
/// <returns></returns>
protected virtual string GetOutputText(string outputText)
{
s_TextBuilder.Length = 0;
m_HrefInfos.Clear();
var indexText = 0;
foreach (Match match in s_HrefRegex.Matches(outputText))
{
s_TextBuilder.Append(outputText.Substring(indexText, match.Index - indexText));
s_TextBuilder.Append("<color='#9ed7ff'>"); // 超鏈接顏色ff6600
var group = match.Groups[1];
var hrefInfo = new HrefInfo
{
startIndex = s_TextBuilder.Length * 4, // 超鏈接里的文本起始頂點索引
endIndex = (s_TextBuilder.Length + match.Groups[2].Length - 1) * 4 + 3,
name = group.Value
};
m_HrefInfos.Add(hrefInfo);
s_TextBuilder.Append(match.Groups[2].Value);
s_TextBuilder.Append("</color>");
indexText = match.Index + match.Length;
}
s_TextBuilder.Append(outputText.Substring(indexText, outputText.Length - indexText));
return s_TextBuilder.ToString();
}
通過正則表達式匹配后把沼,計算出超鏈接的起始頂點索引啊易、結(jié)束頂點索引、name保存到一個列表里饮睬。
鏈接跟圖片索引沖突問題
text中的表情標簽如"[0]"由3個字符組成,每個字符4個頂點租谈,所以占用12個頂點
但是在填充頂點的時候,我們只會用4個頂點渲染圖片续捂,來替換掉原來的12個頂點
所以前面計算的超鏈接的startIndex垦垂,endIndex也要隨之改變
private void HrefInfosIndexAdjust(int imgIndex)
{
foreach (var hrefInfo in m_HrefInfos)//如果后面有超鏈接,需要把位置往前挪
{
if (imgIndex < hrefInfo.startIndex)
{
hrefInfo.startIndex -= 8;
hrefInfo.endIndex -= 8;
}
}
}
計算超鏈接的包圍盒
UIVertex vert = new UIVertex();
// 處理超鏈接包圍框
foreach (var hrefInfo in m_HrefInfos)
{
hrefInfo.boxes.Clear();
if (hrefInfo.startIndex >= toFill.currentVertCount)
{
continue;
}
// 將超鏈接里面的文本頂點索引坐標加入到包圍框
toFill.PopulateUIVertex(ref vert, hrefInfo.startIndex);
var pos = vert.position;
var bounds = new Bounds(pos, Vector3.zero);
for (int i = hrefInfo.startIndex, m = hrefInfo.endIndex; i < m; i++)
{
if (i >= toFill.currentVertCount)
{
break;
}
toFill.PopulateUIVertex(ref vert, i);
pos = vert.position;
if (pos.x < bounds.min.x) // 換行重新添加包圍框
{
hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size));
bounds = new Bounds(pos, Vector3.zero);
}
else
{
bounds.Encapsulate(pos); // 擴展包圍框
}
}
hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size));
}
UGUI填充頂點的函數(shù)
UGUI的顯示對象都是繼承于Graphic的牙瓢,Graphic中OnPopulateMesh函數(shù)用于填充頂點數(shù)據(jù),然后傳遞到GPU渲染间校,這里也是通過重寫該函數(shù)計算出圖片的頂點坐標以及超鏈接的包圍盒
if (EmojiIndex == null) {
EmojiIndex = new Dictionary<string, EmojiInfo>();
//load emoji data, and you can overwrite this segment code base on your project.
TextAsset emojiContent = Resources.Load<TextAsset> ("emoji");
string[] lines = emojiContent.text.Split ('\n');
for(int i = 1 ; i < lines.Length; i ++)
{
if (! string.IsNullOrEmpty (lines [i])) {
string[] strs = lines [i].Split ('\t');
EmojiInfo info;
info.x = float.Parse (strs [3]);
info.y = float.Parse (strs [4]);
info.size = float.Parse (strs [5]);
info.len = 0;
EmojiIndex.Add (strs [1], info);
}
}
}
//key是標簽在字符串中的索引
Dictionary<int,EmojiInfo> emojiDic = new Dictionary<int, EmojiInfo> ();
if (supportRichText) {
MatchCollection matches = Regex.Matches (m_OutputText, "\\[[a-z0-9A-Z]+\\]");//把表情標簽全部匹配出來
for (int i = 0; i < matches.Count; i++) {
EmojiInfo info;
if (EmojiIndex.TryGetValue (matches [i].Value, out info)) {
info.len = matches [i].Length;
emojiDic.Add (matches [i].Index, info);
}
}
}
// We don't care if we the font Texture changes while we are doing our Update.
// The end result of cachedTextGenerator will be valid for this instance.
// Otherwise we can get issues like Case 619238.
m_DisableFontTextureRebuiltCallback = true;
Vector2 extents = rectTransform.rect.size;
var settings = GetGenerationSettings(extents);
var orignText = m_Text;
m_Text = m_OutputText;
cachedTextGenerator.Populate(m_Text, settings);//重置網(wǎng)格
m_Text = orignText;
Rect inputRect = rectTransform.rect;
// get the text alignment anchor point for the text in local space
Vector2 textAnchorPivot = GetTextAnchorPivot(alignment);
Vector2 refPoint = Vector2.zero;
refPoint.x = Mathf.Lerp(inputRect.xMin, inputRect.xMax, textAnchorPivot.x);
refPoint.y = Mathf.Lerp(inputRect.yMin, inputRect.yMax, textAnchorPivot.y);
// Determine fraction of pixel to offset text mesh.
Vector2 roundingOffset = PixelAdjustPoint(refPoint) - refPoint;
// Apply the offset to the vertices
IList<UIVertex> verts = cachedTextGenerator.verts;
float unitsPerPixel = 1 / pixelsPerUnit;
//Last 4 verts are always a new line...
int vertCount = verts.Count - 4;
toFill.Clear();
if (roundingOffset != Vector2.zero)
{
for (int i = 0; i < vertCount; ++i)
{
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
if (tempVertsIndex == 3)
toFill.AddUIVertexQuad(m_TempVerts);
}
}
else
{
float repairDistance = 0;
float repairDistanceHalf = 0;
float repairY = 0;
if (vertCount > 0) {
repairY = verts [3].position.y;
}
for (int i = 0; i < vertCount; ++i) {
EmojiInfo info;
int index = i / 4;//每個字符4個頂點
if (emojiDic.TryGetValue (index, out info)) {//這個頂點位置是否為表情開始的index
HrefInfosIndexAdjust(i);//矯正一下超鏈接的Index
//compute the distance of '[' and get the distance of emoji
//計算表情標簽2個頂點之間的距離矾克, * 3 得出寬度(表情有3位)
float charDis = (verts [i + 1].position.x - verts [i].position.x) * 3;
m_TempVerts [3] = verts [i];//1
m_TempVerts [2] = verts [i + 1];//2
m_TempVerts [1] = verts [i + 2];//3
m_TempVerts [0] = verts [i + 3];//4
//the real distance of an emoji
m_TempVerts [2].position += new Vector3 (charDis, 0, 0);
m_TempVerts [1].position += new Vector3 (charDis, 0, 0);
float fixWidth = m_TempVerts[2].position.x - m_TempVerts[3].position.x;
float fixHeight = (m_TempVerts[2].position.y - m_TempVerts[1].position.y);
//make emoji has equal width and height
float fixValue = (fixWidth - fixHeight);//把寬度變得跟高度一樣
m_TempVerts [2].position -= new Vector3 (fixValue, 0, 0);
m_TempVerts [1].position -= new Vector3 (fixValue, 0, 0);
float curRepairDis = 0;
if (verts [i].position.y < repairY) {// to judge current char in the same line or not
repairDistance = repairDistanceHalf;
repairDistanceHalf = 0;
repairY = verts [i + 3].position.y;
}
curRepairDis = repairDistance;
int dot = 0;//repair next line distance
for (int j = info.len - 1; j > 0; j--) {
int infoIndex = i + j * 4 + 3;
if (verts.Count > infoIndex && verts[infoIndex].position.y >= verts [i + 3].position.y) {
repairDistance += verts [i + j * 4 + 1].position.x - m_TempVerts [2].position.x;
break;
} else {
dot = i + 4 * j;
}
}
if (dot > 0) {
int nextChar = i + info.len * 4;
if (nextChar < verts.Count) {
repairDistanceHalf = verts [nextChar].position.x - verts [dot].position.x;
}
}
//repair its distance
for (int j = 0; j < 4; j++) {
m_TempVerts [j].position -= new Vector3 (curRepairDis, 0, 0);
}
m_TempVerts [0].position *= unitsPerPixel;
m_TempVerts [1].position *= unitsPerPixel;
m_TempVerts [2].position *= unitsPerPixel;
m_TempVerts [3].position *= unitsPerPixel;
float pixelOffset = emojiDic [index].size / 32 / 2;
m_TempVerts [0].uv1 = new Vector2 (emojiDic [index].x + pixelOffset, emojiDic [index].y + pixelOffset);
m_TempVerts [1].uv1 = new Vector2 (emojiDic [index].x - pixelOffset + emojiDic [index].size, emojiDic [index].y + pixelOffset);
m_TempVerts [2].uv1 = new Vector2 (emojiDic [index].x - pixelOffset + emojiDic [index].size, emojiDic [index].y - pixelOffset + emojiDic [index].size);
m_TempVerts [3].uv1 = new Vector2 (emojiDic [index].x + pixelOffset, emojiDic [index].y - pixelOffset + emojiDic [index].size);
toFill.AddUIVertexQuad (m_TempVerts);
i += 4 * info.len - 1;
} else {
int tempVertsIndex = i & 3;
if (tempVertsIndex == 0 && verts [i].position.y < repairY) {
repairY = verts [i + 3].position.y;
repairDistance = repairDistanceHalf;
repairDistanceHalf = 0;
}
m_TempVerts [tempVertsIndex] = verts [i];
m_TempVerts [tempVertsIndex].position -= new Vector3 (repairDistance, 0, 0);
m_TempVerts [tempVertsIndex].position *= unitsPerPixel;
if (tempVertsIndex == 3)
toFill.AddUIVertexQuad (m_TempVerts);
}
}
}
判斷是否點中了鏈接
把屏幕坐標轉(zhuǎn)換到Text中的坐標后,再進行檢測
/// <summary>
/// 點擊事件檢測是否點擊到超鏈接文本
/// </summary>
public void OnPointerClick(PointerEventData eventData)
{
Vector2 lp;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
rectTransform, eventData.position, eventData.pressEventCamera, out lp);
foreach (var hrefInfo in m_HrefInfos)
{
var boxes = hrefInfo.boxes;
for (var i = 0; i < boxes.Count; ++i)
{
if (boxes[i].Contains(lp))
{
if (onHrefClick != null)
{
onHrefClick(hrefInfo.name);
}
Debug.Log("點擊了:" + hrefInfo.name);
return;
}
}
}
}
可以在這里獲取全部代碼
https://github.com/lijia4423/EmojiText.git
參考:
https://blog.uwa4d.com/archives/Sparkle_UGUI.html
http://www.pudn.com/Download/item/id/3121697.html