Can I optimize this script in any way?

I created a script that raycasts every laser in the game every frame. The lasers are beams that, if you touch them, will detect where they have been hit and shrink.

Now I’m still looking for some optimization because I don’t think this is the best result.

LocalScript in StarterPlayerScripts:

local RunService: RunService = game:GetService("RunService")

local lasers = workspace.Lasers

local function raycastLasers()
	for index: number, laser: Model in lasers:GetChildren() do
		if not laser:IsA("Model") then return end

		local part0: Part, part1: Part = laser:FindFirstChild("Part0") :: Part, laser:FindFirstChild("Part1") :: Part
		if not part0 or not part1 then return end
		
		local part1OriginPosition: Vector3 = part1.Position

		local attachment0: Attachment, attachment1: Attachment = part0:FindFirstChildOfClass("Attachment") :: Attachment, part1:FindFirstChildOfClass("Attachment") :: Attachment
		if not attachment0 or not attachment1 then return end

		local rayOrigin: Vector3 = part0.Position
		local rayDestination: Vector3 = part1.Position

		local rayDirection: Vector3 = rayDestination - rayOrigin

		local raycastParams: RaycastParams = RaycastParams.new()
		raycastParams.FilterDescendantsInstances = {part0, part1}
		raycastParams.FilterType = Enum.RaycastFilterType.Exclude

		local raycastResult: RaycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
		if not raycastResult or not raycastResult.Instance then return end
		
		part1.Position = raycastResult.Position
		
		task.delay(0, function() part1.Position = part1OriginPosition end)
	end
end

RunService.RenderStepped:Connect(raycastLasers)

Lasers in the workspace:
image

2 Likes

The only thing I see weird is this line

Why do you need to set it to Exclude? It’s already defaulted to Blacklist
(I dont know what Exclude is, but it sounds like it does the same thing as Blacklist)

2 Likes

Oh thanks, I didn’t know it’s already on exclude/blacklist by default. Exclude and include are 2 new filter types that work the same as whitelist and blacklist.

Oh ok thats cool, but also another thing I noticed is that

Why do you need to delay this function for 0 seconds? Why can’t you set it instantly?

If I do it that way, at the same time the part would move 2 times. With that line everything works correctly, without it there is no effect.

Edit:

With task.delay:
image

Without task.delay:
image

I do not know why this is the case, but this makes me want to keep this line

2 Likes

Actually, are you sure it makes an effect? It looks like the RayCast is going through your legs, im pretty sure they dont use the solid hitbox but use the mesh hitbox.

If I didn’t use task.delay(), it would go through my leg, but not if I do use task.delay().

That isn’t what’s happening, it’s actually just that without that delay there, it just resets the Position back to the Origin instantly, meaning that it doesn’t actually show the result of the Raycast.

If he removes that line entirely, it’d be easy to demonstrate what I mean, but then the laser could only continuously get shorter. He’s getting his Raycast Direction from the delta (x¹ - x²) of the two Part’s Positions. Which is weird, not gonna lie.

Anyways, that means he has to (at some point) set the position of Part1 back to the point at which it started, so the next time it runs (in this case RenderStepped; so every frame,) it gets the proper Origin Position of the part (and consequently the proper Raycast Direction.)


Anyways, this seems like a pretty optimized script for it’s purpose. However, I’d move your RaycastParams out of the loop, especially if they do not change from run to run, since re-instantiating them every frame is just a waste of performance (no matter how miniscule.)

Edit: and to note, you can just change the FilterDescendantsInstances property for each Laser in the for loop.

2 Likes

My script looks like this after I’ve implemented the things you’ve said:

local RunService: RunService = game:GetService("RunService")

local lasers = workspace.Lasers

local raycastParams: RaycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {}

local function raycastLasers()
	for index: number, laser: Model in lasers:GetChildren() do
		if not laser:IsA("Model") then return end

		local part0: Part, part1: Part = laser:FindFirstChild("Part0") :: Part, laser:FindFirstChild("Part1") :: Part
		if not part0 or not part1 then return end
		
		if not table.find(raycastParams.FilterDescendantsInstances, {part0, part1}) then
			table.insert(raycastParams.FilterDescendantsInstances, {part0, part1})
		end
		
		local part1OriginPosition: Vector3 = part1.Position

		local attachment0: Attachment, attachment1: Attachment = part0:FindFirstChildOfClass("Attachment") :: Attachment, part1:FindFirstChildOfClass("Attachment") :: Attachment
		if not attachment0 or not attachment1 then return end
		
		local rayOrigin: Vector3 = part0.Position
		local rayDestination: Vector3 = part1.Position

		local rayDirection: Vector3 = rayDestination - rayOrigin

		local raycastResult: RaycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
		if not raycastResult or not raycastResult.Instance then return end

		part1.Position = raycastResult.Position

		task.delay(0, function() part1.Position = part1OriginPosition end)
	end
