Introduction to Raycasting
Raycasting is one of the most powerful and versatile tools in a Unity developer's toolkit. The concept is simple: you cast an invisible ray from a point in a direction, and Unity reports everything that ray touches in the physics world. Despite its simplicity, raycasting is the foundation of an enormous variety of gameplay systems: shooting mechanics, line-of-sight AI, ground detection for platformers, mouse picking, terrain-following cameras, proximity sensors, and much more.
One critical thing to understand before diving in: raycasts only interact with Colliders. An object without a Collider component is completely invisible to a raycast. The Renderer, Rigidbody, and every other component are irrelevant — only the Collider matters. This means you can have invisible trigger volumes that block raycasts, and visible meshes that are transparent to them, simply by controlling which objects have Colliders.
Common uses of raycasting in games:
- Shooting: cast a ray from the gun muzzle (or screen center for FPS) and detect what it hits
- Ground detection: cast a ray downward from character feet to check if they are grounded
- Line of sight: cast a ray from an enemy toward the player — if it hits something else first, the player is behind cover
- Mouse picking: cast a ray from the camera through the mouse cursor to select 3D objects
- Terrain scanning: cast rays downward to snap objects to terrain surface
- Proximity sensors: cast rays in multiple directions to detect nearby walls or obstacles
Physics.Raycast()
The fundamental raycasting method is Physics.Raycast(). It returns true if the
ray hits anything, or false if it hits nothing within the specified distance.
// Simple form — returns true/false only
bool hit = Physics.Raycast(origin, direction, maxDistance);
// Full form — also returns information about what was hit
RaycastHit hitInfo;
bool hit = Physics.Raycast(origin, direction, out hitInfo, maxDistance);
The RaycastHit struct contains detailed information about the collision point:
hit.collider— the Collider that was hithit.point— the world-space position where the ray struck the surfacehit.normal— the surface normal at the hit point (useful for bullet decals, ricochet)hit.distance— how far along the ray the hit occurredhit.transform— the Transform of the hit object (shortcut forhit.collider.transform)hit.rigidbody— the Rigidbody of the hit object (null if no Rigidbody)hit.textureCoord— UV coordinates at the hit point (requires MeshCollider with convex=false)
Practical Example: Simple Laser Pointer
using UnityEngine;
public class LaserPointer : MonoBehaviour
{
[SerializeField] private float maxRange = 50f;
[SerializeField] private LineRenderer laser;
private void Update()
{
RaycastHit hit;
Vector3 startPoint = transform.position;
Vector3 endPoint;
if (Physics.Raycast(startPoint, transform.forward, out hit, maxRange))
{
endPoint = hit.point;
// Instantiate a spark effect at the hit point
Debug.Log("Hit: " + hit.collider.name + " at distance " + hit.distance);
// Align a decal with the surface normal
// decal.position = hit.point;
// decal.rotation = Quaternion.LookRotation(hit.normal);
}
else
{
// Ray didn't hit anything — extend to max range
endPoint = startPoint + transform.forward * maxRange;
}
// Update LineRenderer to visualize the laser
laser.SetPosition(0, startPoint);
laser.SetPosition(1, endPoint);
}
}
2D Raycasting
The 2D equivalent uses Physics2D.Raycast() which returns a RaycastHit2D
struct directly (not a bool). Check if the hit is valid by testing hit.collider != null:
// 2D Raycast
RaycastHit2D hit = Physics2D.Raycast(origin, direction, maxDistance);
if (hit.collider != null)
{
Debug.Log("2D hit: " + hit.collider.name);
Debug.Log("Hit point: " + hit.point);
Debug.Log("Hit normal: " + hit.normal);
}
Note that in 2D, origin and direction are Vector2 values, and
the ray travels in the X-Y plane.
Physics.RaycastAll()
The standard Physics.Raycast() stops at the first Collider it hits. Sometimes
you need to know about all colliders the ray passes through — for example, a bullet that
penetrates multiple enemies, or a vision cone that needs to collect all visible objects.
Physics.RaycastAll() returns an array of all hits along the ray.
RaycastHit[] hits = Physics.RaycastAll(origin, direction, maxDistance);
// Sort hits by distance so we process them front to back
System.Array.Sort(hits, (a, b) => a.distance.CompareTo(b.distance));
foreach (RaycastHit hit in hits)
{
Debug.Log("Hit " + hit.collider.name + " at " + hit.distance + " meters");
// For a penetrating bullet, reduce damage with each object pierced
ApplyDamage(hit, baseDamage);
baseDamage *= 0.7f; // 30% damage reduction per object
}
There is also a non-allocating version for performance-critical code: Physics.RaycastNonAlloc(),
which writes results into a pre-allocated array you provide rather than creating a new one each call.
This is important in hot code paths to avoid garbage collection spikes.
// Pre-allocate once (e.g., in Start or as a field)
private RaycastHit[] hitBuffer = new RaycastHit[10];
private void Update()
{
int hitCount = Physics.RaycastNonAlloc(origin, direction, hitBuffer, maxDistance);
for (int i = 0; i < hitCount; i++)
{
Debug.Log(hitBuffer[i].collider.name);
}
}
Physics.Linecast()
Physics.Linecast() is a variant that casts between two explicit points in world space,
rather than from a point in a direction with a max distance. It is conceptually simpler when you
already know both endpoints.
// Cast between two known points
bool blocked = Physics.Linecast(startPoint, endPoint, out RaycastHit hit);
// Classic use: line-of-sight check between enemy and player
bool HasLineOfSight(Transform enemy, Transform player)
{
Vector3 directionToPlayer = player.position - enemy.position;
float distanceToPlayer = directionToPlayer.magnitude;
RaycastHit hit;
if (Physics.Linecast(enemy.position, player.position, out hit))
{
// If the first thing hit is the player, we have line of sight
return hit.transform == player;
}
return false;
}
Linecast is ideal for line-of-sight because it reads like exactly what you are doing conceptually: "is there anything blocking the line between A and B?" The ray-based equivalent requires calculating direction and distance separately, which is more verbose.
Shape Casting: SphereCast, BoxCast, CapsuleCast
A thin mathematical ray sometimes misses objects it should logically hit — for example, a narrow ray cast downward for ground detection might slip through a gap in procedurally generated terrain, or a melee attack ray might miss a wide enemy by passing just beside them. Shape casting solves this by sweeping a 3D shape (sphere, box, or capsule) along the ray path instead of a single point.
Physics.SphereCast()
// Sweep a sphere of given radius along the ray
bool hit = Physics.SphereCast(
origin, // start of sweep
radius, // sphere radius
direction, // direction of sweep
out RaycastHit hitInfo,
maxDistance // maximum sweep distance
);
SphereCast is the most commonly used shape cast. Typical uses:
- Ground detection: a sphere of radius matching the character controller's foot provides more reliable results than a thin ray, especially on uneven terrain and ramps.
- Melee hitboxes: sweep a sphere forward from the weapon to detect hits within a volume.
- Wider vision rays: use a fat sphere for "peripheral vision" AI detection.
Physics.BoxCast()
// Sweep a box along the ray
bool hit = Physics.BoxCast(
center, // center of box at start
halfExtents, // half-extents of the box (Vector3)
direction,
out RaycastHit hitInfo,
orientation, // Quaternion for box rotation
maxDistance
);
BoxCast is excellent for platform games where you need to sweep the character's bounding box to check for obstacles or ledges, and for anything that requires rectangular detection (doorways, corridors).
Physics.CapsuleCast()
// Sweep a capsule — defined by two sphere centers and a radius
bool hit = Physics.CapsuleCast(
point1, // top sphere center
point2, // bottom sphere center
radius, // capsule radius
direction,
out RaycastHit hitInfo,
maxDistance
);
CapsuleCast matches the shape of Unity's CharacterController component exactly, making it ideal for checking character movement paths and detecting obstacles the character would collide with.
Filtering with LayerMask
By default, a raycast hits objects on all layers. In most games this is not what you want — you generally want to check specific subsets: only the ground, only enemies, everything except the player, and so on. LayerMask provides this filtering.
Setting Up Layers
Layers are configured in Edit > Project Settings > Tags and Layers. You can define up to 32 layers (layers 0–7 are reserved for Unity). Common patterns: one layer for "Ground", one for "Enemies", one for "Player", one for "Interactable".
Using LayerMask in Code
// Create a mask that includes only the "Ground" and "Terrain" layers
int groundMask = LayerMask.GetMask("Ground", "Terrain");
// Use the mask in a raycast — only hits Ground and Terrain objects
RaycastHit hit;
if (Physics.Raycast(origin, direction, out hit, maxDistance, groundMask))
{
Debug.Log("Hit ground at " + hit.point);
}
// Invert the mask with ~ — hits EVERYTHING EXCEPT Ground and Terrain
int everythingExceptGround = ~groundMask;
if (Physics.Raycast(origin, direction, out hit, maxDistance, everythingExceptGround))
{
Debug.Log("Hit non-ground object: " + hit.collider.name);
}
// You can also combine layer masks with bitwise OR
int combined = LayerMask.GetMask("Enemies") | LayerMask.GetMask("Props");
LayerMask as a Serialized Field
You can expose a LayerMask field in the Inspector, which gives you a convenient dropdown to select layers without writing any mask-building code:
[SerializeField] private LayerMask groundLayer;
[SerializeField] private LayerMask enemyLayer;
// Use directly in Physics functions — no GetMask() needed
Physics.Raycast(origin, direction, out hit, maxDistance, groundLayer);
This is the recommended approach for production code: designers can adjust which layers the raycast targets directly in the Inspector without touching code.
Advanced 2D Ground Check
The most robust way to detect whether a 2D character is grounded is a downward SphereCast (or
CircleCast in 2D) from the character's feet. Using OnCollisionStay2D for this purpose is
unreliable — it can miss frames and produce incorrect grounded states on slopes and edges.
using UnityEngine;
public class GroundedCharacter2D : MonoBehaviour
{
[Header("Ground Check")]
[SerializeField] private LayerMask groundLayer;
[SerializeField] private float groundCheckRadius = 0.2f;
[SerializeField] private float groundCheckDistance = 0.05f;
[SerializeField] private Transform feetTransform; // Empty GameObject at character feet
[Header("Movement")]
[SerializeField] private float moveForce = 8f;
[SerializeField] private float jumpForce = 12f;
private Rigidbody2D rb;
private bool isGrounded;
private void Start()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
// Update ground state every frame
isGrounded = CheckGrounded();
// Jump input — read in Update to catch button press
if (Input.GetButtonDown("Jump") && isGrounded)
{
rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
}
}
private void FixedUpdate()
{
float horizontal = Input.GetAxis("Horizontal");
rb.AddForce(new Vector2(horizontal * moveForce, 0f), ForceMode2D.Force);
}
private bool CheckGrounded()
{
// Cast a circle downward from the feet position
RaycastHit2D hit = Physics2D.CircleCast(
feetTransform.position, // origin
groundCheckRadius, // circle radius
Vector2.down, // direction
groundCheckDistance, // max distance
groundLayer // only check ground layer
);
return hit.collider != null;
}
// Visualize the ground check in the Scene view for debugging
private void OnDrawGizmos()
{
if (feetTransform == null) return;
Gizmos.color = isGrounded ? Color.green : Color.red;
Gizmos.DrawWireSphere(
feetTransform.position + Vector3.down * groundCheckDistance,
groundCheckRadius
);
}
}
The OnDrawGizmos() method visualizes the ground check sphere in the Scene view — green
when grounded, red when airborne. Always add gizmo visualization to physics check code during
development; it saves enormous debugging time.
Exercise: 2D Ground Check Platformer
Using the script above, build a complete 2D platformer character:
- Create a 2D scene with a main platform (Box Collider 2D), some floating platforms, and some slopes.
- Add a Capsule sprite as the player with a Rigidbody2D and Capsule Collider 2D.
- Create an empty child GameObject at the bottom of the capsule — name it "Feet".
- Assign it to
feetTransformin the Inspector. - Set the platform objects to a "Ground" layer and assign that layer in
groundLayer. - Test that: the character can walk left and right; they can jump only when on the ground; they cannot double-jump.
Experiment with the groundCheckRadius (0.1–0.3 is typical) and
groundCheckDistance (0.02–0.1). Too small and you may not detect ground on uneven
surfaces; too large and you may detect ground while airborne.
Exercise: Duck Shooter 3D
Build a 3D shooting gallery: rubber ducks appear on the scene, and the player clicks to shoot them with a raycast from the camera.
Setup
- Create a 3D scene with a ground plane and a few platforms at varying heights.
- Create a Duck prefab: a capsule or duck model with a Collider, tag it "Duck".
- Add a Canvas with a Text element for the score.
- Add a
CameraGameObject (or use Main Camera) and attach the shooter script.
Duck Spawner
using UnityEngine;
public class DuckSpawner : MonoBehaviour
{
[SerializeField] private GameObject duckPrefab;
[SerializeField] private Transform[] spawnPoints;
[SerializeField] private float spawnInterval = 2f;
[SerializeField] private int maxDucks = 8;
private float timer;
private void Update()
{
timer += Time.deltaTime;
if (timer >= spawnInterval && CountActiveDucks() < maxDucks)
{
SpawnDuck();
timer = 0f;
}
}
private void SpawnDuck()
{
Transform spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)];
Instantiate(duckPrefab, spawnPoint.position, spawnPoint.rotation);
}
private int CountActiveDucks()
{
return GameObject.FindGameObjectsWithTag("Duck").Length;
}
}
Shooting Controller
using UnityEngine;
using UnityEngine.UI;
public class DuckShooter : MonoBehaviour
{
[SerializeField] private Camera playerCamera;
[SerializeField] private Text scoreText;
[SerializeField] private LayerMask duckLayer;
[SerializeField] private float maxShootRange = 100f;
[Header("Feedback")]
[SerializeField] private GameObject hitEffectPrefab;
private int score = 0;
private void Update()
{
// Show crosshair highlight when hovering over a duck
HighlightDuckUnderCursor();
if (Input.GetMouseButtonDown(0))
{
Shoot();
}
}
private void Shoot()
{
// Build a ray from the camera through the mouse cursor
Ray ray = playerCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, maxShootRange))
{
if (hit.collider.CompareTag("Duck"))
{
// Spawn hit effect at impact point, aligned to surface normal
if (hitEffectPrefab != null)
{
Quaternion effectRotation = Quaternion.LookRotation(hit.normal);
Instantiate(hitEffectPrefab, hit.point, effectRotation);
}
// Destroy the duck
Destroy(hit.collider.gameObject);
// Update score
score++;
scoreText.text = "Score: " + score;
}
}
}
private void HighlightDuckUnderCursor()
{
Ray ray = playerCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, maxShootRange, duckLayer))
{
// Change cursor or material to indicate hoverable duck
// Cursor.SetCursor(aimCursor, Vector2.zero, CursorMode.Auto);
}
}
}
The key line is playerCamera.ScreenPointToRay(Input.mousePosition). This converts the
2D mouse cursor position into a 3D ray that originates at the camera's near clip plane and travels
through that screen position into the world. This is the standard technique for all mouse-based 3D
interaction in Unity.
Continuous Collision Detection
A thin raycast has a related problem on Rigidbodies: very fast-moving physics objects (bullets, fast projectiles) can "tunnel" through thin colliders in a single physics step because the physics engine only checks for overlaps at discrete timesteps. At 0.02s per step, an object moving at 100 m/s travels 2 meters per step — easily passing through a 0.1m wall without registering a collision.
The solution is Continuous Collision Detection (CCD), which calculates the sweep path between frames and checks for collisions along it:
// In Start() on the fast-moving Rigidbody:
rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
// Or even more thorough (but more expensive):
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
CCD modes have a performance cost. Use Continuous on fast-moving objects that hit
static colliders; use ContinuousDynamic when two fast-moving objects need to collide
with each other. Leave everything else on Discrete (the default). Never turn on CCD
for every object in your scene — apply it surgically only where tunneling actually occurs.
Overlap Functions
Sometimes you don't want to sweep a ray at all — you want to know which colliders currently exist inside a region. Unity's Overlap functions answer exactly this question.
// Return all colliders inside a sphere
Collider[] colliders = Physics.OverlapSphere(center, radius, layerMask);
// Return all colliders inside a box
Collider[] colliders = Physics.OverlapBox(center, halfExtents, rotation, layerMask);
// Return all colliders inside a capsule
Collider[] colliders = Physics.OverlapCapsule(point1, point2, radius, layerMask);
// 2D variants
Collider2D[] colliders2D = Physics2D.OverlapCircleAll(center, radius, layerMask);
Collider2D[] colliders2D = Physics2D.OverlapBoxAll(center, size, angle, layerMask);
Practical uses for Overlap functions:
- Proximity alerts: check if any enemy is within a given radius of the player
- Area-of-effect abilities: collect all enemies in an AoE radius and apply damage/effects
- Pickup range detection: find all interactable items within reach
- Spawn safety checks: verify a spawn point is clear before placing an object there
// Find all enemies within 5 meters of an AoE ability
void ApplyAreaDamage(Vector3 epicenter, float radius, float damage)
{
int enemyLayer = LayerMask.GetMask("Enemies");
Collider[] affected = Physics.OverlapSphere(epicenter, radius, enemyLayer);
foreach (Collider col in affected)
{
HealthComponent health = col.GetComponent<HealthComponent>();
if (health != null)
{
// Damage falls off with distance from epicenter
float distance = Vector3.Distance(epicenter, col.transform.position);
float falloff = 1f - Mathf.Clamp01(distance / radius);
float finalDamage = damage * falloff;
health.TakeDamage(finalDamage);
}
}
}
AddExplosionForce()
Unity provides a dedicated method for applying radial explosion physics:
Rigidbody.AddExplosionForce(). Rather than requiring you to calculate force vectors for
every affected Rigidbody manually, this method handles the spatial math: objects closer to the
explosion receive more force, and objects farther away receive less.
rb.AddExplosionForce(
explosionForce, // total force at the epicenter
explosionPosition, // world position of the explosion
explosionRadius, // maximum radius of effect
upwardsModifier, // extra upward bias (0 = no bias, 1 = realistic upward scatter)
ForceMode.Impulse // typically Impulse for instant effect
);
The upwardsModifier shifts the explosion's effective origin upward, causing objects to be
launched upward rather than purely outward. This looks more dramatic and realistic for game explosions.
A value of 1–3 is typical.
Creating a Complete Bomb System
Let's build a full explosion system using OverlapSphere and AddExplosionForce:
using UnityEngine;
public class Bomb : MonoBehaviour
{
[Header("Explosion Settings")]
[SerializeField] private float explosionForce = 800f;
[SerializeField] private float explosionRadius = 8f;
[SerializeField] private float upwardsModifier = 2f;
[SerializeField] private float fuseTime = 3f;
[Header("Visual Effects")]
[SerializeField] private GameObject explosionEffectPrefab;
[SerializeField] private LayerMask affectedLayers;
private float timer;
private bool hasExploded;
private void Update()
{
if (hasExploded) return;
timer += Time.deltaTime;
if (timer >= fuseTime)
{
Explode();
}
}
private void Explode()
{
if (hasExploded) return;
hasExploded = true;
Vector3 epicenter = transform.position;
// Find all colliders in the explosion radius
Collider[] colliders = Physics.OverlapSphere(epicenter, explosionRadius, affectedLayers);
foreach (Collider col in colliders)
{
Rigidbody affectedRb = col.attachedRigidbody;
if (affectedRb != null && !affectedRb.isKinematic)
{
affectedRb.AddExplosionForce(
explosionForce,
epicenter,
explosionRadius,
upwardsModifier,
ForceMode.Impulse
);
}
// Deal damage if the object has a health component
HealthComponent health = col.GetComponent<HealthComponent>();
if (health != null)
{
float distance = Vector3.Distance(epicenter, col.transform.position);
float damageFalloff = 1f - Mathf.Clamp01(distance / explosionRadius);
health.TakeDamage(100f * damageFalloff);
}
}
// Spawn explosion visual effect
if (explosionEffectPrefab != null)
{
Instantiate(explosionEffectPrefab, epicenter, Quaternion.identity);
}
// Destroy the bomb object
Destroy(gameObject);
}
// Visualize explosion radius in Scene view
private void OnDrawGizmosSelected()
{
Gizmos.color = new Color(1f, 0.3f, 0f, 0.3f);
Gizmos.DrawSphere(transform.position, explosionRadius);
Gizmos.color = new Color(1f, 0.3f, 0f, 1f);
Gizmos.DrawWireSphere(transform.position, explosionRadius);
}
}
This script is fully self-contained: place it on any GameObject (a sphere, a mesh, an invisible
trigger) and it will explode after the fuse time, launching all physics objects in range and dealing
damage to anything with a HealthComponent. The gizmo visualization makes it easy to
tune the explosion radius visually in the Scene view.
Debugging Raycasts Visually
A common frustration with raycasting is that rays are invisible at runtime. Unity provides two tools for visualizing them during development:
// Draw a ray in the Scene view for one frame (only visible in Editor)
Debug.DrawRay(origin, direction * maxDistance, Color.red);
// Draw a line between two points in the Scene view
Debug.DrawLine(startPoint, endPoint, Color.green);
// Persist for multiple frames
Debug.DrawRay(origin, direction * maxDistance, Color.yellow, duration: 2f);
Make sure the Gizmos button is enabled in the Scene view toolbar to see these. A
common workflow is to add Debug.DrawRay() calls next to every Physics.Raycast()
during development, then remove or comment them out in the final build.
Summary
Raycasting is one of the most frequently used physics tools in real Unity projects. To recap the key points from this chapter:
- Raycasts only hit Colliders — no Collider means invisible to raycasts.
- Physics.Raycast() for single hits; Physics.RaycastAll() for all hits along a ray.
- Physics.Linecast() when you know both endpoints — great for line-of-sight checks.
- Shape casting (SphereCast, BoxCast, CapsuleCast) prevents thin-ray misses and gives you volumetric detection.
- LayerMask filters raycasts to specific layers — essential for performance and correctness.
- Camera.ScreenPointToRay() converts mouse position to a 3D ray — the foundation of mouse picking.
- Physics.OverlapSphere() and friends find all colliders in a region without any sweeping.
- AddExplosionForce() handles radial physics forces with distance falloff built in.
- Debug.DrawRay() makes invisible rays visible during development.
In the next chapter we move on to trigonometry — sine, cosine, and Atan2 — and build a complete turret aiming system that demonstrates how trig functions power orientation, circular movement, and angle calculation in Unity.
Need help with Unity3D development?
I'm a senior developer with 16+ years experience, including AAA projects at Ubisoft. Let's discuss how I can help with your game or interactive project.
Start a Conversation