この連載が書籍になりました!『VRを気軽に体験 モバイルVRコンテンツを作ろう!

VRゲームのグラフィックを強化しよう(前編)

2017年3月2日(木)
酒井 駿介

前回はC#スクリプトを用いて、HPや弾を撃つなどのプレイヤー機能と敵の機能などを実装した。仮のグラフィックとしてキューブなどのオブジェクトを配置していたが、これでは全く味気がない。そこで今回は、敵のグラフィックをテクスチャやアニメーションの付いた3Dモデルに置き換え、またシーン全体のライティングを調整してゲーム全体のグラフィックを強化してみたい。

Unity Asset Storeからモデルを入手する

通常、3Dモデルなどのアセットは「Maya」などのDCCツールを用いて自分で制作するか、オンライン上に公開・販売されているものをダウンロードして入手する。後者の場合、第3回でサンプルプロジェクトをダウンロードした「Unity Asset Store」からさまざまなアセットを使用することが可能だ。今回は例として、以下のアニメーション付きキャラクターモデルと背景モデルの2種類のアセットを入手し、ゲームに取り込んでみる。なお、モデルデータの扱いは基本的にはどれも同じなので、自分の好きな世界観に合うアセットを入手してもOKだ(2017年02月現在、無料ダウンロードが可能)。

キャラクターモデルを動かしてみよう

手始めに、Asset Storeから入手したこのゾンビのキャラクターの3Dモデルを動かしてみよう。新しいシーンを作成してAssets/Zombie/Modelz@walkを配置すると、キャラクターがシーンに表示されるはずだ(図3)。このゲームオブジェクトは、以前解説した基本的な3DデータであるFBXデータそのものである。

図3:Sceneに配置してキャラクターを構成する階層をすべて表示したところ

また、Assets/Zombie以下には、マテリアルやそれにアサインされているテクスチャ、アニメーションファイルなども含まれている(図4)。アニメーションファイルもファイル形式上は.fbxとなっている。

図4:本アセットには3DSMaxで開けるオリジナルデータやジョイントの構成が一覧できる画像データも入っている

【コラム】自分で作成したモデルをUnityにインポートするには

自身で用意したモデルをUnityに入れる場合、次の点に気をつけてDCCツールからデータを出力してみよう。

  • 複数人とデータを共有する場合はfbx形式で出力する
  • デフォーマはジョイントのアニメーションにベイクする(ブレンドシェイプはインポート可能)
  • DCCツール上のカメラ・ライト・マテリアルはUnityと互換性がない
  • テクスチャをfbxにembedしない場合は手動でUnityにインポートする

アニメーションコントローラの設定

このモデルには5種類のアニメーションが含まれており、それぞれアニメーションさせることが可能だ。しかし、それにはアニメーションコントローラを作成して必要な設定を行わなければならない。アニメーションコントローラはプロジェクトビュー上で右クリック > Animation Controllerで作成する。名前をZombieとしたらファイルをダブルクリックするとAnimatorウィンドウが開く(図5)。

図5:Animatorウィンドウを開いたところ

このウィンドウでは各アニメーションの遷移を定義する。例えば、キャラクターが移動するときは「walk」アニメーションを再生するが、攻撃するときは「attack」を再生する、といった具合だ。このようなそれぞれのアニメーションの状態をステートと呼び、各ステートを定義するのがこのAnimatorウィンドウなのだ。アニメーションコントローラを作成するとデフォルトで「Entry」と「End」ステートがすでに存在している。それぞれ「開始」と「終了」を意味するステートだ。

手始めにAnimatorウィンドウ上で右クリック > Create State > Emptyと操作して、新規ステートを作ってみよう。New Stateを選択するとインスペクタにステートが表示されるので、名前をWalkにしよう。続いて、Motionフィールドにプロジェクトビューのz@walkの下階層にあるwalkというオブジェクトをアサインしてみよう(図6)。これでWalkステートにwalkアニメーションが設定される。

図6:ステートを作成してMotionフィールドにアニメーションファイルをアサインする

次に、作成したアニメーションコントローラをシーン上のz@walkのAnimatorコンポーネントのControllerにアサインしよう(図7)。この状態でゲームをプレイしてみると、キャラクターがアニメーションするはずだ。

図7:作成したコントローラを、ゲームオブジェクトにアサインする

これは、ゲームを開始したことでアニメーションの初期ステートである「Entry」から歩きステートである「Walk」に遷移した、ということなのだ。また、Animatorウィンドウ上のオレンジの矢印(図8)は各ステートの遷移の方向を意味していることも理解しておこう。この矢印をトランジションと呼び、各ステートを右クリック > Make Transition > 遷移したいステートを押下することで設定できる。また、今回のWalkステートのように最初に作成したステートをデフォルトステートと呼び、ステートがオレンジ色になっている。これは、あらかじめEntryステートからトランジションが設定されているということを覚えておこう。

