Editor拡張を使ってAnimationCurveをプリセット化する

# はじめに
Editor拡張でAnimationCurveのプリセット機能を作成しました。
その際、Animationウィンドウから情報を引っ張ってくるのに苦戦したので
方法をまとめておこうと思います。

↓こんな感じでワンボタンでイージング反映する機能です
![265275292-124a3be5-54bb-4ac9-9b3d-9237feb5da87.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3555386/3f9cf949-93a3-3787-a39f-77756eb13f6d.gif)

# コード全文
https://github.com/haw2fregel/AnimationWindowHelper/blob/main/AnimationWindowHelper.cs

## 流れ
Animationウィンドウは外部から操作できない仕様ぽかったので、
ここを見ながらReflectionを利用してアクセスする形になりました。

https://github.com/Unity-Technologies/UnityCsReference/tree/master/Editor/Mono/Animation/AnimationWindow


AnimationWindowから３つの情報を取得できれば自由に操れるのでゴールです。
①選択中のKeyframeが所属するAnimationCurve
②選択中のKeyframe
③選択中のAnimationClip

## 選択中のAnimationWindowKeyframeを取得
AnimationWindow.animEditor.state.selectedKeys というところに
選択中のキーフレーム情報が保持されているのがわかりました。
まずはこれを取得します。

```
//Animationウィンドウを取得する。開いてなけれ開き、そのインスタンスを取得する。
var animationWindow = EditorWindow.GetWindow(animationWindowType,false,null,true);

//AnimationWindowstateを取得するため、AnimEditorを取得
var animEditorProperty = animationWindowType.GetProperty("animEditor", BindingFlags.Instance | BindingFlags.NonPublic);
var animEditor = animEditorProperty.GetValue(animationWindow);

//Animationウィンドウ内の情報取得のため、AnimationWindowstateを取得。
var stateProperty = animEditorType.GetProperty("state", BindingFlags.Instance | BindingFlags.Public);
var state = stateProperty.GetValue(animEditor);

//今開いてるのがCurveWindowの場合は選択中のキーフレームが上手く取得できないので、
//一度DopeSheetに戻してSelectionKeyの更新をする
var showCurveEditor = animationWindowStateType.GetField("showCurveEditor", BindingFlags.Instance | BindingFlags.Public);
if((bool)showCurveEditor.GetValue(state))
{
    var onEnableWindow = animEditorType.GetMethod("SwitchBetweenCurvesAndDopesheet", BindingFlags . Instance | BindingFlags . NonPublic | BindingFlags . Public);
    onEnableWindow.Invoke(animEditor,null); // DopeSheetに切り替えたことでSelectionKeyが更新される
    onEnableWindow.Invoke(animEditor,null); // 更新が終わったのでCurveWindowに戻す
}

//選択中のキーフレーム取得のため、AnimationWindowState.selectedKeysを取得
//AnimationWindowKeyframe型のListで返ってくるが、そのままだと扱えないのでdynamic型で受け取り
var getSelectionProperty = animationWindowStateType.GetProperty("selectedKeys", BindingFlags.Instance | BindingFlags.Public);
var keys = getSelectionProperty.GetValue(state) as dynamic;
foreach (var key in keys)
{
    //この中でキーを処理
}
```

ここまででツマッたポイントが２つあります。

①カーブウィンドウを開いている場合、selectedKeysが更新されないらしい。
animEditor.SwitchBetweenCurvesAndDopesheet()で一度ドープシートを開くことで更新し、
もう一度実行しカーブウィンドウに戻すことで解消しました。

②selectedKeysを取得したはいいが、なかなかListの要素にアクセスできなかった
NGパターン
```
keys = getSelectionProperty.GetValue(state);
//この場合Listとして認識されないので、foreachで回せず要素を取り出すことができない。

keys = getSelectionProperty.GetValue(state) as List<AnimationWindowKeyframe>;
//かといって正しい型で取得しようとした場合、AnimationWindowKeyframeへのアクセス権が無くエラーを吐く
```
dynamicで取得してあげたら上手く行きました。

## AnimationCurveの取得・編集・保存
AnimationWindowKeyframeから
AnimationCurve、Keyframe、AnimationClipを取得出来るので
自由編集してAnimationClipを保存すれば完了です。
```
//選択しているキーフレームを含む、AnimationWindowCurveを取得
var curveProperty = animationWindowKeyframeType.GetProperty("curve", BindingFlags.Instance | BindingFlags.Public);
var selectWindowCurve = curveProperty.GetValue(key);

//AnimationWindowCurveのままだと編集しにくいので、UnityEngene.AnimationCurveに変換する
var toAnimationCurve = animationWindowCurveType.GetMethod("ToAnimationCurve" ,  BindingFlags.Instance | BindingFlags.Public);
var animationCurve = toAnimationCurve.Invoke(obj:selectWindowCurve , parameters: null) as AnimationCurve;

//選択したキーフレームがAnimationCurve内の何番目か取得
var getIndex = animationWindowKeyframeType.GetMethod("GetIndex" ,  BindingFlags.Instance | BindingFlags.Public);
var index = (int)getIndex.Invoke(key , null);

//選択キーフレームをKeyframeで取得
var keyframe = animationCurve[index];

///////
//ここでanimationCurveを編集する
///////

//保存先のAnimationClipを取得
var clipProperty = animationWindowCurveType.GetProperty("clip", BindingFlags.Instance | BindingFlags.Public);
var clip = clipProperty.GetValue(selectWindowCurve);

//AnimationCurve判別のためにCurveBindingを取得
var bindingProperty = animationWindowCurveType.GetProperty("binding", BindingFlags.Instance | BindingFlags.Public);
var binding = bindingProperty.GetValue(selectWindowCurve);

//AnimationClipにAnimationCurveの編集を反映
AnimationUtility.SetEditorCurve(clip, binding, animationCurve);

```

# あとがき
カーブ調整もう少し楽出来ないかなぁと軽い気持ちではじめましたが、
公式のコードを読み解くだけで一苦労でした…


冒頭GifのEditor拡張はこちらです。

https://github.com/haw2fregel/CreativeEditor