概要
制作しているゲームで2Dでの追尾ミサイルを実装したので、その方法をまとめておきます。
たくさん配置して無限追尾ミサイルで遊ぶと楽しいです。
開発環境
Unity Editor Version:6000.0.22f1
仕様
- 特定のtransform目掛けてゆっくりと回転する
- ミサイルの正面向きに移動する
- 移動速度は徐々に加速させ、最高速度に到達すると加速を止める
完成形
先に完成形を載せておきます。これをミサイルオブジェクトにアタッチすればよいです。
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目掛けてゆっくりと回転する
まずはミサイルと、ターゲットとなるゲームオブジェクト(四角形)を配置しておきます。
本題のコードの話。
ターゲットに向けて回転し続けるにはTransform.LookAtを使えばいい...と思ってたのですが、これは2DだとY軸も回転しておかしくなります。2Dの時はQuaternion.FromToRotation(Vector3 fromDirection, Vector3 toDirection)を使います。第一引数には画像がどの向きを向いているかを指定します。上記の場合、画像は右向きなのでXに1を入れます。仮に上向きならY軸に1,下向きなら-1,左向きならX軸に-1を入れます。第二引数には追尾対象への方向を渡します。
また、ゆっくりと回転させるために補間をかけます。補間はQuaternion.Slerpを使います。
/** 追尾対象 */
[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);
}
ミサイルの正面向きに移動する
追尾対象に向けてではなく、ミサイルの正面向きに移動させます。正面向きのベクトルは Rotation * 画像の向き(fromDirection) で求めます。
/** 移動速度 */
[SerializeField] private float moveSpeed;
void Update()
{
LookAtTarget();
Move();
}
private void Move()
{
// ミサイルが向いている方向に移動
var forward = transform.rotation * fromDirection;
transform.position += forward.normalized * (moveSpeed * Time.deltaTime);
}
最高速度に到達するまで移動量と回転量を加速させる
「最高速度」最高回転量」「最高速度に到達するまでの秒数」を SerializeField
でインスペクターから指定できるようにします。また、Accelerate
関数で加速の計算をしています。
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);
}
}
これで完成です。
これで大体ミサイルっぽい動きになったのではないでしょうか。
移動速度に Easing をかけるともっと良い動きになりそうですね。
例
ちなみに自作しているゲーム内では、一定時間が経過したら回転しないようにしたり、衝突時に爆発エフェクトを再生したりして使用しています。
楽しいですね。