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;
    }
}