図8:EntryステートからWalkステートに遷移したことでキャラクターがアニメーションした

しかし、このままではWalkアニメーションが1ループしただけで終了してしまうので、プロジェクトビューのz@walkを選択し、インスペクタからAnimationタブ > Loop Timeにチェックを入れてApplyしておこう(図9)。これでWalkアニメーションがループ再生される。

図9:この設定でアニメーションがループ再生されるようになる

最終的に、全5種類のアニメーションを図10のような配置でステート設定した。「Dead」という名前のステートがあるが、これはLeft FallおよびRight Fallアニメーションの終了時にエネミーが消滅するのを自然に見せるためのいわばダミーステートで、アニメーションはアサインされていない。

図10:全5種類のアニメーションの最終的なステート配置

Enemyクラスのアサイン

前回Enemyクラスを作成したときは仮モデルとしてキューブオブジェクトを使用したが、今回はz@walkにEnemyクラスをアサインする。Enemy Speedは0.01前後がちょうどいいだろう。このEnemyクラスでは衝突検知のためにコライダとリジッドボディコンポーネントを使用するため、それらもアサインしておこう。今回のような縦に長い形状のキャラクターにはCapsule Colliderを設定するのが一般的だ。コライダのサイズ調整も忘れずに。RigidbodyはUse Gravityのチェックを外しておこう(図11)。これで試しにゲームを実行してみると、キャラクターが歩きながらカメラ(プレイヤー)に近づいてくるのが確認できるはずだ。

図11:キャラクターにEnemyクラス、Capsule Collider、Rigidbodyをアサイン

なお、前回作成した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)。

図12:Animatorフィールドに自身のAnimatorコンポーネントをアサインする

今回の修正は、次の2つの目的がある。

  1. 各アニメーションステートへの遷移処理
  2. アニメーションステートが終了したかどうかの処理

各アニメーションステートへの遷移処理

衝突検知時の分岐でSetTrigger()というメソッドがあるが、これはアニメーショントリガを呼ぶための処理である。アニメーショントリガはアニメーションコントローラで設定したトランジションを実行するためのパラメータで、スクリプトからステート遷移を行うために欠かせないものだ。つまり、スクリプトからSetTrigger("トリガ名")を実行することでトリガによってトランジションが開始され、アニメーションステートが遷移する。例えば、Walkステート中にスクリプトからAttackトリガが呼ばれるとステートがAttackに遷移し、Attackアニメーションが再生されるわけだ。

このアニメーショントリガはまだ未作成なので、Zombieアニメーションコントローラをもう一度開いて設定しよう。図13に示すボタンでアニメーショントリガを作成できる。名前は「Attack」としておこう。

図13:アニメーショントリガの作成

Any StateからAttackステートへのトランジション(矢印)を選択すると、インスペクタにトランジションの内容が表示される。ここでCondisionsに先ほど作成したアニメーショントリガ「Attack」を設定すればOKだ(図14)。同様にAny StateからLeft FallにもLeft Fallトリガを作成してトランジションを設定しておこう。

図14:トランジション(矢印)を選択してAttackステートのConditionにトリガを設定する

これで、スクリプトからプレイヤーへの攻撃時には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)。

図15:StateMachineBehaviour継承クラスをアニメーションステートにアサインする

同様に、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)。

  1. エネミーがプレイヤーによって撃破される場合
    • Walk → 弾丸の衝突検知 → Left Fall → OnFinishedFall()によって消滅
  2. エネミーがプレイヤーに攻撃する場合
    • Walk → プレイヤーの衝突検知 → Attack → OnFinishedAttack()によってプレイヤーHP減少 & Right Fall → OnFinishedFall()によって消滅

図16:カメラ(プレイヤー)にキャラクターが移動&攻撃しているところ

最後に、シーン上のz@walkはゲームオブジェクトとしての状態を保存するためにプレハブ化しておこう。

おわりに

これで、グラフックを強化してゲームの完成度を高めることができた。次回は背景モデルにライティングを施して、ゲームをよりリッチな見た目にしていく。お楽しみに!

アプリ制作会社などでモバイルアプリの開発業務を経て、2015年よりグリー株式会社所属。

Technical Artistチームにて、3D アートアセットパイプラインの構築やシェーダ開発、処理負荷の最適化などにあたっている。

Unity Certified Developer(2016)

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています