方法简答,缺点换行不易处理(需要拆分字符串获取长度换行),且性能较差。
1 public class BattleLogManager : MonoBehaviour
2 {
3
4 public static BattleLogManager Instance;
5
6 public GameObject mContent;
7 // 行预制体
8 public GameObject[] mItemPrefabs;
9 public UISpriteSet mSpriteSet;
10 public Font mFont;
11 public int mFontSize = 35;
12
13 private float mLineWidth = 600;
14
15 void Start()
16 {
17
18 mLineWidth = ((mContent.transform as RectTransform).rect.width);
19 }
20
21 void OnDestroy()
22 {
23 Instance = null;
24 }
25
26 void OnEnable()
27 {
28 Instance = this;
29 }
30
31 // 特别注意:Text本身的RichText标签要过滤掉才能计算字符串宽度从而判断是不是需要换行
32 int AppendText(GameObject go, string str, float offset, out float width, out string richToken)
33 {
34 var objTr = new GameObject("item").transform;
35
36 CharacterInfo ci;
37 var i = 0;
38
39 richToken = "";
40 width = 0.0f;
41 for (; i < str.Length; ++i)
42 {
43 var ch = str[i];
44
45 if (ch == '<' && i < str.Length - 1 && str[i + 1] != '/')
46 {
47 var j = i + 1;
48 for (; j < str.Length; ++j)
49 {
50 if (str[j] == '>')
51 {
52 break;
53 }
54 }
55
56 if (j < str.Length)
57 {
58 richToken = str.Substring(i, j - i + 1);
59 i = j + 1;
60 }
61 }
62
63 if (ch == '<' && i < str.Length - 7 && str[i + 1] == '/')
64 {
65 richToken = "";
66 i += 8;
67 }
68
69 if (i >= str.Length)
70 {
71 if (string.IsNullOrEmpty(richToken))
72 {
73 Global.Log("bad rich format!");
74 }
75 break;
76 }
77
78 ch = str[i];
79
80 mFont.GetCharacterInfo(str[i], out ci);
81 var advance = ci.advance * (mFontSize / 32.0f);
82 if (width + advance + offset > mLineWidth)
83 {
84 break;
85 }
86 width += advance;
87 }
88
89 var newStr = (i == str.Length ? str : str.Substring(0, i)) + (string.IsNullOrEmpty(richToken) ? "" : "</color>");
90
91 objTr.SetParent(go.transform);
92 objTr.localPosition = Vector3.zero;
93 objTr.localScale = Vector3.one;
94 objTr.localRotation = Quaternion.identity;
95
96 var text = objTr.gameObject.AddComponent<UnityEngine.UI.Text>();
97
98 text.font = mFont;
99 text.fontSize = mFontSize;
100 text.alignment = TextAnchor.MiddleCenter;
101 text.text = newStr;
102 text.horizontalOverflow = HorizontalWrapMode.Overflow;
103
104 return i;
105 }
106
107 bool AppendIcon(GameObject go, string icon, float offset, out float width)
108 {
109 Material material;
110 var sprite = mSpriteSet.Get(icon, out material);
111
112 width = 0;
113 if (!sprite)
114 {
115 return true;
116 }
117
118 width = sprite.rect.width;
119
120 if (offset + width > mLineWidth)
121 {
122 return false;
123 }
124 else
125 {
126 var objTr = new GameObject("item").transform;
127
128 objTr.parent = go.transform;
129 objTr.localPosition = Vector3.zero;
130 objTr.localScale = Vector3.one;
131 objTr.localRotation = Quaternion.identity;
132
133 var image = objTr.gameObject.AddComponent<UnityEngine.UI.Image>();
134
135 image.sprite = sprite;
136 image.material = material;
137 image.SetNativeSize();
138 }
139
140 return true;
141 }
142
143 void InnerInsertBattleLog(int itemPrefabIndex, string str, int alignment, float offset)
144 {
145 var itemObjectTr = Instantiate(mItemPrefabs[itemPrefabIndex]).transform;
146
147 itemObjectTr.SetParent(mContent.transform);
148 itemObjectTr.localPosition = Vector3.zero;
149 itemObjectTr.localScale = Vector3.one;
150 itemObjectTr.localRotation = Quaternion.identity;
151 itemObjectTr.GetComponent<UnityEngine.UI.HorizontalLayoutGroup>().childAlignment = (TextAnchor)alignment;
152
153 var textStart = 0;
154
155 for (var i = 0; i < str.Length; )
156 {
157 if (i < str.Length - 1 && str[i] == '$' && str[i + 1] == '<')
158 {
159 var iconStart = i + 2;
160 var iconEnd = -1;
161
162 for (var j = iconStart; j < str.Length; ++j)
163 {
164 if (str[j] == '>')
165 {
166 iconEnd = j;
167 break;
168 }
169 }
170
171 if (iconEnd != -1)
172 {
173 if (textStart != iconStart - 2)
174 {
175 var width = 0.0f;
176 string richToken = null;
177 var subEndIndex = AppendText(itemObjectTr.gameObject, str.Substring(textStart, iconStart - 2 - textStart), offset, out width, out richToken);
178
179 offset += width;
180 if (subEndIndex < iconStart - 2 - textStart)
181 {
182 InnerInsertBattleLog(itemPrefabIndex, richToken + str.Substring(textStart + subEndIndex, str.Length - (textStart + subEndIndex)), alignment, 0);
183 return;
184 }
185 }
186
187 // new line
188 {
189 var width = 0.0f;
190
191 if (!AppendIcon(itemObjectTr.gameObject, str.Substring(iconStart, iconEnd - iconStart), offset, out width))
192 {
193 InnerInsertBattleLog(itemPrefabIndex, str.Substring(iconStart - 2, str.Length - (iconStart - 2)), alignment, 0);
194 return;
195 }
196 offset += width;
197 }
198
199 i = iconEnd + 1;
200 textStart = i;
201 }
202 else
203 {
204 ++i;
205 }
206 }
207 else
208 {
209 ++i;
210 }
211 }
212
213 if (textStart < str.Length)
214 {
215 var width = 0.0f;
216 string richToken = null;
217 var subEndIndex = AppendText(itemObjectTr.gameObject, str.Substring(textStart, str.Length - textStart), offset, out width, out richToken);
218
219 offset += width;
220 if (subEndIndex < str.Length - textStart)
221 {
222 InnerInsertBattleLog(itemPrefabIndex, richToken + str.Substring(textStart + subEndIndex, str.Length - textStart - subEndIndex), alignment, 0);
223 return;
224 }
225 }
226 }
227
228 public void InsertBattleLog(int itemPrefabIndex, string str, int alignment)
229 {
230 InnerInsertBattleLog(itemPrefabIndex, str, alignment, 0);
231 }
232
233 public void ClearAllLog()
234 {
235 for (var i = mContent.transform.childCount - 1; i >= 0; --i)
236 {
237 Destroy(mContent.transform.GetChild(i).gameObject);
238 }
239 }
240 }
241
方法二:
1.利用FontCreator查找多余可利用字符编码。
例如:E7CC-E7D6
2.合成ICON图集,只支持一张,注意:贴图寻址模式必须是 Repeat ,因为ICON uv会被重置为大于1,而字体贴图寻址模式必须是 Clamp 。
例如:
3.使用新的字体材质。
1 fixed4 frag (v2f i) : SV_Target
2 {
3 fixed4 col = i.color;
4 col.a *= tex2D(_MainTex, i.texcoord).a;
5 // 当检测到 uv.x > 1 是说明是被重置的对象 那么选取 Icon 贴图进行显示
6 return lerp(col, tex2D(_IconTex, i.texcoord), step(1, i.texcoord.x));
7 }
4.启动时候重新映射特殊字符UV E7CC-E7D6
1 public class BattleLogManager : MonoBehaviour
2 {
3
4 public static BattleLogManager Instance;
5
6 public UnityEngine.UI.Text mContent;
7
8 private System.Text.StringBuilder mStringBuilder = new System.Text.StringBuilder();
9 private bool mIsDirty = false;
10
11 void OnDestroy()
12 {
13 Instance = null;
14 }
15
16 void OnEnable()
17 {
18 Instance = this;
19 }
20
21 public void InsertBattleLog(int itemPrefabIndex, string str, int alignment)
22 {
23 // 大字符串合并
24 mStringBuilder.AppendLine(str);
25 // mIsDirty 避免在同一帧内多次调用重新设置UI文本
26 mIsDirty = true;
27 }
28
29 public void ClearAllLog()
30 {
31 mStringBuilder = new System.Text.StringBuilder();
32 mContent.text = "";
33 mIsDirty = false;
34 }
35
36 public void LateUpdate()
37 {
38 if (mIsDirty)
39 {
40 mIsDirty = false;
41 mContent.text = mStringBuilder.ToString();
42 }
43 }
44 }
1 public class RichTextConfig : MonoBehaviour
2 {
3
4 public Font font;
5 public int richIconTextureWidth = 256;
6 public int richIconTextureHeight = 128;
7
8 [System.Serializable]
9 public struct Item
10 {
11 public int index;
12 public Sprite sprite;
13 }
14
15 public Item[] items;
16
17 void Start ()
18 {
19 DontDestroyOnLoad(this);
20
21 Build();
22 }
23
24 [ContextMenu("build")]
25 private void Build()
26 {
27 var ci = font.characterInfo;
28
29 for (var i = 0; i < ci.Length; ++i)
30 {
31 for (var j = 0; j < items.Length; ++j)
32 {
33 if (ci[i].index == items[j].index)
34 {
35 var rect = items[j].sprite.textureRect;
36 ci[i].uv = new Rect(2 + rect.x / richIconTextureWidth, rect.y / richIconTextureHeight, rect.width / richIconTextureWidth, rect.height / richIconTextureHeight);
37 }
38 }
39 }
40
41 font.characterInfo = ci;
42 }
43
44 }
45
5.字体材质使用新的材质