弾、ダメージ、エフェクト
Assets/
Scripts/
Prefabs/
Data/
すでにあるものこのレッスンで追加
飛び道具とは、前方へ進み、何かに当たり、ダメージを与えて消える prefab のことです。四つの役割を、小さなスクリプト一つで。
Knight はタイルの列に沿って発射し、人形が被弾し、弾は出てきた pool の中へ戻っていきます。
飛び道具
using UnityEngine;
[RequireComponent(typeof(Collider))]
public class Projectile : MonoBehaviour
{
[SerializeField] private float speed = 18f;
[SerializeField] private float lifeTime = 4f;
[SerializeField] private GameObject hitVfx;
private float damage;
private GameObject owner; // do not hit the shooter
public void Launch(GameObject shooter, float dmg)
{
owner = shooter;
damage = dmg;
Destroy(gameObject, lifeTime); // clean up if it never hits
}
void Update()
{
transform.position += transform.forward * speed * Time.deltaTime;
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject == owner) return;
if (other.TryGetComponent(out CharacterStats target))
target.TakeDamage(damage);
if (hitVfx != null)
Instantiate(hitVfx, transform.position, Quaternion.identity);
Destroy(gameObject);
}
}- 1
を作成し、scale を 0.3、名前は P_Fireball にします。
- 2
Collider で Is Trigger をオンにします——trigger は物理的に反発せず、重なりだけを報告します。
- 3
Projectile.cs をアタッチし、発光する material を与え、Assets/Prefabs にドラッグします。
- 4
シーンからは削除します。prefab は Hierarchy ではなく Project に置くものです。
スキルから発射する
// in SkillData.cs
public GameObject projectilePrefab;
// in SkillCaster.TryCast
if (s.projectilePrefab != null)
{
GameObject go = Instantiate(s.projectilePrefab, castPoint.position, castPoint.rotation);
go.GetComponent<Projectile>().Launch(gameObject, s.damage);
}オブジェクトプール —— 連射のカクつきを止める
Instantiate と Destroy はメモリを確保します。毎秒 20 発撃てばガベージコレクションが引っかかるようになります。プールなら同じオブジェクトをずっと使い回せます。
using System.Collections.Generic;
using UnityEngine;
public class SimplePool : MonoBehaviour
{
[SerializeField] private GameObject prefab;
[SerializeField] private int prewarm = 20;
private readonly Queue<GameObject> pool = new Queue<GameObject>();
void Awake()
{
for (int i = 0; i < prewarm; i++)
{
GameObject go = Instantiate(prefab, transform);
go.SetActive(false);
pool.Enqueue(go);
}
}
public GameObject Get(Vector3 pos, Quaternion rot)
{
GameObject go = pool.Count > 0 ? pool.Dequeue() : Instantiate(prefab, transform);
go.transform.SetPositionAndRotation(pos, rot);
go.SetActive(true);
return go;
}
public void Release(GameObject go)
{
go.SetActive(false);
pool.Enqueue(go);
}
}弾の中の Destroy(gameObject) を、pool.Release(gameObject) を呼ぶ側に置き換えます。
既製版がよければ、Unity 同梱の UnityEngine.Pool.ObjectPool<T> も使えます。
毎秒数回以上生成されるものは何でもプールします:弾、ヒットスパーク、ダメージ数字。
伝わりやすい VFX
Particle System の Stop Action → Destroy をオンにすると、一回きりのエフェクトは自分で後始末します。
着弾のスパークは 0.2~0.4 秒が目安です。もっと長いと画面がぐちゃぐちゃになります。
ほんの少しのカメラシェイク(0.1 秒)と 0.05 秒のヒットストップがあれば、どんなパーティクルより当たり感が良くなります。
using System.Collections;
IEnumerator HitStop(float seconds)
{
Time.timeScale = 0.05f;
yield return new WaitForSecondsRealtime(seconds); // Realtime! timeScale is frozen
Time.timeScale = 1f;
}
// call it
StartCoroutine(HitStop(0.05f));よくあるミス
弾が撃った本人に当たる:Knight 自身のコライダーの内側で生成されるため、OnTriggerEnter が術者に対して発火します。体のすぐ前に置いた castPoint から生成し、transform.root が術者であるヒットは無視してください。
プールから戻ってきた弾は古い状態を引きずっています。OnEnable で位置・回転・すべてのタイマーをリセットしないと、二発目は即座に消えるか、あらぬ方向へ飛びます。
プールに切り替えた後も Destroy(gameObject, lifeTime) が残っていると、外れた弾がプールのオブジェクトごと破棄してプールが枯れていきます。Destroy は二か所とも pool.Release に置き換え、時間差の方は Invoke かコルーチンで呼んでください。
速い弾は薄いコライダーをすり抜けます。30 m/s・30 FPS なら弾は 1 フレームで 1 メートル進み、壁を飛び越すことがあります。Rigidbody の MovePosition で動かして Collision Detection を Continuous にするか、速度を常識的な範囲に抑えてください。