Unityで銃弾を撃つスクリプトメモ③敵を配置してRayCastで弾を当て血しぶきや火花パーティクルやダメージ処理など

unity 銃を撃つ 敵 当たり判定 raycast 3Dゲームの作り方
※記事内に広告が含まれています。

バーチャル3Dクリエイター神部まゆみです(*^-^*)

この記事はUnityで銃弾を撃つスクリプトについての記事の続きです。

前回の記事はこちら。

●PRスペース●
■PR■

現在、約2000個の人気Unityアセットが50%オフで買える Unityニューイヤーセールをやってるみたいです。

Unityニューイヤーセール 50%オフ

Very Animation Umotion Pro なども50%オフなので、欲しいアセットはこの機会に買っておくと良いかも。

veryanimation  umotion pro   

〇まゆみマート|BOOTH

BOOTHでVRoidテクスチャやVRChat向けオブジェクトなどを販売しています。いいねしてくれると励みになります(*^-^*)

やりたいこと:敵を配置してRayCastで銃弾を当て、ダメージ処理する

前回までで銃を撃つ処理はだいたいできたので、敵を配置して銃を撃ってダメージを与えたい。

銃弾のオブジェクトを飛ばせば良いのかと思ったけど、銃弾みたいな速い武器はRayCastでやるみたいです。

ChatGPTパイセンに聞いてみる

とりあえず彼に聞けばなんとかなるだろう…。

unity 銃を撃つ RayCast

ちょっと長くなるので要約します。

銃弾オブジェクトを飛ばすのではなくRayCastで当たり判定だけやるっぽい

ロケットランチャーや弓など弾を見せたい武器を除き、普通の銃は弾速が速すぎるためRayCastで当たり判定だけやる感じっぽい。

unity 銃を撃つ RayCast
unity 銃を撃つ RayCast

速すぎて判定が間に合わない感じかなぁ。

RayCastで当たった座標が取れるので、その場で血しぶきなどを出せば良いっぽい

RayCastして当たり判定が行われると、座標や向きなどの情報が取れるみたいなのでその座標で血しぶきパーティクルなどを出せば良いみたい。

unity 銃を撃つ RayCast
unity 銃を撃つ RayCast

RayCastは銃から出すのではなくカメラから出すっぽい?

銃のマズル(銃口あたりの部品)からRayCastを出すとなんかズレるらしいので、カメラ基準?でだすっぽい。

unity 銃を撃つ RayCast

カメラの見た目と実際の判定にズレが出やすいからってことかな?

プレイヤーはゲーム内のカメラを通して画面を見てるわけだから、プレイヤーの見ている画面に合わせて判定するってことだと思う。たぶん。

レイヤーで判定すれば敵かそれ以外かでパーティクルを分けたりできるっぽい

敵に当たったら血しぶきパーティクルを、それ以外に当たったら火花パーティクルを出す…みたいにしたい場合、レイヤーで判定できるっぽい(タグでもOK)。

unity 銃を撃つ RayCast
unity 銃を撃つ RayCast

RayCastはコライダーにしか当たらないため敵にコライダーは必要

弾オブジェクトを飛ばさないわけだから敵にコライダーはいらないのか?と思ったけど、RayCastの当たり判定に必要みたい。

unity 銃を撃つ RayCast

敵のデータを格納するクラスを作っておくと後々ラクっぽい

最初に敵の設計もやっておくと後々便利みたい。

unity 銃を撃つ RayCast
unity 銃を撃つ RayCast

最初に敵のクラスを作っておくと、それを継承して差分を作ったりできて便利なんだったかな?

スライムからスライムベスを作ったりとか、一から定義しなくても形状など共通の部分だけ継承して使いまわし、色やHPだけ変えて差分を作ったりできる。

とりあえず敵を配置してRayCastを飛ばすと良いか…

銃弾オブジェクトを飛ばすだけなら「銃撃てました!次の記事では当たり判定を~」って分けられるんだけど、RayCastを使う場合は敵とセットでやらないといけない感じですね(^_^;)

unity 銃を撃つ RayCast

実際にやってみる

まぁ多分できると思うので実際にやってみます。

IDamageable.csを作る

この短いファイルは作っておくだけでOK。

ほかスクリプトから参照するので後々便利っぽいです。

public interface IDamageable
{
    void TakeDamage(int damage);
}

Enemyの血しぶきパーティクルと、それ以外の火花パーティクルを配置

