You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 23:35:21