Unity Timelineスクリプトで動的にAnimationTrackにアニメーションキーを打つ

#初めに
TimelineにBlendShapeでキャラに表情つけたものを保存した表情をキーを設定してほしいと言われたので、実装したのを自分が忘れないように書いた記事です。

たぶん、もっといいやり方はあるはず。。なので、もっといい方法あったら教えてください

この記事は2021/08/16に書いています。
timelineの1.5.6で作業しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37068/eac7f816-0427-6474-3baa-2e8da61e3af6.png)

[追記]　2021/08/18
初めに投稿した物だと、いろいろ不具合あったの修正します。。
主にアニメーションカーブ再設定の部分とTimeline editor Repaint周りのあたりを。。

[追記]　2021/08/19
親オブジェクトからアニメーション指定できるようにしました。
あと、一部エラーだったものを修正

##こんなイメージ
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37068/48d1a9f8-b6ce-1187-f1ce-9dccdfc3e88c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37068/7149fecf-3000-7b10-1094-b3f7b6b8da0a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37068/c60b3a10-9cf0-92d0-a9f4-04c5bff1979e.png)


※例ではニコニ立体ちゃんのVRMのBlendShapeで設定してある、表情を表示してます。
BlendShapeでそれぞれ表情をボタン押して、unityの別ウインドウで表情のが見れますが、この表情の値をtimelineでキーを打ってほしいって言うものです。


### スクリプト
timelineのPlayableDirectorから、timelineの情報の引き出して
SkinnedMeshRendererの値を比較して、値設定してあるものtimelineのアニメーションクリップに保存してます。
あとは、アニメーションクリップを打ってもtimelineエディタを再描画してあげないと表示しなかったのでRepaint処理もしてます。

Repaintの処理に関しては、[コガネブログ]さんの記事を参考に書きました。


```c#

/// <summary>
/// timelineにトラックをセットする
/// </summary>
/// <param name="_targetValue"></param>
/// <param name="_skinnedMeshRenderer"></param>
/// <param name="_blendShapeIndex"></param>
private void SetTimelineTrackSave(float _targetValue, SkinnedMeshRenderer _skinnedMeshRenderer,int _blendShapeIndex)
{
    var _propertyName = "blendShape." + _skinnedMeshRenderer.sharedMesh.GetBlendShapeName(_blendShapeIndex);
    var playableDirector = serializedObject.FindProperty("_playableDirector").objectReferenceValue as PlayableDirector;

    if (playableDirector == null)
        return;

    var timelineAsset = playableDirector.playableAsset as TimelineAsset;
    var currentTime = playableDirector.time;
    var weightValue = _targetValue;
    foreach (var timelineClip in timelineAsset.GetRootTracks())
    {
        // タイムラインのそれぞれのタイプを取得
        var type = timelineClip.GetType();
        //Debug.Log($"name {timelineClip.name} {timelineClip.GetType()}");

        // アニメーショントラックか判定
        if (type.Name == "AnimationTrack")
        {
            // TrackAssetをAnimationTrackで取得
            var animationTrack = timelineClip as AnimationTrack;

            // playableDirectorから各トラックを取得する
            var binding = playableDirector.GetGenericBinding(animationTrack);
            //Debug.Log($"binding: {binding?.name}");

            bool isParent = false;
           //　トラックに設定されている、オブジェクトと比較する
            if (GameObjectNameInclude(binding, _skinnedMeshRenderer, ref isParent))
            {
                //　トラックに設定されている、オブジェクトと比較する
                if (binding.name == _skinnedMeshRenderer.name)
                {
                    AnimationCurve curve;

                    //  アニーしょんタックに設定されているアニメーションクリップを取得
                    var animationClip = animationTrack.infiniteClip;
                    // アニメーションがすげにある場合
                    // 新たにアニメーションを上書きすると情報がおかしくなるので
                    // 少々違った方法で
                    if (animationClip)
                    {
                            // アニメーショントラックからアニメーションカーブを取得する
                        var animationCurveList = AnimationUtility.GetAllCurves(animationClip).Where(_ => _.propertyName == _propertyName).ToList();
                        // 
                        var animationCurve = animationCurveList.Count == 0 ? null : animationCurveList[0];


                        EditorCurveBinding curveBinding = new EditorCurveBinding();
　　　　　　　　　　　　　//親がいるならパスに書いてあげないといけない
                        if (isParent)
                        {
                            curveBinding.path = _skinnedMeshRenderer.name;
                        }
                        curveBinding.type = typeof(SkinnedMeshRenderer);
                        curveBinding.propertyName = _propertyName;


                        // セットするカーブ
                        if (animationCurve!=null)
                        {
                            animationCurve.curve.AddKey((float)currentTime, weightValue);
                        }
                        else
                        {
                            curve = new AnimationCurve(new Keyframe(((float)currentTime), weightValue));
                        }

                        // アニメーションクリップを再セットする
                        AnimationUtility.SetEditorCurve(animationClip, curveBinding, animationCurve!=null?  animationCurve.curve : curve);

                        // 再描画を依頼する
                        Repaint();
                    }
                    else
                    {

                        animationTrack.CreateInfiniteClip("");
                        animationClip =  animationTrack.infiniteClip;

                        curve = new AnimationCurve( new Keyframe(((float)currentTime), weightValue));

                        // アニメーションクリップをセットする
                        animationClip.SetCurve(isParent == false ? "" : _skinnedMeshRenderer.name, typeof(SkinnedMeshRenderer), _propertyName, curve);
                        // 再描画を依頼する
                        Repaint();

                    }
                }
            }
        }
    }
}
/// <summary>
/// セットされているオブジェクトの親に自分が含まれているかチェック
/// </summary>
/// <param name="_binding"></param>
/// <param name="_skinnedMeshRenderer"></param>
/// <param name="_isRoot"></param>
/// <returns></returns>
private bool GameObjectNameInclude(Object _binding, SkinnedMeshRenderer _skinnedMeshRenderer,ref bool _isParent)
{
    var ret = false;
    _isParent = false;

    if (_binding.name == _skinnedMeshRenderer.name)
         ret = true;
    if (!ret)
    {
        var parent = _skinnedMeshRenderer.gameObject.transform.parent;
        while (parent)
        {
            if (_binding.name == parent.name)
            {
                _isParent = true;
                ret = true;
                break;
            }

            parent = parent.parent;
        }
    }
    return ret;
}
private void Repaint()
{
    TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
}
```




