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
nilbefore reading it. - Filter out the character or tool that casts the ray, or it hits itself first.
On this page
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.
| Field | Type | What it is |
|---|---|---|
Instance | BasePart or Terrain | The part (or terrain) that was hit. |
Position | Vector3 | The exact point of the hit, in the world. |
Normal | Vector3 | The direction the hit surface faces: up for a floor, sideways for a wall. |
Distance | number | Studs from the origin to the hit. |
Material | Enum.Material | The 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 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
| Setting | What it does |
|---|---|
FilterType | Exclude: hit everything except the list. Include: hit only things in the list. |
FilterDescendantsInstances | The list. Models and folders count with everything inside them, so { character } covers every body part. |
IgnoreWater | Pass through terrain water instead of stopping on its surface. |
CollisionGroup | Use a collision group's rules for which parts the ray can hit. |
RespectCanCollide | Skip 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.
-- 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.
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.
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.
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
| Mistake | What happens | Fix |
|---|---|---|
| Direction of length 1 | The ray is 1 stud long and hits nothing. | Multiply by the range: look * 100. |
| Passing the target position as the direction | The ray goes the wrong way. | Direction is target - origin. |
| Not filtering the caster | Every ray hits your own character or tool first. | Exclude it in FilterDescendantsInstances. |
Reading result.Instance without a check | attempt to index nil with 'Instance' when nothing is hit. | Check if result then. |
Old workspace:FindPartOnRay | Deprecated, with different arguments. | Use workspace:Raycast. |
| Trusting a client's ray for damage | A 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.
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.
- 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
- Roblox Creator Docs: Raycasting. casting, filtering and the result
- Roblox Creator Docs: WorldRoot:Raycast
- Roblox Creator Docs: RaycastParams
- Roblox Creator Docs: RaycastResult
- Roblox Creator Docs: Camera:ViewportPointToRay
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.