Tutorial · Scripting foundations

How to raycast in Roblox

A raycast shoots an invisible line from a point in a direction and tells you the first thing it hits: which part, where exactly, and which way that surface faces. Guns, laser beams, "is the player standing on the ground", "can this NPC see me" and placing objects where the mouse points are all raycasts. This guide explains the one function you need, workspace:Raycast, the settings that make it ignore the right things, and four working uses.

Updated 5 November 20266 min readIntermediateLuauWritten by

In short

Call workspace:Raycast(origin, direction, params). origin is a Vector3 where the ray starts, and direction is a Vector3 whose length is how far the ray goes, for example Vector3.new(0, -10, 0) for 10 studs down. It returns a RaycastResult with Instance, Position, Normal, Distance and Material, or nil when nothing was hit. Use RaycastParams to leave out your own character.

  • The direction carries the distance: multiply a unit direction by the range, such as look * 100.
  • Always check the result for nil before reading it.
  • Filter out the character or tool that casts the ray, or it hits itself first.
On this page
  1. How it works
  2. 1. First ray
  3. 2. RaycastParams
  4. 3. Ground check
  5. 4. Line of sight
  6. 5. Mouse aim
  7. Common mistakes
  8. Common errors
  9. With RoCode
  10. Questions
  11. Sources

How a raycast works

The ray starts at origin and travels along direction until it touches a part or terrain, or runs out of length. Only the first hit is returned. Parts with CanQuery off are skipped, and so is anything your RaycastParams leave out.

What a RaycastResult gives you
FieldTypeWhat it is
InstanceBasePart or TerrainThe part (or terrain) that was hit.
PositionVector3The exact point of the hit, in the world.
NormalVector3The direction the hit surface faces: up for a floor, sideways for a wall.
DistancenumberStuds from the origin to the hit.
MaterialEnum.MaterialThe material at the hit, including terrain materials such as Grass or Water.

Step 1: Cast a ray and see it

A ray is invisible, so the first thing to build is a way to see it. This Script casts a ray forward from a part every second, prints what it hits and draws a thin neon beam along it. Insert an anchored Part named Laser in Workspace, put a Script inside it, and place a wall in front of it.

Script in a Part in Workspace named Laser
-- Script inside a Part named Laser.
-- Casts a ray forward every second, prints the hit and draws the ray.
local laser = script.Parent :: BasePart

local RANGE = 50 -- studs

local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { laser } -- do not hit the laser itself

local beam = Instance.new("Part")
beam.Anchored = true
beam.CanCollide = false
beam.CanQuery = false -- so later rays pass through the beam
beam.Material = Enum.Material.Neon
beam.Color = Color3.fromRGB(255, 60, 60)
beam.Parent = workspace

while true do
	local origin = laser.Position
	local direction = laser.CFrame.LookVector * RANGE
	local result = workspace:Raycast(origin, direction, params)

	local hitPoint = if result then result.Position else origin + direction
	local length = (hitPoint - origin).Magnitude
	beam.Size = Vector3.new(0.2, 0.2, length)
	beam.CFrame = CFrame.lookAt(origin, hitPoint) * CFrame.new(0, 0, -length / 2)

	if result then
		print("Hit", result.Instance.Name, "at", math.floor(result.Distance), "studs")
	end
	task.wait(1)
end

CFrame.lookAt(origin, hitPoint) points the beam's front at the hit, and moving it half its length forward centres it on the ray. Turn the Laser part in Studio while the game runs (switch to the server view first) and the beam follows. Keep a beam like this while you build: it shows at once when a ray goes the wrong way.

Step 2: Choose what the ray can hit

RaycastParams settings
SettingWhat it does
FilterTypeExclude: hit everything except the list. Include: hit only things in the list.
FilterDescendantsInstancesThe list. Models and folders count with everything inside them, so { character } covers every body part.
IgnoreWaterPass through terrain water instead of stopping on its surface.
CollisionGroupUse a collision group's rules for which parts the ray can hit.
RespectCanCollideSkip parts with CanCollide off, such as invisible trigger zones.

A typical setup for a ray cast from a player: exclude their own character and anything decorative. Make the params once and reuse them; update the list when the character respawns.

