Unity中TextMeshPro调用SetCharArray产生GC的原因及解决方法
规避TextMeshPro SetCharArray方法GC分配的实用方案
你已经精准定位了问题:SetCharArray内部通过new string(array)生成新字符串实例,这会触发内存分配和后续GC。下面是几种可落地的解决方法:
1. 预缓存目标字符串(适合内容范围有限的场景)
如果你的文本内容是固定范围的(比如有限的数字组合、固定短语),提前把所有可能的字符串实例缓存到内存中,更新时直接赋值给text.text,完全避免动态字符串分配。
比如针对你的测试场景(生成10位随机数字),可以用字典缓存已生成过的数字字符串,重复使用时直接取缓存:
public class TestText : MonoBehaviour { TextMeshProUGUI text; static readonly char[] numbers = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; Dictionary<ulong, string> stringCache = new Dictionary<ulong, string>(); StringBuilder sb = new StringBuilder(10); void Start() { text = GetComponent<TextMeshProUGUI>(); } void Update() { sb.Clear(); ulong key = 0; for (int i = 0; i < 10; i++) { int num = UnityEngine.Random.Range(0, numbers.Length); sb.Append(numbers[num]); key = key * 10 + (ulong)num; } if (!stringCache.TryGetValue(key, out string cachedStr)) { cachedStr = sb.ToString(); stringCache[key] = cachedStr; } text.text = cachedStr; } }
2. 复用StringBuilder构建字符串(降低GC频率)
创建一个全局的StringBuilder实例(设置匹配需求的初始容量),每次更新时清空并重新构建字符,再调用text.text = sb.ToString()。虽然ToString()仍会分配内存,但StringBuilder会复用内部缓冲区,比SetCharArray的分配更高效,且能减少小对象分配的数量:
public class TestText : MonoBehaviour { TextMeshProUGUI text; char[] numbers = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; StringBuilder sb = new StringBuilder(12); // 匹配你的字符数组长度 void Start() { text = GetComponent<TextMeshProUGUI>(); } void Update() { sb.Clear(); for (int i = 0; i < 10; i++) { sb.Append(numbers[UnityEngine.Random.Range(0, numbers.Length)]); } // 填充剩余位置(如果需要) while (sb.Length < 12) { sb.Append(' '); } text.text = sb.ToString(); } }
3. 直接操作TextMeshPro底层字符数据(无GC分配,但需谨慎)
通过反射访问TextMeshPro的私有字段m_internalTextBackingArray,直接修改字符数组后手动标记Dirty,完全避免字符串分配。注意这种方法依赖TMP的内部实现,可能在版本更新后失效:
using System.Reflection; using UnityEngine; using TMPro; public class TestText : MonoBehaviour { TextMeshProUGUI text; char[] numbers = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; FieldInfo internalTextArrayField; char[] internalCharArray; void Start() { text = GetComponent<TextMeshProUGUI>(); // 获取私有字段 internalTextArrayField = typeof(TMP_Text).GetField("m_internalTextBackingArray", BindingFlags.NonPublic | BindingFlags.Instance); internalCharArray = (char[])internalTextArrayField.GetValue(text); // 初始化数组长度 if (internalCharArray.Length < 12) { internalCharArray = new char[12]; internalTextArrayField.SetValue(text, internalCharArray); } } void Update() { for (int i = 0; i < 10; i++) { internalCharArray[i] = numbers[UnityEngine.Random.Range(0, numbers.Length)]; } // 填充剩余位置 for (int i = 10; i < 12; i++) { internalCharArray[i] = ' '; } // 手动标记Dirty触发刷新 text.m_IsTextBackingStringDirty = true; text.PopulateTextProcessingArray(); text.m_havePropertiesChanged = true; text.SetVerticesDirty(); text.SetLayoutDirty(); } }
额外建议
- 尽量减少每帧更新文本的频率,如果不需要实时刷新,改用固定间隔(比如0.1秒)更新,大幅降低GC压力。
- 升级到最新版本的TextMeshPro,Unity官方会持续优化GC分配问题,新版本可能已经改善了相关API的性能。
内容的提问来源于stack exchange,提问作者user25957020
相关产品推荐
相关产品推荐

