【Unity】追尾弾(誘導ミサイル)の作り方!必ず命中する弾を実装してみよう

追尾弾の作り方 ゲーム開発

Unityでシューティングゲーム等を作っていると、例えば

敵を追尾するミサイル

を実装したくなることがあります。ただこのような追尾弾は意外と作るのが難しいので、初心者の方にとってはなかなか実装しづらいのではないでしょうか。

そこでここではなるべく簡単なC#スクリプトを使って追尾弾を実装する方法をご紹介しますね。

今回作る追尾弾について

動作サンプル

まずはじめに、今回作る追尾弾の動作サンプルを掲載しておきます。

追尾弾サンプル

(画像クリックでGIF再生)

赤い球がターゲットで白いのが追尾弾です。上のGIFではターゲットは静止していますが、もちろん動いていても追尾弾は命中します。

追尾弾の仕様

次に今回の追尾弾の仕様についてですが、次のとおりとします。

  • 物理演算ではなくTransformで移動する。
  • 加速度の上限を設定することができる。加速度の上限を設定しない場合は指定した時間内に必ず命中するが、上限を設定した場合は命中しない場合もある。
  • 当たり判定は別途実装するものとする。

まず追尾弾は物理的な動きではないので、物理演算を使用する必要はなくTransfromによって移動させます。それから今回は簡略化のため当たり判定は実装しません。下記の内容を参考にして追尾弾をゲームに導入する場合は当たり判定を別途実装する必要があるでしょう。

追尾弾のC#スクリプト

では追尾弾のC#スクリプトを掲載します。

※注意:
下記のスクリプトは自由に使っていただいて構いませんが、作者を偽るのは禁止とします。例えば別の場所に掲載する場合は必ずクレジットを明記してください。
using System.Collections;
using UnityEngine;

public sealed class HomingSample : MonoBehaviour
{

    [SerializeField]
    Transform target;
    [SerializeField, Min(0)]
    float time = 1;
    [SerializeField]
    float lifeTime = 2;
    [SerializeField]
    bool limitAcceleration = false;
    [SerializeField, Min(0)]
    float maxAcceleration = 100;
    [SerializeField]
    Vector3 minInitVelocity;
    [SerializeField]
    Vector3 maxInitVelocity;

    Vector3 position;
    Vector3 velocity;
    Vector3 acceleration;
    Transform thisTransform;

    public Transform Target
    {
        set
        {
            target = value;
        }
        get
        {
            return target;
        }
    }

    void Start()
    {
        thisTransform = transform;
        position = thisTransform.position;
        velocity = new Vector3(Random.Range(minInitVelocity.x, maxInitVelocity.x), Random.Range(minInitVelocity.y, maxInitVelocity.y), Random.Range(minInitVelocity.z, maxInitVelocity.z));

        StartCoroutine(nameof(Timer));
    }

    public void Update()
    {
        if (target == null)
        {
            return;
        }

        acceleration = 2f / (time * time) * (target.position - position - time * velocity);

        if (limitAcceleration && acceleration.sqrMagnitude > maxAcceleration * maxAcceleration)
        {
            acceleration = acceleration.normalized * maxAcceleration;
        }

        time -= Time.deltaTime;

        if (time < 0f)
        {
            return;
        }

        velocity += acceleration * Time.deltaTime;
        position += velocity * Time.deltaTime;
        thisTransform.position = position;
        thisTransform.rotation = Quaternion.LookRotation(velocity);
    }

    IEnumerator Timer()
    {
        yield return new WaitForSeconds(lifeTime);

        Destroy(gameObject);
    }

}

C#スクリプトの解説

少し長いC#スクリプトですが、やっていることは大して難しくありません。Update関数内の処理内容に注目すると次のようになっています。

  1. 加速度の算出
  2. (加速度制限がONの場合)加速度を制限
  3. 命中時刻チェック
  4. 速度と座標の算出

ではそれぞれ詳しく見ていきましょう。

加速度の算出

まずは加速度を算出する必要があるので、等加速度直線運動の式を利用して加速度を計算します。等加速度直線運動の式を変形すると

加速度a = 2(x – vt) / t^2

となるので、これに

  • x:taget.position – position
  • v:velocity
  • t:Time.deltaTime

という風に値をあてはめればOKです。

(加速度制限がONの場合)加速度を制限

次に、もし加速度制限がONならば加速度を上限値に制限します。

加速度の大きさはacceleration.magnitudeで取得できますが、単にベクトルの大きさを比較する場合はmagnitudeではなくsqrMagnitudeを使ったほうが高速なのでそちらを利用します。ただしsqrMagnitudeは二乗された値ですので比較対象も二乗する必要があります。

もし加速度上限を超えていれば、「正規化した加速度にmaxAccelerationを掛けたベクトル(=上限値)」をaccelerationに代入します。

命中時刻チェック

命中時刻からTime.deltaTimeを引いて、時刻が0未満になったら処理を終了します。

速度と座標の算出

最後は速度と座標の算出です。速度は加速度にTime.deltaTimeを掛ければ算出できるので、1フレームの移動量は速度にTime.deltaTimeを掛けた分となります。したがって現在の座標は前フレームの座標に移動量を足せば算出できます。

おまけ:追尾弾を生成するためのC#スクリプト(動作確認用)

最後におまけで、追尾弾を生成するための動作確認用のC#スクリプトも掲載しておきます。

using System.Collections;
using UnityEngine;

public class MissileSpawner : MonoBehaviour
{

    [SerializeField]
    Transform target;
    [SerializeField]
    GameObject prefab;
    [SerializeField, Min(1)]
    int iterationCount = 3;
    [SerializeField]
    float interval = 0.1f;

    bool isSpawning = false;
    Transform thisTransform;
    WaitForSeconds intervalWait;

    void Start()
    {
        thisTransform = transform;
        intervalWait = new WaitForSeconds(interval);
    }

    void Update()
    {
        if (isSpawning)
        {
            return;
        }

        StartCoroutine(nameof(SpawnMissile));
    }

    IEnumerator SpawnMissile()
    {
        isSpawning = true;

        Vector3 euler;
        Quaternion rot;
        HomingSample homing;

        for(int i = 0; i < iterationCount; i++)
        {
            homing = Instantiate(prefab, thisTransform.position, Quaternion.identity).GetComponent<HomingSample>();
            homing.Target = target;
        }

        yield return intervalWait;

        isSpawning = false;
    }

}

おわりに

以上、Unityでの追尾弾(誘導ミサイル)の作り方を説明しました。

追尾弾をゲームに導入すると必殺技っぽくなってカッコよさが増すのでおすすめです。ぜひ上記の内容を参考にしていただき、ゲームのクオリティをアップしていただければと思います。

この記事がUnityでのゲーム開発のお役に立てば幸いです。