見出し画像

UnityでVRゲーム製作!XR Interaction Toolkit 銃を撃つ編

どうも皆様 Hello world!
Unityでゲームを作っております、天野スズランと申します。

Unityの「XR Interaction Toolkit」でVR開発をする方法を書いていきたいなぁと思ってますので、皆様どうかお付き合いください。

今回やること

今回は、銃を撃つ機能を実装しようかなと思ってます。

やっぱり、VRですから武器も実際に手で触れた方が楽しいですよね。
というわけで、それを作って行きましょう。

なお、この記事は前回の物を持つ編の続きとなります。
まだ見ていない方は、そちらもご覧ください。

なお、今回は無料アセットを二つ使わせてもらっています。
詳細は後で書きますが、使うものだけ先に載せておきます。一応。
Modern Guns: Handgun | 3D Guns | Unity Asset Store
Quick Outline | Particles/Effects | Unity Asset Store

銃を持てるようにする

銃モデルのインポート

銃を持てるようにするためには、もちろん銃が無いといけません。
しかし、銃なんて複雑なものをモデリングすると大変ですから、以下のアセットを使用させていただきました。

拳銃のアセットで、銃本体はもちろん、簡易のアニメーションがついてたり、マガジンや実包、弾頭や薬莢のモデルもあります。効果音以外は一通り揃ってます。

Modern Guns: Handgun | 3D Guns | Unity Asset Store

銃モデルを出す

インポートしたモデル「M1911 Handgun_Black」を Hierarchy にドラッグアンドドロップしましょう。
Assets > Nokobot > Modern Guns - Handgun > _Prefabs > Handgun Black > M1911 Handgun_Black 内にあります。

(Shooting) とついている方は何かいろいろついているのだが、今回はいらないのでついてない方。

銃に XR Grab Interactable を追加

その銃に、「XR Grab Interactable」を追加しましょう。
詳しくは、以下の記事を見てください(ダイマ)。

銃の当たり判定を追加

今回の銃の場合、Prefabそのままでは当たり判定が存在しません。
「XR Grab Interactable」が物をつかむ時には、当たり判定(=コライダ)が必要ですので、それをつくって行きましょう。

銃のオブジェクトの下に空のオブジェクトを作り「Collider」と名付けましょう。
そして、その直下に空のオブジェクトを二つ作り、「BarrelCollider」「GripCollider」と名付けましょう。

「Collider」は当たり判定、「Barrel」は銃身、「Grip」は銃のグリップです。

そして、その「BarrelCollider」にコライダーを設定しましょう。
Inspectorから、「Add Component」→「Box Collider」を選びます。

そうすると、銃の周りに黄色い線が出てきます。それが当たり判定のエリアです。

今のままだと大きすぎますので、Inspector の Box Collider の中にある 「Edit Collider」のボタンを押し、Collidreの大きさを調整しましょう。
黄色い箱の所に点が出てきますから、それをドラッグすれば調整可能です。

もしくは、Inspector の Center や Size をいじってもいいです。

それから、右上にある「Persp」という所をクリックすると並行投影モードになりますし、その上の「X」とか書いてある所を押すと真横などの視点にできますから、大きさを調整しやすくなります。

以下のように、銃身に重なるようにしましょう。

並行投影モードにすると遠近感が消え、設計図とかで使われるような見た目になります。

次に、グリップ部分の設定をしましょう。

基本的に銃身部分と同じですが、こちらは持ち手部分に合わせて回転させる必要があります。
「GripCollider」にBoxColliderを設定した後、Inspector の Transform にある「Rotation」の「X」を変更しましょう。

当たり判定を傾けたかったので、別のオブジェクトにした次第です。

銃の握り位置を調整する

ここまでの作業で、銃が握れるようになりましたので、一旦実験してみましょう。
近付いて、手を銃へと伸ばして、グリップボタンを押して…

白い球が手の代り。
猫型ロボットにでもなったつもりで銃を握ってみてください。

握れましたね。
握れましたけど、だいぶ下の方を握ってますね…

なぜこうなるのかというと、座標が関係しています。
何か物を持つときは、持つオブジェクトの座標の X=0,Y=0,Z=0 の位置を持ちます。

ですから、銃モデルをもう少し下に持ってくればよいのですが、それはちょっと面倒。でももうちょっと上を握りたい。
そんな時は、握る位置を指定してあげましょう。

