I am trying to make a weapon system that relies on raycasts, I am trying to make it so raycasts have “spread” to where they aren’t deadshot. but when testing i noticed that raycasts seem to become more accurate the further i am from my target. as the raycasts become closer together and the spread becomes smaller.

In general my method of making the spread is very buggy and unreliable, I would like to find a better method to do this especially the spread system.

here is the code where spread should take place in, also i’m using beams for tracers which tracers are also buggy.

for i = 0, Settings["BurstAmount"] do
task.wait(Settings["BurstDelay"])
local rp = RaycastParams.new()
rp.IgnoreWater = true
rp.FilterType = Enum.RaycastFilterType.Blacklist
rp.FilterDescendantsInstances = {}
for j = 0, Settings["BulletsPerShot"] - 1 do
task.spawn(function()
local spreadX = math.rad(math.random(-spread, spread))
local spreadY = math.rad(math.random(-spread, spread))
local Direction = CFrame.new(BarrelAttachment.Position, Hit and Hit.Position or Vector3.new()) * CFrame.Angles(spreadX, spreadY, 0)
-- Apply spread to the direction vector
local raycastResult = Ray.new(BarrelAttachment.Position, Direction.LookVector * Settings["Range"], rp)
if Hit then
local Hum = Hit.Parent:FindFirstChild("Humanoid")
if Hum then
local TracerClone = Tracer:Clone()
TracerClone.Parent = workspace
TracerClone.START.WorldPosition = BarrelAttachment.WorldPosition
TracerClone.END.WorldPosition = raycastResult
if raycastResult then
print("Result!")
else
print("Neither!")
end
TracerClone.Beam.Enabled = true
task.wait(0.1)
TracerClone:Destroy()
else
print("Miss")
end
end
end)
end
end

How is spread determined? math.random is an integer, so if your spread is only set to 1 then it’ll only be -1, 0 or 1. If it’s a decimal lower than .5 it’s only going to give a result of 0.

Something else you can do is print the spreadX and spreadY values after the calculations to show you what your values are going to be.

this is because you’re creating the spread by randomizing the area around the reticle/crosshair world position, which will make the spread narrower (therefore more accurate) the further away the world position is from the barrel attachment

here is a cool function that can be used to generate a spread pattern

I don’t really understand how it’s supposed to work.
here is the current code

the problem causing it is likely easy to find for you but not me since I am clueless when it comes to this as doing this stuff is new to me.

function:

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

Spread:

for j = 0, Settings["BulletsPerShot"] - 1 do
print("SHOT")
task.spawn(function()
local spreadX = math.rad(math.random(-spread, spread))
local spreadY = math.rad(math.random(-spread, spread))
local Direction = CFrame.new(BarrelAttachment.Position, Hit and Hit.Position or Vector3.new()) * CFrame.Angles(spreadX, spreadY, 0)
-- Apply spread to the direction vector
local ray, newraylength
local Parthit, Parthitpos = game.Workspace:FindPartOnRay(ray)
local reflectedDirection, newRay
local TracerClone = Tracer:Clone()
TracerClone.Parent = workspace
TracerClone.START.WorldPosition = BarrelAttachment.WorldPosition
TracerClone.END.WorldPosition = ray.Origin + ray.Direction.magnitude * ray.Direction.Unit
if Parthit then
print("HIT")
reflectedDirection = RandomVectorOffset(-ray.Direction, math.rad(30))
newRay = Ray.new(Parthitpos, reflectedDirection*Settings["Range"])
end
--if Hit then
-- local Hum = Hit.Parent:FindFirstChild("Humanoid")
-- if Hum then
-- local TracerClone = Tracer:Clone()
-- TracerClone.Parent = workspace
-- TracerClone.START.WorldPosition = BarrelAttachment.WorldPosition
-- TracerClone.END.WorldPosition = raycastResult
-- if raycastResult then
-- print("Result!")
-- else
-- print("Neither!")
-- end
-- TracerClone.Beam.Enabled = true
-- task.wait(0.1)
-- TracerClone:Destroy()
-- else
-- print("Miss")
-- end
--end
end)
end
end
end

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
local function GenerateSpread(InitialDirection: Vector3, ShotCount: number, Angle: number): {Vector3}
local SpreadDirections = {}
for i = 1, ShotCount do
table.insert(SpreadDirections, RandomVectorOffset(InitialDirection, math.rad(Angle)))
end
return SpreadDirections
end

in your case:

local P0 = BarrelAttachment.WorldPosition
local P1 = ... --[[mouse world position]]
local Spread = ... --[[gun firerate spread]]
local MaxDistance = ... --[[tracer max distance]]
local InitialDirection = (P1 - P0).Unit
local SpreadDirections = GenerateSpread(InitialDirection, Settings["BulletsPerShot"], Spread)
for _, UnitVector in SpreadDirections do
local TracerClone = Tracer:Clone()
TracerClone.Parent = workspace
TracerClone.START.WorldPosition = P0
TracerClone.END.WorldPosition = P0 + (UnitVector * MaxDistance)
end

Oh thank you it worked, just one last thing to ask. since I am making the weapon rely on raycasts i suppose i’ll just do the raycasts similar to how i did the tracers if I am correct?