Unity有一個Find References in Scene功能非常好用。在Project面板中右鍵一個文件,選擇Find References in Scene就可以在場景中找到所有存在對這個文件有引用的物體。但是很多時候,我們更加需要知道的是,這個場景里面到底引用了哪些文件,比如做優化的時候。
這里有一個插件叫做ResourceChecker,它可以列舉出所有引用到的texture/mesh/mat等。但是他也有一個小小的問題,對於自定義腳本的引用沒有效果。
要實現這個需求,這里主要借助SerializedObject/SerializedProperty。參考官方文檔:
- https://docs.unity3d.com/ScriptReference/SerializedObject.html
- https://docs.unity3d.com/ScriptReference/SerializedProperty.html
SerializedProperty在CustomEditor會經常用到,可以用於反射一個Unity對象的字段(甚至可以可以反射private字段,非常暴力)。
在《Unity文件、文件引用、meta詳解》一文中,曾經提到過unity資源序列化的數據要么是存在meta中,要么就是本身那個資源。比如用Notepad++打開prefab文件后,可能會是這樣的yaml序列化數據。而這些數據都可以通過SerializedProperty獲取得到。
在上圖的例子中可以看到,Property名字為m_PrefabInternal,它的Value中,fileID不為0。由此可以看到這個物體引用了一個對象。
下面,我們用代碼來尋找一個Prefab中引用到指定類型的所有文件,比如Sprite。
public static void FindRef<T>(SerializedObject obj) where T : Object { var prop = obj.GetIterator(); while (prop.Next(true)) { if (prop.propertyType == SerializedPropertyType.ObjectReference) { var value = prop.objectReferenceValue; if (value is T) { var path = AssetDatabase.GetAssetPath(value); Debug.Log(prop.propertyPath + ":" + path); } } } }
這里有幾個API
- SerializedObject.GetIterator:獲取這個SerializedObject上的初始SerializedProperty。用這個SerializedProperty可以逐個列舉出下一個SerializedProperty
- SerializedProperty.Next:獲取下一個SerializedProperty,如果參數為true,就會進入子字段Object的Property.比如一個mono腳本上有A字段,A字段是一個class,它有自己若干個可序列的字段,那么就會逐個列舉這些字段的SerializedProperty。
- SerializedProperty.propertyType:字段的類型,比如是引用,數字,字符等等,見SerializedPropertyType。每一種Type對應的值的類型也是不一樣
- SerializedProperty.objectReferenceValue,引用類型的字段的值。由於這里我們主要查找的是對文件的引用,所以引用的對象肯定在objectReferenceValue中
- SerializedProperty.propertyPath,字段在這個SerializedObject中的路徑。正如上面所言,這個字段可能是屬於子字段的,所以有路徑之說
所以,這段代碼就是,逐個遍歷一個SerializedObject中的所有字段,如果是對象引用的,並且對象的值是我們想要的類型,那么找出在工程中的路徑,打印出來。
我們擴充一下,增加以下函數
public static void FindRefWithGameObject<T>(GameObject obj) where T : Object { var coms = obj.GetComponentsInChildren<Component>(); foreach (var com in coms) { var so = new SerializedObject(com); FindRef<T>(so); } }
很簡單,找到一個GameObject物體下的所有組件,轉為SerializedObject,然后用之前的函數,把每一個組件下,引用到指定T類型的資源的字段全部打印出來。
最后,我們真正來找出所有引用到的Sprite
[MenuItem("Assets/Test/FindSprite")] public static void FindSprite() { var select = Selection.activeGameObject; if (select == null) return; FindRefWithGameObject<Sprite>(select); }
OK,在一個Prefab上右鍵,選擇Test->FindSprite,可以看到打印出所有的引用信息了
在這個demo中可以看到包含了Image控件中的Sprite引用,Button中切換的Sprite引用,自定義腳本Test中對Sprite的引用
至此完成了大部分的引用信息獲取。這里還有一個問題,沒有間接引用的信息,比如,Test腳本中引用一個Animation或者其他文件,而這些文件又引用到Sprite。在這樣的情況下,就需要再對那個文件做遍歷。直到沒有間接引用,才能獲取真正所有的引用。