###参考にしたサイト

https://qiita.com/jukey17/items/af494ee34d0b9669bafb

timelineをスクリプトで一気に作るTips



https://answers.unity.com/questions/1464375/is-it-possible-to-add-blendshape-animation-through.html

アニメーションクリップを作るるのが分からなかったので参考に下記の部分ね
 animationClip.SetCurve("", typeof(SkinnedMeshRenderer), _propertyName, curve);

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37068/28108e07-9e75-9da7-b4fa-971901fb23f2.png)
上記の場合skinnedMeshRendererインデックスから名前取れるので
"blendShape." + _skinnedMeshRenderer.sharedMesh.GetBlendShapeName(_blendShapeIndex);
blendShape.bs_face.eye_TT
と言う文字列が取れます。


https://docs.unity3d.com/Packages/com.unity.timeline@1.0/api/UnityEngine.Timeline.TimelineAsset.EditorSettings.html

timlineの右側の歯車の設定等々は上記からいろいろ調べられるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37068/da6dee95-7b92-1a3e-18f3-97229609068a.png)

https://mebiustos.hatenablog.com/entry/2015/09/16/230000

キーフレームとプロパティ値の設定を見て、アニメーションカーブを再設定する部分の参考に

https://forum.unity.com/threads/adding-animation-keyframes-to-a-infinite-clip-from-a-custom-editor.542498/

同じように悩んでいる人が昔いたみたいです。。

https://qiita.com/ScreenPocket/items/1b05931ff395cc449e80

timelineエディタを再更新する際に参考に

####最後に
最近はunityのエディタ改造が主になっているので、調べてもあんまり前例がなくて組み合わせで何とか
することが多いくてちゃんとどんなサイトから調べたかを書いておかないと自分が忘れちゃう。。


[コガネブログ]:https://baba-s.hatenablog.com/entry/2019/05/19/214400