弾の発射処理の作り方&エフェクトの追加方法

前回の作業でプレイヤーに銃を持たせることができたので、次は実際に弾を発射する処理と発射に伴うエフェクトを作っていきます。

スポンサーリンク

弾の発射オブジェクトの作成

まず、はじめに弾を発射するためのオブジェクトを作ります。

ヒエラルキー上の何もないところに空のゲームオブジェクトを作り、名前を「GunController」に変更してください。次にこのオブジェクトを前回作った「M4A1_PBR」(銃のオブジェクト)の子に設定し、位置がちょうど銃口のところになるようにTransformを調整しましょう。

弾発射オブジェクトの位置

スポンサーリンク

弾を発射する処理について考える

そうしたら次に弾を発射する処理を作っていきたいのですが、その前に「どういうやり方で弾を発射するか?」を考えてみます。やり方はいくつか考えられますが、初心者の方がすぐ思いつく方法はおそらく次のようなものでしょう。

  1. 予めRigidbodyを持った弾オブジェクトを作っておく。
  2. 弾オブジェクトを弾発射オブジェクトから発射する。
  3. 弾オブジェクトが何かに当たったら個別に何らかの処理を行うようにする。

この方法は堅実なやり方で決して悪くはありません。しかし、次のようなことを考えるともっとスマートな方法がありそうです。

  • 一般的に弾丸の速度は非常に速く、目視できないので弾を描画する必要がない。
  • また「弾は撃った瞬間に着弾する」と考えることができるので、飛んでいく様子を物理演算でシミュレーションする必要がない。

そこで今回は弾発射オブジェクトから「レイ(Ray)」を飛ばすやり方を採用することにしました。

RayCastについて

それでRayとは何かというと「透明な光線」のことで、そのRayを使った機能としてRayCastというものがあります。これは「ある方向にRayを飛ばして、オブジェクトに当たったか調べたり、当たったオブジェクトを取得したりする機能」のことです(下の図参照)。

RayCastのイメージ

このRayCastを使えば弾のオブジェクトを作る必要がないので楽ですし、物理演算を行わないので処理が軽いと思います。また、Rayは長さを指定することができるので簡単に射程距離を設定することもできます。何気にメリットが多いやり方です。

弾を発射する処理の流れ

これで弾を発射する方法を選定できたので、次に具体的にどういう手順で処理を行えばよいのかを考えていきましょう。「弾を発射する」だけではザックリしすぎなので、具体的にどういう処理をどういう手順で行えばいいのかをしっかり考えるのがポイントです。

では今回私が考えた処理の流れを説明しますね。

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

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

スポンサーリンク

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

では先ほどの処理をスクリプトで実装していきましょう。私が書いたスクリプト「FirstPersonGunController」は次の通りです(後で説明するエフェクト用の処理も先に追加してあります)。

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

public class FirstPersonGunController : MonoBehaviour
{
  public enum ShootMode { AUTO, SEMIAUTO }

  public bool shootEnabled = true;

  [SerializeField]
  ShootMode shootMode = ShootMode.AUTO;
  [SerializeField]
  int maxAmmo = 100;
  [SerializeField]
  int damage = 1;
  [SerializeField]
  float shootInterval = 0.15f;
  [SerializeField]
  float shootRange = 50;
  [SerializeField]
  Vector3 muzzleFlashScale;
  [SerializeField]
  GameObject muzzleFlashPrefab;
  [SerializeField]
  GameObject hitEffectPrefab;

  bool shooting = false;
  int ammo;
  GameObject muzzleFlash;
  GameObject hitEffect;

  public int Ammo
  {
    set
    {
      ammo = Mathf.Clamp(value, 0, maxAmmo);
    }
    get
    {
      return ammo;
    }
  }

    void Start()
    {
    InitGun();
    }

    void Update()
    {
    if (shootEnabled & ammo > 0 & GetInput())
    {
      StartCoroutine(ShootTimer());
    }
    }

  void InitGun()
  {
    Ammo = maxAmmo;
  }

  bool GetInput()
  {
    switch (shootMode)
    {
      case ShootMode.AUTO:
        return Input.GetMouseButton(0);

      case ShootMode.SEMIAUTO:
        return Input.GetMouseButtonDown(0);
    }

    return false;
  }

