【第3回】Raycastを使った弾の発射処理の作り方

前回の作業でプレイヤーキャラクターが動くようになったので、次はFPSを作るうえで必須の「弾の発射処理」を作っていこうと思います。

FPSにおける弾の発射処理の実装方法は主に2通りあるのですが、ここでは「Raycast(レイキャスト)」という機能を使った作り方を解説していきますね。

スポンサーリンク

弾の発射地点を決めるゲームオブジェクトの作成

まず、弾の発射地点の目印となる空のゲームオブジェクトを作っておきます。

銃のモデルの子として新しいゲームオブジェクトを作り、名前を「Bullet Spawn」に変更したら、次の画像のようにゲームオブジェクトの座標が銃口の位置になるように調整しましょう。

銃口の位置に空のゲームオブジェクトを作成する

あと、銃口のゲームオブジェクトの向きも重要になるので青い矢印がちゃんと正面を向くようにしてください(※今回は親である銃のモデルを180度回転させているので注意)。

Raycastを使った弾の発射処理について

そうしたら弾を発射する処理を作っていきたいのですが、その前に「どういうやり方で弾を発射するか?」をよく考えてみましょう。

Hitscan 対 Projectile

はじめにFPSに詳しい方ならご存じかと思いますが、冒頭に書いた通りFPSで弾を発射するやり方は主に次の2つの方法が有名です。

  1. Hitscan
    …見えない光線を飛ばして、光線が当たった地点を着弾地点とする方法
  2. Projectile
    …物理演算を使って弾の弾道をシミュレーションする方法

Projectileは丁寧に弾の弾道まで再現する一方で、Hitscanは「弾は高速なんだから着弾地点だけ分かればいいよね」という考え方ですね。

それで両者を比較した場合のメリット・デメリットは次の通りです。

  • Hitscan:簡単に実装できて処理が軽い。リアルさには欠ける
  • Projectile:リアルだが実装がやや面倒で処理が重め

これを考慮すると、今回はブラウザで動かすということもあるので処理が軽いHitscanを採用することにします。

Raycastとは?

さて、UnityではHitscanは「Raycast」という機能を使うことで簡単に実現できるので、ここでそのRaycastについて簡単に説明しておきます。これは一言でいうと

ある方向にゲームオブジェクトがあるかどうかを調べるための機能

です。考え方としては、「透明な光線(=レイ)」を指定方向に飛ばしてその光線がコライダーに当たったら「ゲームオブジェクトがある」と判定する感じです(下図)。

RayCastのイメージ

Raycastを使えばレイが当たった座標など色々な情報を取得できます。何かと便利なのでUnityでゲームを作るときは頻繁に使う機能です。

弾の発射処理の流れ

そうしたらここまでの内容からFPSにおける弾の発射処理の流れを考えてみましょう。先に答えを言ってしまいますが、基本的には次のような流れになります。

  1. 弾薬が残っている状態でマウスをクリックしたら
  2. 弾発射タイマーをONにする(既にONの場合は以下の処理はスキップ)
  3. そうしたら今向いている正面の方向に、Bullet Spawnの位置からRayを飛ばす
  4. Rayが何かに当たったらヒット処理を行う
  5. 残弾数を減らす
  6. タイマーをOFFにする

ここでのポイントは弾を一定間隔で発射するために「弾発射タイマー」を導入していることです。これがないとマウスを押している間は物凄い勢いで連射されてしまいます。

弾の発射処理のC#スクリプト

では先ほどの処理の流れをC#スクリプトに落とし込んでいきます。新しいC#スクリプトを作り、名前を「FpsGunController」に変更したら次のスクリプトを書いてください。

using System.Collections;
using UnityEngine;

public class FpsGunControler : MonoBehaviour
{

  [SerializeField]
  Transform bulletSpawn = null;
  [SerializeField, Min(1)]
  int damage = 1;
  [SerializeField, Min(1)]
  int maxAmmo = 30;
  [SerializeField, Min(1)]
  float maxRange = 30;
  [SerializeField]
  LayerMask hitLayers = 0;
  [SerializeField, Min(0.01f)]
  float fireInterval = 0.1f;

  bool fireTimerIsActive = false;
  RaycastHit hit;
  WaitForSeconds fireIntervalWait;

  void Start()
  {
    fireIntervalWait = new WaitForSeconds(fireInterval);	// WaitForSecondsをキャッシュしておく(高速化)
  }

  void Update()
  {
    if (Input.GetButton("Fire1"))
    {
      Fire();
    }
  }

  // 弾の発射処理
  void Fire()
  {
    if (fireTimerIsActive)
    {
      return;
    }

    if(Physics.Raycast(bulletSpawn.position, bulletSpawn.forward, out hit, maxRange, hitLayers, QueryTriggerInteraction.Ignore))
    {
      BulletHit();
    }

    StartCoroutine(nameof(FireTimer));
  }

  // 弾がヒットしたときの処理
  void BulletHit()
  {
    // テスト用
    Debug.Log("弾が「" + hit.collider.gameObject.name + "」にヒットしました。");
  }

  // 弾を発射する間隔を制御するタイマー
  IEnumerator FireTimer()
  {
    fireTimerIsActive = true;

    yield return fireIntervalWait;

    fireTimerIsActive = false;
  }

}

特に難しいことはやっておらず、先述の処理の流れの通りのため解説はいらないと思います。注意点としてはまだダメージ処理など必要な機能が実装されていないので、後で何度も手を加えることになるでしょう。

書けたらこれをFirst Person Controllerにアタッチして、

  • 「Bullet Spawn」に先ほど作った空のゲームオブジェクトを登録する
  • 「Hit Layers」にとりあえず「Default」レイヤーを設定する

という作業を行えばOKです。

テストプレイして動作を確認しよう

ここまでできたらテストプレイして動作を確認してみましょう。マウスの左ボタンを押している間、近くのゲームオブジェクトを向いたときにコンソールに「弾が○○にヒットしました。」というメッセージが表示されれば成功です。


次のページ→マズルフラッシュ&弾のヒットエフェクトの作り方