銃オブジェクトの子供に空のオブジェクトを作成し、「GripPosition」と名付けます。

そして、そのGripPositionをちょっと上の位置に動かします。

それから、その GripPosition を「Attach Transform」に設定します。 
銃オブジェクトの Inspector → XR Grab Interactable → Attach Transform にあります。

では、試してみましょう。

分かり辛いですが、ちょっと上の方を握るようになってますね。
こんな風に、握る位置を変更する事が出来ます。

なお、上のやり方だと銃の握られる位置を指定したわけですが、手の握る位置を調整することもできます。
XROrigin の子供である手オブジェクトにアタッチされている XR Direct Interactor の Attach Transform がその設定項目ですので、興味のある方は適当にいじって遊んでみてください。

銃を強調表示する

次にやりたいのが、持つオブジェクトの強調表示。
手と銃が接触している、つまり銃を握れる判定になっている時に、銃を強調表示しましょう。
こうすることで、プレイヤーは「あ、もうちょっと手を近づけなきゃ持てないんだ」と分かってくれます。

枠線アセットのインポート

まず、枠線のアセットを入れておきましょう。
アウトラインと言って、モデルの周囲に線を引いて、強調してくれます。

Quick Outline | Particles/Effects | Unity Asset Store

枠線のアタッチ

アセットを入れた所で、試しに常時銃を強調表示してみましょう。

銃のオブジェクトである「M1911 Handgun_Black」に「Outline」スクリプトをアタッチしましょう。

Add Component > Outlineでアタッチできます。

結果を見てみましょう。
まあなんとなく、プレイヤーに対して「これ持つで!」って伝えられそうですね。

お好みで、色や太さも変えられます。

枠線の切り替え

しかし、常に光り続けているのは邪魔になっちゃいますね。
ですから、「持てるとき」だけ光るようにしましょう。

まずやるのは、強調表示の解除。一旦無効化しましょう。
銃オブジェクトの Inspector の「Outline」と書いてある所の左のチェックを外しましょう。
こうすることで、一旦無効にすることができます。

次に、手が触れた時に強調表示を有効にする設定をしていきます。

銃のオブジェクトにアタッチされている「XR Grab Interactable」の、「First Hover Entered」という項目があります。
そこには関数などを設定することができるのですが、それは”手と重なったとき”に呼び出されます。

「First Hover Entered」の「+」ボタンを押しましょう。
そうすると、関数を指定できるようになります。

そうして出て来た First Hover Entered の設定箇所の左下に、銃のオブジェクトをドラッグ&ドロップ。

右上にドロップダウンリストが出て来るので、「Outline.enabled」を選択し、その下に出て来たチェックマークをオンにします。
このチェックをオンにすることで、 Outline を有効にすることができます。

同様に、「Last Hover Entered」の方にも設定しておきましょう。
こちらは、”手と重ならなくなったとき”に呼び出されます。
ただし、手と重ならなくなったときには強調表示を解除したいので、チェックは入れないようにしましょう。

実際に見てみましょう。

うん、光ってますね。
さて、ちょっと持ってみましょう。

うん、光ってますね……

このままでは見にくいので、つかんだ時に強調表示を消すようにしてみましょう。

「First Select Entered」に対して設定しておきましょう。
First Select Entered は”手で持った時”に呼び出されますので、Outline を消すように設定しておけば、つかんだ時には強調を消してくれます。

銃を撃つ

さて、これで下ごしらえは十分となりました。
満を持して、弾を撃てるようにしていきましょう。

弾の作成

まずは、発射する弾そのものの作成から行きましょう。

とはいっても、弾というのは、VRじゃない普通のゲームと同じように作れば動きますので、ダメージを与えたりといったことは他の記事を参考にしてください。

しかし、発射した結果が全く見えないのも寂しいもの。
今回は、見た目だけの弾を発射しようかなと思います。

まず、Hierarchy に空のオブジェクトを作りましょう。
名前は「Bullet」とでもしておきましょう。

そして、新しいスクリプト「BulletController」を作り、「Bullet」へアタッチしましょう。
新しいスクリプトは、「Project」ビューで右クリックし、Create -> C# Script で作成できます。

BulletController は、その名の通り弾をコントロールするスクリプトです。

