【Unity】アニメーションコントローラーなどの参照を維持したままFBX内のクリップ名をスクリプトから変更する方法

今更ですが、あけましておめでとうございます。

2017年一発目の記事です。

やってみると分かりにくかったのでメモを兼ねて残しておきます。

 

今回はUnityにインポートしているFBXの内のアニメーションクリップの名前を変更する方法です。

 

単純に変更するのはすごく簡単なのですが、すでにAnimation Controllerのモーションに設定している場合

スクリプトから単純に変更かけると参照が外れてしまいます!!!

 

きっかけはTwitterでこの事で困っていると書かれている方がいて、自分でも試してみると上手くいきませんでした。

 

ちなみに手動でEditor上で名前を変更してApplyボタンを押す方法なら、参照が外れません。

 

なので、まずはこの差を探っていきました。


差を見るために手動で変更したモノとスクリプトから名前だけ変更したモノのmetaファイルを用意し

WinMergeを使って比較してみました。(有名だと思いますがWinMergeはファイルの差分を見れるフリーのツールです)

 

すると、クリップ名は同じように変更されているのに、

metaファイルの上の方にある「fileIDToRecycleName」のところに違いがありました!

スクリプトから名前だけ変更した方は1つ増えてしまっていて、古いIDのものと名前を変更した新しいIDとあります。

 

例:

7400000: test00
7400002: test00_hoge

 

そして、それを設定しているAnimation Controllerのファイル自体の中身をテキストエディタ等で見てみると

 

m_Motion: {fileID: 7400000, guid: 4b6daaf99c41bf64196eec13c61fd464, type: 3}

 

こんな感じで書かれています。

 

つまり、FBXのGUIDだけじゃなく、さらにそのなかにFileIDも書かれていて

その2つによってどのファイルの何が設定されているかを認識させているようです。

 

↑の例でいうと、スクリプトから名前だけを変更した際に

アニメーションクリップのfileIDが7400002に変更されたのに、Animation Controllerは7400000を参照していて

「7400000がなくなった!!」ということで参照が外れてNoneになっていたんですね。

 

理屈はわかったので、そのfileIDも変えないように変えればOKです!

 

まずは、metaファイルにあるfileIDToRecycleNameにどうやってアクセスできるかを調べました。

fileIDToRecycleNameなので、シリアライズプロパティ名はおそらく「m_FileIDToRecycleName」だろうという予想はつきます。

 

SerializedObject soModel = new SerializedObject(modelImporter);

SerializedProperty spRecycleName = soModel.FindProperty("m_FileIDToRecycleName");
 

そして、ここからが分かりにくかった…

 

コロンで区切られているから分かれているんだろうなぁと思いましたが、

中々分からずに結局「m_FileIDToRecycleName」でググりました。

 

すると、エディター拡張でお世話になっているこちらのページが出てきました。

(こちらはエディター拡張入門にも書かれているUnityEditor.DLLのデコンパイルしたものをGitHubに挙げられているものです)

 

●UnityDecompiled/PatchImportSettingRecycleID.cs at master · MattRix/UnityDecompiled · GitHub
https://github.com/MattRix/UnityDecompiled/blob/master/UnityEditor/PatchImportSettingRecycleID.cs


ここにfirstsecondと書かれていて、試してみるとmetaファイルに書かれているm_FileIDToRecycleNameの

左側の数値と右側の文字列が取得できました!

●取得の方法

for(var h = 0; h < spRecycleName.arraySize; h++)
{

        //右側の数値 = first
        Debug.Log(spRecycleName.GetArrayElementAtIndex(h).FindPropertyRelative("first").longValue);

 

        //左側の文字列 = second
        Debug.Log(spRecycleName.GetArrayElementAtIndex(h).FindPropertyRelative("second").stringValue);
}

 

試した感じでは、どうやらクリップ名を変更したときに

m_FileIDToRecycleNameにない名前だとfileIDが増えてしまうようなので、

ここのsecond(文字列)を名前変更する前に同じ名前に変更してしまうことで参照が外れるのが回避できました。

 

以下がその名前を変更するコードです。

このスクリプトではAssetsメニューに追加する形で書いていますが、

選択したものをModelImporterとして処理しているので少し書き換えてもらえば、

AssetPostprocessorクラスのOnPostprocessModelメソッド内の処理に組み込んで

インポート時にクリップ名を変更するようにすることも可能です。

 

下記のスクリプトはあくまでサンプルです。

不具合が出ても責任は取れませんので自己責任でお試し下さい。 (といってもクリップ名にhogeが付くくらいだと思いますがw)



using UnityEngine;
using UnityEditor;
using System.Collections;

public class SelAnimClipRename : Editor
{
        [MenuItem ("Assets/選択中のアニメ名の末尾にhoge追加")]
        private static void ClipRename ()
        {
                var selObjs = Selection.GetFiltered(typeof(GameObject), SelectionMode.Assets);

                if (selObjs.Length == 0 )
                {
                        Debug.Log ("何も選択されていません");
                        return;
                }

                foreach(var selObj in selObjs)
                {
                        var assetPath = AssetDatabase.GetAssetPath(selObj);
                        var modelImporter = AssetImporter.GetAtPath(assetPath) as ModelImporter;

                        SerializedObject soModel = new SerializedObject(modelImporter);
                        soModel.Update();

                        SerializedProperty spClips = soModel.FindProperty("m_ClipAnimations");
                        SerializedProperty spRecycleName = soModel.FindProperty("m_FileIDToRecycleName");

                        if(spClips.arraySize == 0) continue;

                        for(var i = 0; i < spClips.arraySize; i++)
                        {
                                var clipName = spClips.GetArrayElementAtIndex(i).FindPropertyRelative("name");

                                var newClipName = clipName.stringValue + "_hoge";

                                for(var h = 0; h < spRecycleName.arraySize; h++)
                                {
                                        if(spRecycleName.GetArrayElementAtIndex(h).FindPropertyRelative("second").stringValue == clipName.stringValue)
                                        {
                                                spRecycleName.GetArrayElementAtIndex(h).FindPropertyRelative("second").stringValue = newClipName;
                                        }
                                }

                                //クリップ名にすでに_hogeがついてなければ_hogeをつける
                                if(!clipName.stringValue.Contains("_hoge"))
                                {
                                        clipName.stringValue = newClipName;
                                }
                        }

                        soModel.ApplyModifiedProperties();

                        AssetDatabase.ImportAsset(assetPath);
                }
        }
}