Eyas's Blog

Unity Short: Physics Raycasting

Original Photo by Pang KakitCC-BY-SA 4.0

Continuing the “shorts” week in the Unity for Software Engineers series, today we’ll be discussing Unity’s Physics Raycasting. As a reminder, this week is packaged as a series of short-form overviews, introducing readers to the breadth of the Unity toolkit.

Raycasting is Unity’s way of checking collisions between objects in the scene and an invisible ray with a given geometry. Raycasting is used in a myriad of ways. Some examples:

  • Implementing gunshots or laser guns as rays with instantaneous damage, rather than modeling them as a projectile with a finite speed.

    Raycasting can check if a given shot hits an enemy or target.

  • Manual kinematic controllers. While realistic physics is sometimes fun, many platformers and first-person games manually define and fine-tune a character’s motion and collision behavior.

    Raycasting can, for example, check if the player is grounded (by casting a ray from the bottom of the player downwards) and can move, or if the player is colliding with walls, etc.

  • Translating Screen-Space pointer placement to World-Space positions.

    In many games, your pointing device can place items in the world or give commands to units. Translating your pointer’s position (2D, in screen space) to a world position happens by casting a ray from the camera plane forwards.

  • Enemy AI.

    For example, an enemy might cast a ray from its eye level to the player to determine if the player is currently visible to the enemy.

How to Raycast

Let’s start with the basics. Raycasting is done in C# through the static Physics.Raycast() method in the UnityEngine namespace. Raycast has plenty of overloads, but the inputs of the method can be summarized as:

  • A Ray. Provided either as a UnityEngine.Ray object, or two parameters: Vector3 position and Vector3 direction1.
  • Optionally, a maxDistance value, limiting the distance along the ray that collisions will be checked. When absent, this is inferred as infinity.
  • Optionally, a layerMask integer, restricting which objects to be included in the raycast.2 Layer Masks were briefly described in ”Understanding Unity Engine Objects“.
  • Optionally, a QueryTriggerInteraction enum, determining whether trigger colliders should be included in a raycast. By default, this takes the value of Physics.queriesHitTriggers.

A call for Physics.Raycast returns:

  • A boolean, whether the ray collided with anything.
  • Usually, a RaycastHit struct determining where a collision happened, its normal, distance, corresponding collider, and other details.

In addition, Physics.RaycastAll() allows users to find all collisions along a Ray and a distance. Simple versions of this method return a RaycastHit[] array with information about each hit. These overloads will therefore allocate an array on the heap each time the method is called. If you are calling this method frequently (e.g., in Update() or FixedUpdate()), consider using RaycastNonAlloc, which takes in a pre-allocated RaycastHit[] array (that you can reuse between frames) and returns the number of hits it populated in the array.

Raycasting is the most common of physics collision simulation functions, but note that the Physics static class also provides BoxCast, CapsuleCast, and SphereCast variations similar to Raycast.

Pointer Raycasting

If you have a reference to a Camera, you can translate a mouse position on the screen to a Ray using Camera.ScreenPointToRay(Vector3). Note that while this function takes in a Vector3, the z value is ignored. A Vector2 is implicitly converted to a Vector3 by copying the x and y values and setting z to 0.

Given this week’s example NavMeshMover, we can create a rudimentary player controller as follows:

using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(NavMeshMover))]
public class RaycastMover : MonoBehaviour
{
    private NavMeshMover _mover;
    private Camera _camera;

    private void Awake()
    {
        _mover = GetComponent<NavMeshMover>();
        _camera = Camera.main;
    }

    private void Update()
    {
        var mouse = Mouse.current;
        if (mouse == null) return;

        if (mouse.leftButton.isPressed) // alternatively: wasPressedThisFrame
        {
            Vector2 screenPosition = mouse.position.ReadValue();
            Ray ray = _camera.ScreenPointToRay(screenPosition);

            if (Physics.Raycast(ray, out RaycastHit hit))
            {
                _mover.NavigateTo(hit.point);
            }
        }
    }
}

As described in the InputSystem article, a cleaner input-handling implementation involves defining an Input Action and a public callback public void OnMove(InputAction.CallbackContext input) via a Player Input Controller.

Simple Enemy AI Example

Here’s a simplified function in an Enemy AI that determines if it sees a suspicious target:

private Vector3? DetermineSuspectedPosition_Vision()
{
    Vector3 lookStart = EyeLevelPosition();
    Vector3 lookForward = EyeLevelForward();
    Transform target = FindClosestTarget();

    float distance = Vector3.Distance(lookStart, target.position);

    if (distance > _maxChaseRange) return null;

    float angle = Vector3.Angle(target.position - lookStart, lookForward);
    if (Mathf.Abs(angle * 2) > _maxFieldOfView) return null;

    if (!Physics.Raycast(
        lookStart,
        target.position - lookStart,
        out var hit,
        _maxChaseRange,
        ~SeeThrough)) return null;

    if (hit.transform != target) return null;

    return target.position;
}

Collider Raycasting

Every Collider component implements a raycasting method:

public bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance);

This allows you to cast an entire collider along a ray. This is helpful when checking for collisions where a ray, box, sphere, or capsule aren’t enough or when simulating the collisions of a specific mesh.

Closing Thoughts

I tried to demonstrate the breadth of raycasting use cases in Unity. It is a generally cheap3 method of allowing objects to reason about their spatial relationship to the rest of the scene.

Footnotes

  1. The direction Vector3 is a direction vector. You can think of the ray it represents as starting in position and extending through position + direction.

  2. For example, a “UI” layer might be excluded from a raycast to determine gun collisions. Similarly, see-through objects such as glass windows can be grouped into a “See-through” layer and excluded from raycasts by enemy AI when determining its vision.

  3. As long as you’re not using variants of RaycastAll that allocate on every call.