#01 Scanner

Tech Unity C#, Editor Scripting, Batch Mode, Newtonsoft.Json, SHA256 AI Claude Code

源起

專案需要一套 Unity 素材庫管理系統,讓遊戲自動建立流程可以從中取用素材。整個系統分三段:Scanner 掃 Unity 專案裡的素材產出 manifest,Python Importer 從 manifest 下載素材,中間用 JSON 溝通。Scanner 是起點,沒它其他東西都動不了。

設計

Scanner 的工作是遍歷指定資料夾、識別 Prefab 和獨立資源、指派穩定 ID、產出 manifest_raw.json,同時附上縮圖。幾個關鍵決策:

兩階段掃描。 縮圖需要 GPU(AssetPreview),但元資料掃描不需要。分開成 Phase 1(-nographics)和 Phase 2 讓 Batch Mode 跑得更乾淨,也讓 CI 有機會只跑 Phase 1。

ID 系統。 categoryCode × 100000 + 流水號,與 AssetHub 共用同一套。ID 跟 GUID 的對應關係存在 id_registry.json,重新掃描時優先沿用舊 ID,不會因為掃描順序改變而跳號。

Hash Cache。 掃描時計算每個檔案的 SHA256 作為 content_hash,同時把 GUID → Hash 的結果快取在記憶體。同一個 Prefab 的依賴檔案可能被多個條目共用,快取避免重複讀檔。

排除規則。 一開始設計了 HiddenFolders 黑名單,後來發現跟 ignorePatterns 功能重疊,直接合併成一組 glob 規則,設定介面也因此簡化。

遵循 EAS Foundation 模式。 所有 JSON I/O 透過 FileHandler + IFilePath + IFileParser 封裝,不在業務邏輯裡直接讀寫檔案。

整體模組結構:

Assets/Editor/AssetLibrary/
├── AssetLibraryScanner.cs       ← Scan / GenerateThumbnails 兩個靜態入口
├── ScannerConfigEditor.cs       ← EditorWindow
├── CatalogueFileHandler.cs      ← _Catalogue/ JSON I/O
├── Core/
│   ├── ScannerConfig.cs         ← CategoryEntry + ScannerConfig
│   ├── IdRegistry.cs            ← GUID → ID 映射
│   ├── IgnorePatternMatcher.cs  ← 排除規則
│   ├── DependencyResolver.cs    ← 依賴解析
│   ├── BoundsCalculator.cs      ← Prefab bounds
│   ├── TagGenerator.cs          ← 自動 tag 生成
│   ├── HashCalculator.cs        ← content_hash (SHA256)
│   └── ThumbnailGenerator.cs    ← 縮圖生成
└── Data/
    └── ManifestEntry.cs         ← Manifest 資料模型

實現

首次掃描讓 Unity 凍結。 第一版沒有進度條也沒有快取,跑到一半 Editor 就沒有回應。加了 EditorUtility.DisplayProgressBar 和 Hash Cache 之後,掃描期間 UI 雖然卡頓但不會死當,Batch Mode 更是完全沒問題。

縮圖的 Texture not readable 錯誤。 AssetPreview.GetMiniThumbnail 吐回來的 Texture2D 預設不可讀,直接 EncodeToPNG 會炸。解法是先把原始 texture blit 到 RenderTexture,再用 Texture2D.ReadPixels 讀回來,之後就能正常編碼輸出。這個 ToReadableTexture() 步驟是縮圖流程的關鍵:

var rt = RenderTexture.GetTemporary(tex.width, tex.height);
Graphics.Blit(tex, rt);
var readable = new Texture2D(tex.width, tex.height);
readable.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
readable.Apply();
RenderTexture.ReleaseTemporary(rt);

UI 架構從單頁改成兩頁。 早期把分類設定和掃描設定塞在同一個 EditorWindow,欄位一多就很擠。參考 BuildTool 的側邊欄風格改成左側分頁切換,「素材分類」和「掃描設定」各自一頁,左下角放動作按鈕。素材分類使用 EAS Foundation 的 ReorderableTable<T>,四欄:Code、Label、AssetTypes、EntryType。

TagGenerator 的 PascalCase 問題。 拆字邏輯把 SciFiWorlds 拆成 scifiworlds 三個 token,fi 單獨成詞沒有意義。這是已知問題,等 LLM 分析模組上來之後,自動 tag 的角色會大幅降低,目前先留著。

Batch Mode 驗證。 最後用 Batch Mode 跑了完整的兩階段驗證:

Phase 1 命令:

Unity.exe -batchmode -nographics -projectPath <path> -executeMethod AssetLibraryScanner.Scan -quit

Phase 2 命令:

Unity.exe -batchmode -projectPath <path> -executeMethod AssetLibraryScanner.GenerateThumbnails -quit

尾聲

指標數值
Phase 1 掃描時間18.9 秒
Phase 2 縮圖時間277 秒
Prefab 條目2,623
Resource 條目49
總條目2,672
縮圖成功率100%
ID registry3,664 筆
manifest_raw.json2.9 MB

Scanner 作為整個系統的第一段,現在可以穩定在 Batch Mode 跑完兩階段。下一步是 Web Server 和 Python Importer 的 Server 端。