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)
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)
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.
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.
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.
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)
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)
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.
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)
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.