图文混排
从字面意思来理解:就是图片和文字混合在一起。不知道这样的的定义是否正确,起码我是这样理解的,在游戏开发过程中,如果单单从业务逻辑去看的话,图文混排算是比较复杂的。个人感觉也是必须会的技能。
原理
我们期待在文本的合适地方插入我们需要显示的表情。所以我们要取插入表情的位置,但是一条聊天的信息是一段文本的字符串,是一个整体,每一个字符都不是单个对象,所以要在文本字符串中取合适的位置就不能用平时的方法(ps:UILabe的本质是一张面片),好在NGUI底层给我们提供了顶点信息的集合。
/// <summary>
/// Widget's vertices (before they get transformed).
/// </summary>
public BetterList<Vector3> verts = new BetterList<Vector3>();
现在我们已经找到突破口,verts保存的是位置信息,那么只有我们算出表情在第几个字后面就可以取得位置信息了。具体步骤如下:
- 1 定义表情的数据格式,
- 2 在写入字符串时候,加上特效的标记,然后用正则表达去匹配和替换字符。
- 3 遍历整个文本,把之前标记的位置记录下来。并且使用空格去替换标记的内容。
- 4 创建表情对象,把第三步取到的位置信息赋值给刚刚创建的表情对象
核心代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
public class UIEmojiWrapper
{
private bool hasAtlas = false;
public UIAtlas mAtlas;
private GameObject emojiPrefab;
private Dictionary<string, string> emojiName = new Dictionary<string, string>();
private List<Expression> listCfgData = new List<Expression>();
private Queue<GameObject> freeSprite = new Queue<GameObject>();
private List<GameObject> usedSprite = new List<GameObject>();
private Vector3 OutOffScreen = new Vector3(10000f, 10000f, 10000f);
private static UIEmojiWrapper sInstance;
public static UIEmojiWrapper Instance
{
get
{
if (sInstance == null)
{
sInstance = new UIEmojiWrapper();
}
return sInstance;
}
}
public void Init(UIAtlas atlas)
{
if (!hasAtlas)
{
if (atlas == null)
{
Debug.LogError("[UIEmojiWrapper Atlas is null]");
return;
}
mAtlas = atlas;
//预分配
AddEmojiName();
emojiPrefab = new GameObject();
emojiPrefab.layer = 8;
hasAtlas = true;
}
}
public GameObject EmojiPrefab
{
get
{
if (emojiPrefab == null)
{
emojiPrefab = new GameObject();
emojiPrefab.layer = 8;
}
return emojiPrefab;
}
}
//表情字典
public void AddEmojiName()
{
List<Expression> listCfgEmo = SetlistExpression();
if (listCfgEmo == null)
{
return;
}
listCfgData.Clear();
listCfgData = listCfgEmo;
for (int i = 0; i < listCfgEmo.Count; i++)
{
int m_key = (int)listCfgEmo[i].key;
string name = listCfgEmo[i].value;
string sprKey = GetConvertedInt32(m_key.ToString());
emojiName.Add(sprKey, name);
}
}
public string GetConvertedString(string inputString)
{
string[] converted = inputString.Split('-');
for (int j = 0; j < converted.Length; j++)
{
int value = Convert.ToInt32(converted[j], 16);
converted[j] = char.ConvertFromUtf32(value);
}
return string.Join(string.Empty, converted);
}
public string GetConvertedInt32(string inputString)
{
int value = int.Parse(inputString);
string[] converted = new string[1];
converted[0] = char.ConvertFromUtf32(value);
return string.Join(string.Empty, converted);
}
//从对象池取对象
public string GetEmoji(string encode)
{
string em;
if (emojiName.TryGetValue(encode, out em))
{
return em;
}
return null;
}
public bool HasEmoji(string key)
{
// 120000 120001 120002
return emojiName.ContainsKey(key);
}
public List<Expression> GetListCfgData()
{
return listCfgData;
}
// 取表情数据Key
public int GetEmojiKey(string _value)
{
if (listCfgData.Count > 0)
{
for (int i = 0; i < listCfgData.Count; i++)
{
if (_value == listCfgData[i].name)
{
return (int)listCfgData[i].key;
}
}
}
return -1;
}
public void OnPostFill(UIWidget widget, int bufferOffset, BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols)
{
if (widget != null)
{
if (!widget.isVisible)
{
UISprite spt = widget as UISprite;
if (spt != null)
{
PushEmoji(spt.gameObject);
}
}
}
}
public GameObject PopEmoji()
{
if (freeSprite.Count <= 0)
{
if (emojiPrefab == null)
return null;
GameObject tran = GameObject.Instantiate(emojiPrefab) as GameObject;
freeSprite.Enqueue(tran);
}
GameObject sptRet = freeSprite.Dequeue();
if (sptRet != null)
{
usedSprite.Add(sptRet);
}
return sptRet;
}
public void PushEmoji(GameObject spt)
{
//spt.transform.localPosition = OutOffScreen;
// spt.transform.parent = null;
freeSprite.Enqueue(spt);
}
public void PushEmoji(ref List<GameObject> list)
{
for (int i = 0, cnt = list.Count; i < cnt; i++)
{
PushEmoji(list[i]);
}
list.Clear();
}
/// <summary>
/// 字符串替换方法
/// </summary>
/// <param name="myStr">需要替换的字符串</param>
/// <param name="displaceA">需要替换的字符</param>
/// <param name="displaceB">将替换为</param>
/// <returns></returns>
public string Displace(string myStr, string displaceA, string displaceB)
{
string[] strArrayA = Regex.Split(myStr, displaceA);
for (int i = 0; i < strArrayA.Length - 1; i++)
{
strArrayA[i] += displaceB;
}
string returnStr = "";
foreach (string var in strArrayA)
{
returnStr += var;
}
return returnStr;
}
//建议用json或者其他的数据格式
public static List<Expression> SetlistExpression()
{
List<Expression> data = new List<Expression>();
data.Add(new Expression(120000, "ziya", 1, "龇牙"));
data.Add(new Expression(120001, "zhouma", 1, "咒骂"));
data.Add(new Expression(120002, "youxian", 1, "悠闲"));
data.Add(new Expression(120003, "weixiao", 1, "微笑"));
data.Add(new Expression(120004, "qiaoda", 1, "敲打"));
data.Add(new Expression(120005, "nanguo", 1, "难过"));
data.Add(new Expression(120006, "daxiao", 1, "大笑"));
data.Add(new Expression(120008, "deng", 1, "瞪"));
data.Add(new Expression(120009, "dese", 1, "得瑟"));
data.Add(new Expression(120010, "dese", 1, "得意"));
data.Add(new Expression(120011, "lianhong", 1, "脸红"));
data.Add(new Expression(120012, "keai", 1, "可爱"));
data.Add(new Expression(120013, "han", 1, "汗"));
data.Add(new Expression(120014, "ganga", 1, "尴尬"));
data.Add(new Expression(120015, "se", 1, "色"));
data.Add(new Expression(120016, "kun", 1, "困"));
data.Add(new Expression(120017, "ku", 1, "酷"));
data.Add(new Expression(120018, "baiyan", 1, "白眼"));
data.Add(new Expression(120019, "baoya", 1, "龅牙"));
data.Add(new Expression(120020, "beida", 1, "被打"));
return data;
}
}
public class Expression
{
public uint key; // 16进制编号
public string value; // 前缀
public uint sin; // 是否序列帧
public string name; // 名称
public Expression(uint key, string value, uint sin, string name)
{
this.key = key;
this.value = value;
this.sin = sin;
this.name = name;
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
public class UIEmojiLabel : UILabel
{
private char emSpace = '\u2001';
private List<GameObject> mEmojiList = new List<GameObject>();
public UIAtlas emAtlas = null;
protected override void Awake()
{
base.Awake();
UIEmojiWrapper.Instance.Init(emAtlas);
}
protected override void OnStart()
{
base.OnStart();
//表情统一管理器,只初始化一次
}
//自动转码
public override void OnFill(BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols)
{
base.OnFill(verts, uvs, cols);
// StartCoroutine(SetUITextThatHasEmoji(text));
}
public void ShowEmojiText(string strText)
{
//strText = "图形测试 (ziya) 呵呵 (youxian)"
//找到表情特殊标记()
Regex reg = new Regex(@"(?is)(?<=\()[^\)]+(?=\))");
MatchCollection mc = reg.Matches(strText);
if (mc != null)
{
for (int i = 0; i < mc.Count; i++)
{
string oldStr = string.Format("({0})", mc[i].Value); //旧字符
int key = UIEmojiWrapper.Instance.GetEmojiKey(mc[i].Value);
if (key > 0)
{
string newStr = UIEmojiWrapper.Instance.GetConvertedInt32(key.ToString()); //薪字符
strText = strText.Replace(oldStr, newStr);
}
}
}
text = strText;
StartCoroutine(SetUITextThatHasEmoji(strText));
}
private struct PosStringTuple
{
public int pos;
public string emoji;
public PosStringTuple(int p, string s)
{
this.pos = p;
this.emoji = s;
}
}
private void DestroyEmojiSpr()
{
if (this.mEmojiList.Count > 0)
{
for (int i = 0; i < this.mEmojiList.Count; i++)
{
Destroy(this.mEmojiList[i]);
}
this.mEmojiList.Clear();
}
}
public IEnumerator SetUITextThatHasEmoji(string inputString)
{
inputString = inputString.Replace(" ", "&&");
inputString = inputString.Replace(" ", "&");
List<PosStringTuple> emojiReplacements = new List<PosStringTuple>();
StringBuilder sb = new StringBuilder();
//先回收
DestroyEmojiSpr();
int i = 0;
while (i < inputString.Length)
{
string singleChar = inputString.Substring(i, 1);
string doubleChar = "";
string fourChar = "";
if (i < (inputString.Length - 1))
{
doubleChar = inputString.Substring(i, 2);
}
if (i < (inputString.Length - 3))
{
fourChar = inputString.Substring(i, 4);
}
if (UIEmojiWrapper.Instance.HasEmoji(fourChar))
{
// Check 64 bit emojis first
sb.Append(emSpace);
int blankSpaceConut = GetBlankSpaceConut(sb);
emojiReplacements.Add(new PosStringTuple(sb.Length - 1, fourChar));
i += 4;
}
else if (UIEmojiWrapper.Instance.HasEmoji(doubleChar))
{
sb.Append(emSpace);
emojiReplacements.Add(new PosStringTuple(sb.Length - 1, doubleChar));
i += 2;
}
else if (UIEmojiWrapper.Instance.HasEmoji(singleChar))
{
sb.Append(emSpace);
emojiReplacements.Add(new PosStringTuple(sb.Length - 1, singleChar));
i++;
}
else
{
sb.Append(inputString[i]);
i++;
}
}
this.text = sb.ToString();
yield return null;
for (int j = 0; j < emojiReplacements.Count; j++)
{
int emojiIndex = emojiReplacements[j].pos;
//表情替换,计算位置,大小
GameObject go = GameObject.Instantiate(UIEmojiWrapper.Instance.EmojiPrefab);
mEmojiList.Add(go);
if (go != null)
{
UISprite spt = go.AddComponent<UISprite>();
string emoji = UIEmojiWrapper.Instance.GetEmoji(emojiReplacements[j].emoji);
if (!string.IsNullOrEmpty(emoji))
{
spt.atlas = UIEmojiWrapper.Instance.mAtlas;
spt.name = emoji;
spt.spriteName = emoji + "_1";
spt.width = this.ChatPrintedSize;
spt.height = this.ChatPrintedSize;
spt.depth = this.depth + 10;
spt.transform.parent = this.transform;
spt.transform.localScale = Vector3.one;
Vector3 pos = new Vector3();
try
{
pos = new Vector3(this.geometry.verts[emojiIndex * 4].x + spt.width / 2, (this.geometry.verts[emojiIndex * 4].y + spt.height / 2) - 8);
spt.transform.localPosition = pos;
UISpriteAnimation sprAni = go.AddComponent<UISpriteAnimation>();
sprAni.namePrefix = emoji + "_";
sprAni.framesPerSecond = 3;
sprAni.Snap = false;
sprAni.Play();
isVertsBuff = false;
}
catch
{
Debug.LogError("geometry.verts == null");
isVertsBuff = true;
ShowEmojiText(oldText);
}
}
}
}
if (!isVertsBuff)
{
this.text = text.Replace("&&", " ");
this.text = text.Replace("&", " ");
}
}
}
#if !UNITY_3_5 && !UNITY_FLASH
#define DYNAMIC_FONT
#endif
using UnityEngine;
using UnityEditor;
/// <summary>
/// Inspector class used to edit UILabels.
/// </summary>
[CanEditMultipleObjects]
#if UNITY_3_5
[CustomEditor(typeof(UILabel))]
#else
[CustomEditor(typeof(UILabel), true)]
#endif
public class UIEmojiLabelInspector : UILabelInspector
{
/// <summary>
/// Draw the label's properties.
/// </summary>
protected override bool ShouldDrawProperties()
{
bool isValid = base.ShouldDrawProperties();
EditorGUI.BeginDisabledGroup(!isValid);
NGUIEditorTools.DrawProperty("Atlas", serializedObject, "emAtlas");//表情所在的图集Atlas
EditorGUI.EndDisabledGroup();
return isValid;
}
}
主要事项
1 上面的代码改了一下部分NGUI的接口,直接拷贝会报错,你可以自己手动加,都是很简单的接口。我也会上传一份dome
2 在Lua层调C#的时候,有机会出现第一次会报verts为空,我还没有找到原因,欢迎大神指点,我现在的解决方法是延迟执行。
如果大佬们有好的方法,也求推荐。
3 看看 最终效果是带动态的哦!!
表情聊天— 图文混排下载