Help with spread in raycasting

gun.Activated:Connect(function()
	local target = mouse.Target
	local targetSurface = mouse.TargetSurface
	local hit = mouse.Hit

	local start = handle.CFrame.p
	local lookAt = hit.p
	local ray = Ray.new(start, (lookAt - start).unit*300)
	
	print(target.Name)
	
	local partOnRay = game.Workspace:FindPartOnRay(ray, character)
	wait(0.2)

	local targetCharacter = target.Parent
	if target.Name == 'Handle' then
		targetCharacter = target.Parent.Parent
	end
	
	local targetHumanoid = targetCharacter:FindFirstChild('Humanoid')
	if targetHumanoid then
		-- do damage
	else
		shoot:FireServer(player.TeamColor.Color, hit, target, targetSurface)
	end
end)

I’m currently making a gun, using raycasting (because I don’t know how else to) but I’m having problems with it not spreading the bullet shot. Atm the bullet will just go wherever you have clicked. I want to incorporate a ‘spread’ so the further away from the target you are, the greater spread. So shooting something in front of you would hit the spot you clicked, but shooting from a distance would give a random spread. Any information on this would be great! Or if there’s a better way of achieving gun projectiles!

Hmmm, well, I think we’ll want to first determine (randomly) which direction to offset the projectile’s course, and then determine the magnitude of the offset based on the magnitude of the direction (we’ll make it a unit direction for simplicity) and some predefined scale (possibly tuned to the gun that is being fired?). I’m imagining something like this:

local cos = math.cos
local sin = math.sin
local rnd = math.random

local up = Vector3.new(0, 1, 0)
local spread = 1/100 -- +-1 stud precision per 100 studs distance

local function newDir(dir)
  dir = dir.Unit

  local angle = rnd() * math.pi * 2
  local axisA = up:Cross(dir)
  local axisB = dir:Cross(axisA)

  return dir + spread * (cos(angle) * axisA + sin(angle) * axisB)
end

I’d integrate it into your existing code something like this:

local start = handle.CFrame.p
local lookAt = hit.p
local ray = Ray.new(start, 300 * newDir(lookAt - start))
3 Likes