  IEnumerator ShootTimer()
  {
    if (!shooting)
    {
      shooting = true;

      //マズルフラッシュON
      if (muzzleFlashPrefab != null)
      {
        if(muzzleFlash != null)
        {
          muzzleFlash.SetActive(true);
        }
        else
        {
          muzzleFlash = Instantiate(muzzleFlashPrefab, transform.position, transform.rotation);
          muzzleFlash.transform.SetParent(gameObject.transform);
          muzzleFlash.transform.localScale = muzzleFlashScale;
        }
      }

      Shoot();

      yield return new WaitForSeconds(shootInterval);

      //マズルフラッシュOFF
      if(muzzleFlash != null)
      {
        muzzleFlash.SetActive(false);
      }

      //ヒットエフェクトOFF
      if(hitEffect != null)
      {
        if (hitEffect.activeSelf)
        {
          hitEffect.SetActive(false);
        }
      }

      shooting = false;
    }
    else
    {
      yield return null;
    }
  }

  void Shoot()
  {
    Ray ray = new Ray(transform.position, transform.forward);
    RaycastHit hit;

    //レイを飛ばして、ヒットしたオブジェクトの情報を得る
    if (Physics.Raycast(ray, out hit, shootRange))
    {
      //ヒットエフェクトON
      if (hitEffectPrefab != null)
      {
        if (hitEffect != null)
        {
          hitEffect.transform.position = hit.point;
          hitEffect.transform.rotation = Quaternion.FromToRotation(Vector3.forward, hit.normal);
          hitEffect.SetActive(true);
        }
        else
        {
          hitEffect = Instantiate(hitEffectPrefab, hit.point, Quaternion.identity);
        }
      }

      //★ここに敵へのダメージ処理などを追加

    }

    Ammo--;
  }
}

スクリプトを書けたらGunControllerオブジェクトにアタッチしておいてください。

スクリプトの解説

InitGun()メソッド

銃の初期化用のメソッドです。

GetInput()メソッド

マウス入力を取得するためのメソッドです。押しっぱなしで連射する「AUTO」モードと、1クリックごとに弾を発射する「SEMIAUTO」モードを用意しました。

ShootTimer()メソッド

弾を一定間隔で発射するためのコルーチンです。まだタイマーが作動していないなら後述のShoot()メソッドを呼び出して一定時間待ち、タイマーをOFFにします。そのほか後述のエフェクト関係の処理も行っています。

Shoot()メソッド

実際に弾を発射する処理を行います。先ほど紹介したRayCastを使い、何らかのオブジェクトに当たったら処理を行うようにしてあります。具体的な処理はまだ書いてありませんが、とりあえずヒットエフェクトの処理だけは用意しておきました。

エフェクトの処理について

ここで、鋭い人はエフェクトの表示処理を見て「なんか変だな」と思ったかもしれません。というのも、ここでは各エフェクトのインスタンスを最初に1つずつだけ生成して、あとはその表示・非表示と位置を切り替えているだけだからです。

こういうやり方を採用している理由は負荷の軽減です。別に弾を発射するたびにエフェクトのインスタンスを毎回作り、それを毎回破棄してもいいのですが、こうした処理は結構負荷がかかります。そこで最初に1回だけインスタンスを生成して不要になったら非表示、必要になったらまた表示…という処理を行っているのです(分かりにくい話ですみません)。

スポンサーリンク

エフェクトの追加

さて、これでスクリプトがひとまずできたので仕上げにパーティクルエフェクトを追加します。今回追加するエフェクトは

  • マズルフラッシュ(発砲したときに出る光)
  • ヒットエフェクト

の2種類です。エフェクトは自分で作ってもいいのですが大変なので、アセットストアで適当なものを探しましょう。ここでは「War FX」という無料のパーティクルパックを使わせていただくことにしました。

War FX

ダウンロードしてインポートしたら、先ほどのスクリプトのコンポーネントの「Muzzle Flash Prefab」と「Hit Effect Prefab」のところに好きなエフェクトのプレハブを設定してください。私は下の図のように設定を行いました。

エフェクトの設定例

テストプレイしてエフェクトがきちんと表示されれば成功です。

マズルフラッシュとヒットエフェクト


次のページ→弾数ゲージの作り方