转载请注明出处。
原来游戏使用的UI框架是NGUI的,那时候不需要考虑图文混排的,NGUI本身就支持图文混排,只需要我们为需要进行图文混排的图片都生成一个图集,然后设置好每个图片的占位符,这样就可以直接栽Label组件直接用这个占位符生成相应的图片。但是UGUI原生的Text组件并不支持图文混排,所以没有办法只能是我们自己去想办法。
由于UGUI的Text组件不支持图文混排,所以我去查了下Unity还给我们提供了一个组件叫做TextMesh,这个组件是可以支持图文混排的,但是他是3D的组件,我们在2D的UI里面要使用这个组件会造成的主要问题是无法合批,也无法收UI排序的影响,最主要的是ScrollRect组件无法mask他,这样会导致超出ScrollRect范围的TextMesh依然会显示出来,所以这个组件就直接Pass了。另外一种做法是使用TextMeshPro第三方插件,这个插件也是官方推荐的,而且性能也不错,使用的是距离场来实现的,里面还有很多UI效果,所以是很不错的插件,但是我并不想在项目中引入过多的第三方的东西,因为TextMeshPro我们也就会用到他扩展的Text组件其他的效果基本上用不到,所以为了以后维护方便,我也放弃这种方式,最后选择自己去实现。
我们都知道Text组件之所以会生成文字/图片都是因为他会生成网格,然后通过uv去Font里面根据相应的文字的坐标等参数,在一张字体贴图上去采样这个文字,然后显示出来,而显示图片原理也是一样,只是去采样的图集不一样而已,其次就是如何区分是要显示文本还是显示图片,这时候就需要一个占位符来告诉我们这个地方要生成图片,例如这样[#ID=***]这样的格式,最后一点就是类似gif这种动图要如何显示的问题,其实我们可以把gif理解为帧动画,每帧改变采样的uv,让uv有规律的改变,这样就能做到像gif那样动起来了,我们只需要告诉这个动画需要显示多少帧就行了,然后为每个表情生成一个配置文件,类似于下面的格式
ID UV_X UV_Y Frame
[#ID=1] 0,0 0,0.25 2
而且是直接继承自Text组件,所以不需要担心排序,等相关,我们只需要负责告诉Text这个地方需要一个Emoji,然后赋值上UV和Vertex就行了。
这里是根据单个图片的名字生成json文件的配置,并生成相应的图集,把所有的图片合成一张大图
private static Dictionary<string, EmojiInfo> GenerateConfig(Dictionary<string, List<Texture2D>> rSpriteDic)
{
Dictionary<string, EmojiInfo> rEmojiInfoDic = new Dictionary<string, EmojiInfo>();
foreach (var sprite in rSpriteDic)
{
EmojiInfo rInfo = new EmojiInfo();
rInfo.mKey = sprite.Key;
rInfo.mFrame = sprite.Value.Count;
rEmojiInfoDic.Add(sprite.Key, rInfo);
}
return rEmojiInfoDic;
}
private static Dictionary<string, List<Texture2D>> GetALLSprites()
{
Dictionary<string, List<Texture2D>> rSpritesDic = new Dictionary<string, List<Texture2D>>();
string[]rGUID= AssetDatabase.FindAssets("t:texture", new string[] { "Assets/Textures/EmojiSprites" });
for (int i = 0; i < rGUID.Length; i++)
{
string rPath= AssetDatabase.GUIDToAssetPath(rGUID[i]);
var rTex= AssetDatabase.LoadAssetAtPath<Texture2D>(rPath);
string[]rNameSplit= rTex.name.Split('_');
string rKey= string.Format("[image={0}]",rNameSplit[0]);
if (rSpritesDic.ContainsKey(rKey))
rSpritesDic[rKey].Add(rTex);
else
{
List<Texture2D> rSprites = new List<Texture2D>();
rSprites.Add(rTex);
rSpritesDic.Add(rKey, rSprites);
}
}
return rSpritesDic;
}
然后就是重写Text的OnPopulateMesh方法
//利用正则表达式找到符合约定的占位符
MatchCollection rMatches=Regex.Matches(text,"\\[image=[a-z0-9A-Z]+\\]");
StringBuilder rTempString=new StringBuilder();
for (int i = 0; i < rMatches.Count; i++)
{
EmojiInfo rInfo;
if (mEmojiInfos.TryGetValue(rMatches[i].Value,out rInfo))
{
//因为会把“[]”去掉,[]是不需要生成顶点的
rFindEmojis.Add(rMatches[i].Index-i*2,rInfo);
//从上一个匹配的位置截取到下一个匹配的位置
rTempString.Append(text.Substring(rLastIndex,rMatches[i].Index-rLastIndex));
//然后把[Image=**]替换成一个中文字符
rTempString.Append(mReplaceString);
//记录下索引的位置
rLastIndex=rMatches[i].Index+rMatches[i].Length;
}
}
if (rLastIndex<text.Length)
rTempString.Append(text.Substring(rLastIndex,text.Length));
//这里是直接复制的UGUI的Text生成定点的代码
Vector2 extent=rectTransform.rect.size;
var settings= GetGenerationSettings(extent);
cachedTextGenerator.Populate(rTempString.ToString(), settings);
然后是利用生成的顶点来设置采样的UV
float charDis = (verts[i + 1].position.x - verts[i].position.x);
//之所以是先把3=i是为了让图片正着显示
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
m_TempVerts[2].position += new Vector3(charDis, 0, 0);
m_TempVerts[1].position += new Vector3(charDis, 0, 0);
//让emoji长宽相等
float fixValue = (m_TempVerts[2].position.x - m_TempVerts[3].position.x - (m_TempVerts[2].position.y - m_TempVerts[1].position.y));
m_TempVerts[2].position -= new Vector3(fixValue, 0, 0);
m_TempVerts[1].position -= new Vector3(fixValue, 0, 0);
m_TempVerts[0].position *= unitsPerPixel;
m_TempVerts[1].position *= unitsPerPixel;
m_TempVerts[2].position *= unitsPerPixel;
m_TempVerts[3].position *= unitsPerPixel;
//计算Emoji的UV,利用uv0传递帧数,uv1是emoji的纹理坐标
m_TempVerts[0].uv1 = new Vector2(float.Parse(rInfo.mUV_X), float.Parse(rInfo.mUV_Y));
m_TempVerts[1].uv1 = new Vector2(float.Parse(rInfo.mUV_X + (32f/1024f)), float.Parse(rInfo.mUV_Y));
m_TempVerts[2].uv1 = new Vector2(float.Parse(rInfo.mUV_X + (32f/1024f)), float.Parse(rInfo.mUV_Y+ (32f/1024f)));
m_TempVerts[3].uv1 = new Vector2(float.Parse((rInfo.mUV_X)), float.Parse(rInfo.mUV_Y + (32f/1024f)));
m_TempVerts[0].uv0 = new Vector2(rInfo.mFrame, 0);
m_TempVerts[1].uv0 = new Vector2(rInfo.mFrame, 0);
m_TempVerts[2].uv0 = new Vector2(rInfo.mFrame, 0);
m_TempVerts[3].uv0 = new Vector2(rInfo.mFrame, 0);
rHelper.AddUIVertexQuad(m_TempVerts);
i+=3;
实际效果就如上图所示。其中我这边是固定死的每张表情是32X32大小的,所以如果想要改成更大或者更小的可以自己在代码里设置。完整的项目demo放在https://github.com/LongTimeEnjoy/ExtensionUI
觉得还不错的朋友star一下哦