PcoWSkbVqDnWTu_dm2ix
We use cookies on this site to enhance your user experience

Raycasting: Making a Laser Gun

Raycasting: Making a Laser Gun

Jul 03 2018, 11:32 AM PST 10 min

Ray-casting in Roblox allows you to detect whether a part intersects a line segment. This tutorial covers how to use ray-casting to create a laser gun.

Tutorial

Setting Up

PaintballGunInToolbox.png

In order to create a laser gun, we need a tool for the player to hold. Go to the toolbox, select Roblox Sets, and then select the Weapons set. Click on the paintball gun, and place it in the StarterPack when you’re prompted to do so. Open it up and remove everything except the part named Handle. After that, insert a LocalScript into the tool. Make sure it’s not a Script or it won’t work when you play the game online! Feel free to rename the tool, but don’t change the name of the part!

Framework

Now that we’ve set up, it’s time to start scripting our gun. Let’s start with some variables:

local tool = script.Parent
local player = game:GetService("Players").LocalPlayer

The tool variable references the tool, and the player variable references the player who’s using the gun. Now, let’s do something when the tool is equipped by connecting to the tool’s Tool/Equipped event:

local tool = script.Parent
local player = game:GetService("Players").LocalPlayer

tool.Equipped:connect(function(mouse)
	print("Tool equipped!")
end)

Now, whenever the tool is equipped, it will print Tool equipped! to the Output window. You may notice the mouse parameter of the function; this is a Mouse object that we can use to respond to the player’s mouse clicks. Connect to the mouse’s Mouse/Button1Down event, like this:

local tool = script.Parent
local player = game:GetService("Players").LocalPlayer

tool.Equipped:connect(function(mouse)
	print("Tool equipped!")
	
	mouse.Button1Down:connect(function()
		print("Mouse pressed!")
	end)
end)

Now, when you click your mouse, Mouse pressed! will be printed to the output. Go ahead and try it! Play the game, equip your tool, and press the mouse a few times. You should see something like this:

Mouse pressed!
Mouse pressed!
Mouse pressed!
Tool equipped!
Mouse pressed!

Raycasting

Now that we’ve got our framework set up, let’s get started on casting a ray. Add this to your mouse down function:

local ray = Ray.new(tool.Handle.CFrame.p, (mouse.Hit.p - toolHandle.CFrame.p).unit * 300)

This will create a ray that starts at the center of the tool’s handle (tool.Handle.CFrame.p) and goes out 300 studs towards the mouse’s position ((mouse.Hit.p - tool.Handle.CFrame.p).unit * 300). But on its own, this doesn’t detect parts in the way of the ray, so we need to use the Workspace/FindPartOnRay method of Workspace. FindPartOnRay takes four arguments: the ray to use when checking for parts, an object to ignore, whether to treat all terrain cells as full cubes, and whether to ignore water. Since this is a laser, we want to ignore water, and we also don’t want the player to be able to shoot themselves, so we want to call it like this:

local part, position = workspace:FindPartOnRay(ray, player.Character, false, true)

part is the part that was hit (or type|nil if there wasn’t a part in the way), and position is the position that the ray hit the part at. Let’s add this into our function. In your Button1Down connection, add the previous lines. Your script should now look like this:

local tool = script.Parent
local player = game:GetService("Players").LocalPlayer

tool.Equipped:connect(function(mouse)
	print("Tool equipped!")
	
	mouse.Button1Down:connect(function()
		print("Mouse pressed!")
		local ray = Ray.new(tool.Handle.CFrame.p, (mouse.Hit.p - tool.Handle.CFrame.p).unit * 300)
		local part, position = workspace:FindPartOnRay(ray, player.Character, false, true)
	end)
end)

Once you’ve done that, you should be ready to make a laser beam!

Making a Laser Beam

Now that we’re able to see what’s in the way of the mouse, we can make a laser beam between the ray’s starting point (the center of the tool’s handle) and the position that the ray hit. Let’s make our beam look like this:

RaycastingGunBeamExample.png

To get started, we need to create a part in the Button1Down connection, like this:

local beam = Instance.new("Part", workspace)

But this part is just a gray brick, not a beam. To make it look like our example, we need to do several things:

  • We need to set its BasePart/BrickColor property to Bright red.
  • We need to set its FormFactorPart/FormFactor property to Custom
  • We need to set its BasePart/Material property to Neon
  • We need to set its BasePart/Transparency property to 0.25
  • We need to set its BasePart/Anchored and BasePart/Locked properties to true and its BasePart/CanCollide property to false.

Let’s do that! Just after you create the part, set the beam’s properties:

beam.BrickColor = BrickColor.new("Bright red")
beam.FormFactor = "Custom"
beam.Material = "Neon"
beam.Transparency = 0.25
beam.Anchored = true
beam.Locked = true
beam.CanCollide = false

But if we put this in the workspace, it’s just going to look like a red brick. We need to modify its BasePart/Size and BasePart/CFrame properties so that it stretches between the handle and the hit point. To do that, use this:

local distance = (tool.Handle.CFrame.p - position).magnitude
beam.Size = Vector3.new(0.3, 0.3, distance)
beam.CFrame = CFrame.new(tool.Handle.CFrame.p, position) * CFrame.new(0, 0, -distance / 2)

Right now, the gun works fine - it creates a beam where the ray hit the part. You can even fire it at the sky and it’ll make a beam up there (even though there’s not a part in the sky). But there’s one problem: the beams aren’t removed. To remove the beams, we should use the Debris/AddItem method of the Debris service. Add this to the bottom of the Button1Down connection:

