Source file unity-csharp/arcade-classics/space-invaders/Projectile.cs from the GDnD code examples. Download raw file
// GDnD wiki example
// Demonstrates: a pooled, transform-driven bullet that hits a target layer and returns itself to the pool
// Related pages: [[overview-arcade-classics-as-learning-projects]], [[object-pool-pattern]], [[unity-collider2d-and-triggers]]
using UnityEngine;
// Transform-driven like the rest of the series — no Rigidbody. The bullet moves
// straight up or down, checks for a target on a given layer, and releases itself
// back to the pool when it hits something or leaves the screen.
public class Projectile : MonoBehaviour
{
[SerializeField] private Vector2 hitBoxSize = new Vector2(0.15f, 0.4f);
private ProjectilePool pool;
private float verticalDirection;
private float speed;
private LayerMask targetMask;
private float halfSize;
private Camera cachedCamera;
private void Awake()
{
cachedCamera = Camera.main;
halfSize = GetComponent<SpriteRenderer>().bounds.extents.y;
}
public void SetPool(ProjectilePool ownerPool)
{
pool = ownerPool;
}
// Aim it: +1 fires upward (player), -1 fires downward (invaders).
public void Launch(Vector3 position, float direction, float launchSpeed, LayerMask mask)
{
transform.position = position;
verticalDirection = Mathf.Sign(direction);
speed = launchSpeed;
targetMask = mask;
}
private void Update()
{
transform.position += Vector3.up * (verticalDirection * speed * Time.deltaTime);
if (TryHitTarget() || IsOffScreen())
{
pool.Release(this);
}
}
private bool TryHitTarget()
{
Collider2D hit = Physics2D.OverlapBox(transform.position, hitBoxSize, 0f, targetMask);
if (hit == null)
{
return false;
}
// Hit anything that knows how to be hit, without caring what it is.
IHittable hittable = hit.GetComponent<IHittable>();
hittable?.Hit();
return true;
}
private bool IsOffScreen()
{
float topEdge = cachedCamera.orthographicSize + halfSize;
return Mathf.Abs(transform.position.y) > topEdge;
}
}