end

RunService.RenderStepped:Connect(raycastLasers)

Is this what you meant or did I do it wrong?

Sorry for the late response;

You’re close but not quite there yet.
Here’s the correct way to do it.

Version 1 | Move RaycastParams variable out
local RunService: RunService = game:GetService("RunService")

local lasers = workspace.Lasers
local raycastParams: RaycastParams = RaycastParams.new() -- move this out
raycastParams.FilterType = Enum.RaycastFilterType.Exclude

local function raycastLasers()
	for index: number, laser: Model in lasers:GetChildren() do
		if not laser:IsA("Model") then return end

		local part0: Part, part1: Part = laser:FindFirstChild("Part0") :: Part, laser:FindFirstChild("Part1") :: Part
		if not part0 or not part1 then return end

		local part1OriginPosition: Vector3 = part1.Position

		local attachment0: Attachment, attachment1: Attachment = part0:FindFirstChildOfClass("Attachment") :: Attachment, part1:FindFirstChildOfClass("Attachment") :: Attachment
		if not attachment0 or not attachment1 then return end
		-- Just a note, but this seems to be redundant and serve no actual purpose?
		-- Are you making sure the laser has attachments so that you know if it has a beam (or otherwise, whatever makes the effect?) If so, then that makes a lot more sense and ignore this

		local rayOrigin: Vector3 = part0.Position
		local rayDestination: Vector3 = part1.Position

		local rayDirection: Vector3 = rayDestination - rayOrigin

		raycastParams.FilterDescendantsInstances = {part0, part1} -- Set the FilterDescendantsInstances

		local raycastResult: RaycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
		if not raycastResult or not raycastResult.Instance then return end

		part1.Position = raycastResult.Position

		task.delay(0, function() part1.Position = part1OriginPosition end)
	end
end

RunService.RenderStepped:Connect(raycastLasers)

Version 2 | Use FilterDescendantsInstances as intended (Recommended)
local RunService: RunService = game:GetService("RunService")

local lasers = workspace.Lasers
local raycastParams: RaycastParams = RaycastParams.new() -- move this out
raycastParams.FilterDescendantsInstances = {lasers} -- Set the filter to the Laser parent instance; this works since it's Filter"Descendants"Instances, meaning all "Descendants" are filtered
raycastParams.FilterType = Enum.RaycastFilterType.Exclude

local function raycastLasers()
	for index: number, laser: Model in lasers:GetChildren() do
		if not laser:IsA("Model") then return end

		local part0: Part, part1: Part = laser:FindFirstChild("Part0") :: Part, laser:FindFirstChild("Part1") :: Part
		if not part0 or not part1 then return end

		local part1OriginPosition: Vector3 = part1.Position

		local attachment0: Attachment, attachment1: Attachment = part0:FindFirstChildOfClass("Attachment") :: Attachment, part1:FindFirstChildOfClass("Attachment") :: Attachment
		if not attachment0 or not attachment1 then return end
		-- Just a note, but this seems to be redundant and serve no actual purpose?
		-- Are you making sure the laser has attachments so that you know if it has a beam (or otherwise, whatever makes the effect?) If so, then that makes a lot more sense and ignore this

		local rayOrigin: Vector3 = part0.Position
		local rayDestination: Vector3 = part1.Position

		local rayDirection: Vector3 = rayDestination - rayOrigin

		local raycastResult: RaycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
		if not raycastResult or not raycastResult.Instance then return end

		part1.Position = raycastResult.Position

		task.delay(0, function() part1.Position = part1OriginPosition end)
	end
end

RunService.RenderStepped:Connect(raycastLasers)
2 Likes

if not attachment0 or not attachment1 then return end
I added this line so that exploiters can’t make their own lasers unless the laser has all the required instances.

raycastParams.FilterDescendantsInstances = {lasers}
Isn’t it bad that all beams and attachments are also in the FilterDescendantsInstances table?

