Beam from Handle to Mouse Location - Need scripting help

Goal: A beam (not part) shoots from a tool to the mouse location.
Stretch Goal: As the player moves, the beam stays connected between the tool handle and the original mouse click position.

Reference Material:

The Issue:
The beam end point doesn’t align with the raycasting end point. In the tutorial they are showing a “beam” which is really just a red neon part and it aligns using CFrame. I’m using a real beam and going off of the getWorldMousePosition() function in the code. It’s not working.
Roblox Question Beam

What I’ve tried:
I’ve tried lots of things but the most common answer I saw was using the GetMouse instead of the UserINputService:GetMouseLocation(). I tried this, following another forum post, and I’m still getting the beam not aligning properly.

Any help would be greatly appreciated.

Explorer Layout

StarterPack–>Tool–>LocalScript "ToolController

local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")


local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer) --Pull in module script that renders laser

local tool = script.Parent

local MAX_MOUSE_DISTANCE = 1000
local MAX_LASER_DISTANCE = 50


local function getWorldMousePosition()
	local mouseLocation = UserInputService:GetMouseLocation()

	-- Create a ray from the 2D mouseLocation
	local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)

	-- The unit direction vector of the ray multiplied by a maximum distance
	local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE

	-- Raycast from the ray's origin towards its direction
	local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)

	if raycastResult then
		-- Return the 3D point of intersection
		--print(raycastResult.Position)
		return raycastResult.Position
	else
		-- No object was hit so calculate the position at the end of the ray
		return screenToWorldRay.Origin + directionVector
	end
end

local function fireWeapon()
	local mouseLocation = getWorldMousePosition()
	
	-- Calculate a normalised direction vector and multiply by laser distance
	local targetDirection = (mouseLocation - tool.Handle.Position).Unit

	-- The direction to fire the weapon multiplied by a maximum distance
	local directionVector = targetDirection * MAX_LASER_DISTANCE

	-- Ignore the player's character to prevent them from damaging themselves
	local weaponRaycastParams = RaycastParams.new()
	weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
	local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
	
	-- Check if any objects were hit between the start and end position
	local hitPosition
	if weaponRaycastResult then
		hitPosition = weaponRaycastResult.Position

		-- The instance hit will be a child of a character model
		-- If a humanoid is found in the model then it's likely a player's character
		local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
		if characterModel then
			local humanoid = characterModel:FindFirstChild("Humanoid")
			if humanoid then
				print("Player hit")
			end
		end
	else
		-- Calculate the end position based on maximum laser distance
		hitPosition = tool.Handle.Position + directionVector
	end
	
	LaserRenderer.createLaser(tool.Handle, hitPosition)
	
end


local function testFire()
	local mouseLocation = getWorldMousePosition()
	tool.Handle.EndAttachment.Position = Vector3.new(mouseLocation.X, mouseLocation.Y, mouseLocation.Z)
end


local function toolEquipped()
	--tool.Handle.Equip:Play()
end

local function toolActivated()
	--tool.Handle.Activate:Play()
	fireWeapon()
	--testFire()
end

-- Connect events to appropriate functions
tool.Equipped:Connect(toolEquipped)
tool.Activated:Connect(toolActivated)

StarterPlayer–>StarterPlayerScripts–>LocalScript “LaserRenderer”

local LaserRenderer = {}

local SHOT_DURATION = 1 -- Time that the laser is visible for

-- Create a laser beam from a start position towards an end position
function LaserRenderer.createLaser(toolHandle, endPosition)
	local startPosition = toolHandle.Position
	
	print("Start")
	print(startPosition)
	print("End")
	print(endPosition)

	local laserDistance = (startPosition - endPosition).Magnitude
	local laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)
	
	print("lookat")
	print(CFrame.lookAt(startPosition, endPosition))
	
	local laserPart = Instance.new("Part")
	laserPart.Size = Vector3.new(0.2, 0.2, laserDistance)
	laserPart.CFrame = laserCFrame
	laserPart.Anchored = true
	laserPart.CanCollide = false
	laserPart.Color = Color3.fromRGB(225, 0, 0)
	laserPart.Material = Enum.Material.Neon
	laserPart.Parent = workspace
	
	--TEST
	
	
	-- create attachments
	local att0 = Instance.new("Attachment")
	local att1 = Instance.new("Attachment")
	
	att0.Name = 'Attatchment0'
	att1.Name = 'Attachment1'
	-- parent to terrain (can be part instead)
	att0.Parent = toolHandle
	att1.Parent = toolHandle
	
	--local posStart
	--posStart = (laserPart.CFrame * CFrame.new(0,0,laserPart.Size.Z/2)).p
	
	--local posEnd
	--posEnd = (laserPart.CFrame * CFrame.new(0,0,laserPart.Size.X/2)).p
	
	

	-- create beam
	local beam = Instance.new("Beam")
	beam.Attachment0 = att0
	beam.Attachment1 = att1
	

	-- position attachments
	--att0.Position = Vector3.new(startPosition.X, startPosition.Y, startPosition.Z)
	att0.Position = Vector3.new(startPosition)
	att1.Position = Vector3.new(endPosition.X, endPosition.Y, endPosition.Z)
	

	print("att0 Pos")
	print(att0.Position)
	print("att1 Pos")
	print(att1.Position)

	-- appearance properties
	beam.Color = ColorSequence.new({ -- a color sequence shifting from white to blue
		ColorSequenceKeypoint.new(0, Color3.fromRGB(255, 255, 255)),
		ColorSequenceKeypoint.new(1, Color3.fromRGB(0, 255, 255)),
	})
	beam.LightEmission = 1 -- use additive blending
	beam.LightInfluence = 0 -- beam not influenced by light
	--beam.Texture = "rbxasset://textures/particles/sparkles_main.dds" -- a built in sparkle texture
	beam.Texture = "rbxassetid://6060542252" -- a built in sparkle texture
	beam.TextureMode = Enum.TextureMode.Wrap -- wrap so length can be set by TextureLength
	beam.TextureLength = 1 -- repeating texture is 1 stud long
	beam.TextureSpeed = 1 -- slow texture speed
	beam.Transparency = NumberSequence.new({ -- beam fades out at the end
		NumberSequenceKeypoint.new(0, 0),
		NumberSequenceKeypoint.new(0.8, 0),
		NumberSequenceKeypoint.new(1, 1),
	})
	beam.ZOffset = 0 -- render at the position of the beam without offset

	-- shape properties
	--beam.CurveSize0 = 2 -- create a curved beam
	--beam.CurveSize1 = -2 -- create a curved beam
	beam.FaceCamera = true -- beam is visible from every angle
	beam.Segments = 10 -- default curve resolution
	beam.Width0 = 2 -- starts small
	beam.Width1 = 2 -- ends big

	-- parent beam
	beam.Enabled = true
	beam.Parent = att0
	
	
	--https://create.roblox.com/docs/reference/engine/classes/Beam#summary
	--https://devforum.roblox.com/t/need-help-making-a-beam-follow-the-direction-of-the-users-mouse/2444140/4
	
	
	
	-- Add laser beam to the Debris service to be removed & cleaned up
	game.Debris:AddItem(laserPart, SHOT_DURATION)
	game.Debris:AddItem(beam, SHOT_DURATION)
end

return LaserRenderer
2 Likes