How would I add some spread to this simple raycast script?

Local script:

local remote = game.ReplicatedStorage:WaitForChild(“Remotes”):WaitForChild(“GunEvent”)
local TweenService = game:GetService(“TweenService”)

remote.OnClientEvent:Connect(function(player, part, position, dist, barrelPos)

local bullet = Instance.new("Part")
bullet.Anchored = true
bullet.CanCollide = false
bullet.BrickColor = BrickColor.new("White")

bullet.Size = Vector3.new(0.05,0.05,dist)
bullet.Transparency = 0.8
bullet.Material = Enum.Material.Neon
bullet.CFrame = CFrame.new(barrelPos, position) * CFrame.new(0,0, -dist/2)

bullet.Parent = game.Workspace

game.Debris:AddItem(bullet, 0.25)

local Info = TweenInfo.new(

0.25,


Enum.EasingStyle.Cubic, 

Enum.EasingDirection.Out,

0, 
false,

0 

)

local Goals =

{

Transparency = 1;

}

local tween = TweenService:Create(bullet,Info,Goals)

tween:Play()

end)

Server script:

local remote = game.ReplicatedStorage:WaitForChild(“Remotes”):WaitForChild(“GunEvent”)

remote.OnServerEvent:Connect(function(player, barrelPos, mousePos)
local ray = Ray.new(barrelPos, (mousePos - barrelPos).unit * 100)
local part, position = game.Workspace:FindPartOnRay(ray, player.Character, false, true)

local dist = (barrelPos - mousePos).magnitude

if part then
	local humanoid = part.Parent:FindFirstChild("Humanoid")
	if humanoid then
		humanoid:TakeDamage(7)
	end
end

remote:FireAllClients(player, part, position, dist, barrelPos)

end)

1 Like

Spread can be simulated a lot of different ways in raycast guns. Here are two methods. One at the start, and one at the end.

If we do our bullet spread at the start, then when we calculate our ray we add a random tweak to our direction vector. This is the more complicated of the two methods, but more can be more effective.

local ray = Ray.new(barrelPos, (mousePos - barrelPos).unit * 100) --Same ray as before
local dist = (barrelPos - mousePos).magnitude --Get our distance, same as before
local spread_amount = .02 --You will need to tweak this
local spread_direction = Vector3.new(ray.direction.X + math.random()  * spread_amount, ray.direction.Y + math.random()  * spread_amount, ray.direction.Z + math.random()  * spread_amount).Unit
ray = Ray.new(ray.origin, spread_direction) --Reconstruct our ray with our random direction tweak

The other type of spread is at the end, where we adjust the end position of the bullet an amount proportional to the distance

local spread_amount = 0.02 --At 100 studs, between 0 to 0.2 studs of drift in every direction
position = Vector3.new(position.X + (spread_amount * spread_amount * math.random),position.Y + (spread_amount * spread_amount * math.random),position.Z + (spread_amount * spread_amount * math.random))

The type of spread you use depends on your replication setup, other parts of your guns, or just plain personal preference.

3 Likes

Thanks this helped, but one question. Where does the “position” part go in local spread_amount = 0.02 --At 100 studs, between 0 to 0.2 studs of drift in every direction
position = Vector3.new(position.X + (spread_amount * spread_amount * math.random),position.Y + (spread_amount * spread_amount * math.random),position.Z + (spread_amount * spread_amount * math.random))

Where do I implement the position variable in my script?

1 Like

You can do it in the server side script right before you send it to the clients if you only care about cosmetic spread, or you can apply the random shift in position to the mousepos before you cast your ray serverside to get spread that will effect aiming.

Yes, I was thinking of putting it right before it sends it to the client but where do I put the position variable in the script? Like where do I implement it. I am really new to Lua sorry

Let me mark this code up, that might help.

You see, you already declared your position variable. You just need to adjust it using that code snippet before you continue with our script.

Ohhhh I get it, I will try that.

I tried implementing the snippet into the position variable but it came out with this output error:

[ServerScriptService.GunMain:9: attempt to perform arithmetic (mul) on number and function]

Here’s my script, hopefully I did it right:

