C++幼女先輩

プログラミング成分多め

UNITY Editor拡張で、タイムラインをスクリプトから生成する

お題

タイムラインを自動生成しよう
トラックやクリップも外から作成してみます
それに伴い、エディタ拡張の勉強

メニューよりEditorWindowを表示

とりあえず最も汎用的な?メニュの拡張から

メニューの拡張

Editor拡張で最も使われると思うので、よーく覚えよう

UnityEditor.MenuItem - Unity スクリプトリファレンス

MenuItemを使う
ドキュメントの通り、非常に機能が豊富なので細かい事は説明しません

まず、Editor専用コードなので、Editorディレクトリを作りその下にコードを置く事
そして、Editor拡張は静的関数しか呼べないので、そういう設計にする事

主な機能をざっと

メニューアイテムの追加

[MenuItem("hoge/fuga")]
のように属性をつけると、メニューに hoge->fuga と追加される
メニュー名に日本語も使えます
当然そのメニューを選んだ時に、その関数が呼ばれます
ホットキーも簡単に付ける事ができます
また、Validateもする事が出来ますし、メニューの表示順番も変更できます
Validate関数を設定すれば、関数がFalseを返すと無効、Trueを返すと有効になります

ヒエラルキー

MenuItemのパスの頭をGameObjectにすると、ヒエラルキーで右クリックした際のメニューに追加できます

    [MenuItem("GameObject/CustomEditor")]
    private static void Create()
    {
        Debug.Log("Menu");
    }

f:id:murasame-labo:20210618062518p:plain

ヒエラルキー右クリックでちゃんと表示されました

f:id:murasame-labo:20210618063104p:plain

もちろん、メニューの のGameObjectにも表示される

Assets

MenuItemのパスの先頭をAssetsにすると、Assetsビューのコンテキストメニューに表示される

    [MenuItem("Assets/CustomEditor", false, 1)]
    private static void Create()
    {
        Debug.Log("Menu");
    }

f:id:murasame-labo:20210618063228p:plain

もちろん、メニューのAssetsにも表示される

f:id:murasame-labo:20210618063546p:plain

CONTEXT

コンポーネントの 右上の3点アイコンを押したときのメニューを作れます!
もちろん、自作コンポーネントだけじゃなく、UNITYのデフォルトコンポーネントも可能です

    [MenuItem("CONTEXT/PlayableDirector/CustomEditor", false, 1)]
    private static void Create()
    {
        Debug.Log("Menu");
    }

今回はPlayableDirectorコンポーネントにメニューを追加しました

f:id:murasame-labo:20210618064111p:plain

今回使うもの

今回は、選択したGameObjectにPlayableDirectorコンポーネントを追加したいので
ヒエラルキー上のGameObjectのコンテキストメニューに追加する

コンテキストメニュー

public class CustomEditorWindow : EditorWindow
{

    [MenuItem("GameObject/CustomEditor", false, 1)]
    private static void Create()
    {
        // 生成
        GetWindow<CustomEditorWindow>("CustomEditor");

    }

ヒエラルキーのGameObjectメニューにCustomEditorを追加します

そして、CustomEditorWindow(自分のクラス)を呼び、GUIを表示させます
今回は EditorWindowを継承し、ウインドウを表示させます

EditorWindow

    private void OnGUI()
    {
        using (new GUILayout.HorizontalScope())
        {
            targetGo_ = Selection.activeGameObject;

            if (GUILayout.Button("CreateTimeline"))
            {
               // ここにボタン押したときの処理が入る
            }
        }
    }

とりあえずボタンが1個あるだけの単純なウインドウ

f:id:murasame-labo:20210618070129p:plain

以上でEditor拡張のおさらい終了

Timeline等をコードから生成

本題という事になる

GameObjectにPlayableDirectorのアタッチ

                if(targetGo is null)
                {
                    return;
                }
                // 選択中のGameObjectにPlayableDirectorコンポーネントをつける
                var director = targetGo.GetComponent<PlayableDirector>();
                if(director == null)
                {
                    director = targetGo.AddComponent<PlayableDirector>();
                }

AddComponentを使って普通にPlayableDirectorを作成する

TimelineAssetを作成しAssetデータベースへ保存する

TimelineAssetを作成します
つまり、タイムラインの情報すべてを保存するものです
ScriptableObjectで作成します

ただし、このままでは、シーン上に作られます
実際の運用では、タイムラインはファイル(Asset)にして読み込んで使うと思うので
AssetDatabaseに登録します
拡張子は playableです

                // TimelineAssetの作成
                var timelineAsset = playableDirector.playableAsset as TimelineAsset;
                if (timelineAsset == null)
                {
                    timelineAsset = ScriptableObject.CreateInstance<TimelineAsset>();
                    director .playableAsset = timelineAsset;
                }


                var path = "Assets/Resources/sample.playable";
                AssetDatabase.DeleteAsset(path); 
                AssetDatabase.CreateAsset(timelineAsset, path);
                AssetDatabase.SaveAssets();

AssetDatabaseの仕様で、ディレクトリが無い場合はエラーになるので、ディレクトリは事前に作っておく事
そして、既にAssetが存在する場合は上書きするとエラーになるので、事前にDeleteしておきます
SaveAssetは、即座にデータベースに反映するおまじないです

トラックの作成&バインディングを設定

今回はGroupトラックを作成し、そこにトラックを作成します
型は前回作った NewPlayableTrackです
Bindingにはゲームオブジェクトをつけます。今回はCubeという名前のゲームオブジェクトをつけます

                var groupTrack = timelineAsset.CreateTrack<GroupTrack>(null, "Group");
                var track = timelineAsset.CreateTrack<NewPlayableTrack>(groupTrack, "NewPlayable");

                var b = GameObject.Find("Cube");
                director.SetGenericBinding(track, b);

クリップ作成とKeyFrame作成

クリップを作成します。もちろん NewPlayableAssetで
そしてそのクリップにKeyFrame(AnimationCurve)を設定します

今回は最初が1fで、デフォルトのカーブでエンドが0fになる形で
もちろん、カーブのEaseを変えたり、KeyFrameを増やす事は容易

                var clip = track.CreateClip<NewPlayableAsset>();

                clip.CreateCurves(null);
                var curve = new AnimationCurve();

                curve.AddKey(new Keyframe(0f, 1f));
                curve.AddKey(new Keyframe((float)clip.duration, 0f));
                clip.curves.SetCurve("", typeof(NewPlayableAsset), "hoge", curve);

リフレッシュ

最後にリフレッシュ

                EditorUtility.SetDirty(timelineAsset);
                TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);

成果物

f:id:murasame-labo:20210618090053p:plain

とりあえず、目的の事は出来た

タイムラインをスクリプトから生成する