導入
以下の図に示すように、キャラクタの足 IK を設定するときにウェイト値を取得するために、2 つのアニメーション カーブ Left および Right FootIK Weight が使用されます。アニメーション クリップが多い場合、手動で編集するのはさらに面倒です。これら 2 つのアニメーション カーブをプログラムでベイク処理することを検討できます。足の IK ウェイト カーブです。
実装のアイデア
- 新しいエディタ ウィンドウを作成し、OnGUI で現在選択されているゲーム オブジェクトを取得し、そのオブジェクト上の Animator アニメーション コンポーネントを取得します。
- アニメーション コンポーネントでアニメーション クリップ配列を取得し、配列を走査してスクロール ビューでアニメーション クリップをリストし、ウェイト カーブをベイク処理するためのベイク ボタンを追加します。
private Vector2 scroll;
private void OnGUI()
{
GameObject selectedGameObject = Selection.activeGameObject;
if (selectedGameObject == null)
{
EditorGUILayout.HelpBox("未选中任何游戏物体", MessageType.Warning);
return;
}
Animator animator = selectedGameObject.GetComponent<Animator>();
if (animator == null)
{
EditorGUILayout.HelpBox("所选游戏物体不包含Animator组件", MessageType.Warning);
return;
}
AnimationClip[] clips = animator.runtimeAnimatorController.animationClips;
if (clips.Length == 0)
{
EditorGUILayout.HelpBox("Animator组件中的动画片段数量为零", MessageType.Warning);
return;
}
scroll = GUILayout.BeginScrollView(scroll);
for (int i = 0; i < clips.Length; i++)
{
AnimationClip clip = clips[i];
GUILayout.BeginHorizontal();
GUILayout.Label(clip.name);
GUILayout.FlexibleSpace();
if (GUILayout.Button("Bake", GUILayout.Width(50f)))
EditorCoroutineUtility.StartCoroutine(BakeCoroutine(selectedGameObject, animator, clip), this);
GUILayout.EndHorizontal();
}
GUILayout.EndScrollView();
}
- アニメーション クリップに基づいて、アセット パス、アセット インポーター、および対応する ModelImporterClipAnimation を取得します。ターゲット カーブが既に存在する場合は、再生成のためにフィルタリングします。
//获取资产路径
string assetPath = AssetDatabase.GetAssetPath(clip);
//获取资产导入器
ModelImporter importer = AssetImporter.GetAtPath(assetPath) as ModelImporter;
ModelImporterClipAnimation[] clipAnimations = importer.clipAnimations;
ModelImporterClipAnimation target = clipAnimations.FirstOrDefault(m => m.name == clip.name);
//过滤(如果已经有对应的两条曲线)
target.curves = target.curves.Where(m
=> m.name != "Left FootIK Weight"
&& m.name != "Right FootIK Weight").ToArray();
//保存、重新导入
importer.SaveAndReimport();
- サンプリングは、AnimationClip.SampleAnimation 関数を通じて実行されます (フレーム数 = アニメーション期間 * サンプリング レート)。サンプリングの際、足が地面に着いているかどうかに応じて現在のフレームの値が記録され、足が地面に着いている場合は重みが 1、着地していない場合は重みが 0 となります。初期姿勢はサンプリング前に記録され、サンプリング後に復元されることに注意してください。
//采样率30
int samplingRate = 30;
int frames = Mathf.CeilToInt(clip.length * samplingRate);
Keyframe[] leftFootKeyFrames = new Keyframe[frames];
Keyframe[] rightFootKeyFrames = new Keyframe[frames];
//采样前记录初始姿态
Dictionary<Transform, (Vector3, Quaternion)> pose = new Dictionary<Transform, (Vector3, Quaternion)>();
Transform[] children = animator.GetComponentsInChildren<Transform>();
for (int i = 0; i < children.Length; i++)
{
Transform child = children[i];
pose.Add(child, (child.position, child.rotation));
}
//采样
for (int i = 0; i < frames; i++)
{
clip.SampleAnimation(go, (float)i / samplingRate);
bool isFootGroundedLeft = IsFootGrounded(animator, HumanBodyBones.LeftFoot);
bool isFootGroundedRight = IsFootGrounded(animator, HumanBodyBones.RightFoot);
//在地面时权重为1 不在地面时权重为0
leftFootKeyFrames[i] = new Keyframe((float)i / frames, isFootGroundedLeft ? 1f : 0f);
rightFootKeyFrames[i] = new Keyframe((float)i / frames, isFootGroundedRight ? 1f : 0f);
yield return null;
}
//采样后恢复初始姿态
foreach (var kv in pose)
{
kv.Key.position = kv.Value.Item1;
kv.Key.rotation = kv.Value.Item2;
}
- フィルタリングには、前後のフレームとフレーム値が同じフレームは必要ありません。
//过滤(当帧值与前帧、后帧值都一样)
leftFootKeyFrames = Enumerable.Range(0, frames)
.Where(i =>
{
bool sameWithPrev = (i - 1) >= 0 && leftFootKeyFrames[i - 1].value == leftFootKeyFrames[i].value;
bool sameWithLast = (i + 1) < frames && leftFootKeyFrames[i + 1].value == leftFootKeyFrames[i].value;
return !sameWithPrev || !sameWithLast;
})
.Select(i => leftFootKeyFrames[i])
.ToArray();
rightFootKeyFrames = Enumerable.Range(0, frames)
.Where(i =>
{
bool sameWithPrev = (i - 1) >= 0 && rightFootKeyFrames[i - 1].value == rightFootKeyFrames[i].value;
bool sameWithLast = (i + 1) < frames && rightFootKeyFrames[i + 1].value == rightFootKeyFrames[i].value;
return !sameWithPrev || !sameWithLast;
})
.Select(i => rightFootKeyFrames[i])
.ToArray();
- 新しく生成された 2 つのアニメーション カーブを追加します。
ClipAnimationInfoCurve leftAnimInfoCurve = new ClipAnimationInfoCurve()
{
name = "Left FootIK Weight",
curve = new AnimationCurve(leftFootKeyFrames)
};
ClipAnimationInfoCurve rightAnimInfoCurve = new ClipAnimationInfoCurve()
{
name = "Right FootIK Weight",
curve = new AnimationCurve(rightFootKeyFrames)
};
//添加生成的两条曲线
target.curves = target.curves.Concat(
new ClipAnimationInfoCurve[2] {
leftAnimInfoCurve, rightAnimInfoCurve }).ToArray();
importer.clipAnimations = clipAnimations;
importer.SaveAndReimport();
関連ツール
生成プロセスではコルーチンが使用されます。コルーチンは実行時に MonoBehaviour の StartCoroutine を通じて開始でき、ツールはエディター環境で動作します。次の図に示すように、パッケージ マネージャーでコルーチン ツール エディター コルーチンを使用できます。