Enemyに当たった時に血しぶきパーティクルを、それ以外の壁とかに当たったら火花パーティクルが表示されるようにしたいので二種類作る。

とりあえず動作するかのテストなのでテキトーでOK。動いたら勝手に作りこんでくださいw

RayCastでどこの座標に当たったか判定してくれるので、とりあえずシーン内に配置されていればスクリプトで勝手にその場所に出してくれます。

なのでどこでもいいからシーンに配置さえしてあればOK。

パーティクルは ゲームオブジェクト ⇒ エフェクト ⇒ パーティクルシステム で作れます。

unity 銃を撃つ 敵 当たり判定 raycast

パーティクルの設定

二つ作るけど、コピペして分かりやすく色だけ変えればOK。

unity 銃を撃つ 敵 当たり判定 raycast

やったことは、

  • 「ゲーム開始時に再生」のチェックを外した
  • 「アクションを停止」を「破棄」に
  • 「ループ」のチェックを外した
  • すぐ消したいので「継続時間」と「開始時の生存期間」を少なめにした

くらいかなあ。

アクションを停止を破棄にしないとパーティクルが消えず無限に存在してしまうため、何度も銃を撃つ処理をしているとヒエラルキーがパーティクルだらけになります(-_-;)

プレイヤーにGunShoot.csを追加

これをコピペしてプレイヤーにアタッチします。

using UnityEngine;

public class GunShoot : MonoBehaviour
{
    [Header("射撃設定")]
    public Camera aimCamera;
    public float range = 100f;
    public int damage = 10;
    public LayerMask hitMask;

    [Header("ヒットエフェクト")]
    public GameObject bloodEffect;
    public GameObject sparkEffect;

    public void Fire()
    {
        Ray ray = aimCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
        RaycastHit hit;

        if (Physics.Raycast(ray, out hit, range, hitMask))
        {
            SpawnHitEffect(hit);
            ApplyDamage(hit);
        }
    }

    void SpawnHitEffect(RaycastHit hit)
    {
        GameObject effectPrefab;

        if (hit.collider.gameObject.layer == LayerMask.NameToLayer("Enemy"))
        {
            effectPrefab = bloodEffect;
        }
        else
        {
            effectPrefab = sparkEffect;
        }

        if (effectPrefab == null)
            return;

        Quaternion rot = Quaternion.LookRotation(hit.normal);

        GameObject effect = Instantiate(effectPrefab, hit.point, rot);

        // ★ ここが重要
        ParticleSystem ps = effect.GetComponent<ParticleSystem>();
        if (ps != null)
        {
            ps.Play();
        }
    }


    void ApplyDamage(RaycastHit hit)
    {
        IDamageable damageable = hit.collider.GetComponent<IDamageable>();
        if (damageable != null)
        {
            damageable.TakeDamage(damage);
        }
    }
}

こんな感じ。

unity 銃を撃つ 敵 当たり判定 raycast

・Aim Camera ゲームのカメラを指定。これを元にRayを飛ばして当たり判定

・範囲 射程距離。100なら100m先までRayを飛ばす

・HitMask 当たり判定をつけたいレイヤーを指定。複数指定可。

・BloodEffect 当たったオブジェクトがEnemyレイヤーの時に出すパーティクル

・SparkEffect 当たったオブジェクトがEnemy以外の時に出すパーティクル

って感じ。

HitMaskにEnemyレイヤーを指定しなかった場合、BloodEffectは出ない。

当たったのがEnemyレイヤーならその場でBloodEffectを再生、それ以外ならその場でSparkEffectを再生、って感じのスクリプトにしてある。

Aim.csを書き換える

発砲処理のところにGunShootのFireメソッドを呼ぶように書き換えます。

using UnityEngine;
using StarterAssets;
using System.Collections;

public class Aim : MonoBehaviour
{
    public Animator animator;

    private ThirdPersonController thirdPersonController;
    private StarterAssetsInputs starterAssetsInputs;

    [Header("発砲音")]
    public AudioSource audioSource;
    public AudioClip gunShotSE;

    [Header("マズルフラッシュ(ライト)")]
    public Light muzzleFlashLight;
    public float flashDuration = 0.05f;

    private bool isFiring;

    [Header("射撃処理")]
    public GunShoot gunShoot;

    void Awake()
    {
        // 念のため最初は消灯
        if (muzzleFlashLight != null)
        {
            muzzleFlashLight.enabled = false;
        }
    }

