Dodgy Raycast weapon spread

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

Here are two gifs.

Spread at the value of 1:
https://gyazo.com/fd37172b05a52e19abf8cc3f53d49d34

Spread at the value of 0.9999:
https://gyazo.com/09418fc7bcb65674e930543286249ed5

Any help to stop this problem occurring would be much appreciated.

1 Like

math.random needs an integer representation (the numbers that is), it means you can’t do math.random(-.9,.9), in that case what you can do is :

local spread = 0.98
spread = spread * 100
local firstSpread =  math.random(-spread,spread) / 100

(This is probably not the best to do tbh, but since nobody is replying decided to do so)

Oh and in your script it’d look like this :

Spread = Spread * 100
local ray = Ray.new(accurateRay.Origin, CFrame.Angles(math.rad(math.random(-Spread, Spread) / 100), math.rad(math.random(-Spread, Spread) / 100), math.rad(math.random(-Spread, Spread) / 100)) * accurateRay.Direction)
1 Like

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

1 Like

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.

Imo, this is the best way to do spread. The code is also more efficient.

1 Like

is this the code you were referencing?

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

1 Like