Why would it be bad? Ultimately, you’re filtering out every laser individually, and adding instances to a blacklist table means the raycast is checking less overall instances.
Unless you want some lasers to hit each other, noting that typically “lasers” can go through each other since they’re light, this is the most logical solution.

2 Likes

I’m now using the second method, but only one of my lasers does raycasts now. I didn’t have this issue before.

Try method 1? If method 1 works then just use it, this shouldn’t be occurring though so that’s weird.

2 Likes

Method 1 does not seem to work either, I tried getting rid of things one by one but it never got to work.

Current code:

local RunService: RunService = game:GetService("RunService")

local lasers = workspace.Lasers

local raycastParams: RaycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {lasers}

local function raycastLasers()
	for index: number, laser: Model in lasers:GetChildren() do
		if not laser:IsA("Model") then return end

		local part0: Part, part1: Part = laser:FindFirstChild("Part0") :: Part, laser:FindFirstChild("Part1") :: Part
		if not part0 or not part1 then return end
		
		local rayOrigin: Vector3 = part0.Position
		local rayDestination: Vector3 = part1.Position

		local rayDirection: Vector3 = rayDestination - rayOrigin

		local raycastResult: RaycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
		if not raycastResult or not raycastResult.Instance then return end
		
		local part1OriginPosition: Vector3 = part1.Position

		part1.Position = raycastResult.Position

		task.delay(0, function() part1.Position = part1OriginPosition end)
	end
end

RunService.RenderStepped:Connect(raycastLasers)

Try just this, don’t change it or anything unless needed; if this doesn’t work then just revert back to what was working previously and call it good. I’m not entirely sure why this is happening, it’s puzzling.

local RunService: RunService = game:GetService("RunService")

local lasers = workspace.Lasers
local raycastParams: RaycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude

local function raycastLasers()
	for index: number, laser: Model in lasers:GetChildren() do
		if not laser:IsA("Model") then return end

		local part0: Part, part1: Part = laser:FindFirstChild("Part0") :: Part, laser:FindFirstChild("Part1") :: Part
		if not part0 or not part1 then return end

		local part1OriginPosition: Vector3 = part1.Position

		local attachment0: Attachment, attachment1: Attachment = part0:FindFirstChildOfClass("Attachment") :: Attachment, part1:FindFirstChildOfClass("Attachment") :: Attachment
		if not attachment0 or not attachment1 then return end
		-- Just a note, but this seems to be redundant and serve no actual purpose?
		-- Are you making sure the laser has attachments so that you know if it has a beam (or otherwise, whatever makes the effect?) If so, then that makes a lot more sense and ignore this

		local rayOrigin: Vector3 = part0.Position
		local rayDestination: Vector3 = part1.Position

		local rayDirection: Vector3 = rayDestination - rayOrigin
		
		raycastParams.FilterDescendantsInstances = {lasers, part0, part1}

		local raycastResult: RaycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
		if not raycastResult or not raycastResult.Instance then return end

		part1.Position = raycastResult.Position

		task.delay(0, function() part1.Position = part1OriginPosition end)
	end
end

RunService.RenderStepped:Connect(raycastLasers)
2 Likes

Unfortunately, that didn’t work. I find it very strange that this is the only way it does work:

local RunService: RunService = game:GetService("RunService")

local lasers = workspace.Lasers

local raycastParams: RaycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {}

local function raycastLasers()
	for index: number, laser: Model in lasers:GetChildren() do
		if not laser:IsA("Model") then return end

		local part0: Part, part1: Part = laser:FindFirstChild("Part0") :: Part, laser:FindFirstChild("Part1") :: Part
		if not part0 or not part1 then return end

		if not table.find(raycastParams.FilterDescendantsInstances, {part0, part1}) then
			table.insert(raycastParams.FilterDescendantsInstances, {part0, part1})
		end

		local part1OriginPosition: Vector3 = part1.Position

		local rayOrigin: Vector3, rayDestination: Vector3 = part0.Position, part1.Position

		local rayDirection: Vector3 = rayDestination - rayOrigin

		local raycastResult: RaycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
		if not raycastResult or not raycastResult.Instance then return end

		part1.Position = raycastResult.Position

		task.delay(0, function() part1.Position = part1OriginPosition end)
	end
end

RunService.RenderStepped:Connect(raycastLasers)

I will find a solution to this problem, thank you very much for thinking along and the suggestions!

No problem, I’ll be around on the DevForum occasionally; so if I spot any topics you’ve made I may pop in and see if I can be of any help solving them.

Have a good day/night.

2 Likes