
Unityゲーム役立ち小ネタスクリプト8:ホーミング弾
現在、3Dキャラモデルアセット「バトルメカガール01」を作っています。
特に深い考えなく銀髪爆乳メカ武装ガールを作りたかっただけなんですが
コンストレイントで可動するサブアームと多数の武器ギミックを持つモデルとなったので、fbxとテクスチャだけ配布しても誰も使えないので、(Blenderで作ってfbxにしたら消えた)コンストレイントをUnity上で作り直して、
専用モーションも作って、エフェクトも自作して、それでも表現が追いつかないのでスクリプトでいろいろ動かして…
ここまで作るならもう独自のキャラクターコントローラまで作ってゲームのテンプレートとして販売するとこまでいくか…ってなってます。
今回はそんな経緯で作り出したホーミングビーム・ミサイルの挙動を実現するスクリプトを紹介します。

SampleHoming.cs
using UnityEngine;
namespace FUNSET
{
public class SampleHoming : MonoBehaviour
{
public Transform target; // ターゲットのトランスフォーム
public float speed = 10f; // ミサイルの速度
public float maxRotationSpeed = 5f; // 最大回転速度
public float accelerationTime = 0.5f; // 回転速度が最大になるまでの時間
public float starttime = 0.5f;//回転速度が加算されるまでの時間
public float maxLifetime = 10f; // 最大存続時間
public float hitDistance = 1f; // ヒットとみなす距離
private float startTime;
private float currentRotationSpeed;
public string TargetName = "LockOnTarget";
void Start()
{
startTime = Time.time; // 初期時間を保存
currentRotationSpeed = 0f; // 初期回転速度を0に設定
//ターゲットをシーン上から名前で取得(1つのみ)
if (target == null)
{
target = GameObject.Find(TargetName).transform;
}
}
void Update()
{
if (target == null)
{
return; // ターゲットが設定されていない場合は何もしない
}
if (starttime > 0) { starttime -= Time.deltaTime; }
else
{
// 回転速度を徐々に増加
float elapsedTime = Time.time - startTime;
currentRotationSpeed = Mathf.Lerp(0f, maxRotationSpeed, elapsedTime / accelerationTime);
}
// ターゲットの方向を計算
Vector3 direction = (target.position - transform.position).normalized;
// ターゲットの方向に向かって回転
Quaternion lookRotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, currentRotationSpeed * Time.deltaTime);
// 前進
transform.Translate(Vector3.forward * speed * Time.deltaTime);
// 一定時間経過後に削除
if (Time.time - startTime >= maxLifetime)
{
Destroy(gameObject);
}
// ターゲットに一定距離近づいたら削除
if (Vector3.Distance(transform.position, target.position) <= hitDistance)
{
Destroy(gameObject);
}
}
// メソッドで強制削除
public void ForceDestroy()
{
Destroy(gameObject);
}
}
}

これを、飛ばしたいエフェクト=オブジェクトのrootにアタッチしてプレハブにします。
標的の判別は、ホーミング弾のプレハブが生成されると、シーン上のオブジェクトをTargetNameで指定された名前で検索して、一致するオブジェクトがあったらターゲットになります。
発射直後はStarttimeの時間が経過するまで追尾せず直進し、AccelerationTimeで決めた時間がまで旋回速度が上がっていく仕様になってます。
一定時間経過するかターゲットの一定距離に近づいたら消滅するようになってますが、これはうまく命中せずずっとターゲットの周りをぐるぐる飛び続ける不具合の対策で(これらは正直デバッグ用です)、実質的なターゲットとの衝突判定・処理は別のスクリプトでやります。
samplehit.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace FUNSET
{
public class samplehit : MonoBehaviour
{
[SerializeField, Header("trigger"), Tooltip("")]
Collider hitcollider;
[SerializeField]
int hitlayer;
[SerializeField, Header("コライダーを有効化するまでの時間"), Tooltip("")]
float hittimer;
float hitcounter;
[SerializeField]
GameObject[] HitObjects;
[SerializeField, Header("生成と同時にルートオブジェクトを削除する"), Tooltip("")]
bool destroy;
private void Start()
{
hitcounter = hittimer;
if (hitcollider == null) { hitcollider = GetComponent<Collider>(); }
hitcollider.enabled = false;
}
private void Update()
{
hitcounter -= Time.deltaTime;
if(hitcounter < 0) { hitcollider.enabled = true; }
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.layer == hitlayer)
{
for (int i = 0; i < HitObjects.Length; i++)
{
GameObject a = Instantiate(HitObjects[i], transform.position, transform.rotation);
}
if (destroy) { Destroy(transform.root.gameObject); }
}
}
}
}

ホーミング弾プレハブのrootにこのスクリプトをアタッチし、リジッドボディとコライダーをアタッチします。
コライダーをトリガーに設定して、Hitcolliderにコライダーを設定、ターゲットオブジェクトにコライダーをアタッチして、ターゲットのレイヤーをHitlayerに設定。
Hittimerには、当たり判定を有効化するまでの時間を設定
HitObjectsに、衝突時に生成するプレハブ(爆発エフェクト、SEなど)を設定。
Destroyで、衝突後に自身=rootオブジェクトを削除するチェックをオンにします。
これでホーミング挙動関係なく、弾がターゲットのコライダーに当たったら爆発します。
爆発エフェクト、SEのプレハブには(なんならホーミング弾にも)時間で自身を削除するスクリプトもアタッチしておきます。
こういうやつ↓
using UnityEngine;
public class DestroyDelay : MonoBehaviour {
public float DestroyTime;
float Dcount;
void Start ()
{
if (DestroyTime <= 0) { DestroyTime = 1.0f; }
}
void FixedUpdate ()
{
Dcount += Time.deltaTime;
if (Dcount > DestroyTime)
{
Dcount = 0;
Destroy(this.gameObject);
}
}
}
これだけだと一つのターゲットにしか弾が飛んでいかないので、ゲームで実際に使うには、ターゲットを複数保存できるようにする>ホーミング弾を生成したら弾のターゲットを上書きする&弾の数をターゲットと併せて調整するスクリプトを作る必要があるでしょう。(それ以前にダメージやHPを管理するあれこれが必要ですが)

ホーミング弾のエフェクト部分の作り方については解説は省略します。パーティクルのサブエミッターやトレイルを組み合わせて作ればいいと思いますが、それ以上の見た目を追求すると、チューブ上のメッシュを生成しながら透明度を可変…、とかになるんでしょうか?

私の創作活動の進捗、当記事のご意見・ご感想には、Discordをぜひご登録ください。
Discord Kelorin Jo https://discord.gg/wbUJVdJcga