:自動化清理冗余資源與構(gòu)建產(chǎn)物的完整方案)
1. 項目概述為什么UE項目會“發(fā)胖”如果你是一個Unreal Engine的深度用戶無論是獨立開發(fā)者還是團(tuán)隊中的技術(shù)美術(shù)大概率都經(jīng)歷過這樣的場景項目開發(fā)到中后期每次打開編輯器都要等上好幾分鐘硬盤空間像被黑洞吞噬一樣飛速減少一個看似普通的項目動輒占用幾十甚至上百GB。更頭疼的是當(dāng)你嘗試打包Package項目時漫長的等待后可能因為磁盤空間不足而失敗。這一切的罪魁禍?zhǔn)淄皇悄愕暮诵膬?nèi)容而是項目中堆積如山的“冗余資源”。所謂冗余資源就像你家里那些“總覺得以后會用上”但實際上幾年都沒碰過的舊物。在UE項目中它們主要包括未引用資產(chǎn)在內(nèi)容瀏覽器中靜靜地躺著但沒有任何關(guān)卡、藍(lán)圖、材質(zhì)或數(shù)據(jù)表引用到它們??赡苁窃缙诘鷱U棄的模型、測試用的音效、或者導(dǎo)入后忘記刪除的臨時文件。派生緩存文件UE為了加速編輯和預(yù)覽會為原始資源如靜態(tài)網(wǎng)格體、紋理生成大量中間格式的緩存文件例如DerivedDataCacheDDC和Intermediate文件夾下的內(nèi)容。這些文件體積龐大且在項目遷移或引擎版本升級后可能失效。舊版本備份啟用源代碼控制如Perforce、Git LFS后本地工作區(qū)可能會保留舊版本的文件副本。平臺特定的構(gòu)建產(chǎn)物為不同平臺Windows、Android等構(gòu)建后留下的Saved、Binaries、Build目錄文件尤其是開發(fā)Development配置的構(gòu)建包含大量調(diào)試符號體積驚人。手動清理這些資源是一項繁瑣且高風(fēng)險的工作。你需要小心翼翼地檢查引用關(guān)系避免誤刪關(guān)鍵資產(chǎn)需要知道哪些緩存可以安全清除還需要處理不同平臺構(gòu)建的殘留。這個過程既耗時又容易出錯。因此一個自動化、智能化且安全的清理工具就成了提升開發(fā)效率和維護(hù)項目健康的剛需。這正是“ProjectCleaner”這類工具誕生的背景——它不是一個簡單的刪除腳本而是一個專為UE項目“瘦身健體”設(shè)計的綜合解決方案。2. 核心思路與工具選型為什么是ProjectCleaner面對項目臃腫的問題社區(qū)和官方都提供過一些方法比如手動刪除Saved、Intermediate文件夾或者使用編輯器內(nèi)置的“引用查看器”和“遷移工具”來輔助分析。但這些方法要么太粗暴可能誤刪必要文件要么太零散效率低下。ProjectCleaner的設(shè)計思路是提供一個集中、自動且可配置的清理流程。它的核心功能通常圍繞以下幾個模塊構(gòu)建資產(chǎn)依賴關(guān)系分析這是清理的基石。工具需要深度掃描整個Content目錄構(gòu)建一個資產(chǎn)引用關(guān)系圖。通過這張圖可以準(zhǔn)確識別出那些沒有任何入度即沒有被任何其他資產(chǎn)引用的“孤兒資產(chǎn)”。一個健壯的工具還會考慮間接引用比如通過藍(lán)圖基類、數(shù)據(jù)表或游戲?qū)嵗M(jìn)行的引用。多維度垃圾識別除了未引用資產(chǎn)工具還應(yīng)能識別空文件夾清理后殘留的目錄結(jié)構(gòu)。無效或損壞的資產(chǎn)導(dǎo)入失敗或引擎無法識別的文件。特定類型的冗余文件例如僅用于編輯器預(yù)覽的巨型紋理_UE4_Thumbnail或者Shader編譯產(chǎn)生的中間文件。安全隔離與預(yù)覽直接刪除是危險的。優(yōu)秀的工具會提供一個“預(yù)覽模式”列出所有將被清理的項目并允許用戶手動排除某些資產(chǎn)。更好的做法是先將資產(chǎn)移動到項目內(nèi)的一個臨時隔離區(qū)如ToBeDeleted文件夾確認(rèn)項目運行無誤后再行永久刪除。緩存與構(gòu)建產(chǎn)物清理提供選項讓用戶選擇清理DerivedDataCache、Intermediate、Saved下的特定子目錄如Cooked、Binaries甚至包括Plugins文件夾下的中間文件。與工作流集成支持命令行調(diào)用便于集成到CI/CD流水線中在 nightly build 或發(fā)布前自動執(zhí)行清理任務(wù)。市面上有一些工具如免費的“Asset Cleaner”插件或一些開源腳本。但“ProjectCleaner”通常指代一類更集成、功能更全面的工具或自定義方案。在選型或自建時你需要權(quán)衡準(zhǔn)確性依賴分析算法是否可靠是否會誤判系統(tǒng)關(guān)鍵資產(chǎn)如GameInstance藍(lán)圖、項目設(shè)置引用的資產(chǎn)安全性是否有隔離和回滾機(jī)制性能掃描大型項目數(shù)萬資產(chǎn)的速度如何可配置性能否自定義清理規(guī)則、忽略列表如永遠(yuǎn)不想掃描的文件夾基于這些考量一個理想的ProjectCleaner實現(xiàn)往往會結(jié)合UE提供的命令行工具如AssetRegistry查詢和自定義的C/Python腳本來實現(xiàn)深度掃描與處理。3. 實戰(zhàn)部署構(gòu)建你自己的ProjectCleaner工作流下面我將以一個結(jié)合了UE Editor Utility Widget編輯器工具和Python腳本的混合方案為例拆解一個高可用ProjectCleaner的實現(xiàn)步驟。這個方案平衡了易用性、安全性和靈活性。3.1 環(huán)境準(zhǔn)備與項目設(shè)置首先你需要在UE項目中啟用必要的模塊和插件。啟用Python插件Unreal Engine內(nèi)置了Python支持這是實現(xiàn)自動化腳本的關(guān)鍵。在編輯器菜單欄點擊“編輯” - “插件”在搜索框中輸入“Python”確?!癙ython Editor Script Plugin”和“Editor Scripting Utilities”已啟用并重啟編輯器。創(chuàng)建工具目錄在你的項目目錄下創(chuàng)建一個清晰的結(jié)構(gòu)來管理清理工具。例如YourProject/ ├── Content/ │ └── ... ├── Source/ │ └── ... └── Tools/ ├── ProjectCleaner/ │ ├── Python/ # 存放核心掃描邏輯的Python腳本 │ ├── Utilities/ # 存放Editor Utility Widget藍(lán)圖 │ └── Config/ # 存放忽略列表等配置文件 └── ...編寫核心Python掃描腳本在Tools/ProjectCleaner/Python/下創(chuàng)建asset_scanner.py。這個腳本的核心是利用UE的Python API來獲取資產(chǎn)注冊表Asset Registry信息。# asset_scanner.py import unreal import json import os from collections import defaultdict def find_unreferenced_assets(): 核心函數(shù)查找未被任何其他資產(chǎn)引用的資產(chǎn)。 返回一個字典包含未引用資產(chǎn)列表和引用關(guān)系數(shù)據(jù)。 print(開始掃描資產(chǎn)引用關(guān)系...) # 獲取資產(chǎn)注冊表子系統(tǒng) asset_registry unreal.AssetRegistryHelpers.get_asset_registry() # 獲取所有資產(chǎn)數(shù)據(jù)過濾掉引擎內(nèi)容等 package_path /Game # 掃描項目Content目錄 asset_datas asset_registry.get_assets_by_path(package_path, recursiveTrue) # 構(gòu)建引用關(guān)系圖 reference_graph defaultdict(set) # key: 被引用資產(chǎn), value: 引用它的資產(chǎn)集合 referencer_graph defaultdict(set) # key: 引用資產(chǎn), value: 它引用的資產(chǎn)集合 total_assets len(asset_datas) print(f共發(fā)現(xiàn) {total_assets} 個資產(chǎn)正在分析引用關(guān)系...) for i, asset_data in enumerate(asset_datas): if i % 1000 0: print(f分析進(jìn)度: {i}/{total_assets}) asset_package_name asset_data.package_name # 獲取該資產(chǎn)引用了哪些資產(chǎn) references asset_registry.get_referencers(asset_package_name, unreal.AssetRegistryDependencyOptions()) for ref in references: reference_graph[ref].add(asset_package_name) referencer_graph[asset_package_name].add(ref) # 找出未被任何資產(chǎn)引用的“根節(jié)點”即入度為0的資產(chǎn) unreferenced [] for asset_data in asset_datas: asset_package_name asset_data.package_name if asset_package_name not in reference_graph: # 注意需要排除一些特殊資產(chǎn)如默認(rèn)地圖、游戲?qū)嵗?if not _is_system_asset(asset_package_name): unreferenced.append(str(asset_package_name)) print(f掃描完成。發(fā)現(xiàn) {len(unreferenced)} 個未被引用的資產(chǎn)。) return { unreferenced_assets: unreferenced, total_scanned: total_assets } def _is_system_asset(package_name: str) - bool: 判斷一個資產(chǎn)是否為系統(tǒng)關(guān)鍵資產(chǎn)不應(yīng)被清理。 system_keywords [ /Game/Maps/, # 默認(rèn)地圖可能被項目設(shè)置引用 /Game/Blueprints/GameInstance, /Game/Config/, # 你可以在這里添加更多需要忽略的路徑模式 ] for keyword in system_keywords: if keyword in package_name: return True return False if __name__ __main__: # 當(dāng)腳本獨立運行時執(zhí)行掃描并輸出結(jié)果到JSON文件 result find_unreferenced_assets() output_path os.path.join(os.path.dirname(__file__), .., Output, unreferenced.json) os.makedirs(os.path.dirname(output_path), exist_okTrue) with open(output_path, w) as f: json.dump(result, f, indent4) print(f結(jié)果已保存至: {output_path})注意直接使用get_referencers可能無法捕獲所有類型的引用例如通過C代碼硬編碼的引用、項目設(shè)置中的默認(rèn)地圖。在生產(chǎn)環(huán)境中你可能需要結(jié)合多種方法例如額外檢查DefaultEngine.ini配置文件中的引用。3.2 創(chuàng)建可視化清理工具Editor Utility Widget為了讓非程序員也能安全使用我們創(chuàng)建一個簡單的編輯器界面。創(chuàng)建Editor Utility Widget在內(nèi)容瀏覽器中右鍵 -“編輯器工具集” - “編輯器工具集部件Editor Utility Widget”命名為WBP_ProjectCleaner。設(shè)計UI打開這個Widget拖入以下控件一個Button文本為“開始掃描”點擊后調(diào)用Python腳本。一個ListView或TreeView用于顯示掃描出的未引用資產(chǎn)列表。每個列表項旁有一個CheckBox用于選擇是否清理該資產(chǎn)。一個Button文本為“移動到隔離區(qū)”用于執(zhí)行安全清理。幾個CheckBox選項“清理空文件夾”、“清理DerivedDataCache”、“清理Intermediate目錄”。編寫藍(lán)圖邏輯“開始掃描”按鈕其點擊事件中使用“執(zhí)行Python腳本”節(jié)點調(diào)用我們上面寫的asset_scanner.py。然后讀取生成的unreferenced.json文件將資產(chǎn)列表填充到ListView中。“移動到隔離區(qū)”按鈕其點擊事件中遍歷所有被選中的資產(chǎn)使用“復(fù)制資產(chǎn)”節(jié)點將它們復(fù)制到項目內(nèi)一個預(yù)設(shè)的Content/ToBeDeleted/目錄下然后使用“刪除資產(chǎn)”節(jié)點刪除原始資產(chǎn)。務(wù)必先復(fù)制再刪除這是安全底線。其他清理選項對于“清理DDC”等可以使用“執(zhí)行控制臺命令”節(jié)點運行命令如r.cleardderiveddatacache需確認(rèn)命令可用性或者直接調(diào)用Python的shutil.rmtree來刪除Saved/DerivedDataCache目錄建議在編輯器關(guān)閉時進(jìn)行。3.3 配置忽略列表與規(guī)則在Tools/ProjectCleaner/Config/下創(chuàng)建ignore_list.json讓工具更智能。{ ignore_paths: [ /Game/Art/Common/MasterMaterials/*, // 永遠(yuǎn)不要掃描的材質(zhì)函數(shù)庫 /Game/Core/UI/Fonts/*, // 字體文件可能被動態(tài)加載 /Game/Config/* // 配置文件 ], ignore_patterns: [ *_BuiltData*, // 某些插件生成的數(shù)據(jù) */Developers/* // 開發(fā)者目錄下的內(nèi)容 ], protected_asset_classes: [ /Script/Engine.World, // 地圖資產(chǎn) /Script/Engine.GameInstance // 游戲?qū)嵗{(lán)圖 ] }在你的Python掃描腳本中在判斷_is_system_asset函數(shù)時加入對這個忽略列表的讀取和匹配邏輯。4. 深度清理超越未引用資產(chǎn)一個專業(yè)的ProjectCleaner不應(yīng)止步于未引用資產(chǎn)。項目空間的“水分”還藏在其他地方。4.1 清理派生數(shù)據(jù)緩存DDC與中間文件DDC是UE性能的利器但也是空間的殺手。它存儲了針對你本地顯卡驅(qū)動、引擎版本編譯的Shader、網(wǎng)格體數(shù)據(jù)等。當(dāng)你升級了顯卡驅(qū)動或切換了開發(fā)設(shè)備舊的DDC就可能失效。清理它們是安全的但會導(dǎo)致下次打開項目時重新編譯耗時。手動/腳本清理最簡單的方法是關(guān)閉UE編輯器直接刪除項目目錄下的Saved/DerivedDataCache文件夾。你也可以在Python腳本中添加如下函數(shù)import shutil def clean_derived_data_cache(project_path): ddc_path os.path.join(project_path, Saved, DerivedDataCache) if os.path.exists(ddc_path): shutil.rmtree(ddc_path) print(f已刪除DDC: {ddc_path}) # 也可以清理Intermediate intermediate_path os.path.join(project_path, Intermediate) if os.path.exists(intermediate_path): shutil.rmtree(intermediate_path) print(f已刪除Intermediate: {intermediate_path})編輯器內(nèi)命令在編輯器輸出日志Output Log中輸入r.cleardderiveddatacache可以清理一部分DDC但可能不徹底。4.2 處理平臺構(gòu)建產(chǎn)物為不同平臺打包后Saved目錄下會生成Cooked、StagedBuilds等文件夾Binaries和Build目錄也會膨脹。清理Cooked數(shù)據(jù)Saved/Cooked/目錄存放著針對特定平臺的資源烹飪結(jié)果。如果你短期內(nèi)不再需要為該平臺打包可以安全刪除對應(yīng)的子文件夾如Saved/Cooked/Windows。清理構(gòu)建中間文件Binaries/和Build/目錄下的.obj、.pdb等文件在重新構(gòu)建時會再生。使用Visual Studio的“清理解決方案”功能或直接刪除這些文件夾需要隨后在IDE中重新生成項目文件可以釋放大量空間。4.3 識別與處理重復(fù)資產(chǎn)有時同一個資源可能被以不同的名稱或路徑導(dǎo)入了多次。手動查找非常困難。你可以擴(kuò)展Python腳本通過計算資產(chǎn)的哈希值如文件MD5或比較其導(dǎo)入設(shè)置和源文件路徑來識別重復(fù)項。UE的Python API可能不直接提供哈希但你可以通過unreal.EditorAssetLibrary.get_metadata_tag獲取一些唯一性標(biāo)識進(jìn)行初步比對更精確的方法需要調(diào)用外部工具或讀取文件二進(jìn)制。5. 集成到CI/CD與自動化流程對于團(tuán)隊項目將清理作為自動化流程的一部分至關(guān)重要。創(chuàng)建批處理腳本編寫一個.batWindows或.shLinux/macOS腳本按順序執(zhí)行清理任務(wù)。echo off REM cleanup_script.bat set PROJECT_PATHD:\YourUnrealProject set UE_EDITORC:\Program Files\Epic Games\UE_5.3\Engine\Binaries\Win64\UnrealEditor-Cmd.exe echo Step 1: 運行Python腳本分析未引用資產(chǎn)需在編輯器外運行 python %PROJECT_PATH%\Tools\ProjectCleaner\Python\asset_scanner.py echo Step 2: 使用UE命令行工具運行一個特定的Editor Utility Widget如果工具已集成到插件中 %UE_EDITOR% %PROJECT_PATH%\YourProject.uproject -runWBP_ProjectCleaner.PerformCleanup -unattended -noshadercompile echo Step 3: 清理DDC和Intermediate在編輯器關(guān)閉后進(jìn)行 rmdir /s /q %PROJECT_PATH%\Saved\DerivedDataCache rmdir /s /q %PROJECT_PATH%\Intermediate echo 清理完成。 pause在CI流水線中調(diào)用在Jenkins、GitLab CI等工具的配置中在構(gòu)建步驟Build之前添加一個“清理工作區(qū)”的步驟調(diào)用上述腳本。確保此步驟配置在獲取最新代碼之后這樣每次構(gòu)建都在一個“干凈”的項目基礎(chǔ)上進(jìn)行避免殘留文件干擾。版本控制忽略設(shè)置確保你的.gitignore或Perforce忽略列表包含了不需要版本控制的文件從源頭上減少冗余文件被提交的可能。一個標(biāo)準(zhǔn)的UE項目.gitignore應(yīng)包含# 編譯生成文件 Binaries/ Build/ Intermediate/ Saved/ DerivedDataCache/ *.sln *.vcxproj *.vcxproj.filters # 平臺特定文件 *.app *.ipa *.apk # 其他 .vs/ .idea/ *.opendb實操心得對于Saved文件夾團(tuán)隊有時會選擇性提交Saved/Config下的項目設(shè)置文件。因此更精確的做法是忽略Saved下的其他子文件夾如Saved/Cooked、Saved/StagedBuilds、Saved/Autosaves等而保留Saved/Config。6. 常見問題、排查與避坑指南即使有了自動化工具清理工作仍需謹(jǐn)慎。以下是我在實際操作中積累的一些經(jīng)驗和常見問題的解決方法。6.1 資產(chǎn)誤刪與恢復(fù)問題工具錯誤地將一個正在使用的材質(zhì)或藍(lán)圖判定為未引用并將其刪除導(dǎo)致游戲運行時出現(xiàn)粉紅錯誤Missing Asset。排查與解決立即檢查隔離區(qū)如果你的工具設(shè)計了隔離區(qū)Content/ToBeDeleted第一時間去這里找回被誤刪的資產(chǎn)直接拖回原位置即可。檢查引用分析邏輯軟引用Soft Reference你的掃描腳本是否正確處理了軟引用軟引用如通過SoftObjectPtr或資產(chǎn)路徑字符串加載在資產(chǎn)注冊表中可能不會顯示為硬依賴。你需要額外解析資產(chǎn)文件如藍(lán)圖的文本源文件*.asset來查找字符串形式的路徑引用。這大大增加了復(fù)雜度也是許多簡單清理工具不準(zhǔn)確的原因。代碼中的引用C代碼中通過ConstructorHelpers::FObjectFinder或FSoftObjectPath加載的資產(chǎn)不會被資產(chǎn)注冊表捕獲。這部分需要人工審計代碼并將這些資產(chǎn)路徑加入忽略列表。版本控制是你的安全網(wǎng)在執(zhí)行大規(guī)模清理前務(wù)必提交Commit所有更改到版本控制系統(tǒng)。一旦發(fā)生誤刪可以立即回滾Revert到清理前的狀態(tài)。這是最可靠的安全措施。6.2 清理后編輯器變慢或Shader編譯卡頓問題清理了DDC和Intermediate后再次打開項目編輯器響應(yīng)緩慢且長時間顯示“編譯著色器”。原因與對策這是正?,F(xiàn)象。DDC的清理導(dǎo)致引擎需要重新為所有材質(zhì)和網(wǎng)格體編譯著色器。對策如下分批清理不要在緊要關(guān)頭如打包發(fā)布前清理整個DDC??梢远ㄆ谌缑恐芤淮芜M(jìn)行維護(hù)性清理。利用共享DDC在團(tuán)隊環(huán)境中可以設(shè)置一個網(wǎng)絡(luò)共享的DDC服務(wù)器Derived Data Cache Server這樣團(tuán)隊成員可以共享已編譯的著色器數(shù)據(jù)減少重復(fù)編譯。清理本地DDC后可以從共享緩存快速拉取。保留核心DDC更精細(xì)的做法是只清理Saved/DerivedDataCache/下以舊驅(qū)動版本或無關(guān)平臺命名的文件夾保留當(dāng)前主要開發(fā)平臺如D3D11、D3D12的緩存。6.3 工具掃描速度過慢或卡死問題對于超大型項目數(shù)萬資產(chǎn)掃描腳本運行極慢甚至內(nèi)存溢出。優(yōu)化策略增量掃描不要每次都全量掃描。記錄上次掃描的結(jié)果和時間戳只掃描自上次以來新增或修改的資產(chǎn)更新引用關(guān)系圖。多進(jìn)程/異步處理將資產(chǎn)列表分塊利用Python的multiprocessing模塊進(jìn)行并行分析。注意UE Python API的線程安全性最好在獨立的子進(jìn)程中調(diào)用。優(yōu)化算法使用更高效的數(shù)據(jù)結(jié)構(gòu)如鄰接表存儲引用關(guān)系。避免在循環(huán)中進(jìn)行重復(fù)的get_referencers調(diào)用可以先批量收集所有資產(chǎn)數(shù)據(jù)再進(jìn)行圖分析。提供進(jìn)度反饋在UI中顯示明確的進(jìn)度條和當(dāng)前正在分析的資產(chǎn)讓用戶感知到工具在運行而非卡死。6.4 特殊資產(chǎn)的處理有些資產(chǎn)看似未被引用實則不可或缺。資產(chǎn)類型為何容易被誤判處理建議游戲?qū)嵗{(lán)圖通常在C代碼或項目設(shè)置中指定而非被其他資產(chǎn)直接引用。將其路徑如/Game/Core/BP_GameInstance加入工具的永久忽略列表。默認(rèn)地圖在DefaultEngine.ini的/Script/EngineSettings.GameMapsSettings中配置。掃描時讀取該配置文件將GameDefaultMap和GlobalDefaultGameMode對應(yīng)的資產(chǎn)排除。項目設(shè)置中引用的資產(chǎn)如默認(rèn)玩家控制器、HUD類、物理材質(zhì)等。解析DefaultEngine.ini和DefaultGame.ini提取所有/Script/...路徑的資產(chǎn)引用。動態(tài)加載的資產(chǎn)通過LoadObject或StreamableManager在運行時按路徑加載。這最難處理。需要團(tuán)隊建立規(guī)范將所有動態(tài)加載的資產(chǎn)路徑集中管理在一個數(shù)據(jù)表或配置文件中然后讓清理工具讀取這個配置文件作為白名單。我個人在實際操作中的體會是ProjectCleaner工具的價值30%在于其自動化能力70%在于其背后體現(xiàn)的資產(chǎn)管理和團(tuán)隊規(guī)范。一個混亂的項目再好的清理工具也治標(biāo)不治本。因此在項目初期就建立良好的習(xí)慣至關(guān)重要使用清晰的文件夾結(jié)構(gòu)命名規(guī)范及時刪除實驗性的、廢棄的資產(chǎn)對于必須存在的“孤立”資產(chǎn)如基礎(chǔ)材質(zhì)函數(shù)庫建立一個/Game/Core/System或/Game/Art/Common目錄集中存放并明確告知所有成員和工具“此目錄免檢”。最后無論工具多么智能在執(zhí)行大規(guī)模清理操作前備份你的項目或者確保版本控制處于一個干凈、可回退的狀態(tài)這是永遠(yuǎn)不能省略的“金科玉律”。