ModuleScript in ReplicatedStorage named RayHelpers
-- ModuleScript in ReplicatedStorage named RayHelpers.
-- Small helpers used by the next steps.
local RayHelpers = {}

-- Params that ignore the given instances (and everything inside them).
function RayHelpers.excluding(ignore: { Instance }): RaycastParams
	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Exclude
	params.FilterDescendantsInstances = ignore
	params.IgnoreWater = true
	return params
end

-- The character a hit part belongs to, if any.
function RayHelpers.characterOf(hit: Instance): Model?
	local model = hit:FindFirstAncestorOfClass("Model")
	if model and model:FindFirstChildOfClass("Humanoid") then
		return model
	end
	return nil
end

return RayHelpers

Step 3: Is the player on the ground?

A short ray straight down from the character's root part tells you whether there is ground under them, how far it is, and what it is made of. This LocalScript prints the surface under the player twice a second, which is the basis for footstep sounds, fall damage or "only on grass" abilities.

LocalScript in StarterCharacterScripts named GroundCheck
-- LocalScript in StarterCharacterScripts named GroundCheck.
local ReplicatedStorage = game:GetService("ReplicatedStorage")

type RayHelpersModule = {
	excluding: (ignore: { Instance }) -> RaycastParams,
	characterOf: (hit: Instance) -> Model?,
}
local RayHelpers = require(ReplicatedStorage:WaitForChild("RayHelpers")) :: RayHelpersModule

local character = script.Parent :: Model
local root = character:WaitForChild("HumanoidRootPart") :: BasePart
local params = RayHelpers.excluding({ character })

while character.Parent do
	-- root part centre to feet is about 3 studs on a standard character; look a little further
	local result = workspace:Raycast(root.Position, Vector3.new(0, -4, 0), params)
	if result then
		print("Standing on", result.Instance.Name, result.Material.Name)
	else
		print("In the air")
	end
	task.wait(0.5)
end

For simply "is the player in the air", Humanoid.FloorMaterial is already there and costs nothing: it is Air when they are not standing on anything. Use a ray when you need the part itself or a longer reach, for example how far a fall is going to be.

Step 4: Can the NPC see the player?

Cast from the NPC's head to the player's head. If the first thing hit belongs to the player's character, nothing is in the way. This server function is what a guard, a turret or a horror monster uses before it chases.

Script in ServerScriptService named LineOfSight
-- Script in ServerScriptService named LineOfSight.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

type RayHelpersModule = {
	excluding: (ignore: { Instance }) -> RaycastParams,
	characterOf: (hit: Instance) -> Model?,
}
local RayHelpers = require(ReplicatedStorage:WaitForChild("RayHelpers")) :: RayHelpersModule

local SIGHT_RANGE = 80 -- studs

local function canSee(npc: Model, target: Model): boolean
	local eyes = npc:FindFirstChild("Head")
	local targetHead = target:FindFirstChild("Head")
	if not (eyes and eyes:IsA("BasePart") and targetHead and targetHead:IsA("BasePart")) then
		return false
	end
	local offset = targetHead.Position - eyes.Position
	if offset.Magnitude > SIGHT_RANGE then
		return false
	end
	local result = workspace:Raycast(eyes.Position, offset, RayHelpers.excluding({ npc }))
	return result ~= nil and RayHelpers.characterOf(result.Instance) == target
end

local guard = workspace:WaitForChild("Guard") :: Model

while true do
	for _, player in Players:GetPlayers() do
		local character = player.Character
		if character and canSee(guard, character) then
			print("Guard sees", player.Name)
		end
	end
	task.wait(0.5)
end

The direction here is the full offset to the target, so the ray is exactly as long as the distance between them. Checking every half second is plenty for most NPCs; the NPC pathfinding guide shows how to chase once the guard sees someone.

Step 5: Find what the mouse points at

Camera:ViewportPointToRay turns a point on the screen into a ray from the camera into the world. Its direction has length 1, so multiply it by a range. This LocalScript prints what is under the mouse when you click, which is the start of a building tool or a click-to-move.

