以前のチュートリアルはこちらです:
アニメーション
チュートリアルの最初にMixamoからキャラクターのモデルとアイドリングアニメーションをダウンロードして、使ってきました。歩き、走り、ジャンプができるようになりましたが、アニメーションが変わらず極めて不自然ですね。今回のチュートリアルで新しいアニメーションのパターンをダウンロードしてキャラクターの演出を改善していきます。
始める前に少し注意しておきたいのは、皆さんが普段遊んでいる三人称視点のゲームはキャラクターアニメーションのパターンが非常に多いことです。キャラクターがあらゆるシチュエーションにおいて自然に動いて、生きているように見えるのは、アニメーターの大変な作業のおかげです。例えば「歩く」動作でもこんなアニメーションのバリエーションを用意しなければならないかも知れません。
- 普通のスピードで歩く
- ゆっくり歩く
- 急いで歩く
- 曲がりながら歩く(普通、ゆっくり、早く)
- 慎重に歩く
- しゃがみながら歩く
- 崖っぷちでバランスを取りながら歩く
- ケガして歩く
- 武器を持って歩く
- 銃や弓で狙って歩く
など、など、など。
数多くのアニメーションクリップを用意して、条件によって再生を切り替えるのが今も普通の方法ですが、アニメーションの動きを動的に生成するプロシージャルアニメーションという手法もあります。また、数年前から機械学習を使ってより高度な方法でキャラクターを動かしてさらに自然な歩き方をさせる研究が発表されていますが、市販のゲームでまだ普及していません。
プロシージャルアニメーションも機械学習アニメーションも非常に面白いですが、ここで説明するのは従来のアニメーションクリップの切り替えです。
アニメーションクリップの用意
チュートリアルを始めた時と同様にMixamoで必要なクリップをダウンロードします。使うクリップを徐々に増やしていきますが、とりあえずこの2つのクリップを用意しましょう。
- 移動
- ジャンプ
移動アニメーションはキャラクターの移動速度によって「Walk」か「Run」で検索しましょう。私は少し早めの移動のキャラクターにしたいので「Run」でさがしました。
いいアニメーションクリップを見つけたら、設定で「In Place」を有効にします。キャラクターの実際の移動はスクリプトが行うので、キャラクターが静止して走る様子を確認したいです。
では、気に入るアニメーションクリップを見つけたらダウンロードします。
ダウンロード設定で必ず「Format」を「FBX for Unity」にして、「Skin」を「Without Skin」にします。ここの「Skin」はキャラクターのモデルデータです。すでにあるので、アニメーションデータだけをダウンロードします。
歩きのアニメーションのダウンロードが終わったら次はよさそうな「Jump」のアニメーションを探します。様々なジャンプがありますが、一応静止した状態のジャンプをしておきます。
ダウンロードフォルダーに2つのFBXファイルが追加されました。
これから使うアニメーションのアセットがどんどん増えるのでUnityの方でアニメーション用のフォルダーを作成します。
このフォルダーの中にダウンロードしたFBXファイルを移動します。
両方のアニメーションを選択して、インスペクターで読み込み設定を修正します。
「Rig」のアニメーションタイプを「ヒューマノイド」にします。次は最初に用意したモデルのアバター定義(リグの設定)を使用するように設定します。そうしないと姿勢が少しおかしくなります。
「適用する」ボタンを押して再読み込みをします。
デフォルトでは読み込んだアニメーションがループ再生されないので、この設定も変えなければなりませんが、個別にアセットを編集しないとその設定が表示されません。一つずつ歩きのアニメーションクリップと走りのアニメーションクリップを選択して、インスペクターのアニメーションタブで「時間をループ」を有効にして、変更を適用します。
これから他のアニメーションクリップを追加しますが、同じ手順でダウンロードして読み込んでください。
アニメーターの準備
Unityでアニメーションクリップの切り替えはアニメーターが行います。プレーヤーキャラクターのインスペクターでAnimatorコンポーネントの「コントローラ」をダブルクリックしてアニメーター画面を表示します。
これはアニメーションクリップの遷移図になっています。最初(Entry)はデフォルトのアニメーションクリップ(オレンジ色の)である「Idle」が再生されるようになっています。追加した2つのクリップをこの画面に追加しますが、その前にあらかじめ整理しておきましょう。アニメーションのクリップが結構多くなるとこの遷移図がスパゲッティ状態になってしまわないように、サブステートマシンを作成します。「ステートマシン」とは様々な状態を条件によって切り替える遷移図のことでゲームアニメーション以外にゲームAIでよく使われます。
サブステートマシンの名称を「OnGround」と「InAir」にします。
これは、地面に立っている時と立っていない時のアニメーションクリップを入れておきます。
次に、遷移を追加します。最初は「Entry」を右クリックして、「遷移を作成」オプションを選びます。
出てくる線を「OnGround」に繋げます。同様に「OnGround」から「InAir」へ、そして「InAir」から「OnGround」へ遷移を作成します。
「Idle」をサブステートマシンに移動したいので右クリックして、「コピー」しておきます。
「OnGround」をダブルクリックして中の遷移図を表示して、そこに「Idle」を貼り付けます。
張り付けた「Idle」アニメーションをデフォルトに設定したいので、右クリックして、「レイヤーデフォルトステートとして設定する」を選択します。
空いているところを右クリックして空のステートを追加します。
インスペクターで名称を「Run」にして、「Motion]を先ほどダウンロードした走りのアニメーションにします。
「Idle」と「Run」をこういう風に繋げておきます。
そして、「Idle」と「Run」を「Up (BaseLayer)」に繋げます。
サブステートマシンから出ていきます。
不要となったIdleを削除します。
「InAir」をダブルクリックしてサブステートマシンの中に入ります。
ここに右クリックして新しいステートを追加します。
インスペクターでステートの名称を「Jump」にして再生されるアニメーションを先ほど用意した「Jump」にします。
「Jump」を「Up (BaseLayer)」に繋げて、「OnGround」ステートマシンを選択します。
パラメータの設定
アニメーションの切り替えはスクリプトから設定するパラメータで行います。パラメータはアニメーター画面で登録して、スクリプトで値を設定します。最初は「地面に立っているかどうか」と「移動速度」と「ジャンプした」という3つのパラメータを使いましょう。
パラメータタブで、新しいパラメータを登録して、その種類を「Bool」にします。「Bool」とは理論値のことですね。名称を「OnGround」にしておきます。
移動速度は小数点数の「Float」にして、名称を「MovementSpeed」にします。
ジャンプは値ではなく単発的なイベントですので、種類を「Trigger」にします。
パラメータの名称は自由に設定できますが、スクリプトで使う時にスペルミス(特に大文小文字の区別)がない様に注意しましょう。
アニメーターで設定したパラメータはスクリプトで制御されます。Characterスクリプトを編集していきます。
今まではキャラクターの更新をFixedUpdateで行ってきました。Unityの設計では物理演算の更新の描画の更新が独立して行われます。FixedUpdateは一定の間隔(1秒間に60回)で実行されることが(ある定地)保障されて、物理演算に影響がでそうな処理をここで行わなければなりません。描画やアニメーションはデフォルトの設定でUpdateで更新されて、実行の間隔は保証されていません。60FPSを上回るのも下回るのも有りうる訳です。
ということで、キャラクターを移動させたりするのは必ずFixedUpdateから行いますが、アニメーションに関する更新はUpdateで行います。
パラメータの値を更新するのはAnimatorクラスの「Set○○○」メソッドで行います。「○○○」の部分はパラメータの種類になります。例えば、「Bool」の「OnGround」の設定はこうなります。
void Update()
{
if (animator != null)
{
animator.SetBool("OnGround", isOnGround);
}
}
「isOnGround」は以前のチュートリアルで実装した地面検出の結果が記憶されていますね。最初の引数はパラメータ名ですが、アニメーターで設定した名称とまったく同じじゃないとスクリプトが動作しません。
次は移動速度の「MovementSpeed」です。キャラクターの移動を実装した時に、ApplyMotionメソッドでこのようなコードを書きました。
Vector3 velocity = rb.velocity;
if (groundRigidbody != null)
{
velocity -= groundRigidbody.velocity;
}
Vector3 groundVelocity = ProjectOnPlane(velocity, groundNormal);
最後に計算した「groundVelocity」を記憶したいので、スクリプトの冒頭にプライベートプロパティとして宣言を追加します。
bool isOnGround = false; // 地面に立っているかどうか
bool isJumping = false; // ジャンプしているかどうか
Vector3 groundVelocity = Vector3.zero; // 移動速度
そして、このプロパティを使うように上記のコードを少し修正します。
Vector3 velocity = rb.velocity;
if (groundRigidbody != null)
{
velocity -= groundRigidbody.velocity;
}
groundVelocity = ProjectOnPlane(velocity, groundNormal);
これでUpdateでMovementSpeedを更新する事ができます。
void Update()
{
if (animator != null)
{
animator.SetBool("OnGround", isOnGround);
animator.SetFloat("MovementSpeed", groundVelocity.magnitude);
}
}
最後のジャンプは常に変わる値ではなく、単発的に起こるイベントですので、Updateではなく起きた時に設定します。Jumpメソッドを編集して、アニメーターのトリガーを実行します。
public void Jump(bool state)
{
if (state && isOnGround)
{
rb.velocity += Vector3.up * jumpSpeed;
isJumping = true;
if (animator != null) {
animator.SetTrigger("Jump"); // ジャンプトリガーを実行する
}
}
if (!state)
{
isJumping = false;
}
}
遷移の条件を設定する
では、スクリプトでパラメータが変わるようになったので、アニメーターで遷移の条件を登録しましょう。まず、ジャンプから。InAirのサブステートマシンを開いて、どのステートからでも「Jump」のトリガーが実行された時に「Jump」のアニメーションに遷移するようにしたいので、「Any State」と「Jump」を繋げます。
新しい遷移を選択して、インスペクターで条件を追加します。
サブマシンから出ていく遷移はキャラクターが地面に立った時に移動するようにします。
現在のアニメーションの再生が終わるのを待たずに遷移したいので、「終了時間あり」のチェックを外します。
次はOnGroundのサブステートマシンを編集します。
「Idle」と「Run」の遷移はMovementSpeedの一定の閾値を設けます。
こちらもアイドリングと走りの切り替えはアニメーションの再生が終わるのを待たずに行いたいので、「終了時間あり」をオフにします。
「Idle」と「Run」からサブステートマシンの外へ繋がる遷移は同じ条件の「地面に立っていない」ことにします。
ここもやはり、両方の遷移の「終了時間あり」をオフにします。
やっと動作確認ができるようになります。
動作は概ね正しいですが、重要な設定が残ります。
まず、上のGIFでは分かりにくいですが、走りのスピードとキャラクターの移動速度が一致していなくて、キャラクターが氷の上で滑っているように見えてしまいます。キャラクターの移動速度はゲームプレーに大きな影響ができるので、アニメーションに合わせるというよりもアニメーションの方を調整します。幸いにアニメーションデータを修正しなくても簡単に変えられます。アニメーターで「Run」のアニメーションを選択して、インスペクターで「速度」を調整して、キャラクターが自然に走っているようにします。
そして、仮にアイドリングと走りの閾値を「0.1」に設定しましたが、変えてみてもいいですよ。ただし、キャラクターの動きが早いと止まった時がどうしても不自然になるかも知れません。できれば、アイドリングと走りの間に歩きなど、中間のステートを追加したいですが、これは次のチュートリアルで説明します。
最初は気づかないかも知れませんが、最も不自然なのはジャンプです。ジャンプも落下も地面に立っていない時はいつも同じジャンプのアニメーションをループ再生していますが、よくないですね。落下した時に落下のアニメーションを再生して、ジャンプも立ち上がりから着地まで、高さと関係なく正しく一連の流れを実装したいですね。これも今後のチュートリアルで説明します。
Leave a Reply