Unityで2Dの追尾ミサイルを実装する

## 概要

制作しているゲームで2Dでの追尾ミサイルを実装したので、その方法をまとめておきます。
たくさん配置して無限追尾ミサイルで遊ぶと楽しいです。

```
開発環境
Unity Editor Version:6000.0.22f1
```
## 仕様

- 特定のtransform目掛けてゆっくりと回転する
- ミサイルの正面向きに移動する
- 移動速度は徐々に加速させ、最高速度に到達すると加速を止める

## 完成形

先に完成形を載せておきます。これをミサイルオブジェクトにアタッチすればよいです。

``` C#
using UnityEngine;

public class Missile : MonoBehaviour
{
        /** 追尾対象 */
        [SerializeField] private Transform target;
        /** 画像の正面向き */
        [SerializeField] private Vector3 fromDirection;
        /** 回転最高速度 */
        [SerializeField] private float maxRotationSpeed;
        /** 移動最高速度 */
        [SerializeField] private float maxSpeed;
        /** 最高速度に到達するまでの秒数 */
        [SerializeField] private float reachMaxSpeedSeconds;

        private float _elapsedSeconds;
        private float _elapsedSecondsRatio;
        
        void Update()
        {
            Accelerate();
            LookAtTarget();
            Move();
        }
        
        private void Accelerate()
        {
            // 加速する
            _elapsedSeconds+=Time.deltaTime;
           _elapsedSecondsRatio = _elapsedSeconds / reachMaxSpeedSeconds;
            float maxRatio = 1.0f;
            if (maxRatio < _elapsedSecondsRatio)
            {
                _elapsedSecondsRatio = 1.0f;
            }
        }
        
        private void LookAtTarget()
        {
            // 追尾対象への向きと距離
            Vector3　heading = target.position - transform.position;
            // 追尾対象への回転量
            Quaternion targetRotation = Quaternion.FromToRotation(fromDirection, heading.normalized);
            // ゆっくりと回転させる
            float rotationSpeed = maxRotationSpeed * _elapsedSecondsRatio;
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
        }
        
        private void Move()
        {
            float moveSpeed = maxSpeed *  _elapsedSecondsRatio;
            
            // ミサイルが向いている方向に移動
            var forward = transform.rotation * fromDirection;
            transform.position += forward.normalized * (moveSpeed * Time.deltaTime);
        } 
}
```

## 特定のtransform目掛けてゆっくりと回転する

まずはミサイルと、ターゲットとなるゲームオブジェクト(四角形)を配置しておきます。

![missile_first.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4040766/95ae7e37-b0db-4200-b46f-9211a1c2b038.jpeg)

本題のコードの話。
ターゲットに向けて回転し続けるには[Transform.LookAt](https://docs.unity3d.com/ja/2020.1/ScriptReference/Transform.LookAt.html)を使えばいい...と思ってたのですが、これは2DだとY軸も回転しておかしくなります。2Dの時は[Quaternion.FromToRotation(Vector3 fromDirection, Vector3 toDirection)](https://docs.unity3d.com/ja/2022.2/ScriptReference/Quaternion.FromToRotation.html)を使います。第一引数には画像がどの向きを向いているかを指定します。上記の場合、画像は右向きなのでXに1を入れます。仮に上向きならY軸に1,下向きなら-1,左向きならX軸に-1を入れます。第二引数には追尾対象への方向を渡します。

また、ゆっくりと回転させるために補間をかけます。補間は[Quaternion.Slerp](https://docs.unity3d.com/ja/2019.4/ScriptReference/Quaternion.Slerp.html)を使います。

``` c#
        /** 追尾対象 */
        [SerializeField] private Transform target;
        /** 回転速度 */
        [SerializeField] private float rotationSpeed;
        /** 画像の正面向き */
        [SerializeField] private Vector3 fromDirection;
        
        void Update()
        {
            LookAtTarget();
        }

        private void LookAtTarget()
        {
            // 追尾対象への向きと距離
            Vector3 heading = target.position - transform.position;
            // 追尾対象へどれくらい回転するか
            Quaternion targetRotation = Quaternion.FromToRotation(fromDirection, heading.normalized);
            // ゆっくりと回転させる
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
        }
```
![missile_01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4040766/624f529d-9895-4195-bb09-df5a720206a9.gif)

## ミサイルの正面向きに移動する

追尾対象に向けてではなく、ミサイルの正面向きに移動させます。正面向きのベクトルは Rotation * 画像の向き(fromDirection) で求めます。

``` c#
        /** 移動速度 */
        [SerializeField] private float moveSpeed;

        void Update()
        {
            LookAtTarget();
            Move();
        }

        private void Move()
        {
            // ミサイルが向いている方向に移動
            var forward = transform.rotation * fromDirection;
            transform.position += forward.normalized * (moveSpeed * Time.deltaTime);
        }
```

![missile_02.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4040766/c3886fbc-ca68-4b1f-bbf6-55bcb32261b0.gif)

## 最高速度に到達するまで移動量と回転量を加速させる

「最高速度」最高回転量」「最高速度に到達するまでの秒数」を `SerializeField` でインスペクターから指定できるようにします。また、`Accelerate` 関数で加速の計算をしています。

``` C#
using UnityEngine;

public class Missile : MonoBehaviour
{
        /** 追尾対象 */
        [SerializeField] private Transform target;
        /** 画像の正面向き */
        [SerializeField] private Vector3 fromDirection;
        /** 回転最高速度 */
        [SerializeField] private float maxRotationSpeed;
        /** 移動最高速度 */
        [SerializeField] private float maxSpeed;
        /** 最高速度に到達するまでの秒数 */
        [SerializeField] private float reachMaxSpeedSeconds;

        private float _elapsedSeconds;
        private float _elapsedSecondsRatio;
        
        void Update()
        {
            Accelerate();
            LookAtTarget();
            Move();
        }
        
        private void Accelerate()
        {
            // 加速する
            _elapsedSeconds+=Time.deltaTime;
           _elapsedSecondsRatio = _elapsedSeconds / reachMaxSpeedSeconds;
            float maxRatio = 1.0f;
            if (maxRatio < _elapsedSecondsRatio)
            {
                _elapsedSecondsRatio = 1.0f;
            }
        }
        
        private void LookAtTarget()
        {
            // 追尾対象への向きと距離
            Vector3　heading = target.position - transform.position;
            // 追尾対象への回転量
            Quaternion targetRotation = Quaternion.FromToRotation(fromDirection, heading.normalized);
            // ゆっくりと回転させる
            float rotationSpeed = maxRotationSpeed * _elapsedSecondsRatio;
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
        }
        
        private void Move()
        {
            float moveSpeed = maxSpeed *  _elapsedSecondsRatio;
            
            // ミサイルが向いている方向に移動
            var forward = transform.rotation * fromDirection;
            transform.position += forward.normalized * (moveSpeed * Time.deltaTime);
        } 
}
```

![missile_03.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4040766/c80a08bc-b577-409a-86dd-f5e641236502.gif)

これで完成です。
これで大体ミサイルっぽい動きになったのではないでしょうか。
移動速度に Easing をかけるともっと良い動きになりそうですね。

## 例

ちなみに自作しているゲーム内では、一定時間が経過したら回転しないようにしたり、衝突時に爆発エフェクトを再生したりして使用しています。

![game.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4040766/50cf37b1-d6d0-4b64-93f4-b99a335f3ef3.gif)

楽しいですね。

## 参照

- <https://docs.unity3d.com/ja/2019.4/Manual/DirectionDistanceFromOneObjectToAnother.html>