Unity重建SVG Sprite报错:Not allowed to override geometry on sprite
Unity SpriteRenderer更新Sprite时的竞态条件问题
在动态创建带图标的GameObject流程中,首次运行正常,但远程更新SVG文件后重建对象时,会触发Not allowed to override geometry on sprite错误,同时出现999+条Incomplete mesh data in Sprite. Please reimport or recreate Sprite.警告。
问题复现代码
创建GameObject的核心逻辑:
for (int i = 0; i < data.Count; i++) { GameObject obj = new(data[i].Details.UID.ToString()); obj.transform.SetParent(itemsContainer.transform); obj.AddComponent<RectTransform>(); SelectionItem levelViewItem = obj.AddComponent<SelectionItem>(); GameObject icon = new("icon"); RectTransform iconTransform = icon.AddComponent<RectTransform>(); icon.transform.SetParent(obj.transform); SpriteRenderer iconSprite = icon.AddComponent<SpriteRenderer>(); iconSprite.sprite = data[i].Details.LoadIcon(); // 从磁盘加载并生成Sprite levelViewItem.icon = iconSprite; temp[i] = levelViewItem; }
临时解决方案(存在延迟问题):
移除循环内的Sprite赋值,延迟2秒后批量设置:
await Task.Delay(2000); foreach (var item in temp) { SpriteRenderer ren = item.transform.GetChild(0).GetComponent<SpriteRenderer>(); ren.sprite = books[0].Details.LoadIcon(); }
SVG转Sprite的核心代码
public static Sprite ProcessSvg(string svgFilePath, string name) { int pixelsPerUnit = 1; bool flipYAxis = true; if (!string.IsNullOrEmpty(svgFilePath)) { using (StreamReader textReader = File.OpenText(svgFilePath)) { var sceneInfo = SVGParser.ImportSVG(textReader); List<Geometry> geometries = TessellateScene(sceneInfo.Scene, new TessellationOptions { StepDistance = 1f, SamplingStepSize = 1, MaxCordDeviation = 0, MaxTanAngleDeviation = 0 }); Sprite sprite = BuildSprite(geometries, pixelsPerUnit, Alignment.Center, Vector2.zero, 128, flipYAxis); sprite.name = name; return sprite; } } else { Debug.LogError("SVG content is empty!"); return null; } }
问题原因分析
- 同步循环中,SVG解析、Sprite生成与GameObject初始化同时进行,Sprite的网格数据尚未完全构建完成,就被Unity渲染线程尝试读取,导致数据不完整。
- 重建对象时,旧Sprite可能仍被未销毁的对象引用,或未被垃圾回收,新Sprite的几何数据被错误覆盖,触发
Not allowed to override geometry on sprite错误。
解决方案
1. 异步生成Sprite,主线程赋值
先完成所有GameObject的结构创建,再在后台线程批量生成Sprite,最后回到主线程赋值(Unity API必须在主线程调用):
// 第一步:创建所有UI结构,暂不设置Sprite for (int i = 0; i < data.Count; i++) { GameObject obj = new(data[i].Details.UID.ToString()); obj.transform.SetParent(itemsContainer.transform); obj.AddComponent<RectTransform>(); SelectionItem levelViewItem = obj.AddComponent<SelectionItem>(); GameObject icon = new("icon"); RectTransform iconTransform = icon.AddComponent<RectTransform>(); icon.transform.SetParent(obj.transform); SpriteRenderer iconSprite = icon.AddComponent<SpriteRenderer>(); levelViewItem.icon = iconSprite; temp[i] = levelViewItem; } // 第二步:后台线程批量生成Sprite await Task.Run(() => { var spriteCache = new Dictionary<string, Sprite>(); foreach (var itemData in data) { string uid = itemData.Details.UID.ToString(); if (!spriteCache.ContainsKey(uid)) { spriteCache[uid] = itemData.Details.LoadIcon(); } } // 回到主线程赋值 UnityMainThreadDispatcher.Instance.Enqueue(() => { for (int i = 0; i < temp.Length; i++) { string uid = data[i].Details.UID.ToString(); temp[i].icon.sprite = spriteCache[uid]; } }); });
注:需要实现UnityMainThreadDispatcher单例,用于在后台线程完成后将任务排入主线程执行(可通过Update方法遍历任务队列实现)。
2. 重建前彻底清理旧资源
在重建对象前,销毁所有旧对象并强制回收未使用资源,避免旧Sprite干扰:
// 销毁容器内所有旧对象 foreach (Transform child in itemsContainer.transform) { Destroy(child.gameObject); } // 强制回收未使用资源 Resources.UnloadUnusedAssets(); GC.Collect();
3. 优化Sprite生成的有效性检查
在ProcessSvg方法中,添加Sprite有效性检查,确保返回的Sprite数据完整:
Sprite sprite = BuildSprite(geometries, pixelsPerUnit, Alignment.Center, Vector2.zero, 128, flipYAxis); sprite.name = name; // 检查Sprite是否有效 if (sprite == null || sprite.texture == null || sprite.rect.width <= 0 || sprite.rect.height <= 0) { Debug.LogError($"生成无效Sprite:{name}"); return null; } return sprite;
内容的提问来源于stack exchange,提问作者Ando
相关产品推荐
相关产品推荐

