Raycasts: asking the world what is in front of you
How do I detect what the player is aiming at or standing on?
Physics.Raycast fires an invisible line and reports the first collider it meets, with the point, the distance and the surface normal. Always pass a LayerMask and a maximum distance — without them a ray tests every layer out to infinity, and one fired from a third-person camera hits the player standing in front of it first.
A raycast is a question: if I fire an invisible line from here, in this direction, what does it hit first? Shooting, ground checks, what is under the player's finger, what the camera is looking at, whether an enemy can see you — all of it is one function with four arguments.
One invisible line, and the first collider it meets hands back the point, the surface normal and the distance.
The shape of it
using UnityEngine;
public class Shooter : MonoBehaviour
{
public float range = 50f;
public LayerMask hittable; // set this in the Inspector
void Fire()
{
// origin, direction, out result, how far, what counts
if (Physics.Raycast(transform.position, transform.forward,
out RaycastHit hit, range, hittable))
{
Debug.Log("hit " + hit.collider.name + " at " + hit.distance + "m");
}
}
}What the hit tells you
hit.point — the exact spot in world space. This is where a bullet hole, a spark or a decal goes.
hit.normal — the direction the surface faces. Align an effect to it and the spark lies flat on the wall instead of sticking out of it.
hit.distance — how far the ray travelled. Useful for damage falloff, and for drawing a tracer the right length.
hit.collider — what was hit. Call GetComponent on it to find the health script, or CompareTag to sort friend from wall.
// Quaternion.LookRotation(hit.normal) makes the effect's forward axis point
// straight out of the wall.
Instantiate(impactPrefab, hit.point, Quaternion.LookRotation(hit.normal));Layer masks, properly
A LayerMask is not a layer number. It is 32 bits, one per layer, and the bit is set when that layer counts. Declaring the field as LayerMask gives you the tick-box dropdown in the Inspector and saves you from ever doing the arithmetic.
// WRONG: layer 8 as a number means bit 3, which is layer 3.
Physics.Raycast(origin, dir, out hit, range, 8);
// Right, in code:
int mask = 1 << LayerMask.NameToLayer("Enemy");
// Right, and better: let the Inspector do it.
public LayerMask hittable;
// Everything EXCEPT the player's own layer:
int allButPlayer = ~(1 << LayerMask.NameToLayer("Player"));From the camera, or from a finger
void Update()
{
// Mouse. On a phone, Input.mousePosition follows the first touch too,
// so this works on both without changing anything.
if (!Input.GetMouseButtonDown(0)) return;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, 100f, selectable))
{
hit.collider.GetComponent<Selectable>()?.Select();
}
}Seeing the ray
A raycast is invisible, which makes debugging it guesswork unless you draw it. Two lines turn a mystery into something you can watch in the Scene view while the game runs.
// Green to where it hit, red for the full length when it missed.
if (Physics.Raycast(origin, dir, out RaycastHit hit, range, mask))
Debug.DrawLine(origin, hit.point, Color.green, 1f);
else
Debug.DrawRay(origin, dir * range, Color.red, 1f);When a line is too thin
SphereCast sweeps a ball along the ray. This is what a ground check wants, and what a thick projectile wants, because a line slips through gaps that a body would not.
RaycastAll returns everything along the line, unsorted. Use it to shoot through several enemies — but sort by distance yourself, because the order is not guaranteed.
RaycastNonAlloc fills an array you own instead of allocating a new one. On a phone, an Update that calls RaycastAll every frame produces garbage the collector eventually has to stop the game to clear.
Triggers are ignored or included depending on Physics settings. Pass QueryTriggerInteraction explicitly rather than relying on a project-wide default somebody may change.
the lesson that builds this
Projectiles, damage and VFX
This post is a standalone recipe. In the course, the same thing is built as part of the one project that runs through all five tabs — Character & Combat, lesson 05.