I have successfully added spread to my ranged weapons, though this spread is not very good as when I set the spread value to 1, this makes the weapons spread too hight. When I put the weapons spread below 1 it will completely negate the spread so you have pinpoint accuracy.

This is how my Raycast code looks:

local accurateRay = Ray.new(Head, (mousePos - Head).unit * 400)
local ray = Ray.new(accurateRay.Origin, CFrame.Angles(math.rad(math.random(-Spread, Spread)), math.rad(math.random(-Spread, Spread)), math.rad(math.random(-Spread, Spread))) * accurateRay.Direction)
local Part, Position, Vector = game.Workspace:FindPartOnRay(ray, player.Character)
local Distance = (muzzlePos - Position).magnitude

As a note to @MagikTheDogâ€™s answer, math.random without arguments returns a value between 0 and 1, meaning you can also do (math.random()*2-1)*spread

I found the randomSpread function on a different post. It takes an origin cframe that has the position and is facing toward the target, and returns a cframe that you can get the .LookVector of to get the offsetted direction. I added the lookAt function at the top so you can easily create the origin cframe.

Note: I used .fromMatrix() because .new(pos, look) is deprecated.

local RANGE = 400
local TAU = math.pi * 2
local UP_VECTOR = Vector3.new(0, 1, 0)
local function lookAt(pos, look)
local forwardVector = (pos - look).Unit
local rightVector = forwardVector:Cross(UP_VECTOR)
local upVector = rightVector:Cross(forwardVector)
return CFrame.fromMatrix(pos, rightVector, upVector)
end
local function randomSpread(cframe, spreadDiameter)
local targetCFrame = cframe * CFrame.new(0, 0, -RANGE)
* CFrame.Angles(0, 0, TAU * math.random())
* CFrame.new(0, math.random() * spreadDiameter / 2, 0)
return lookAt(cframe.Position, targetCFrame.Position)
end

Also, I recommend using workspace:Raycast() over workspace:FindPartOnRay() because you donâ€™t have to create a ray object every time you want to use it.

local rng_v = Random.new()
function RandomVectorOffset(v, maxAngle) --returns uniformly-distributed random unit vector no more than maxAngle radians away from v
return (CFrame.new(Vector3.new(), v)*CFrame.Angles(0, 0, rng_v:NextNumber(0, 2*math.pi))*CFrame.Angles(math.acos(rng_v:NextNumber(math.cos(maxAngle), 1)), 0, 0)).LookVector
end

I agree that this is better, but you should use .fromMatrix() in this function because .new(pos, look) is deprecated.

I would but it runs faster this way. I also still think that method was wrongly deprecated (or at least, could be improved on). (Update 2021: Replace with CFrame.lookAt which is itâ€™s replacement.)