Unity工程隨着復雜度的提升,常會有Prefab上的腳本丟失的情況,如下圖所示:
首先失去關聯的腳本,是沒有線索找到原來是什么文件的,那么有沒有辦法批處理將這些MissingScript進行一下清理呢?就我使用的Unity4.6所提供的接口來說,沒有非常完美的解決辦法,但有一些還算可以用來解決問題的做法。
方法1:找到所有包含有Missing腳本的Prefab,然后逐個手動刪除。
方法2:通過代碼自動批處理解決。
第一種方法比較笨拙,對於資源量不是很大的工程,可以這樣做,查找Missing腳本Prefab的代碼如下:
using UnityEngine; using UnityEditor; using System.Collections; using System.Collections.Generic; public class ClearMissingScript { [MenuItem("Custom/SelectMissing")] static void SelectMissing(MenuCommand command) { string []guids=AssetDatabase.FindAssets("t:Prefab", ["Assets/PrefabDir"]); List<GameObject> selection = new List<GameObject>(); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); GameObject obj=AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)) as GameObject; Component[] cs = obj.GetComponents<Component>(); foreach (Component c in cs) { if (c == null) { selection.Add(obj); } } } Selection.objects = selection.ToArray(); } }
上面的腳本會幫你自動選中所有的包含腳本丟失的Prefab,有耐心的話就挨個處理吧...
第二種方法會智能一下,但是有一些問題,不確定是不是我代碼寫的有問題哈,代碼如下所示:
public class CleanUpMissingScripts { [MenuItem("Edit/CleanupMissingScripts &c")] public static void CleanupMissingScripts() { for(int i = 0; i < Selection.gameObjects.Length; ++i) { var gameObject = Selection.gameObjects[i]; var components = gameObject.GetComponents<Component>(); SerializedObject serializedObject = new SerializedObject(gameObject); SerializedProperty prop = serializedObject.FindProperty("m_Component"); int r = 0; for(int j = 0; j < components.Length; j++) { if(components[j] == null) { prop.DeleteArrayElementAtIndex(j - r); r++; } } serializedObject.ApplyModifiedProperties(); } AssetDatabase.Refresh(); } }
這個腳本利用SerializedObject和SerializedProperty兩個類來進行處理,思路上比較清晰,但是有問題:處理完之后看似清除了所有的Missing腳本,但一運行游戲,這些Missing腳本又回來了,於是換個思路,我們先將處理asset實例化出來,然后對實例化后的GameObject進行處理,再將處理完畢的GameObject寫進asset,代碼如下所示:
public class MissingScriptsEditor : EditorWindow { private static EditorWindow window; private List<GameObject> lstTmp = new List<GameObject>(); [MenuItem("Custom/MissingScripteEditor")] private static void Execute() { if (window == null) window = (MissingScriptsEditor)GetWindow(typeof(MissingScriptsEditor)); window.Show(); } private void OnGUI() { GUILayout.BeginVertical("box"); if (GUILayout.Button("CleanUpSelection", GUILayout.Height(30f))) { CleanUpSelection(); } GUILayout.EndVertical(); } private void CleanUpSelection() { var lstSelection = Selection.GetFiltered(typeof(GameObject), SelectionMode.DeepAssets); for(int i = 0; i < lstSelection.Length; ++i) { EditorUtility.DisplayProgressBar("Checking", "逐個分析中,請勿退出!", (float)i / (float)lstSelection.Length); var gameObject = lstSelection[i] as GameObject; var components = gameObject.GetComponents<Component>(); for (int j = 0; j < components.Length; j++) { // 如果組建為null if (components[j] == null) { CleanUpAsset(gameObject); break; } } } EditorUtility.ClearProgressBar(); AssetDatabase.Refresh(); foreach (var go in lstTmp) { GameObject.DestroyImmediate(go); } lstTmp.Clear(); } private void CleanUpAsset(Object asset) { GameObject go = PrefabUtility.InstantiatePrefab(asset) as GameObject; // 創建一個序列化對象 SerializedObject serializedObject = new SerializedObject(go); // 獲取組件列表屬性 SerializedProperty prop = serializedObject.FindProperty("m_Component"); var components = go.GetComponents<Component>(); int r = 0; for (int j = 0; j < components.Length; j++) { // 如果組建為null if (components[j] == null) { // 按索引刪除 prop.DeleteArrayElementAtIndex(j - r); r++; } } // 應用修改到對象 serializedObject.ApplyModifiedProperties(); // 將數據替換到asset // PrefabUtility.ReplacePrefab(go, asset); PrefabUtility.CreatePrefab(AssetDatabase.GetAssetPath(asset), go); go.hideFlags = HideFlags.HideAndDontSave; // 刪除臨時實例化對象 lstTmp.Add(go); } }
此代碼提供了簡單的編輯器界面,對於在Project中選中的Prefab進行處理,處理完之后可以很干凈的刪除所有的MissingScript引用,但是在Ctrl+S或運行游戲的時候會出現一些額外的提示,或者會直接崩掉。但是,重新開啟Unity之后就好了,並且問題也解決了。
所以建議使用這種方法,並且在處理完之后重啟一下Unity,然后問題成功解決!
上面的代碼僅供參考,並且此處貼出編輯器處理完之后的報錯,希望知道此問題的人能夠留言幫我解答一下原因: