本講座はVR版FPSゲームの作り方を解説しています。今回がその第17回目となります。
前回まででVRへの移行はほぼ終わっていますが、最後にVRにして出てくるちょっとした不具合やプラス要素を解説しておきます。
前回の記事:
具体的には弓矢の傾き調整、レーザーポインター照準器のVR移植、大量のオブジェクトの生成消滅を実現する際に役立つオブジェクトプールのテクニック、そして最後にVRにおける酔い止め対策について解説します。
このような調整はゲームを作成する際には必ず必要にはなりますが、個人個人でお好きなようにしても良いところでもあります。今回の記事を参考にしてそれぞれご自身のゲームに適合する形に改造してみてください。
矢の傾きのズレを補正する方法
現在矢を放った瞬間を見ると少し斜めに傾いている方もいるかと思います。
傾いていない方もいるかとは思いますが、傾いてる前提で原因と調整をしていきます。特に問題ない方は飛ばしても構いません。
コライダーの調整
まずは下記画像をみてください。発射した瞬間を想定してちょっと極端にした画像です(イメージです)。
Z軸前方(正面)に力を加えているのですが、画像のように矢が横に傾いてしまう方がいると思います。この場合このまま斜め状態で飛んでいきます。
この原因は、コライダーの形状です。ぱっと見はいい感じに調整しているのですが、今回使用している矢アセットの形状や位置も要因の一つで中心がずれることがあります。
この中心、というのは正確には「重心」で、「Rigidbody」で任意に重心(centerOfMass)設定をしていない場合はコライダーから自動計算されています(これまで特別に設定はしていなのでご自身で記載していない場合は自動計算が適用されています)。
なのでもし矢が斜めになってしまう方はまずはコライダーを再調整してみましょう。
ここでは真っ直ぐ飛びやすい調整方法の一例を載せておきます。
コライダー変更
現在「Arrow」には「CapsuleCollider」が付与されており、ざっくりと矢を包むような形状になっているかと思います。
正確に真後ろから正面に力を加えればこのままでいいのですが、ちょっとした中心のズレやVRのコントローラーでの矢の発射にした影響などで矢が傾くのを防ぐためにコライダーの形状を変更します。
まずは「CapsuleCollider」を削除して「SphereCollider(円形のコライダー)」を付与します。そしてそのコライダーを矢の先端あたりに適当な大きさで設定します。下記の画像を参考に設定しましょう。
これでカプセル形状よりも、他の影響を受けず真っ直ぐ飛ぶようになるかと思います。処理の変更は特に必要ありません。
矢の先端を移動方向へ向ける
VRで矢を放つと、重力を設定している場合速度が低めなのもあって矢の方向がちゃんとしていないことが目立ってくるかと思います。
銃弾だと必要はないのですが、矢なのでここは進行方向に向くように調整しておきましょう。
コードを修正
変更は「Arrow.cs」です。
先に修正部分、関連部分を掲載します。修正部分にマーカーを付けています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
public class Arrow : MonoBehaviour { ・・・ void Update() { if( rigid != null && isAttack == true ) transform.forward = rigid.velocity.normalized; } // -------------------------------------------------------------------------- /// <summary> /// 生成時コールバック. /// </summary> // -------------------------------------------------------------------------- public void OnCreated() { Init(); } // -------------------------------------------------------------------------- /// <summary> /// 発射. /// </summary> /// <param name="direction"> 発射方向. </param> /// <param name="forceMode"> フォースモード. </param> // -------------------------------------------------------------------------- public void Shoot( Vector3 direction, ForceMode forceMode, float chargeValue ) { ・・・ StartCoroutine( Direction() ); } // -------------------------------------------------------------------------- /// <summary> /// 少し待機してから攻撃中フラグを変更. /// </summary> // -------------------------------------------------------------------------- IEnumerator Direction() { yield return new WaitForSeconds( 0.2f ); isAttack = true; } // -------------------------------------------------------------------------- /// <summary> /// 初期化処理. /// </summary> // -------------------------------------------------------------------------- void Init() { if( rigid == null ) rigid = GetComponent<Rigidbody>(); isAttack = false; } ・・・ } |
上記一番上の「Update()」関数内は一旦後にします。
次の「OnCreate()」関数は、変更はしていません。この関数は生成時に実行されており、ここで「Init()」関数が実行されています。この「Init()」に追記します。
上記では一番下にある「Init()」関数ですが、追加部分は「isAttack = false」です。初期値が必ず「false」になるようにしておきます。
そして「Shoot()」関数への追加は(内容は省略して)一番最後に「StartCoroutine( Direction() )」でコルーチンを実行します。
そのコルーチンが一つ下の「Direction()」関数です。
内容は「yield return new WaitForSeconds( 0.2f )」で「0.2秒」待機してから「isAttack」を「true」にします。
ここまでを文章で説明すると、まず初期化で「isAttack」フラグを「false」の状態にします。このフラグは攻撃している時を表すフラグです。
そして、「Shoot()」関数で攻撃をするときに、コルーチンを使用して「0.2秒」だけ遅れて「isAttack」を「true」にし、攻撃状態に変更します。
この処理を行う理由の前に「Update()」関数を解説します。
「if文」の条件「if( rigid != null && isAttack == true ) 」は、リジッドボディ「rigid」が存在する時かつ「isAttack」フラグが「true」の時、つまり攻撃状態の時です。
その時「transform.forward = rigid.velocity.normalized」で「transform.forward」つまり「矢の正面」を「rigid.velocity.normalized」「移動速度」の方向に向けて「飛んでいる方向を向き続ける」ようにします。
ここでなぜ先のコルーチンで「0.2秒」待つかと言うと、もし攻撃と同じタイミングでフラグ変更してしまうと打ち出す瞬間の一瞬の移動速度のない状態も含まれ、少しだけ変な方向を向く時間ができてしまいます。
そのため矢を発射して移動方向が定まってからフラグを変更しています。
これで完了です。矢を発射すると重力を設定しているとちゃんと進行方向を向いて飛んでいくようになりました。
VR空間でのレーザーポインタ・照準器の作成
VRでは、先に作成したPC、スマホのようにターゲットの画像を置くことが難しくなります。
今の何の当てもなく弓を構えて狙う、というのもリアルでいいですが、ここでは照準の目安にできるレーザーポインタの作成方法をお伝えします。
なお以前も少しお話ししましたが、矢に重力を持たせる場合は別途計算が必要になるので、ここではあくまで直線的なレーザーを飛ばすのみにします。
LineRendererを使ってレーザーポインターを作る
ここではRendererの中の一つであるLineRendererを利用していきます。
LineRendererの設定
まずは矢のプレハブ「Arrow」を開いて、一番親の「Arrow」にInspectorで「LineRenderer」を付与します。
すると、紫色の四角が表示されます。まずは色を変えていきましょう。
「Assets/AppMain/Material」フォルダに新しくマテリアルを作成し「LineMaterial」とします。色はお好きにして構いませんが、邪魔になりにくいように「RendererMode」を「Fade」にして、色選択の一番下「A」(透明度)を小さくしておくことをお勧めします。
そして「LineRenderer」コンポーネントの下の方「Materials」の項目に作成したマテリアルをドラック&ドロップして設定します。すると表示されている四角が設定した色になったかと思います。
続いて、値を設定していきます。
すべてを解説するのは大変なので、今回使用するもののみ説明します。
使用するのは上の方にある「Position」の中の「Size」の「0」「1」とリストになっている部分、そしてその下のグラフ部分です。
まずは、下のグラフの左上にある「Width」を「0.01」に設定します。これは文字通りラインの幅です。グラフは形状を設定しますが、今回は変更せず横一直線のままでOKです。
次にその上にあるグラフです。これはラインの経路を設定します。今回は直線に伸ばすだけなのでリストは2つ下記のように数値を設定すればOKです。
Index | X | Y | Z |
0 | 0 | -0.1 | 0.8 |
1 | 0 | -0.1 | 10 |
最後に少し下にある「UseWorldSpace」です。これはチェックを外しておきましょう。
「UseWorldSpace」はワールド座標を使用するかのフラグで、「True」(チェックあり)の場合は、Sizeの位置はワールド座標となり、「False」(チェックなし)の場合はローカル座標となります。
これで上記画像のように、矢の先端からレーザーが直線が出るようになります。
C#スクリプトでレーザーポインタの処理を作成
ここまででレーザーは出ますが、矢を放ってもレーザーが出たままなのでオン・オフできるようにします。
「Arrow.cs」に処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public class Arrow : MonoBehaviour { ・・・ // ラインレンダラー. [SerializeField] LineRenderer line = null; ・・・ // -------------------------------------------------------------------------- /// <summary> /// 発射. /// </summary> /// <param name="direction"> 発射方向. </param> /// <param name="forceMode"> フォースモード. </param> // -------------------------------------------------------------------------- public void Shoot( Vector3 direction, ForceMode forceMode, float chargeValue ) { ・・・ line.enabled = false; } ・・・ // -------------------------------------------------------------------------- /// <summary> /// 初期化処理. /// </summary> // -------------------------------------------------------------------------- void Init() { ・・・ line.enabled = true; } ・・・ } |
とても簡単です。まず「SerializeField」で「LineRenderer」変数を用意して「line」とします。これは「SerializeField」でなく「Start」などで「GetComponent」してもOKです。
「SerializeField」の場合は後ほど、Inspectorで「LineRenderer」が付与されているゲームオブジェクト(Arrow)をドラック&ドロップするのを忘れないようにしましょう。
そして「Shoot()」関数、矢を発射するときに「line.enabled = false」で「LineRenderer」の「enable」を「false」にして無効化します。
「Init()」関数、つまり生成後には「line.enabled = true」して有効化しています。
これで、矢を構えている時にはラインが表示され発射したら、消えるようになりました。
(↓見にくいですが矢の先端から青いレーザーが出ています。)
なお、「LineRenderer」は「Size」のリストに値を細かく設定することで曲線のように表示することもできます。
ここでは解説しませんが「Rigidbody」の「Mass(質量)」や重力などから物理学的に軌道を算出できる方は、このリストにその値を入れることで矢の曲線軌道を表示することもできます。
発展的課題:矢のオブジェクトプール化に挑戦しよう
ここでは、「プール化」という方法を簡単に解説しておきます。
プールというのは文字通り水を貯めるプールのことで、そこに何らかのデータ、リソースなどを一時的に溜めておいて必要に応じて取り出すような方法を「プール化」と言います。
Unityではゲームオブジェクトをプール化することを「オブジェクトプール」と呼んだりします。
Unityでは、「Instantiate(生成)」「Destroy(破棄)」の処理を繰り返すことが、(メモリ等々の理由で)処理としてあまり良くないとされているため、たくさんの生成、破棄を繰り返す場合には「プール化」を検討することになります。
実際は今回程度の量ならさほど処理に影響はありませんが、あえて該当するものとすれば矢、敵になります。ここでは矢のみ解説していきます。
マシンガンや2Dシューティングのような大量の弾がある時には是非検討しましょう。
なおここでは、大まかに必要な関数を解説しますが、その実行部分などは長くなってしまうので省きます。
下記を見ればここまでやって来られた方なら問題なく使用できるかと思いますので、参考にして最後はご自身で作成してみてください。
プール用取得処理と返還処理の作成
では、まとめて追加部分を掲載します。追加するのは「AppPlayerContorller.cs」です。
これで矢を必要な時には生成、使用したら非アクティブにして保管して使い回すという処理が出来ました。
歩行やカメラ回転によるVR酔い対策の実装方法
最後に酔い対策についてお話ししておきます。
現在VRのゲームでよく使用される対策は
- トンネリング
- スナップターン(<=> スムースターン)
- ワープ移動 ( <=> スムース移動、通常移動 )
大きく分けるとこの3つです。ゲーム性によっては必要なかったり、その他周りの風景ごと単純(単調)にしたりなどがあります。
今回はカーレースゲームと異なり、自分で視点を変えたり歩いて戦ったりする場合を考慮した酔い止め対策に触れていきます。
関連記事:Unity VRレースゲームの作り方11 VRゲーム用入力処理・UI・酔い対策の実装
それでは解説していきましょう。
トンネリング
言い方が違う場合もありますが、見えている風景の周囲を黒などで覆って視界を狭めることを言います。
(画像はイメージです)
上記画像のように移動している時に正面方向以外を見えなくします。横など中心以外の景色の移り変わりが酔いにつながるのでそれを見えなくしてしまう方法です。
自由移動のできるVRゲームではよく使われており、大体設定で強度やオンオフを切り替えられるようになっています。
作成するとしたら、今回ダメージ受けた時に表示している赤い画像のように黒い真ん中が抜けた画像を表示するか、球状のゲームオブジェクトのマテリアルをつけたりすることで比較的簡単に実装できるはずです。
スナップターン
これは今回はすでに実装してます。
回転するときに、一定角度ごとにカクカクと回転させる方法です。逆に酔いやすい方法を「スムースターン」などと呼びます。スムーズにくるくる回転させることです。これをやると結構な確率で酔いますので基本はスナップターンにしておいた方がいいでしょう。
もしスムースターンを実装する場合は、現在右コントローラースティックで回転していますので、右スティックを横に入れている間にUpdateなどで回転させればOKです。これも簡単に実装できます。
ワープ移動
これは文字通りですね。
現在は左スティックで自由に移動できるようにしていますが、これをワープで移動できるようにします。
例えばトリガーを押している間にコントローラー先からレーザーを出して、トリガーを離した時にそのヒットしている地面の位置に移動するような形です。
多くのVRゲームで採用されている移動方式ですが、酔い止め対策の意味もあったんです。
今回は弓を射るのにレーザー(Raycast)を使用しているので、それと同じような処理で実装できます。簡単と言うほどでもないですが、そこまで難易度は高くないので興味のある方は作ってみましょう。
この3つ(ゲームによってはもっと少なくてもOK)の対応をしていれば、酔いの対策をしていると言えるでしょう。あとはプレイヤーの耐性次第。
本格的なVRゲームを作成をする場合は酔い止め対策もぜひ実装しておきましょう。
最後に
以上で弓を使用したFPSゲームの講座は終了となります。お疲れ様でした!
PC、スマホ、VR移植と盛りだくさんの講座となりました。
今回は弓を使用しましたが、銃や他でも色々な応用はできます。
VRの場合は弓と銃では操作方法が違ってきますが、おそらく銃の方がボタンを押すだけで弾を放てるので簡単に実装できるでしょう。
ぜひこのFPSゲームの作り方講座が今後のご自身の開発に役立てば幸いです。
コメント