Unity UGUI图文混排(五) -- 一张图集对应多个Text

时间:2022-12-29 12:53:56

继上一篇说的更新了一张图集对应多个Text的功能,为了节省资源嘛

这里,但是也没有舍弃之前的一个Text一个图集,因为我感觉应该两个都有用,于是我重新写了一个脚本

1.其实大体跟前面的都没变,解析标签,获取表情的相关数据,这里只是将绘制图片的功能,移植到SpriteGraphic上,本地增加了一个刷新图片绘制信息的函数。

麻烦的是去找到SpriteGraphic绘制图片,也是因为这个感觉有很大的潜在问题,不过基本能用,具体看脚本

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Text.RegularExpressions; public class InlieSpriteText : Text { /// <summary>
/// 用正则取标签属性 名称-大小-宽度比例
/// </summary>
private static readonly Regex m_spriteTagRegex =
new Regex(@"<quad name=(.+?) size=(\d*\.?\d+%?) width=(\d*\.?\d+%?) />", RegexOptions.Singleline); /// <summary>
/// 图片资源
/// </summary>
private SpriteAsset m_spriteAsset;
/// <summary>
/// 图片渲染组件
/// </summary>
private SpriteGraphic m_spriteGraphic;
/// <summary>
/// CanvasRenderer
/// </summary>
private CanvasRenderer m_spriteCanvasRenderer; /// <summary>
/// 图片渲染管理
/// </summary>
private SpriteGraphicManager m_SGManager; #region 动画标签解析
//最多动态表情数量
int AnimNum = 8;
// List<int> m_AnimIndex;
List<SpriteTagInfor[]> m_AnimSpiteTag;
public List<InlineSpriteInfor[]> m_AnimSpriteInfor;
#endregion /// <summary>
/// 初始化
/// </summary>
protected override void OnEnable()
{
//在编辑器中,可能在最开始会出现一张图片,就是因为没有激活文本,在运行中是正常的。可根据需求在编辑器中选择激活...
base.OnEnable();
//对齐几何
alignByGeometry = true; #region 为了将SpriteGraphicManager显示到最上级,这里的SpriteGraphicManager可能会放在最下面,所以需要从全局去找
if (m_SGManager == null)
m_SGManager = GameObject.FindObjectOfType<SpriteGraphicManager>();
#endregion if (m_SGManager != null)
{
m_spriteGraphic = m_SGManager.GetComponent<SpriteGraphic>();
m_spriteCanvasRenderer = m_SGManager.GetComponent<CanvasRenderer>();
m_spriteAsset = m_spriteGraphic.m_spriteAsset;
} //初始化 调用顶点绘制
SetVerticesDirty();
} /// <summary>
/// 在设置顶点时调用
/// </summary>
public override void SetVerticesDirty()
{
base.SetVerticesDirty(); // m_AnimIndex = new List<int>();
m_AnimSpiteTag = new List<SpriteTagInfor[]>(); foreach (Match match in m_spriteTagRegex.Matches(text))
{
if (m_spriteAsset == null)
return; #region 解析动画标签
List<string> tempListName = new List<string>();
for (int i = 0; i < m_spriteAsset.listSpriteInfor.Count; i++)
{
// Debug.Log((m_spriteAsset.listSpriteInfor[i].name));
if (m_spriteAsset.listSpriteInfor[i].name.Contains(match.Groups[1].Value))
{
tempListName.Add(m_spriteAsset.listSpriteInfor[i].name);
}
}
if (tempListName.Count > 0)
{
SpriteTagInfor[] tempArrayTag = new SpriteTagInfor[tempListName.Count];
for (int i = 0; i < tempArrayTag.Length; i++)
{
tempArrayTag[i] = new SpriteTagInfor();
tempArrayTag[i].name = tempListName[i];
tempArrayTag[i].index = match.Index;
tempArrayTag[i].size = new Vector2(float.Parse(match.Groups[2].Value) * float.Parse(match.Groups[3].Value), float.Parse(match.Groups[2].Value));
tempArrayTag[i].Length = match.Length;
}
m_AnimSpiteTag.Add(tempArrayTag);
}
#endregion
}
} readonly UIVertex[] m_TempVerts = new UIVertex[4];
/// <summary>
/// 绘制模型
/// </summary>
/// <param name="toFill"></param>
protected override void OnPopulateMesh(VertexHelper toFill)
{
// base.OnPopulateMesh(toFill); if (font == null)
return; // 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);
cachedTextGenerator.Populate(text, settings); 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 = (textAnchorPivot.x == 1 ? inputRect.xMax : inputRect.xMin);
refPoint.y = (textAnchorPivot.y == 0 ? inputRect.yMin : inputRect.yMax); // 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(); //清楚乱码
for (int i = 0; i < m_AnimSpiteTag.Count; i++)
{
if (m_AnimSpiteTag[i].Length > 0)
{
//UGUIText不支持<quad/>标签,表现为乱码,我这里将他的uv全设置为0,清除乱码
for (int m = m_AnimSpiteTag[i][0].index * 4; m < m_AnimSpiteTag[i][0].index * 4 + 4; m++)
{
UIVertex tempVertex = verts[m];
tempVertex.uv0 = Vector2.zero;
verts[m] = tempVertex;
}
}
}
//计算标签 其实应该计算偏移值后 再计算标签的值 算了 后面再继续改吧
// CalcQuadTag(verts); 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
{
for (int i = 0; i < vertCount; ++i)
{
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
if (tempVertsIndex == 3)
toFill.AddUIVertexQuad(m_TempVerts);
}
} //计算标签 计算偏移值后 再计算标签的值
List<UIVertex> vertsTemp = new List<UIVertex>();
for (int i = 0; i < vertCount; i++)
{
UIVertex tempVer=new UIVertex();
toFill.PopulateUIVertex(ref tempVer,i);
vertsTemp.Add(tempVer);
}
CalcQuadTag(vertsTemp); m_DisableFontTextureRebuiltCallback = false; //更新绘制图片信息
if(m_SGManager!=null)
m_SGManager.UpdateSpriteInfor();
//DrawSprite();
} private IList<UIVertex> _OldVerts; #region 计算标签
/// <summary>
/// 解析quad标签 主要清除quad乱码 获取表情的位置
/// </summary>
/// <param name="verts"></param>
void CalcQuadTag(IList<UIVertex> verts)
{ m_AnimSpriteInfor = new List<InlineSpriteInfor[]>(); Vector3 _TempStartPos = Vector3.zero;
if(m_SGManager!=null)
_TempStartPos = transform.position - m_SGManager.transform.position; for (int i = 0; i < m_AnimSpiteTag.Count; i++)
{
SpriteTagInfor[] tempTagInfor = m_AnimSpiteTag[i];
InlineSpriteInfor[] tempSpriteInfor = new InlineSpriteInfor[tempTagInfor.Length];
for (int j = 0; j < tempTagInfor.Length; j++)
{
tempSpriteInfor[j] = new InlineSpriteInfor();
tempSpriteInfor[j].textpos = _TempStartPos + verts[((tempTagInfor[j].index + 1) * 4) - 1].position;
//设置图片的位置
tempSpriteInfor[j].vertices = new Vector3[4];
tempSpriteInfor[j].vertices[0] = new Vector3(0, 0, 0) + tempSpriteInfor[j].textpos;
tempSpriteInfor[j].vertices[1] = new Vector3(tempTagInfor[j].size.x, tempTagInfor[j].size.y, 0) + tempSpriteInfor[j].textpos;
tempSpriteInfor[j].vertices[2] = new Vector3(tempTagInfor[j].size.x, 0, 0) + tempSpriteInfor[j].textpos;
tempSpriteInfor[j].vertices[3] = new Vector3(0, tempTagInfor[j].size.y, 0) + tempSpriteInfor[j].textpos; //计算其uv
Rect newSpriteRect = m_spriteAsset.listSpriteInfor[0].rect;
for (int m = 0; m < m_spriteAsset.listSpriteInfor.Count; m++)
{
//通过标签的名称去索引spriteAsset里所对应的sprite的名称
if (tempTagInfor[j].name == m_spriteAsset.listSpriteInfor[m].name)
newSpriteRect = m_spriteAsset.listSpriteInfor[m].rect;
}
Vector2 newTexSize = new Vector2(m_spriteAsset.texSource.width, m_spriteAsset.texSource.height); tempSpriteInfor[j].uv = new Vector2[4];
tempSpriteInfor[j].uv[0] = new Vector2(newSpriteRect.x / newTexSize.x, newSpriteRect.y / newTexSize.y);
tempSpriteInfor[j].uv[1] = new Vector2((newSpriteRect.x + newSpriteRect.width) / newTexSize.x, (newSpriteRect.y + newSpriteRect.height) / newTexSize.y);
tempSpriteInfor[j].uv[2] = new Vector2((newSpriteRect.x + newSpriteRect.width) / newTexSize.x, newSpriteRect.y / newTexSize.y);
tempSpriteInfor[j].uv[3] = new Vector2(newSpriteRect.x / newTexSize.x, (newSpriteRect.y + newSpriteRect.height) / newTexSize.y); //声明三角顶点所需要的数组
tempSpriteInfor[j].triangles = new int[6];
}
m_AnimSpriteInfor.Add(tempSpriteInfor); _OldVerts = verts;
}
}
#endregion #region 更新图片的信息
public void UpdateSpriteInfor()
{
if (_OldVerts == null)
return; CalcQuadTag(_OldVerts);
}
#endregion }

2.这里新写了一个SpriteGraphicManager脚本用来管理SpriteGraphic的图片绘制,和获取InlieSpriteText传来的相关绘制图片的信息,SpriteGraphicManager就是绑定在SpriteGraphic上的,因为UGUI的渲染顺序是从上到下,SpriteGraphic的放置位置就显得比较尴尬了,应该可以用shader更改渲染层级,没去试,先这样吧,我这里将SpriteGraphic放在最下面的

using UnityEngine;
using System.Collections;
using System.Collections.Generic; /********
为了图片渲染在最上面
需要将他放砸canvas的最下层
应该可以改shader的渲染顺序 没去试 就这样写吧
*********/ [RequireComponent(typeof(SpriteGraphic))]
public class SpriteGraphicManager : MonoBehaviour { /// <summary>
/// 需要渲染的图片信息列表
/// </summary>
private List<InlineSpriteInfor> listSprite;
#region 动画标签解析
//最多动态表情数量
int AnimNum = 8;
List<InlineSpriteInfor[]> m_AnimSpriteInfor;
#endregion #region 更新图片信息
public void UpdateSpriteInfor()
{
listSprite = new List<InlineSpriteInfor>();
m_AnimSpriteInfor = new List<InlineSpriteInfor[]>(); // inline
// InlieSpriteText[] AllInlieSpriteText = GetComponentsInChildren<InlieSpriteText>();
// 找到所有InlieSpriteText的物体 ---- 这里隐藏问题蛮大的 他搜索的所有的InlieSpriteText
// 包括InlieSpriteText也是全局搜索的SpriteGraphicManager,意思SpriteGraphicManager最好只有一个
// 当然 可以自定义根据功能 自己改了 我这里是这么定义的
InlieSpriteText[] AllInlieSpriteText = GameObject.FindObjectsOfType<InlieSpriteText>(); for (int i = 0; i < AllInlieSpriteText.Length; i++)
{
if (AllInlieSpriteText[i].m_AnimSpriteInfor != null)
{
AllInlieSpriteText[i].UpdateSpriteInfor();
for (int j = 0; j < AllInlieSpriteText[i].m_AnimSpriteInfor.Count; j++)
{
m_AnimSpriteInfor.Add(AllInlieSpriteText[i].m_AnimSpriteInfor[j]);
listSprite.Add(AllInlieSpriteText[i].m_AnimSpriteInfor[j][0]);
}
}
}
DrawSprite();
}
#endregion #region update刷新动画
float fTime = 0.0f;
int iIndex = 0;
void Update()
{
if (m_AnimSpriteInfor == null)
return; fTime += Time.deltaTime;
if (fTime >= 0.1f)
{
//刷新一次 更新绘制图片的相关信息
UpdateSpriteInfor(); for (int i = 0; i < m_AnimSpriteInfor.Count; i++)
{
if (iIndex >= m_AnimSpriteInfor[i].Length)
{
listSprite[i] = m_AnimSpriteInfor[i][0];
}
else
{
listSprite[i] = m_AnimSpriteInfor[i][iIndex];
}
}
DrawSprite();
iIndex++;
if (iIndex >= AnimNum)
{
iIndex = 0;
}
fTime = 0.0f;
}
}
#endregion #region 绘制图片
/// <summary>
/// 绘制图片
/// </summary>
void DrawSprite()
{
Mesh m_spriteMesh = new Mesh(); List<Vector3> tempVertices = new List<Vector3>();
List<Vector2> tempUv = new List<Vector2>();
List<int> tempTriangles = new List<int>(); for (int i = 0; i < listSprite.Count; i++)
{
for (int j = 0; j < listSprite[i].vertices.Length; j++)
{
tempVertices.Add(listSprite[i].vertices[j]);
}
for (int j = 0; j < listSprite[i].uv.Length; j++)
{
tempUv.Add(listSprite[i].uv[j]);
}
for (int j = 0; j < listSprite[i].triangles.Length; j++)
{
tempTriangles.Add(listSprite[i].triangles[j]);
}
}
//计算顶点绘制顺序
for (int i = 0; i < tempTriangles.Count; i++)
{
if (i % 6 == 0)
{
int num = i / 6;
tempTriangles[i] = 0 + 4 * num;
tempTriangles[i + 1] = 1 + 4 * num;
tempTriangles[i + 2] = 2 + 4 * num; tempTriangles[i + 3] = 1 + 4 * num;
tempTriangles[i + 4] = 0 + 4 * num;
tempTriangles[i + 5] = 3 + 4 * num;
}
} m_spriteMesh.vertices = tempVertices.ToArray();
m_spriteMesh.uv = tempUv.ToArray();
m_spriteMesh.triangles = tempTriangles.ToArray(); if (m_spriteMesh == null)
return; GetComponent<CanvasRenderer>().SetMesh(m_spriteMesh);
GetComponent<SpriteGraphic>().UpdateMaterial();
}
#endregion }

3.差不多一张图集对应多个Text的功能就完了,看一下截图,因为这都是根据之前的更新的,看了之前工程的同学都应该能看明白,我架设你们都看过了

Unity UGUI图文混排(五) -- 一张图集对应多个Text

4.这里还更新了一个小东西,就是做聊天demo的时候感觉之前的标签太长,比如<quad name=meat size=20 width=1 />,确实有点长,本来应该表情也绘制在输入框的,一是自己也没去测试,二是有同学已经测试,但是发现不少问题,我暂时也就将标签缩短了<#meat>

Unity UGUI图文混排(五) -- 一张图集对应多个Text

5.之前的工程的基本功能都是基本完善的,最新的基本都是一些功能和逻辑上的扩展:
 工程链接