UGUI表情系统&超链接解决方案
最近帮一个同事解决图文混排的问题,发现了一种犀利的UGUI表情系统的解决方案
https://blog.uwa4d.com/archives/Sparkle_UGUI.html
使用重新生成UGUI文字Mesh的方式来支持表情图片。在Shader中判断是否有第二个套UV传入来渲染表情,动态表情也在GPU端计算~ 可以合并DrawCall,支持UGUI的遮罩、自适应等等
我在原作者的基础上扩展了一些改进,使其能支持超链接
超链接的思路是先计算出超链接的顶点包围盒,监听到点击事件的时候,跟超链接包围和进行碰撞检测来判断是否点击到了某个连接。如果一个超链接跨行的时候,就需要创建多个包围盒来处理。
如何解析超链接标签?
<a href='xx'>点击加入队伍</a>
在Text发生变化的时候,UGUI会调用SetVerticesDirty函数把组件注册到ReBuilder队列里面,等待下一帧重绘。所以我们在SetVerticesDirty函数中写上解析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();
}
通过正则表达式匹配后,计算出超链接的起始顶点索引、结束顶点索引、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填充顶点的函数
UGUI的显示对象都是继承于Graphic的,Graphic中OnPopulateMesh函数用于填充顶点数据,然后传递到GPU渲染,这里也是通过重写该函数计算出图片的顶点坐标以及超链接的包围盒
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);//重置网格
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);
}
}
}
判断是否点中了链接
把屏幕坐标转换到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
UGUI表情系统&超链接解决方案的更多相关文章
- UGUI表情系统解决方案
参考链接: https://blog.uwa4d.com/archives/Sparkle_UGUI.html http://tech.seasungame.com/blog/index.php/20 ...
- Unity琐碎(3) UGUI 图文混排解决方案和优化
感觉使用Unity之后总能看到各种各样解决混排的方案,只能说明Unity不够体恤下情啊.这篇文章主要讲一下个人在使用过程中方案选择和优化过程,已做记录.顺便提下,开源很多意味着坑,还是要开实际需求. ...
- Atitit 软件项目系统托盘图标解决方案
Atitit 软件项目系统托盘图标解决方案 1.1. Nodejs node-webkit还实现了本地化的API,例如菜单栏,系统的托盘图标支持1 1.2. Java c# qt c++1 1.3 ...
- Atitit 跨平台的系统截图解决方案
Atitit 跨平台的系统截图解决方案 1.1. Nodes js 方案desktop-screenshot进行系统截图1 1.2. Win 方案,autoit dsl,可能不跨台1 1.3. Jav ...
- 系统加速解决方案之Windows XP
系统加速解决方案之Windows XP 在使用Windows XP的过程中,系统速度会随着时间的推移越来越慢,你可重装系统,但重装后,那么多的应用软件也要重新安装,如何在不安装系统的前提下提升Wind ...
- elementaryOS系统托盘解决方案
在用 eOS 的时候,你可能会遇到系统托盘的问题,有些需要托盘的软件比如说 QQ,没办法在 eOS 的 Wingpanel 上显示,一最小化就不见了,或者出现一个 System tray 的窗口,很麻 ...
- 运营商DNS系统安全解决方案
DNS系统面临的主要风险 目前,DNS面临的安全问题主要可以分为三类:DNS欺骗攻击.拒绝服务攻击.系统漏洞,下文将分别进行介绍. DNS欺骗攻击 当一个DNS服务器遭到欺骗攻击,使用了来自一个恶 ...
- 误卸载glibc类库导致系统崩溃解决方案
由于系统中没有yum环境需要编译安装redis,但系统中却没有安装gcc和gcc-c++,挂载本地镜像安装gcc和gcc-c++由于版本太高,由于一时疏忽误将系统的依赖库glibc使用rpm -e 命 ...
- 分布式定时任务调度系统技术解决方案(xxl-job、Elastic-job、Saturn)
1.业务场景 保险人管系统每月工资结算,平安有150万代理人,如何快速的进行工资结算(数据运算型) 保险短信开门红/电商双十一 1000w+短信发送(短时汇聚型) 工作中业务场景非常多,所涉及到的场景 ...
随机推荐
- Windows解决anaconda下双python版本安装TensorFlow
首先,就是双版本anaconda的安装: 以前安装好的是python2.7版本,而TensorFlow的安装仅支持3.5版本的.但是自己本来的2.7版本又不想遗弃.所以安装双版本的: 在anacond ...
- Netty——简单创建服务器、客户端通讯
Netty 是一个基于NIO的客户.服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用.Netty相当简化和流线化了网络应用的编程开发过程 ...
- Sqlserver将数据从一个表插入到另一个表
.如果是整个表复制表达如下: insert into table1 select * from table2 .如果是有选择性的复制数据表达如下: insert into table1(column1 ...
- 在C#程序中模拟发送键盘按键消息
using System.Runtime.InteropServices; 引入键盘事件函数 [DllImport("user32.dll")]public static exte ...
- VIM于换行EOL的思考
\n LF 0A 将当前光标切换到下一行(不一定行首)\r CR OD 将当前光标置于行首 在windows与unix系统中,unix将\n代表换行并置于行首,而windows保持原意.即unix:\ ...
- Xshell 的安装教程
Xshell就是一个远程控制RHEL的软件:其他的还有很多,用什么都无所谓(根据公司情况). 下面我们来安装下这个工具: 双击exe 点下一步: 选 免费的 然后下一步:(免费的功能足够用了) 点接受 ...
- html5知识点:DOM编程
DOM是Document Object Model的缩写,中文名称是文档对象模型. DOM是处理HTML页面的标准编程接口,DOM可被JavaScript用来读取.改变HTML的内容和结构. 前端三大 ...
- IBM的websphere MQ的c#使用
1.关于websphere MQ的常用名词(针对Websphere MQ7.5版本) 队列管理器:为应用程序提供消息传递服务的程序.使用消息队列接口(MQI)的应用程序可以将消息放置到队列并可从队列中 ...
- Java日志框架那些事儿
文章首发于[博客园-陈树义],点击跳转到原文Java日志框架那些事儿. 在项目开发过程中,我们可以通过 debug 查找问题.而在线上环境我们查找问题只能通过打印日志的方式查找问题.因此对于一个项目而 ...
- tensorflow 学习笔记 多层感知机
# -*- coding: utf-8 -*- """ Created on Thu Mar 9 19:20:51 2017 @author: Jarvis " ...