次に、その BulletController をいじっていきます。
Project 上のスクリプトをダブルクリックすると編集できます。

まずは弾の速度。小数点の使える float 型の「m_bulletSpeed」を用意し、初期値として27を入力してみました。
本来の M1911 は270m/sとからしいのですが、それだと見えないのでとりあえず1/10に。

そして、Update に、position を正面へ進ませるように記述
指定した速度(m_bulletSpeed) で、正面( transform.forward) に、 一フレーム分(Time.deltaTime) だけ進ませてます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BulletController : MonoBehaviour
{
    /// <summary>
    /// 弾の速度 (m/s)
    /// </summary>
    [SerializeField]
    private float m_bulletSpeed = 27.0f;

    // Update is called once per frame
    void Update()
    {
        //弾を前に進ませる
        transform.position +=
            transform.forward * m_bulletSpeed * Time.deltaTime;
    }
}

しかし、それだけだと見えません。
見えないと面白くありませんし、光の筋を表示させましょう。
え?めんどい?そういう人は Cube あたり子供にしておくと分かりやすいです。

Bullet の Inspector 画面から、Add Component -> Trail Renderer を選択しましょう。
これは、オブジェクトの通った位置に線を表示してくれるコンポーネントです。

しかし、初期状態だとマテリアルが設定されていないため、そのままでは紫の帯が飛んでいくだけです。
マテリアルの設定をしましょう。

Materials -> Element 0 の右側の二重丸みたいなところをクリックすると、既存のマテリアルを全部表示してくれますので、「Default-Line」と検索し、選択しましょう。

その他、設定は以下のようにしました。
Width は線の幅。最大0.05m ( = 5cm ) にしました。
Time は線の長さ。0.25秒分の長さになります。
Color は線の色。オレンジを徐々に透明にしています。

ここで、試しに実行してみましょう。
以下のようになれば大体OKです。
一時停止ボタンを使えば、カメラでとらえやすくなると思います。

ぶっちゃけ弾があるかどうかわかればいいので、クオリティは適当でOKです。

次に、今完成した弾をプレハブ (Prefab) にします。
Hierarchy から Project ビューへとドラッグ&ドロップすると、プレハブ化できます。
また、プレハブにした時点で、Hierarchy の Bullet は消しても問題ありません。

プレハブにしておくと、後々ゲームに出しやすいです。

銃の発射機構を作る

弾が完成したところで、その弾を発射するための処理を組んでいきます。
弾を発砲するためには Script を組むのが早いので、新しく「GunController」スクリプトを作り、銃にアタッチしましょう。

次に、GunController の設計です。

GunController が知っていたい情報は何かというと、やはり実体化したい銃弾のデータ「銃弾プレハブ」と、その銃弾を実体化する位置すなわち「銃口位置」でしょう。
スクリプトに、その二つを設定できるように、変数として用意します。

まずは銃弾のプレハブ。
GameObject 型の m_bulletPrefab という名前にしました。
なお、この変数は Inspector で設定をしたいので、public または[SerializeField] private にしておきましょう。

    /// <summary>
    /// 銃弾のプレハブ。
    /// 発砲した際に、このオブジェクトを弾として実体化する。
    /// </summary>
    [SerializeField]
    private GameObject m_bulletPrefab = null;

次に銃口位置。
こちらは、位置 (座標) と角度さえわかればよいので、Transform 型で、名前は m_muzzlePos にしました。

    /// <summary>
    /// 銃口の位置。
    /// 銃弾を実体化する時の位置や向きの設定などに使用する。
    /// </summary>
    [SerializeField]
    private Transform m_muzzlePos = null;

次に、そのプレハブを実体化します。
実体化というのは、実際にゲーム中に登場させる事で、召喚とか生成とかのイメージに近いです。

新しく「ShootAmmo」関数を作りましょう。
そして、銃弾プレハブを実体化。Instantiate 関数が使えます。
そして、実体化した銃弾オブジェクトに、銃口の座標と角度を入れていきます。

    /// <summary>
    /// 銃弾を生成する。
    /// </summary>
    private void ShootAmmo()
	{
        //弾のプレハブか銃口位置が設定されていなければ処理を行わず帰る。ついでに煽る。
        if(m_bulletPrefab == null ||
            m_muzzlePos == null)
		{
            Debug.Log(" Inspector の設定が間違ってるでww m9(^Д^)プギャー ");
            return;
		}

        //弾を生成する。
        GameObject bulletObj = Instantiate(m_bulletPrefab);

        //弾の位置を、銃口の位置と同一にする。
        bulletObj.transform.position = m_muzzlePos.position;

        //弾の向きを、銃口の向きと同一にする。
        bulletObj.transform.rotation = m_muzzlePos.rotation;

    }