game:GetService("Debris"):AddItem(beam, 0.1)

This means that 0.1 (1/10) seconds after the beam is added, it’ll be removed. Your script should now look like this:

local tool = script.Parent
local player = game:GetService("Players").LocalPlayer

tool.Equipped:connect(function(mouse)
	print("Tool equipped!")
	
	mouse.Button1Down:connect(function()
		print("Mouse pressed!")
		local ray = Ray.new(tool.Handle.CFrame.p, (mouse.Hit.p - tool.Handle.CFrame.p).unit * 300)
		local part, position = workspace:FindPartOnRay(ray, player.Character, false, true)
		
		local beam = Instance.new("Part", workspace)
		beam.BrickColor = BrickColor.new("Bright red")
		beam.FormFactor = "Custom"
		beam.Material = "Neon"
		beam.Transparency = 0.25
		beam.Anchored = true
		beam.Locked = true
		beam.CanCollide = false
		
		local distance = (tool.Handle.CFrame.p - position).magnitude
		beam.Size = Vector3.new(0.3, 0.3, distance)
		beam.CFrame = CFrame.new(tool.Handle.CFrame.p, position) * CFrame.new(0, 0, -distance / 2)
		
		game:GetService("Debris"):AddItem(beam, 0.1)
	end)
end)

Damaging Objects

Now that we’re drawing our beam, we need to make it damage players and other things with a Humanoid object in them. First, we need an if statement after we create our beam to check if the part exists:

if part then
end

Sometimes there won’t be a part for the ray to find, so part will be nil. We need to account for this to prevent the script from erroring. Now that we’ve done this, we should look in two places for the object’s humanoid: the part’s parent (in case we hit an arm or a leg) and the parent of the part’s parent (in case we hit a hat or a tool). To look for that, use this:

if part then
	local humanoid = part.Parent:FindFirstChild("Humanoid")

	if not humanoid then
		humanoid = part.Parent.Parent:FindFirstChild("Humanoid")
	end
end

First we check in the part’s parent. If we didn’t find it, then we look in the parent of the part’s parent. Hopefully we found a humanoid, but sometimes we won’t, so we need to make sure the humanoid exists before we try damaging it. Change your if statement to look like this:

if part then
	local humanoid = part.Parent:FindFirstChild("Humanoid")
	
	if not humanoid then
		humanoid = part.Parent.Parent:FindFirstChild("Humanoid")
	end
	
	if humanoid then
		humanoid:TakeDamage(30)
	end
end

Now we’re checking to make sure the humanoid exists, then we’re dealing 30 damage to it using the Humanoid/TakeDamage method. TakeDamage will not damage the humanoid if it is protected by a ForceField. Your script should now look like this:

local tool = script.Parent
local player = game:GetService("Players").LocalPlayer

tool.Equipped:connect(function(mouse)
	print("Tool equipped!")
	
	mouse.Button1Down:connect(function()
		print("Mouse pressed!")
		local ray = Ray.new(tool.Handle.CFrame.p, (mouse.Hit.p - tool.Handle.CFrame.p).unit * 300)
		local part, position = workspace:FindPartOnRay(ray, player.Character, false, true)
		
		local beam = Instance.new("Part", workspace)
		beam.BrickColor = BrickColor.new("Bright red")
		beam.FormFactor = "Custom"
		beam.Material = "Neon"
		beam.Transparency = 0.25
		beam.Anchored = true
		beam.Locked = true
		beam.CanCollide = false
		
		local distance = (tool.Handle.CFrame.p - position).magnitude
		beam.Size = Vector3.new(0.3, 0.3, distance)
		beam.CFrame = CFrame.new(tool.Handle.CFrame.p, position) * CFrame.new(0, 0, -distance / 2)
		
		game:GetService("Debris"):AddItem(beam, 0.1)
		
		if part then
			local humanoid = part.Parent:FindFirstChild("Humanoid")
			
			if not humanoid then
				humanoid = part.Parent.Parent:FindFirstChild("Humanoid")
			end
			
			if humanoid then
				humanoid:TakeDamage(30)
			end
		end
	end)
end)

And we’re done! Enjoy your new laser gun!

Final Product

RaycastingGunFinished.png

local tool = script.Parent
local player = game:GetService("Players").LocalPlayer
 
tool.Equipped:connect(function(mouse)
	print("Tool equipped!")
 
	mouse.Button1Down:connect(function()
		print("Mouse pressed!")
		local ray = Ray.new(tool.Handle.CFrame.p, (mouse.Hit.p - tool.Handle.CFrame.p).unit * 300)
		local part, position = workspace:FindPartOnRay(ray, player.Character, false, true)
 
		local beam = Instance.new("Part", workspace)
		beam.BrickColor = BrickColor.new("Bright red")
		beam.FormFactor = "Custom"
		beam.Material = "Neon"
		beam.Transparency = 0.25
		beam.Anchored = true
		beam.Locked = true
		beam.CanCollide = false
 
		local distance = (tool.Handle.CFrame.p - position).magnitude
		beam.Size = Vector3.new(0.3, 0.3, distance)
		beam.CFrame = CFrame.new(tool.Handle.CFrame.p, position) * CFrame.new(0, 0, -distance / 2)
 
		game:GetService("Debris"):AddItem(beam, 0.1)
 
		if part then
			local humanoid = part.Parent:FindFirstChild("Humanoid")
 
			if not humanoid then
				humanoid = part.Parent.Parent:FindFirstChild("Humanoid")
			end
 
			if humanoid then
				humanoid:TakeDamage(30)
			end
		end
	end)
end)
Tags:
  • tool
  • coding