ユニティちゃんトリプルアクセル(6回転してしまっている)
こちらはUnity #2 Advent Calender 2018 24日目の記事です。
筆者は2018全日本フィギュアスケート最終日に観戦に行きます。
最終日は男子のフリーですが、現役復帰した高橋大輔選手が演技するのが楽しみです。
最近は女子でもトリプルアクセルを余裕で降りる選手が増え、年々レベルが上がってるのを実感します。
というわけで、前回は非実装だったトリプルアクセルを今回なんとか実装してみようかと思います。
概要
- Unityちゃんにトリプルアクセルをやってもらう。
- アニメーションを作り込んで行くのはしんどいので、Final IKと組み合わせて少ない変数で制御する
- モーキャプという手もあるが、陸地で足元をやるのは限界があるし、そもそも筆者は氷上でもトリプルアクセルを跳べない
- 最終的にはプロシージャルアニメーションみたいな感じでやりたい(願望)
- 上半身の動きは今回考えない(モーキャプでブレンドすることを想定)
Final IKで足の曲がりをいい感じにしてもらう
膝の曲げなどの設定をしていくのを出来るだけ減らしたいのでFinal IKのLeg IKを設定します。
左右の足にIKをきかせたいので、UnityちゃんのモデルにふたつLeg IKをアタッチします。
で、Targetにつま先、Bend Goalに膝のIKのターゲットをとりたいのでUnityちゃんと同じ階層に左右の足のターゲットと、そのターゲットの子オブジェクト(膝用)をつま先の前方におきます
トリプルアクセルの途中動作を書いていく
で、下のようなスクリプトを書いてUnityちゃんとIKターゲットの親オブジェクトにアタッチします。
基本的に3つのオブジェクト(Unityちゃん、左右のIKターゲット)の向きと高さで制御できるようになっています。
using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.UI; public class FootManager : MonoBehaviour { [SerializeField] private Transform character; [SerializeField] private Transform rightTarget; [SerializeField] private Transform leftTarget; private void OnGUI() { if (GUILayout.Button("Preparation")) { character.localEulerAngles = new Vector3(0f, 90f, 0f); rightTarget.localEulerAngles = new Vector3(0f, 180f, 0f); leftTarget.localEulerAngles = new Vector3(0f, 90f, 0f); character.localPosition = new Vector3(0f, -0.01f, 0f); rightTarget.localPosition = new Vector3(0.1f, 0f, -0.05f); leftTarget.localPosition = new Vector3(0.1f, 0.1f, 0.1f); } if (GUILayout.Button("Steping")) { character.localEulerAngles = new Vector3(0f, 45f, 0f); rightTarget.localEulerAngles = new Vector3(45f, 90f, 90f); leftTarget.localEulerAngles = new Vector3(0f, 0f, 0f); character.localPosition = new Vector3(0f, -0.09f, 0f); rightTarget.localPosition = new Vector3(0.5f, 0.5f, -0.5f); leftTarget.localPosition = new Vector3(0.1f, 0f, 0.1f); } if (GUILayout.Button("TakingOff1")) { character.localEulerAngles = new Vector3(0f, 0f, 0f); rightTarget.localEulerAngles = new Vector3(0f, 0f, 0f); leftTarget.localEulerAngles = new Vector3(0f, 0f, 0f); character.localPosition = new Vector3(0f, -0.09f, 0f); rightTarget.localPosition = new Vector3(0.3f, 0.1f, 0.2f); leftTarget.localPosition = new Vector3(0.1f, 0f, 0.1f); } if (GUILayout.Button("TakingOff2")) { character.localEulerAngles = new Vector3(0f, -45f, 0f); rightTarget.localEulerAngles = new Vector3(0f, -45f, 0f); leftTarget.localEulerAngles = new Vector3(0f, -45f, 0f); character.localPosition = new Vector3(0f, -0.02f, 0f); rightTarget.localPosition = new Vector3(0.0f, 0.3f, 0.4f); leftTarget.localPosition = new Vector3(0.1f, 0.0f, 0.0f); } if (GUILayout.Button("TakingOff3")) { character.localEulerAngles = new Vector3(0f, -90f, 0f); rightTarget.localEulerAngles = new Vector3(0f, -90f, 0f); leftTarget.localEulerAngles = new Vector3(0f, -90f, 0f); character.localPosition = new Vector3(0f, 0.1f, 0f); rightTarget.localPosition = new Vector3(0.0f, 0.3f, 0.4f); leftTarget.localPosition = new Vector3(0.1f, 0.12f, 0.0f); } if (GUILayout.Button("TakingOff4")) { character.localEulerAngles = new Vector3(0f, -180f, 0f); rightTarget.localEulerAngles = new Vector3(0f, -190f, 0f); leftTarget.localEulerAngles = new Vector3(0f, -190f, 0f); character.localPosition = new Vector3(0f, 0.27f, 0f); rightTarget.localPosition = new Vector3(0.05f, 0.3f, 0.1f); leftTarget.localPosition = new Vector3(0.0f, 0.33f, 0.0f); } if (GUILayout.Button("AirPosition1")) { character.localEulerAngles = new Vector3(0, 0, 0); rightTarget.localEulerAngles = new Vector3(0, -20, 0); leftTarget.localEulerAngles = new Vector3(0, -20, 0); character.localPosition = new Vector3(0, 0.28f, 0); rightTarget.localPosition = new Vector3(-0.05f, 0.3f, 0.0f); leftTarget.localPosition = new Vector3(0.05f, 0.33f, 0.1f); } if (GUILayout.Button("AirPosition2")) { character.localEulerAngles = new Vector3(0, -180, 0); rightTarget.localEulerAngles = new Vector3(0, -200, 0); leftTarget.localEulerAngles = new Vector3(0, -200, 0); character.localPosition = new Vector3(0, 0.3f, 0); rightTarget.localPosition = new Vector3(0.05f, 0.32f, 0.1f); leftTarget.localPosition = new Vector3(-0.05f, 0.35f, 0.0f); } if (GUILayout.Button("AirPosition3")) { character.localEulerAngles = new Vector3(0, 0, 0); rightTarget.localEulerAngles = new Vector3(0, -20, 0); leftTarget.localEulerAngles = new Vector3(0, -20, 0); character.localPosition = new Vector3(0, 0.25f, 0); rightTarget.localPosition = new Vector3(-0.05f, 0.3f, 0.0f); leftTarget.localPosition = new Vector3(0.05f, 0.33f, 0.1f); } if (GUILayout.Button("AirPosition4")) { character.localEulerAngles = new Vector3(0, -180, 0); rightTarget.localEulerAngles = new Vector3(0, -200, 0); leftTarget.localEulerAngles = new Vector3(0, -200, 0); character.localPosition = new Vector3(0, 0.2f, 0); rightTarget.localPosition = new Vector3(0.05f, 0.25f, 0.1f); leftTarget.localPosition = new Vector3(-0.05f, 0.28f, 0.0f); } if (GUILayout.Button("AirPosition5")) { character.localEulerAngles = new Vector3(0, 0, 0); rightTarget.localEulerAngles = new Vector3(0, -20, 0); leftTarget.localEulerAngles = new Vector3(0, -20, 0); character.localPosition = new Vector3(0, 0.15f, 0); rightTarget.localPosition = new Vector3(-0.05f, 0.2f, 0.0f); leftTarget.localPosition = new Vector3(0.05f, 0.23f, 0.3f); } if (GUILayout.Button("Landing")) { character.localEulerAngles = new Vector3(0, -180, 0); rightTarget.localEulerAngles = new Vector3(0, -200, 0); leftTarget.localEulerAngles = new Vector3(0, -200, 0); character.localPosition = new Vector3(0, -0.07f, 0); rightTarget.localPosition = new Vector3(0.05f, 0.0f, 0.1f); leftTarget.localPosition = new Vector3(0.45f, 0.03f, -0.2f); } if (GUILayout.Button("Check")) { character.localEulerAngles = new Vector3(0, -180, 0); rightTarget.localEulerAngles = new Vector3(0, -180, 0); leftTarget.localEulerAngles = new Vector3(0, -240, -90); character.localPosition = new Vector3(0, -0.17f, 0); rightTarget.localPosition = new Vector3(0.1f, 0.0f, 0.2f); leftTarget.localPosition = new Vector3(0.46f, 0.4f, 0.7f); } } }
コルーチンでLerpを使いながら実行
では上の変数を参考に補完も加えながら、コルーチン化してみたいと思います。
以下がコードです。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FootManager2 : MonoBehaviour { [SerializeField] private Transform character; [SerializeField] private Transform rightTarget; [SerializeField] private Transform leftTarget; // Use this for initialization void Start () { } // Update is called once per frame void Update () { } private void OnGUI() { if (GUILayout.Button("Triple Axel")) { StartCoroutine(TripleAxel()); } } IEnumerator TripleAxel() { Vector3[] char_tmp; Vector3[] right_tmp; Vector3[] left_tmp; int span; //Preparation character.localEulerAngles = new Vector3(0, 90, 0); rightTarget.localEulerAngles = new Vector3(0, 180, 0); leftTarget.localEulerAngles = new Vector3(0, 90, 0); character.localPosition = new Vector3(0, -0.01f, 0); rightTarget.localPosition = new Vector3(0.1f, 0, -0.05f); leftTarget.localPosition = new Vector3(0.1f, 0.1f, 0.1f); for (int i = 0; i <= 10; i++) { yield return 0; } //Steping span = 3; char_tmp = new[] {character.localEulerAngles, character.localPosition}; right_tmp = new[] {rightTarget.localEulerAngles, rightTarget.localPosition}; left_tmp = new[] {leftTarget.localEulerAngles, leftTarget.localPosition}; for (int i = 0; i <= span; i++) { character.localEulerAngles = Vector3.Lerp(char_tmp[0], new Vector3(0, 45, 0), (float)i / span); rightTarget.localEulerAngles = Vector3.Lerp(right_tmp[0], new Vector3(45, 90, 90), (float)i / span); leftTarget.localEulerAngles = Vector3.Lerp(left_tmp[0], new Vector3(0, 0, 0), (float)i / span); character.localPosition = Vector3.Lerp(char_tmp[1], new Vector3(0, -0.09f, 0), (float)i / span); rightTarget.localPosition = Vector3.Lerp(right_tmp[1], new Vector3(0.5f, 0.5f, -0.5f), (float)i / span); leftTarget.localPosition = Vector3.Lerp(left_tmp[1], new Vector3(0.1f, 0, 0.1f), (float)i / span); yield return 0; } //TakingOff1 span = 3; char_tmp = new[] {character.localEulerAngles, character.localPosition}; right_tmp = new[] {rightTarget.localEulerAngles, rightTarget.localPosition}; left_tmp = new[] {leftTarget.localEulerAngles, leftTarget.localPosition}; for (int i = 0; i <= span; i++) { character.localEulerAngles = Vector3.Lerp(char_tmp[0], new Vector3(0, 0, 0), (float)i / span); rightTarget.localEulerAngles = Vector3.Lerp(right_tmp[0], new Vector3(0, 0, 0), (float)i / span); leftTarget.localEulerAngles = Vector3.Lerp(left_tmp[0], new Vector3(0, 0, 0), (float)i / span); character.localPosition = Vector3.Lerp(char_tmp[1], new Vector3(0, -0.09f, 0), (float)i / span); rightTarget.localPosition = Vector3.Lerp(right_tmp[1], new Vector3(0.3f, 0.1f, 0.2f), (float)i / span); leftTarget.localPosition = Vector3.Lerp(left_tmp[1], new Vector3(0.1f, 0, 0.1f), (float)i / span); yield return 0; } //TakingOff2 span = 3; char_tmp = new[] {character.localEulerAngles, character.localPosition}; right_tmp = new[] {rightTarget.localEulerAngles, rightTarget.localPosition}; left_tmp = new[] {leftTarget.localEulerAngles, leftTarget.localPosition}; for (int i = 0; i <= span; i++) { character.localEulerAngles = Vector3.Lerp(char_tmp[0], new Vector3(0f, -45f, 0f), (float)i / span); rightTarget.localEulerAngles = Vector3.Lerp(right_tmp[0], new Vector3(0f, -45f, 0f), (float)i / span); leftTarget.localEulerAngles = Vector3.Lerp(left_tmp[0], new Vector3(0f, -45, 0f), (float)i / span); character.localPosition = Vector3.Lerp(char_tmp[1], new Vector3(0f, -0.02f, 0f), (float)i / span); rightTarget.localPosition = Vector3.Lerp(right_tmp[1], new Vector3(0.0f, 0.3f, 0.4f), (float)i / span); leftTarget.localPosition = Vector3.Lerp(left_tmp[1], new Vector3(0.1f, 0.0f, 0.0f), (float)i / span); yield return 0; } //TakingOff3 span = 3; char_tmp = new[] {character.localEulerAngles, character.localPosition}; right_tmp = new[] {rightTarget.localEulerAngles, rightTarget.localPosition}; left_tmp = new[] {leftTarget.localEulerAngles, leftTarget.localPosition}; for (int i = 0; i <= span; i++) { character.localEulerAngles = Vector3.Lerp(char_tmp[0], new Vector3(0f, -90f, 0f), (float)i / span); rightTarget.localEulerAngles = Vector3.Lerp(right_tmp[0], new Vector3(0f, -90f, 0f), (float)i / span); leftTarget.localEulerAngles = Vector3.Lerp(left_tmp[0], new Vector3(0f, -90, 0f), (float)i / span); character.localPosition = Vector3.Lerp(char_tmp[1], new Vector3(0f, 0.1f, 0f), (float)i / span); rightTarget.localPosition = Vector3.Lerp(right_tmp[1], new Vector3(0.0f, 0.3f, 0.4f), (float)i / span); leftTarget.localPosition = Vector3.Lerp(left_tmp[1], new Vector3(0.1f, 0.12f, 0.0f), (float)i / span); yield return 0; } //TakingOff4 span = 3; char_tmp = new[] {character.localEulerAngles, character.localPosition}; right_tmp = new[] {rightTarget.localEulerAngles, rightTarget.localPosition}; left_tmp = new[] {leftTarget.localEulerAngles, leftTarget.localPosition}; for (int i = 0; i <= span; i++) { character.localEulerAngles = Vector3.Lerp(char_tmp[0], new Vector3(0f, -180f, 0f), (float)i / span); rightTarget.localEulerAngles = Vector3.Lerp(right_tmp[0], new Vector3(0f, -190f, 0f), (float)i / span); leftTarget.localEulerAngles = Vector3.Lerp(left_tmp[0], new Vector3(0f, -190f, 0f), (float)i / span); character.localPosition = Vector3.Lerp(char_tmp[1], new Vector3(0f, 0.27f, 0f), (float)i / span); rightTarget.localPosition = Vector3.Lerp(right_tmp[1], new Vector3(0.05f, 0.3f, 0.1f), (float)i / span); leftTarget.localPosition = Vector3.Lerp(left_tmp[1], new Vector3(0.0f, 0.33f, 0.0f), (float)i / span); yield return 0; } //AirPosition1 span = 3; char_tmp = new[] {character.localEulerAngles, character.localPosition}; right_tmp = new[] {rightTarget.localEulerAngles, rightTarget.localPosition}; left_tmp = new[] {leftTarget.localEulerAngles, leftTarget.localPosition}; for (int i = 0; i <= span; i++) { character.localEulerAngles = Vector3.Lerp(char_tmp[0], new Vector3(0, 0, 0), (float)i / span); rightTarget.localEulerAngles = Vector3.Lerp(right_tmp[0], new Vector3(0, -20, 0), (float)i / span); leftTarget.localEulerAngles = Vector3.Lerp(left_tmp[0], new Vector3(0, -20, 0), (float)i / span); character.localPosition = Vector3.Lerp(char_tmp[1], new Vector3(0, 0.28f, 0), (float)i / span); rightTarget.localPosition = Vector3.Lerp(right_tmp[1], new Vector3(-0.05f, 0.3f, 0.0f), (float)i / span); leftTarget.localPosition = Vector3.Lerp(left_tmp[1], new Vector3(0.05f, 0.33f, 0.1f), (float)i / span); yield return 0; } //AirPosition2 span = 3; char_tmp = new[] {character.localEulerAngles, character.localPosition}; right_tmp = new[] {rightTarget.localEulerAngles, rightTarget.localPosition}; left_tmp = new[] {leftTarget.localEulerAngles, leftTarget.localPosition}; for (int i = 0; i <= span; i++) { character.localEulerAngles = Vector3.Lerp(char_tmp[0], new Vector3(0, -180, 0), (float)i / span); rightTarget.localEulerAngles = Vector3.Lerp(right_tmp[0], new Vector3(0, -200, 0), (float)i / span); leftTarget.localEulerAngles = Vector3.Lerp(left_tmp[0], new Vector3(0, -200, 0), (float)i / span); character.localPosition = Vector3.Lerp(char_tmp[1], new Vector3(0, 0.3f, 0), (float)i / span); rightTarget.localPosition = Vector3.Lerp(right_tmp[1], new Vector3(0.05f, 0.32f, 0.1f), (float)i / span); leftTarget.localPosition = Vector3.Lerp(left_tmp[1], new Vector3(-0.05f, 0.35f, 0.0f), (float)i / span); yield return 0; } //AirPosition3 span = 3; char_tmp = new[] {character.localEulerAngles, character.localPosition}; right_tmp = new[] {rightTarget.localEulerAngles, rightTarget.localPosition}; left_tmp = new[] {leftTarget.localEulerAngles, leftTarget.localPosition}; for (int i = 0; i <= span; i++) { character.localEulerAngles = Vector3.Lerp(char_tmp[0], new Vector3(0, 0, 0), (float)i / span); rightTarget.localEulerAngles = Vector3.Lerp(right_tmp[0], new Vector3(0, -20, 0), (float)i / span); leftTarget.localEulerAngles = Vector3.Lerp(left_tmp[0], new Vector3(0, -20, 0), (float)i / span); character.localPosition = Vector3.Lerp(char_tmp[1], new Vector3(0, 0.25f, 0), (float)i / span); rightTarget.localPosition = Vector3.Lerp(right_tmp[1], new Vector3(-0.05f, 0.3f, 0.0f), (float)i / span); leftTarget.localPosition = Vector3.Lerp(left_tmp[1], new Vector3(0.05f, 0.33f, 0.1f), (float)i / span); yield return 0; } //AirPosition4 span = 3; char_tmp = new[] {character.localEulerAngles, character.localPosition}; right_tmp = new[] {rightTarget.localEulerAngles, rightTarget.localPosition}; left_tmp = new[] {leftTarget.localEulerAngles, leftTarget.localPosition}; for (int i = 0; i < span; i++) { character.localEulerAngles = Vector3.Lerp(char_tmp[0], new Vector3(0, -180, 0), (float)i / span); rightTarget.localEulerAngles = Vector3.Lerp(right_tmp[0], new Vector3(0, -200, 0), (float)i / span); leftTarget.localEulerAngles = Vector3.Lerp(left_tmp[0], new Vector3(0, -200, 0), (float)i / span); character.localPosition = Vector3.Lerp(char_tmp[1], new Vector3(0, 0.2f, 0), (float)i / span); rightTarget.localPosition = Vector3.Lerp(right_tmp[1], new Vector3(0.05f, 0.25f, 0.1f), (float)i / span); leftTarget.localPosition = Vector3.Lerp(left_tmp[1], new Vector3(-0.05f, 0.28f, 0.0f), (float)i / span); yield return 0; } //AirPosition5 span = 3; char_tmp = new[] {character.localEulerAngles, character.localPosition}; right_tmp = new[] {rightTarget.localEulerAngles, rightTarget.localPosition}; left_tmp = new[] {leftTarget.localEulerAngles, leftTarget.localPosition}; for (int i = 0; i <= span; i++) { character.localEulerAngles = Vector3.Lerp(char_tmp[0], new Vector3(0, 0, 0), (float)i / span); rightTarget.localEulerAngles = Vector3.Lerp(right_tmp[0], new Vector3(0, -20, 0), (float)i / span); leftTarget.localEulerAngles = Vector3.Lerp(left_tmp[0], new Vector3(0, -20, 0), (float)i / span); character.localPosition = Vector3.Lerp(char_tmp[1], new Vector3(0, 0.15f, 0), (float)i / span); rightTarget.localPosition = Vector3.Lerp(right_tmp[1], new Vector3(-0.05f, 0.2f, 0.0f), (float)i / span); leftTarget.localPosition = Vector3.Lerp(left_tmp[1], new Vector3(0.05f, 0.23f, 0.3f), (float)i / span); yield return 0; } //Landing span = 3; char_tmp = new[] {character.localEulerAngles, character.localPosition}; right_tmp = new[] {rightTarget.localEulerAngles, rightTarget.localPosition}; left_tmp = new[] {leftTarget.localEulerAngles, leftTarget.localPosition}; for (int i = 0; i <= span; i++) { character.localEulerAngles = Vector3.Lerp(char_tmp[0], new Vector3(0, -180, 0), (float)i / span); rightTarget.localEulerAngles = Vector3.Lerp(right_tmp[0], new Vector3(0, -200, 0), (float)i / span); leftTarget.localEulerAngles = Vector3.Lerp(left_tmp[0], new Vector3(0, -200, 0), (float)i / span); character.localPosition = Vector3.Lerp(char_tmp[1], new Vector3(0, -0.07f, 0), (float)i / span); rightTarget.localPosition = Vector3.Lerp(right_tmp[1], new Vector3(0.05f, 0.0f, 0.1f), (float)i / span); leftTarget.localPosition = Vector3.Lerp(left_tmp[1], new Vector3(0.45f, 0.03f, -0.2f), (float)i / span); yield return 0; } //Check span = 3; char_tmp = new[] {character.localEulerAngles, character.localPosition}; right_tmp = new[] {rightTarget.localEulerAngles, rightTarget.localPosition}; left_tmp = new[] {leftTarget.localEulerAngles, leftTarget.localPosition}; for (int i = 0; i <= span; i++) { character.localEulerAngles = Vector3.Lerp(char_tmp[0], new Vector3(0, -180, 0), (float)i / span); rightTarget.localEulerAngles = Vector3.Lerp(right_tmp[0], new Vector3(0, -180, 0), (float)i / span); leftTarget.localEulerAngles = Vector3.Lerp(left_tmp[0], new Vector3(0, -240, -90), (float)i / span); character.localPosition = Vector3.Lerp(char_tmp[1], new Vector3(0, -0.17f, 0), (float)i / span); rightTarget.localPosition = Vector3.Lerp(right_tmp[1], new Vector3(0.1f, 0.0f, 0.2f), (float)i / span); leftTarget.localPosition = Vector3.Lerp(left_tmp[1], new Vector3(0.46f, 0.4f, 0.7f), (float)i / span); yield return 0; } } }
動画
あれ!!!6回転以上してる!!!???
多分、Lerpで回りすぎているみたいです。この辺りを調査しないと…誰か……
プロシージャルアニメーションにしたいんだったらここを詰めないといけないかもです。
ただ、もうそろそろ男子フリーが始まるので今回はここまで!!