    void Start()
    {
        thirdPersonController = GetComponent<ThirdPersonController>();
        starterAssetsInputs = GetComponent<StarterAssetsInputs>();
    }

    void Update()
    {
        HandleAim();
        HandleFire();
    }

    void HandleAim()
    {
        if (starterAssetsInputs.aim)
        {
            animator.SetBool("Aim", true);
            thirdPersonController.MoveSpeed = 0f;
        }
        else
        {
            animator.SetBool("Aim", false);
            thirdPersonController.MoveSpeed = 2.0f;
        }
    }

    void HandleFire()
    {
        if (starterAssetsInputs.aim && starterAssetsInputs.fire)
        {
            FireBullet();

            // ★ ここが超重要
            starterAssetsInputs.fire = false;

            animator.SetTrigger("Fire");
        }
    }


    void PlaySound()
    {
        if (audioSource != null && gunShotSE != null)
        {
            audioSource.Stop(); // 前の音を強制的に止める
            audioSource.PlayOneShot(gunShotSE);
        }
    }

    void PlayMuzzleFlash()
    {
        if (muzzleFlashLight == null)
            return;

        StopAllCoroutines();
        StartCoroutine(MuzzleFlashCoroutine());
    }

    IEnumerator MuzzleFlashCoroutine()
    {
        muzzleFlashLight.enabled = true;
        yield return new WaitForSeconds(flashDuration);
        muzzleFlashLight.enabled = false;
    }

    void FireBullet()
    {
        Debug.Log("Bang!");
        // Raycast / 弾生成 / ダメージ処理など

        // Raycast 発射
        if (gunShoot != null)
        {
            gunShoot.Fire();
        }
        else
        {
            Debug.LogWarning("GunShoot is not assigned!");
        }

        PlaySound();
        PlayMuzzleFlash();
    }
}

追加されたpublicパラメータはGunShootだけで、ここにGunShootコンポーネントを指定します。

unity 銃を撃つ 敵 当たり判定 raycast

敵を配置する

とりあえずスフィアを配置、レイヤーをEnemyにする。

敵にEnemyStatus.csを追加

これで最低限の敵データのクラスが作れます。

using UnityEngine;

public class EnemyStatus : MonoBehaviour
{
    public int maxHP = 100;
    public int attackPower = 10;

    [HideInInspector]
    public int currentHP;

    void Awake()
    {
        currentHP = maxHP;
    }
}

HPと、一応攻撃力も指定したけどまた攻撃は作ってません。

unity 銃を撃つ 敵 当たり判定 raycast

敵にEnemyDamageReceiver.csを追加

これを敵に追加すればダメージが通り、HPが0になると消滅します。

using UnityEngine;

public class EnemyDamageReceiver : MonoBehaviour, IDamageable
{
    public EnemyStatus status;

    void Awake()
    {
        if (status == null)
        {
            status = GetComponent<EnemyStatus>();
        }
    }

    public void TakeDamage(int damage)
    {
        status.currentHP -= damage;
        Debug.Log($"Enemy Hit! HP: {status.currentHP}");

        if (status.currentHP <= 0)
        {
            Die();
        }
    }

    void Die()
    {
        Debug.Log("Enemy Dead");
        Destroy(gameObject);
    }
}

ステータスにはEnemyStatusコンポーネントを指定、何気に無敵時間も指定できるようにした。

unity 銃を撃つ 敵 当たり判定 raycast

これで動いた!

長かったけどこれで動きました!

んーなんか最低限ゲームっぽくなってきたな…。

あとは血しぶきを凝るとかだけど、一応これで適当に敵を配置してEnemyレイヤーにしとけば最低限敵を倒して進む感じのゲームにはなると思います。

↓ゴール判定は一応以前やったから、短いクソゲーを作って公開するだけならできそうですw

つづく?

案外手軽に銃を撃つ処理が作れて良かった。

これでゲームスタート、開始、敵が出てきて倒す、ゴール、までは最低限やってきたとは思うけど、あとは作りこんでいったりかなぁ。

ある程度形ができたらオープニングやイベントムービーを作ったりとかしたいんだけど、というかむしろそっちをやりたいかな(^_^;)

凝りだすとキリがないけど、まぁ記事のネタになるからぼちぼちやっていこうと思いますw

また続きが書けたら追記します(*^_^*)

タイトルとURLをコピーしました