初めに
TimelineにBlendShapeでキャラに表情つけたものを保存した表情をキーを設定してほしいと言われたので、実装したのを自分が忘れないように書いた記事です。
たぶん、もっといいやり方はあるはず。。なので、もっといい方法あったら教えてください
この記事は2021/08/16に書いています。
timelineの1.5.6で作業しています。
[追記] 2021/08/18
初めに投稿した物だと、いろいろ不具合あったの修正します。。
主にアニメーションカーブ再設定の部分とTimeline editor Repaint周りのあたりを。。
[追記] 2021/08/19
親オブジェクトからアニメーション指定できるようにしました。
あと、一部エラーだったものを修正
こんなイメージ
※例ではニコニ立体ちゃんのVRMのBlendShapeで設定してある、表情を表示してます。
BlendShapeでそれぞれ表情をボタン押して、unityの別ウインドウで表情のが見れますが、この表情の値をtimelineでキーを打ってほしいって言うものです。
スクリプト
timelineのPlayableDirectorから、timelineの情報の引き出して
SkinnedMeshRendererの値を比較して、値設定してあるものtimelineのアニメーションクリップに保存してます。
あとは、アニメーションクリップを打ってもtimelineエディタを再描画してあげないと表示しなかったのでRepaint処理もしてます。
Repaintの処理に関しては、コガネブログさんの記事を参考に書きました。
/// <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);
}
参考にしたサイト
timelineをスクリプトで一気に作るTips
アニメーションクリップを作るるのが分からなかったので参考に下記の部分ね
animationClip.SetCurve("", typeof(SkinnedMeshRenderer), _propertyName, curve);
上記の場合skinnedMeshRendererインデックスから名前取れるので
"blendShape." + _skinnedMeshRenderer.sharedMesh.GetBlendShapeName(_blendShapeIndex);
blendShape.bs_face.eye_TT
と言う文字列が取れます。
timlineの右側の歯車の設定等々は上記からいろいろ調べられるようです。
キーフレームとプロパティ値の設定を見て、アニメーションカーブを再設定する部分の参考に
同じように悩んでいる人が昔いたみたいです。。
timelineエディタを再更新する際に参考に
最後に
最近はunityのエディタ改造が主になっているので、調べてもあんまり前例がなくて組み合わせで何とか
することが多いくてちゃんとどんなサイトから調べたかを書いておかないと自分が忘れちゃう。。