LocalScript in StarterPlayerScripts named MouseTarget
-- LocalScript in StarterPlayerScripts named MouseTarget.
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")

local RANGE = 500

local player = Players.LocalPlayer

UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
	if gameProcessedEvent or input.UserInputType ~= Enum.UserInputType.MouseButton1 then
		return
	end
	local camera = workspace.CurrentCamera
	local mouse = UserInputService:GetMouseLocation()
	local ray = camera:ViewportPointToRay(mouse.X, mouse.Y)

	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Exclude
	params.FilterDescendantsInstances = if player.Character then { player.Character } else {}

	local result = workspace:Raycast(ray.Origin, ray.Direction * RANGE, params)
	if result then
		print("Clicked", result.Instance:GetFullName(), "at", result.Position)
	end
end)

GetMouseLocation includes the top bar, and ViewportPointToRay expects exactly that, so the two match. For a weapon, send the hit position to the server and cast again there from the player's own position; the gun script guide does exactly that, so a cheater cannot shoot through walls.

Common raycast mistakes

What goes wrong, and the fix
MistakeWhat happensFix
Direction of length 1The ray is 1 stud long and hits nothing.Multiply by the range: look * 100.
Passing the target position as the directionThe ray goes the wrong way.Direction is target - origin.
Not filtering the casterEvery ray hits your own character or tool first.Exclude it in FilterDescendantsInstances.
Reading result.Instance without a checkattempt to index nil with 'Instance' when nothing is hit.Check if result then.
Old workspace:FindPartOnRayDeprecated, with different arguments.Use workspace:Raycast.
Trusting a client's ray for damageA cheater sends any hit they like.Cast again on the server.

Common errors and fixes

Draw the ray with the beam from Step 1 before you debug anything else: most problems are visible at once.

Raycast always returns nil

The ray is too short (a direction of length 1), it points away from the target, or the target has CanQuery off. Print direction.Magnitude and draw the ray.

The ray hits my own character

Add the character to FilterDescendantsInstances with FilterType set to Exclude. After a respawn, update the list with the new character.

The ray goes through some parts

Those parts have CanQuery off, are in the filter list, or are excluded by CollisionGroup or RespectCanCollide. Check those settings on the part.

The ray stops at water

Terrain water counts as a hit. Set params.IgnoreWater = true.

Build it with RoCode

If you would rather not set this up by hand, RoCode can build it in the place you have open. RoCode is an independent AI agent for Roblox Studio, not made by Roblox: you describe the change in a web chat, and its Studio plugin creates the scripts and instances in your Explorer, so there is no code to paste.

You type

Make the Guard NPC chase players it can see: every half second, raycast from its head to each player within 80 studs, ignoring the guard itself, and only chase when nothing is in the way.

RoCode does
  • Can look through your place first with Search Game and Search Scripts, so it finds your NPC and any movement code you already have
  • Writes the Script with Create Script and is told to compile-check it with Check Script
  • Sets an undo point before and after each batch of changes, so Ctrl+Z steps back one batch at a time

With plugin 3.9 or newer RoCode can run a short playtest and read the errors, but it checks only what it chose to test, so walk behind a wall yourself and watch the guard. How RoCode connects to Studio.

Questions

How do I raycast in Roblox?

Call workspace:Raycast(origin, direction, params). The direction's length is the range. Check the result for nil, then read result.Instance and result.Position.

How do I make a raycast ignore a part?

Create RaycastParams, set FilterType to Exclude and put the part (or its model) in FilterDescendantsInstances. Or turn off CanQuery on the part.

How do I see a raycast?

Rays are invisible. Draw a thin anchored, non-colliding part from the origin to the hit point, as in Step 1, while you build.

What is the difference between Raycast and Shapecast?

A raycast is a line with no thickness. Blockcast, Spherecast and Shapecast sweep a shape along the same path, which is better for thick projectiles or a character-sized check.

Should raycasts run on the server or the client?

On the client for aiming and visuals, on the server for anything that decides damage or rewards. Weapons usually do both: aim on the client, check again on the server.

Sources and further reading

How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 5 November 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.

Try RoCode on your own place.

Free every day, no card required. Install the plugin, describe the feature, and review what lands in Studio.