如何检测缺失的Sprite导致运行时白图

解读

在国内 Unity 项目里,**白图(白色方块)**是最常见也最让策划和美术“崩溃”的现场表现之一。面试时,面试官真正想确认的是:

  1. 你能否快速定位是“资源丢失”还是“引用丢失”;
  2. 你能否在真机/热更环境自动化捕获并上报,而不是等玩家截图投诉;
  3. 你能否在不污染包体的前提下给出可落地的监控方案
    因此,回答必须覆盖“编辑器预防 → 打包期检测 → 运行时监控 → 工具化上报”四个环节,并给出可复制的代码片段

知识点

  • Sprite 资源生命周期:Importer → Atlas → AssetBundle/Addressable → Runtime SpriteRenderer/Image
  • 白图根因
    – 打包时 Sprite 被裁剪或未打入包(AssetBundle 冗余收集规则遗漏)
    – 运行时异步加载失败(Addressable 的 AsyncOperation.Status != Succeeded)
    图集丢失(SpriteAtlasManager.atlasRequested 未正确处理)
    脚本引用丢失(Missing Reference,并非 null,== 判断为 false 但 != null)
  • 检测手段
    – 编辑器预检:AssetDatabase.GetDependencies + SpriteUtility 遍历 UI Atlas 引用
    – 打包后检测:AssetBundleManifest 比对 Sprite 的 GUID 与文件哈希
    – 运行时监控:SpriteRenderer.sprite == null 或 Image.sprite == null 且 不是 Missing Reference;对图集资源监听 SpriteAtlasManager.atlasRequested 超时
    – 真机上报:通过反射获取 Unity 内部缺失资源日志(Unity 2021.2+ 可用 UnityEngine.Logs.LogDriver.logMessageReceived)捕获 “Sprite is NULL” 关键字,结合符号表定位到具体 UI 预制
  • 性能与包体:检测代码必须走条件编译(#if ENABLE_SPRITE_MISSING_CHECK)并在 Release 包中自动降级为仅上报,不执行高频轮询

答案

我采用“三道闸门”方案,把白图消灭在上线前和灰度阶段。

  1. 编辑器闸门——预制体批量预检
    写一个 EditorWindow,递归遍历所有 *.prefab 和 *.unity 文件,对里面所有 SpriteRenderer、Image 组件使用
    PrefabUtility.GetPropertyModifications 拿到 sprite 属性,再判断 AssetDatabase.Contains(sprite) 是否为 false;
    如果是 Addressable 资源,则检查 AssetReferenceSprite.RuntimeKeyIsValid() 是否为 true。
    每天 Jenkins 构建前跑一次,失败即中断打包,把缺失列表以 Excel 形式飞书机器人推送给美术。

  2. 打包闸门——AssetBundle 差异比对
    在打包脚本里,把收集到的 Sprite 资源路径与上一次构建的 manifest 做交叉对比;
    如果本次某个 UI 面板所依赖的 Sprite 的 CRC 变化为 0(说明被裁剪),则抛出 BuildFailedException 并高亮提示。
    这样能在出包前就拦截“资源存在但打不进去”的情况。

  3. 运行时闸门——真机白图秒级上报
    在首包/热更后,启动一个 MonoBehaviour 巡检协程,每 5 帧采样一次当前所有 Canvas 下动态实例化的 Image:

    if (img.sprite == null && img.mainTexture == null && !IsMissingReference(img.sprite))
        ReportSpriteMissing(img.gameObject);
    

    对 SpriteRenderer 同理。
    上报内容包含:场景名、UI 路径、GameObject 的 InstanceID、当前版本号、渠道包名,通过腾讯 Bugly 或字节 MARS 实时回捞
    为避免误报,图集资源单独处理:注册 SpriteAtlasManager.atlasRequested 回调,若 1 秒内未成功绑定图集,同样上报“图集丢失”事件。

    整个检测模块放在 Assembly Definition Runtime.Check 中,Release 包用 #if ENABLE_SPRITE_MISSING_CHECK 宏控制,默认开启但采样频率降到 30s,对帧率无感知。

上线三个月内,我们项目白图投诉从 每日 20+ 降到 0,并且能在 5 分钟内定位到具体美术资源,完全满足国内快节奏版本迭代

拓展思考

  • 如何检测动态换肤导致的白图?
    换肤系统往往通过“资源名+后缀”拼接路径,如果拼写大小写不一致,Windows 编辑器能加载,Linux 真机就白图。
    解决:在编辑器下强制开启 AssetDatabase.ForceReserialize 把路径统一小写,并在运行时加载前用 ResourceManager.LocateObject 预判断,不存在立即回退默认资源并上报。

  • 如何防止 Addressable 热更后 Sprite 被提前卸载?
    使用 AssetReferenceSprite.ReleaseInstance 而不是直接置 null,避免引用计数为 0 被卸载;
    同时给高频 UI 做常驻池,通过 ResourceLocationMap.Locate 把依赖的 Sprite 打进同一个 group,降低冗余

  • 如何在 WebGL 端检测白图?
    WebGL 的纹理上传是异步的,Texture2D.isReadable 为 false 时无法回读;
    可在 Shader 中做采样 fallback:如果采样结果 a==1 且 r+g+b==3(纯白),则把颜色替换为洋红,并在 JS 侧通过 canvas.toDataURL 截帧自动识别洋红色占比,超过阈值即上报。
    这样无需修改 C# 代码,也能在浏览器控制台实时看到白图触发。

  • 面试加分项:提到 Unity 2022.3 新增的 “Sprite Missing” 回调实验接口,并说明你会持续跟进官方 Roadmap,体现你对引擎源码的敏感度。