これで、実体化処理が出来ました。が、このままだとこの関数が呼び出されず、銃弾が実体化されません。
ですので、トリガーが引かれた時に呼び出される関数も作って、その関数から ShootAmmo 関数を呼び出しましょう。

/// <summary>
/// VRコントローラーのトリガーが握られた時に呼び出す。
/// </summary>
public void Activate()
{
    ShootAmmo();
}

一応、ここまでのスクリプトを貼っておきます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GunController : MonoBehaviour
{
    /// <summary>
    /// 銃弾のプレハブ。
    /// 発砲した際に、このオブジェクトを弾として実体化する。
    /// </summary>
    [SerializeField]
    private GameObject m_bulletPrefab = null;

    /// <summary>
    /// 銃口の位置。
    /// 銃弾を実体化する時の位置や向きの設定などに使用する。
    /// </summary>
    [SerializeField]
    private Transform m_muzzlePos = null;

    /// <summary>
    /// VRコントローラーのトリガーが握られた時に呼び出す。
    /// </summary>
    public void Activate()
	{
        ShootAmmo();
    }

    /// <summary>
    /// 銃弾を生成する。
    /// </summary>
    private void ShootAmmo()
	{
        //弾のプレハブか銃口位置が設定されていなければ処理を行わず帰る。ついでに煽る。
        if(m_bulletPrefab == null ||
            m_muzzlePos == null)
		{
            Debug.Log(" Inspector の設定が間違ってるでww m9(^Д^)プギャー ");
            return;
		}

        //弾を生成する。
        GameObject bulletObj = Instantiate(m_bulletPrefab);

        //弾の位置を、銃口の位置と同一にする。
        bulletObj.transform.position = m_muzzlePos.position;

        //弾の向きを、銃口の向きと同一にする。
        bulletObj.transform.rotation = m_muzzlePos.rotation;

    }
}

次に、銃口の位置を示すオブジェクトを作りましょう。
Hierarchy から空のオブジェクトを作り、Muzzleとでも名前を変えましょう。

それから、位置を調整しましょう。
銃口の先あたりに来るようにするとそれっぽく見えます。
この時、Z 軸 (青い矢印) が正面に向くようにしましょう。

並行投影モードにすると設定しやすいです。

で、今作った Muzzle をMuzzle Pos へ、さっき作った Bullet を Bullet Prefab へドラッグ&ドロップ

Bullet Prefab は、スクリプトに書いた m_bulletPrefab の事。名前は勝手に直してくれます。

引き金を引く

次は、引き金を引いた時に、GunController の関数を呼び出すように設定しましょう。

XR Grab Interactable の下の方に、Activate という所があります。
ここに設定された関数は、持たれた状態でトリガーが引かれた時に呼び出されます。
ここに、GunController の Activate 関数を入れましょう。

Inspector の Activate の「+」ボタンを押し、アタッチした GunController をドラッグ&ドロップ、Activate 関数を指定しましょう。

Inspector の Activate と GunController の Activate とで紛らわしいので注意。

試しに撃ってみましょう。

うん、撃ててますね。
今回はこれで完成です、お疲れさま。

音を鳴らしたかったりする場合は、同じように Activate に設定するか、Script から鳴らしましょう。

最後に

続きもそのうち上げていきたいです。次回は「カードキーを挿す」の予定。

今回、記事の投稿までにだいぶ時間かかっちゃったので、次回はもうちょっと短めに書こうかと悩み中。1万字弱書いてるんですよね、今回。
長いと読みにくいですし、3千程度に出来たらなぁ。

それから、一応Vtuberやってますので、気が向いたらフォローしてやってください。
たまーにつぶやいて、ごく稀に配信してます。
Twitter:@Suzuran_Amano
Youtubeちゃんねる:スズランちゃんねる - YouTube

この記事が、あなたの開発の助けになることを祈っています。

この記事が気に入ったらサポートをしてみませんか?