How to Raycast in Roblox (Raycasting Guide)
Quick answer: Call workspace:Raycast(origin, direction, params) with a Vector3 origin and direction to cast an invisible ray and get a RaycastHit describing the first thing it hits. Use a RaycastParams object to exclude your character or other parts you want the ray to pass through.
Raycasting in Roblox is done by calling workspace:Raycast(origin, direction), where origin is a Vector3 point where the ray starts and direction is a Vector3 that says which way the ray travels and how far it goes. If the ray hits something, the function returns a RaycastHit with details about what was hit; if it hits nothing, it returns nil.
What is a raycast actually doing?
A raycast fires an invisible line into the 3D world and reports the first object that line touches. Games use it constantly: guns checking whether a shot connects, characters detecting the ground beneath them, lasers, security cameras, hover vehicles, and click-to-move systems all run on raycasts. Unlike the Touched event, a raycast does not require anything to physically move into anything else — you can fire one from anywhere at any moment and get an answer immediately, which makes it ideal for instant-hit weapons and ground checks.
How do I call workspace:Raycast?
The modern API is a single method on Workspace:
local origin = Vector3.new(0, 10, 0)
local direction = Vector3.new(0, -20, 0) -- straight down, 20 studs
local result = workspace:Raycast(origin, direction)
if result then
print("Hit", result.Instance.Name, "at", result.Position)
else
print("Nothing hit")
end
Two details trip up almost everyone learning this:
- Direction includes distance. A direction of
(0, -20, 0)casts 20 studs downward. The length of the direction vector is the maximum range of the ray. - Nil means miss. There is no error when the ray hits nothing — you simply get nil, so always check before reading properties off the result.
The old Ray.new() constructor still exists (workspace:Raycast(Ray.new(origin, direction))), but it is legacy style. New code should always pass origin and direction directly, because that form lets you supply a third argument: the RaycastParams.
How do I aim a ray from a part or the camera?
In practice you rarely hardcode directions. You build them from a CFrame's LookVector multiplied by the range in studs:
local gun = workspace.LaserGun.Tip -- some attachment point on your weapon
local RANGE = 200 -- studs
local direction = gun.CFrame.LookVector * RANGE
local result = workspace:Raycast(gun.Position, direction)
LookVector is a unit vector (length 1), so multiplying it by 200 produces a vector pointing where the part faces, exactly 200 studs long. For tools held by a player, you often want to aim from the camera through the mouse instead:
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local camera = workspace.CurrentCamera
local origin = camera.CFrame.Position
local direction = (mouse.Hit.Position - origin).Unit * 300
If you want the shot triggered by a click or key press rather than running every frame, combine this with input handling as described in how to detect key presses in Roblox.
How do I ignore my own character with RaycastParams?
Without configuration, a ray fired from inside a character immediately hits that character. Fix it with a RaycastParams:
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
local result = workspace:Raycast(gun.Position, direction, params)
- FilterType Exclude — the ray ignores everything listed (and their descendants). This is what you want 90% of the time, e.g. skipping the shooter's character.
- FilterType Include — the ray only tests against the listed instances and ignores everything else. Useful for things like "only check against the floor model."
You can also set params.IgnoreWater = true if terrain water should be transparent to the ray, and reuse the same params object for every shot instead of rebuilding it.
What does a RaycastHit contain?
When the ray hits something, you get back an object with four properties you will use constantly:
if result then
local hitPart = result.Instance -- the BasePart or Terrain that was hit
local hitPos = result.Position -- Vector3 point of impact
local normal = result.Normal -- unit Vector3 facing away from the surface
local dist = result.Distance -- studs travelled from origin to hit
end
- Instance tells you what was hit — usually you check for a Humanoid here to decide whether a shot damaged someone.
- Position is where to place effects like bullet holes or sparks.
- Normal points perpendicular to the surface at the impact, so decals oriented with CFrame.lookAt(hitPos, hitPos + normal) sit flat against walls.
- Distance lets you compute the exact segment the beam covered, handy for drawing tracers of the right length.
How do I build a simple laser gun?
Putting it together: a LocalScript listens for activation, casts from the muzzle toward the mouse, excludes the shooter, damages a Humanoid if one was hit, and briefly draws a visible beam so players can see the shot:
local tool = script.Parent
local player = tool.Parent.Parent -- adjust to your rig hierarchy
tool.Activated:Connect(function()
local mouse = player:GetMouse()
local origin = tool.Handle.Position
local direction = (mouse.Hit.Position - origin).Unit * 300
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { tool, player.Character }
local result = workspace:Raycast(origin, direction, params)
-- draw the beam: thin neon part stretched between origin and endpoint
local endpoint = result and result.Position or (origin + direction)
local beam = Instance.new("Part")
beam.Anchored = true
beam.CanCollide = false
beam.Material = Enum.Material.Neon
beam.Size = Vector3.new(0.2, 0.2, (endpoint - origin).Magnitude)
beam.CFrame = CFrame.lookAt(origin, endpoint) * CFrame.new(0, 0, -beam.Size.Z / 2)
beam.Parent = workspace
game:GetService("Debris"):AddItem(beam, 0.1)
if result then
local humanoid = result.Instance.Parent:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid:TakeDamage(15)
end
end
end)
The visualization part is optional but invaluable while developing — you instantly see whether your origin, direction, and filter are behaving. Note the beam uses CFrame.lookAt plus a half-length offset so the part spans exactly from origin to endpoint.
How do I detect multiple objects along one path?
workspace:Raycast stops at the first hit only. To collect several hits along the same line — say, penetrating walls or counting every target in the way — cast again starting just past each hit until you miss:
local origin = gun.Position
local remaining = 500
while remaining > 0 do
local result = workspace:Raycast(origin, Vector3.new(direction.Unit.X * remaining,
direction.Unit.Y * remaining, direction.Unit.Z * remaining), params)
if not result then break end
table.insert(hits, result.Instance)
local traveled = result.Distance + 0.1 -- step slightly past the surface
origin = origin + direction.Unit * traveled
remaining -= traveled
end
Keep penetration logic simple at first; most games never need more than the single first hit.
Why does my raycast return nil when something is clearly there?
The usual suspects: the ray starts inside an excluded instance, the direction vector accidentally has near-zero length, FilterType is set to Include but you forgot to add the parts you actually want to test, or the target is non-collidable geometry the ray skips by default. Debug by temporarily removing the params argument and visualizing the beam — if the drawn line passes through the object, the problem is your filter, not your math.
Raycasting pairs naturally with the other event-driven patterns on this site: trigger shots with input detection, and use Touched events when physical overlap matters more than line-of-sight.
Learn Luau by doing
Reading about raycasts only gets you so far — the fastest way to internalize origins, directions, and filters is to build small challenges that force you to use them. On Luablox lessons you work through interactive Luau exercises in order, the /theory reference explains the underlying language concepts, and /problems gives you hands-on tasks to apply them. Try a mini-project like a laser pointer that changes color based on what material it hits.
Frequently asked questions
What is the difference between Ray.new and workspace:Raycast?
Ray.new() just constructs a Ray data object; workspace:Raycast(origin, direction) actually performs the cast and returns a RaycastHit or nil. Modern code passes origin and direction directly to workspace:Raycast so a RaycastParams filter can be supplied as a third argument.
How do I make a raycast ignore my own character?
Create a RaycastParams with RaycastParams.new(), set FilterType to Enum.RaycastFilterType.Exclude, put the character model in FilterDescendantsInstances, and pass the params as the third argument to workspace:Raycast.
What units does the raycast direction use?
Direction is a plain Vector3 measured in studs. The magnitude of the vector is the maximum distance the ray travels, so Vector3.new(0, -50, 0) casts 50 studs straight down.
Why does workspace:Raycast return nil?
nil simply means the ray reached its full length without hitting anything — it is not an error. Always guard the result with an if statement before accessing Instance, Position, Normal, or Distance.
Can a raycast detect multiple objects?
Not in one call — workspace:Raycast stops at the first hit. To find multiple objects along the same line, loop: record each hit, restart the ray slightly past the last Position, and repeat until the ray misses.
Learn core Roblox scripting and Luau programming concepts, including: how to raycast roblox, raycast roblox, workspace raycast, raycastparams roblox, roblox laser gun script.