VRゲームのグラフィックを強化しよう(前編)
前回はC#スクリプトを用いて、HPや弾を撃つなどのプレイヤー機能と敵の機能などを実装した。仮のグラフィックとしてキューブなどのオブジェクトを配置していたが、これでは全く味気がない。そこで今回は、敵のグラフィックをテクスチャやアニメーションの付いた3Dモデルに置き換え、またシーン全体のライティングを調整してゲーム全体のグラフィックを強化してみたい。
Unity Asset Storeからモデルを入手する
通常、3Dモデルなどのアセットは「Maya」などのDCCツールを用いて自分で制作するか、オンライン上に公開・販売されているものをダウンロードして入手する。後者の場合、第3回でサンプルプロジェクトをダウンロードした「Unity Asset Store」からさまざまなアセットを使用することが可能だ。今回は例として、以下のアニメーション付きキャラクターモデルと背景モデルの2種類のアセットを入手し、ゲームに取り込んでみる。なお、モデルデータの扱いは基本的にはどれも同じなので、自分の好きな世界観に合うアセットを入手してもOKだ(2017年02月現在、無料ダウンロードが可能)。
- Zombie(図1)
- Stylized Simple Cartoon City(図2)
キャラクターモデルを動かしてみよう
手始めに、Asset Storeから入手したこのゾンビのキャラクターの3Dモデルを動かしてみよう。新しいシーンを作成してAssets/Zombie/Modelz@walk
を配置すると、キャラクターがシーンに表示されるはずだ(図3)。このゲームオブジェクトは、以前解説した基本的な3DデータであるFBXデータそのものである。
また、Assets/Zombie
以下には、マテリアルやそれにアサインされているテクスチャ、アニメーションファイルなども含まれている(図4)。アニメーションファイルもファイル形式上は.fbxとなっている。
自身で用意したモデルをUnityに入れる場合、次の点に気をつけてDCCツールからデータを出力してみよう。
- 複数人とデータを共有する場合はfbx形式で出力する
- デフォーマはジョイントのアニメーションにベイクする(ブレンドシェイプはインポート可能)
- DCCツール上のカメラ・ライト・マテリアルはUnityと互換性がない
- テクスチャをfbxにembedしない場合は手動でUnityにインポートする
アニメーションコントローラの設定
このモデルには5種類のアニメーションが含まれており、それぞれアニメーションさせることが可能だ。しかし、それにはアニメーションコントローラを作成して必要な設定を行わなければならない。アニメーションコントローラはプロジェクトビュー上で右クリック > Animation Controller
で作成する。名前をZombie
としたらファイルをダブルクリックするとAnimatorウィンドウが開く(図5)。
このウィンドウでは各アニメーションの遷移
を定義する。例えば、キャラクターが移動するときは「walk」アニメーションを再生するが、攻撃するときは「attack」を再生する、といった具合だ。このようなそれぞれのアニメーションの状態をステート
と呼び、各ステートを定義するのがこのAnimatorウィンドウなのだ。アニメーションコントローラを作成するとデフォルトで「Entry」と「End」ステートがすでに存在している。それぞれ「開始」と「終了」を意味するステートだ。
手始めにAnimatorウィンドウ上で右クリック > Create State > Empty
と操作して、新規ステートを作ってみよう。New Stateを選択するとインスペクタにステートが表示されるので、名前をWalk
にしよう。続いて、Motion
フィールドにプロジェクトビューのz@walkの下階層にあるwalk
というオブジェクトをアサインしてみよう(図6)。これでWalk
ステートにwalk
アニメーションが設定される。
次に、作成したアニメーションコントローラをシーン上のz@walkのAnimatorコンポーネントのControllerにアサインしよう(図7)。この状態でゲームをプレイしてみると、キャラクターがアニメーションするはずだ。
これは、ゲームを開始したことでアニメーションの初期ステートである「Entry」から歩きステートである「Walk」に遷移した、ということなのだ。また、Animatorウィンドウ上のオレンジの矢印(図8)は各ステートの遷移の方向を意味していることも理解しておこう。この矢印をトランジション
と呼び、各ステートを右クリック > Make Transition > 遷移したいステートを押下
することで設定できる。また、今回のWalkステートのように最初に作成したステートをデフォルトステート
と呼び、ステートがオレンジ色になっている。これは、あらかじめEntryステートからトランジションが設定されているということを覚えておこう。
しかし、このままではWalkアニメーションが1ループしただけで終了してしまうので、プロジェクトビューのz@walkを選択し、インスペクタからAnimationタブ > Loop Timeにチェックを入れてApplyしておこう(図9)。これでWalkアニメーションがループ再生される。
最終的に、全5種類のアニメーションを図10のような配置でステート設定した。「Dead」という名前のステートがあるが、これはLeft FallおよびRight Fallアニメーションの終了時にエネミーが消滅するのを自然に見せるためのいわばダミーステートで、アニメーションはアサインされていない。
Enemyクラスのアサイン
前回Enemyクラスを作成したときは仮モデルとしてキューブオブジェクトを使用したが、今回はz@walkにEnemyクラスをアサインする。Enemy Speedは0.01前後がちょうどいいだろう。このEnemyクラスでは衝突検知のためにコライダとリジッドボディコンポーネントを使用するため、それらもアサインしておこう。今回のような縦に長い形状のキャラクターにはCapsule Colliderを設定するのが一般的だ。コライダのサイズ調整も忘れずに。RigidbodyはUse Gravityのチェックを外しておこう(図11)。これで試しにゲームを実行してみると、キャラクターが歩きながらカメラ(プレイヤー)に近づいてくるのが確認できるはずだ。
なお、前回作成したEnemyクラスはMain CameraにPlayer.csがアサインされていないと動作しない。注意しよう。
スクリプトからのステート制御
続いて、エネミーの行動によって再生するアニメーションを細かく制御していく。前回作成したEnemy.csを次のように修正しよう。
~~~
public Animator anim;
~~~
void OnCollisionEnter(Collision collision) {
GameObject collisionTarget = collision.gameObject;
if (collisionTarget.name.Contains ("Main Camera")) {
// 行動を停止
Stop ();
// 攻撃ステート開始
anim.SetTrigger("Attack");
}
else if(collisionTarget.name.Contains("Bullet"))
{
// 自身のコライダを無効
gameObject.GetComponent<Collider>().enabled = false;
// 行動を停止
Stop ();
// 撃破ステート開始
anim.SetTrigger ("Left Fall");
}
}
void Stop(){
// 移動を停止 & Rigidbodyを無効
enemySpeed = 0;
gameObject.GetComponent<Rigidbody> ().isKinematic = true;
}
public void OnFinishedAttack() {
// 自身のコライダを無効
gameObject.GetComponent<Collider>().enabled = false;
// プレイヤーの HP を攻撃力分減らす
player.playerHP -= enemyAttack;
}
public void OnFinishedFall() {
// 自身(エネミー)を Scene 上から削除
Destroy (gameObject);
}
新たにanim
というメンバ変数をpublicで設けたので、ゲームオブジェクト上のフィールドにEnemy自身のAnimatorコンポーネントをアサインすることを忘れずに(図12)。
今回の修正は、次の2つの目的がある。
- 各アニメーションステートへの遷移処理
- アニメーションステートが終了したかどうかの処理
各アニメーションステートへの遷移処理
衝突検知時の分岐でSetTrigger()
というメソッドがあるが、これはアニメーショントリガ
を呼ぶための処理である。アニメーショントリガはアニメーションコントローラで設定したトランジションを実行するためのパラメータで、スクリプトからステート遷移を行うために欠かせないものだ。つまり、スクリプトからSetTrigger("トリガ名")を実行することでトリガによってトランジションが開始され、アニメーションステートが遷移する。例えば、Walkステート中にスクリプトからAttackトリガが呼ばれるとステートがAttackに遷移し、Attackアニメーションが再生されるわけだ。
このアニメーショントリガはまだ未作成なので、Zombieアニメーションコントローラをもう一度開いて設定しよう。図13に示すボタンでアニメーショントリガを作成できる。名前は「Attack」としておこう。
Any StateからAttackステートへのトランジション(矢印)を選択すると、インスペクタにトランジションの内容が表示される。ここでCondisionsに先ほど作成したアニメーショントリガ「Attack」を設定すればOKだ(図14)。同様にAny StateからLeft FallにもLeft Fallトリガを作成してトランジションを設定しておこう。
これで、スクリプトからプレイヤーへの攻撃時にはAttackトリガが、エネミー撃破時にはLeft Fallトリガが呼ばれ、それぞれのステートにトランジションするようになった。
アニメーションステートが終了したかどうかの処理
「あるステートが終了したタイミングで何らかの処理をしたい」というケースがある。例えば、Left Fallアニメーションが終了したらOnFinishedFall()
メソッドを呼びたい、という場合だ。このような場合はStateMachineBehaviour
を継承したクラスを用意することで対応できる。
まず、名前をEnemyFall.csとする新規クラスを作成して、次のようにしてみよう。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyFall : StateMachineBehaviour {
override public void OnStateExit (Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.GetComponent<Enemy> ().OnFinishedFall ();
}
}
スクリプト内のOnStateExit()
はアニメーションステートが終了するときに呼び出されるオーバーライドメソッドだ。ここでは、アニメーション再生が終わりステートが終了する間際にエネミーを消去するメソッドであるOnFinishedFall()
が呼ばれるようになっている。なお、StateMachineBehaviour
継承クラスはゲームオブジェクトではなくアニメーションステートにアサインしなければならないため、アニメーションコントローラを開いてインスペクタからLeft FallおよびRight Fallステートにアサインしよう(図15)。
同様に、Attackステート終了時にもプレイヤーのHPを減らす処理OnFinishedAttack()
を実行したいため、以下の内容でスクリプトを作成しAttackステートにアサインしておこう。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAttack : StateMachineBehaviour {
override public void OnStateExit (Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.GetComponent<Enemy> ().OnFinishedAttack ();
}
}
なお、StateMachineBehaviour継承クラスでは、他にもさまざまなステートのタイミングで処理を呼び出すことができる。ぜひAPIリファレンスで確認してみよう。
動作確認をしよう
ここまでの作業が完了したら、改めて動作確認をしてみよう。次の流れで処理が実行されていれば完璧だ(図16)。
- エネミーがプレイヤーによって撃破される場合
- Walk → 弾丸の衝突検知 → Left Fall → OnFinishedFall()によって消滅
- エネミーがプレイヤーに攻撃する場合
- Walk → プレイヤーの衝突検知 → Attack → OnFinishedAttack()によってプレイヤーHP減少 & Right Fall → OnFinishedFall()によって消滅
最後に、シーン上のz@walkはゲームオブジェクトとしての状態を保存するためにプレハブ化しておこう。
おわりに
これで、グラフックを強化してゲームの完成度を高めることができた。次回は背景モデルにライティングを施して、ゲームをよりリッチな見た目にしていく。お楽しみに!