├── ReadMe.md.meta ├── Editor.meta ├── Editor ├── AnimatorControllerCleaner.cs.meta └── AnimatorControllerCleaner.cs └── ReadMe.md /ReadMe.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 28b35b7a8d107904cb4d6b01a0442890 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e33d9d62a1a645e489c94c463603f22e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/AnimatorControllerCleaner.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a9555f2e0aaa2994ba608aa26bdf2327 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # AnimatorControllerCleaner v1.1 2 | UnityのAnimatorController内に参照されないのに残ってしまったデータを削除します。 3 | 4 | ## 動作確認環境 5 | * Unity2019.4.31f1 6 | * VRChat Package Resolver Tool 0.1.19
7 | (YamlDotNetに依存していますが、`Packages/com.vrchat.core.vpm-resolver/Editor/Dependencies/YamlDotNet.dll`に含まれているのでこれを使います。)
8 | VCCを使っていない場合はAsset StoreからYamlDotNet for UnityをImportしてください。 9 | 10 | ## 使い方 11 | 1. `Edit > Project Settings... > Editor > Asset Serialization > Mode`をForce Textに設定。 12 | 2. AnimatorControllerCleaner.unitypackageをimport 13 | 3. ゴミを削除したいAnimatorControllerをProject上で選択して右クリック `CleanAnimatorControllers`を実行。 14 | 15 | ## 何を行っているのか 16 | AnimatorControllerのAssetをTextModeで保存した場合、Yaml形式となっています。 17 | 18 | Yamlをパースし、Root要素であるAnimatorControllerから参照しているオブジェクトを辿って行きます。 19 | 20 | AnimatorControllerから辿った参照ツリーに含まれていないオブジェクトを除外してYamlファイルを保存しなおしています。 21 | 22 | ## 注意事項 23 | テストは行っていますが、完璧な動作は保証できません。一度AnimatorControllerを複製した上で実行し、問題ないことを確認することをおすすめします。 24 | 25 | 26 | ## 更新履歴 27 | ### v1.0 28 | 登録 29 | 30 | ### v1.1 31 | 最後のオブジェクトの内容がひとつ前のオブジェクトの内容に置き換わってしまっていた不具合を修正。 -------------------------------------------------------------------------------- /Editor/AnimatorControllerCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | using System.Linq; 7 | using System.Reflection; 8 | using UnityEngine; 9 | using UnityEditor; 10 | using YamlDotNet.RepresentationModel; 11 | 12 | public static class AnimatorControllerCleaner{ 13 | private const string YamlHeader = "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n"; 14 | private const string ObjectHeaderPrefix = "--- !u!"; 15 | 16 | [MenuItem("Assets/CleanAnimatorControllers")] 17 | private static void CleanAnimatorContollers(){ 18 | var Assets = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets); 19 | if(Assets.Length == 0) return; 20 | foreach(var Asset in Assets){ 21 | // Debug.Log(Asset); 22 | var path = AssetDatabase.GetAssetPath(Asset); 23 | // Debug.Log(path); 24 | if(path.EndsWith(".controller")){ 25 | var pathArray = new string[] {path}; 26 | AssetDatabase.ForceReserializeAssets(pathArray); 27 | CleanAnimatorController(path); 28 | } 29 | } 30 | AssetDatabase.Refresh(); 31 | } 32 | 33 | private static void CleanAnimatorController(string path){ 34 | var objList = new List(); 35 | // データの取得 36 | foreach(var obj in ParseAnimator(path)){ 37 | // Debug.Log(obj); 38 | objList.Add(obj); 39 | } 40 | bool[] refArray = new bool[objList.Count]; 41 | for(int i=0; i refArray[index]); 71 | var yaml = PresentAnimator(objListFiltered.ToList()); 72 | 73 | File.WriteAllText(path, yaml); 74 | } 75 | 76 | 77 | struct AnimatorClassObj{ 78 | public int classID; 79 | public string fileID; 80 | public YamlNode node; 81 | public string content; 82 | 83 | public AnimatorClassObj( 84 | int classID, string fileID, 85 | YamlNode node, string content) 86 | { 87 | this.classID = classID; 88 | this.fileID = fileID; 89 | this.node = node; 90 | this.content = content; 91 | } 92 | } 93 | 94 | private static void SearchReference(int i, ref List objList, ref bool[] refArray) 95 | { 96 | if(refArray[i]) return; 97 | refArray[i] = true; 98 | AnimatorClassObj obj = objList[i]; 99 | 100 | // classID is documented on https://docs.unity3d.com/ja/2019.4/Manual/ClassIDReference.html 101 | switch(obj.classID) 102 | { 103 | case 91: // AnimatorController 104 | case 114: // MonoBehaviour 105 | case 206: // BlendTree 106 | case 1101: // AnimatorStateTransition 107 | case 1102: // AnimatorState 108 | case 1107: // AnimatorStateMachine 109 | case 1109: // AnimatorTransition 110 | case 1111: // AnimatorTransitionBase 111 | break; 112 | default: 113 | Debug.LogWarning("unexcepted classID "+obj.classID); 114 | throw new System.Exception("unexcepted classID "+obj.classID); 115 | } 116 | // obj内のfileID要素を辿る 117 | foreach(var fileID in RecursiveSearch(obj.node, "fileID")){ 118 | // Debug.Log(fileID); 119 | if (fileID.GetType() == typeof(string)) 120 | { 121 | var matchObj = objList.Where(x => x.fileID == (string)fileID); 122 | if (matchObj.Count() != 0) 123 | { 124 | var j = objList.IndexOf(matchObj.First()); 125 | SearchReference(j, ref objList, ref refArray); 126 | } 127 | } 128 | } 129 | } 130 | 131 | public static IEnumerable RecursiveSearch(YamlNode node, string key) 132 | { 133 | switch (node) 134 | { 135 | case YamlMappingNode mappingNode: 136 | foreach (var childNode in mappingNode.Children) 137 | { 138 | if (childNode.Key.ToString() == key) 139 | { 140 | yield return childNode.Value.ToString(); 141 | } 142 | 143 | foreach (var result in RecursiveSearch(childNode.Value, key)) 144 | { 145 | yield return result; 146 | } 147 | } 148 | break; 149 | 150 | case YamlSequenceNode sequenceNode: 151 | foreach (var childNode in sequenceNode.Children) 152 | { 153 | foreach (var result in RecursiveSearch(childNode, key)) 154 | { 155 | yield return result; 156 | } 157 | } 158 | break; 159 | } 160 | } 161 | 162 | private static int GetClassIDByObjectHeader(string objectHeader) 163 | { 164 | return int.Parse(objectHeader.Substring(ObjectHeaderPrefix.Length).Split(' ')[0]); 165 | } 166 | 167 | private static string GetFileIDByObjectHeader(string objectHeader) 168 | { 169 | return objectHeader.Substring(ObjectHeaderPrefix.Length).Split(' ')[1].Substring(1); 170 | } 171 | 172 | private static IEnumerable ParseAnimator(string yamlPath) 173 | { 174 | var lines = File.ReadLines(yamlPath); 175 | var sb = new StringBuilder(); 176 | string objectHeader = null; 177 | string content = null; 178 | foreach(var line in lines) 179 | { 180 | if(line.StartsWith(ObjectHeaderPrefix)) 181 | { 182 | if(objectHeader != null){ 183 | content = sb.ToString(); 184 | yield return new AnimatorClassObj( 185 | GetClassIDByObjectHeader(objectHeader), 186 | GetFileIDByObjectHeader(objectHeader), 187 | ParseYaml(content), 188 | content 189 | ); 190 | sb.Clear(); 191 | } 192 | objectHeader = line; 193 | continue; 194 | } 195 | 196 | // 最初の2行については、まだobjectHeaderが出てきていないので読み飛ばす。 197 | if(objectHeader != null) sb.Append(line + "\n"); 198 | } 199 | if(objectHeader == null) yield break; 200 | content = sb.ToString(); 201 | yield return new AnimatorClassObj( 202 | GetClassIDByObjectHeader(objectHeader), 203 | GetFileIDByObjectHeader(objectHeader), 204 | ParseYaml(content), 205 | content 206 | ); 207 | } 208 | 209 | private static YamlNode ParseYaml(string text) 210 | { 211 | var yamlStream = new YamlStream(); 212 | var sr = new StringReader(text); 213 | yamlStream.Load(sr); 214 | return yamlStream.Documents[0].RootNode; 215 | } 216 | 217 | private static string PresentYaml(YamlNode node) 218 | { 219 | var yamlDocument = new YamlDocument(node); 220 | var yamlStream = new YamlStream(yamlDocument); 221 | var sw = new StringWriter(); 222 | sw.NewLine = "\n"; 223 | yamlStream.Save(sw); 224 | return sw.ToString(); 225 | } 226 | 227 | private static string PresentAnimator(List objList) 228 | { 229 | var sw = new StringWriter(); 230 | sw.Write(YamlHeader); 231 | foreach(var obj in objList){ 232 | sw.Write(ObjectHeaderPrefix + obj.classID + " &" + obj.fileID + "\n"); 233 | sw.Write(obj.content); 234 | } 235 | return sw.ToString(); 236 | } 237 | } 238 | --------------------------------------------------------------------------------