Doesn’t seem to be working. I changed the spread to be ‘1/2’ so I could see it better (because if its 2 studs away, then if it’s like 10 away I should be able to see it be quite a distance away from the mouse. But it stills just lands where the mouse has clicked

local up = Vector3.new(0, 1, 0)
local spread = 10/1

local function newDirection(direction)
	direction = direction.Unit

	local angle = math.random()*math.pi * 2
	local axisA = up:Cross(direction)
	local axisB = direction:Cross(axisA)
	
	return direction + spread*(math.cos(angle)*axisA + math.sin(angle)*axisB)
end

EDIT even with an exaggerated number like 10/1 (move 10 studs away from click for every stud in distance) it still hits the exact spot i clicked from at least 50+ studs

Well, here are some questions for you then:

  1. Is the direction changing? By how much?
  2. When it say it just lands where the mouse has clicked, you mean the ray just hits where the mouse has clicked? Are you setting a part’s CFrame to the hit position of the raycast (if something was hit)?
  3. Could I see how you hooked up my code?

I’ll show you everything I’ve got: LocalScript

local up = Vector3.new(0, 1, 0)
local spread = 10/1

local function newDirection(direction)
	direction = direction.Unit

	local angle = math.random()*math.pi*2
	local axisA = up:Cross(direction)
	local axisB = direction:Cross(axisA)
	
	return direction + spread*(math.cos(angle)*axisA + math.sin(angle)*axisB)
end

gun.Activated:Connect(function()
	if configuration.RELOADING then return end
	if configuration.ACTIVATED then return end

	configuration.ACTIVATED = true

	local target = mouse.Target
	local targetSurface = mouse.TargetSurface
	local hit = mouse.Hit

	if not target then 
		configuration.ACTIVATED = false 
		return 
	end
	
	local start = handle.CFrame.p
	local lookAt = hit.p
	local ray = Ray.new(start, 300 * newDirection(lookAt - start))
		
	local partOnRay = game.Workspace:FindPartOnRay(ray, character)
	wait(0.2)

	local targetCharacter = target.Parent
	if target.Name == 'Handle' then
		targetCharacter = target.Parent.Parent
	end
	
	local targetHumanoid = targetCharacter:FindFirstChild('Humanoid')
	if targetHumanoid then
		-- do damage
	else
		shoot:FireServer(player.TeamColor.Color, hit, target, targetSurface)
	end
	configuration.ACTIVATED = false
end)

Server

local function weld(part1, part2)
	local weld = Instance.new('Weld', part1)
	weld.C0 = part1.CFrame:inverse()*part2.CFrame
	weld.Part0 = part1
	weld.Part1 = part2
	return weld
end

shoot.OnServerEvent:connect(function(player, color, hit, target, targetSurface)
	local bullet = Instance.new('Part')
	bullet.CFrame = CFrame.new(hit.p)*CFrame.Angles(target.CFrame:toEulerAnglesXYZ())
	bullet.Name = 'Bullet'
	bullet.Anchored = false
	bullet.CanCollide = false
	bullet.Transparency = 0.5
	
	bullet.TopSurface = Enum.SurfaceType.SmoothNoOutlines
	bullet.BottomSurface = Enum.SurfaceType.SmoothNoOutlines	
	bullet.RightSurface = Enum.SurfaceType.SmoothNoOutlines
	bullet.LeftSurface = Enum.SurfaceType.SmoothNoOutlines	
	bullet.FrontSurface = Enum.SurfaceType.SmoothNoOutlines
	bullet.BackSurface = Enum.SurfaceType.SmoothNoOutlines
	
	local x = 2
	local y = 2
	local z = 2
	if targetSurface == Enum.NormalId.Back or targetSurface == Enum.NormalId.Front then
		z = 0.1
	elseif targetSurface == Enum.NormalId.Right or targetSurface == Enum.NormalId.Left then
		x = 0.1
	elseif targetSurface == Enum.NormalId.Top or targetSurface == Enum.NormalId.Bottom then
		y = 0.1
	end
	
	bullet.Size = Vector3.new(x, y, z)
	bullet.BrickColor = BrickColor.new(color)
	debris:AddItem(bullet, 10)
	bullet.Parent = projectiles
	weld(bullet, target)
end)

I’ve kinda cut out the variables, etc. Just waste of lines for you to read. Basically it creates a part where you have shot, so that’s how I know where the ‘bullet’ has landed basically. I’m not sure if something inside the server script might be ignoring what you’ve done?

EDIT Ok going over it, I see the fireserver event is firing the hit, which is basically where the mouse has clicked, so that’s probably why. How would I go about integrating the ‘actual spot’ it should be hitting?

As it turns out, the workspace:FindPartOnRay() method returns a tuple of three values:

  1. The part hit
  2. The point of intersection
  3. The surface normal at the point of intersection on the part

Which mean that we can do something like this:

local hitPart, hitPoint = workspace:FindPartOnRay(ray, character)

But note that if no part was hit, then the hitPoint shouldn’t be used (I forget what value it actually gets set too though)

How would I go about intergrating it into my code?

Well, I’d change this section like so:

if configuration.RELOADING then return end
if configuration.ACTIVATED then return end

configuration.ACTIVATED = true

local start = handle.CFrame.p
local lookAt = mouse.hit.p
local direction = 300 * newDirection(lookAt - start)
local ray = Ray.new(start, direction)

local target, hit = game.Workspace:FindPartOnRay(ray, character)

if not target then
  configuration.ACTIVATED = false
  return
end
...

And in the server script just be aware that hit is now a Vector3 instead of a CFrame, so use hit instead of hit.p

5 Likes

Works now :smiley: thank you!!! :smiley:

1 Like