remote.OnServerEvent:Connect(function(player, barrelPos, mousePos)
local ray = Ray.new(barrelPos, (mousePos - barrelPos).unit * 50)
local part, position = game.Workspace:FindPartOnRay(ray, player.Character, false, true)

local spread_amount = 0.02
position = Vector3.new(position.X + (spread_amount * spread_amount * math.random),position.Y + (spread_amount * spread_amount * math.random),position.Z + (spread_amount * spread_amount * math.random))

That’s my bad, I was writing code without checking it first. Try seeing if you can find where a number and a function are multiplied together. Change the function value to a function call. Then check this solution.

something.function -- is a function value
something.function() -- is a function call, can return a number or anything really

Solution:

Change math.random to math.random()

Hm, the error went away after I added the () after math.random, but the bullets still fire in a straight line with no spread. I tried implementing the other code you sent me but it says that direction is not a valid member of Ray. Here’s a GIF of what it looks like:

https://gyazo.com/85645dcdfcd419befe603480351f0e03

There’s no spread.

Try upping the spread amount, that value is meant to be tweaked. The way your code is setup right now (with the spread not being proportional to distance), the max spread is going to be 0.0004 studs, a bit on the small side. Try 1 instead.

It’s still not working, I even tried with 500 spread. It still shoots in a straight line, I don’t know why exactly.

Could you send an updated version of your code, I’m not sure what would be going wrong.

Yeah, sure.

LOCAL SCRIPT:

local remote = game.ReplicatedStorage:WaitForChild(“Remotes”):WaitForChild(“GunEvent”)
local cam = game.Workspace.CurrentCamera
local bulletsFolder = game.Workspace.bulletsFolder

remote.OnServerEvent:Connect(function(player, barrelPos, mousePos)
local ray = Ray.new(barrelPos, (mousePos - barrelPos).Unit * 50)
local part, position = game.Workspace:FindPartOnRay(ray, player.Character, false, true)

coroutine.resume(coroutine.create(function()
	local IgnoreList = {player.Character,
	workspace.Camera,
	cam
		}
		
	for i,v in pairs(workspace:GetDescendants()) do
		if (v:IsA("Accessory"))
		or (v:IsA("Tool"))
		or (v:IsA("Part")) and v.Transparency >= 1  then
			table.insert(IgnoreList,v)
		end
	end

local part, position = game.Workspace:FindPartOnRayWithIgnoreList(ray, IgnoreList)

local dist = math.clamp((barrelPos - mousePos).Magnitude, 0.05, 50)

if part then
	local humanoid = part.Parent:FindFirstChild("Humanoid")
	if humanoid then
		humanoid:TakeDamage(7)
	end
end

remote:FireAllClients(player, part, position, dist, barrelPos)

end))
end)

SERVER SCRIPT:

local remote = game.ReplicatedStorage:WaitForChild(“Remotes”):WaitForChild(“GunEvent”)
local TweenService = game:GetService(“TweenService”)
local bulletsFolder = game.Workspace.bulletsFolder

remote.OnClientEvent:Connect(function(player, part, position, dist, barrelPos)

local bullet = Instance.new("Part")
bullet.Anchored = true
bullet.CanCollide = false
bullet.BrickColor = BrickColor.new("White")
bullet.Name = "bullet"

bullet.Size = Vector3.new(0.075,0.075,dist)
bullet.Transparency = 0.95
bullet.Material = Enum.Material.Neon
bullet.CFrame = CFrame.new(barrelPos, position) * CFrame.new(0,0, -dist/2)

bullet.Parent = bulletsFolder

game.Debris:AddItem(bullet, 2)

local Info = TweenInfo.new(

1,


Enum.EasingStyle.Cubic, 

Enum.EasingDirection.Out,

0, 
false,

0 

)

local Goals =

{

Transparency = 1;

}

local tween = TweenService:Create(bullet,Info,Goals)

tween:Play()

end)

I don’t see the random spread amount in either of your scripts. Perhaps you deleted accediently while adding some more code?

Sorry here’s the updated server script. It still doesn’t work.

SERVER SCRIPT:

local remote = game.ReplicatedStorage:WaitForChild(“Remotes”):WaitForChild(“GunEvent”)
local cam = game.Workspace.CurrentCamera
local bulletsFolder = game.Workspace.bulletsFolder

remote.OnServerEvent:Connect(function(player, barrelPos, mousePos)
local ray = Ray.new(barrelPos, (mousePos - barrelPos).Unit * 50)
local part, position = game.Workspace:FindPartOnRay(ray, player.Character, false, true)
local spread_amount = 1
position = Vector3.new(position.X + (spread_amount * spread_amount * math.random()),position.Y + (spread_amount * spread_amount * math.random()),position.Z + (spread_amount * spread_amount * math.random()))

coroutine.resume(coroutine.create(function()
	local IgnoreList = {player.Character,
	workspace.Camera,
	cam,
	bulletsFolder
		}
		
	for i,v in pairs(workspace:GetDescendants()) do
		if (v:IsA("Accessory"))
		or (v:IsA("Tool"))
		or (v:IsA("Part")) and v.Transparency >= 1  then
			table.insert(IgnoreList,v)
		end
	end

local part, position = game.Workspace:FindPartOnRayWithIgnoreList(ray, IgnoreList)

local dist = math.clamp((barrelPos - mousePos).Magnitude, 0.05, 50)

if part then
	local humanoid = part.Parent:FindFirstChild("Humanoid")
	if humanoid then
		humanoid:TakeDamage(7)
	end
end

remote:FireAllClients(player, part, position, dist, barrelPos)

end))
end)

When you cast the ray for a second time with the ignore list, you over override the changes to position you made before. You should A) caculate spread after the ray is made and B) only make one raycast if your not going to use the raycast from before.

     local ray = Ray.new(barrelPos, (mousePos - barrelPos).Unit * 50)
     ---Removed the raycast which was not being used, yet you still need the ray for the later raycast
	
     coroutine.resume(coroutine.create(function()
	           local IgnoreList = {player.Character,
	           workspace.Camera,
	            cam,
	            bulletsFolder
	  }
			
     for i,v in pairs(workspace:GetDescendants()) do
			if (v:IsA("Accessory"))
			or (v:IsA("Tool"))
			or (v:IsA("Part")) and v.Transparency >= 1  then
				table.insert(IgnoreList,v)
			end
    end
	
	local part, position = game.Workspace:FindPartOnRayWithIgnoreList(ray, IgnoreList)

    --Moved the spread caculation to after the raycast, so it actually has an effect. 
	local spread_amount = 20
	position = Vector3.new(position.X + (spread_amount * spread_amount * math.random()),position.Y + (spread_amount * spread_amount * math.random()),position.Z + (spread_amount * spread_amount * math.random()))
3 Likes

Yo thank you so much! It works after you put it right before it fires the remote event.