Part of these game systems:
Projectile System
Configurable projectile with speed, damage, lifetime, and optional homing. Integrates with Object Pool for zero-alloc shooting.
How to Use
1
Create a projectile prefab with a Trigger Collider
2
Attach Projectile script
3
Set speed, damage, and lifetime
4
Spawn from weapon: Instantiate(bulletPrefab, muzzle.position, muzzle.rotation)
5
For pooling: ObjectPool.Instance.Spawn("bullet", pos, rot)
6
Enable homing for seeking missiles
7
Assign impact VFX prefab for hit effects
Source Code
C#
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// Configurable projectile with speed, damage, lifetime, and optional homing.
/// Implements IPoolable for object pool integration.
/// </summary>
public class Projectile : MonoBehaviour, IPoolable
{
[Header("Movement")]
[SerializeField] private float speed = 20f;
[SerializeField] private float lifetime = 5f;
[Header("Damage")]
[SerializeField] private float damage = 10f;
[SerializeField] private LayerMask hitLayers = ~0;
[Header("Homing")]
[SerializeField] private bool isHoming = false;
[SerializeField] private float homingStrength = 5f;
[SerializeField] private float homingRange = 20f;
[Header("Impact")]
[SerializeField] private GameObject impactEffect;
[SerializeField] private bool destroyOnHit = true;
[Header("Events")]
public UnityEvent<GameObject> OnHit;
private float timer;
private Transform homingTarget;
private string poolTag;
/// <summary>Set the pool tag for despawning.</summary>
public void SetPoolTag(string tag) => poolTag = tag;
private void OnEnable()
{
timer = 0f;
if (isHoming)
FindHomingTarget();
}
private void Update()
{
timer += Time.deltaTime;
if (timer >= lifetime)
{
DestroyProjectile();
return;
}
// Homing
if (isHoming && homingTarget != null)
{
Vector3 dir = (homingTarget.position - transform.position).normalized;
Vector3 newForward = Vector3.Lerp(transform.forward, dir, homingStrength * Time.deltaTime);
transform.forward = newForward;
}
// Move forward
transform.position += transform.forward * speed * Time.deltaTime;
}
private void OnTriggerEnter(Collider other)
{
if (((1 << other.gameObject.layer) & hitLayers) == 0) return;
// Apply damage
HealthSystem health = other.GetComponent<HealthSystem>();
if (health != null)
health.TakeDamage(damage);
OnHit?.Invoke(other.gameObject);
// Spawn impact effect
if (impactEffect != null)
Instantiate(impactEffect, transform.position, Quaternion.identity);
if (destroyOnHit)
DestroyProjectile();
}
private void FindHomingTarget()
{
Collider[] colliders = Physics.OverlapSphere(transform.position, homingRange, hitLayers);
float closest = float.MaxValue;
homingTarget = null;
foreach (var col in colliders)
{
if (col.gameObject == gameObject) continue;
float dist = Vector3.Distance(transform.position, col.transform.position);
if (dist < closest)
{
closest = dist;
homingTarget = col.transform;
}
}
}
private void DestroyProjectile()
{
if (!string.IsNullOrEmpty(poolTag) && ObjectPool.Instance != null)
ObjectPool.Instance.Despawn(poolTag, gameObject);
else
Destroy(gameObject);
}
// IPoolable
public void OnSpawn() { timer = 0f; }
public void OnDespawn() { homingTarget = null; }
}