Ideal way to make shotgun spread?

I want to make a nice, roughly circular shotgun spread.

Here is my current code, but it doesn’t make it very circular at all, and sometimes has really whack results (like often its all in a vertical line)

function GetSpreadOnAxis()
	if not GunData.Spread then
		GunData.Spread = 0 -- No inaccuracy
	end

	return 1 + Lerp(0 - (GunData.Spread / 10), 0 + (GunData.Spread / 10), math.random())
end

local ray = Ray.new(
		Character.Head.CFrame.p, 
		((InitPosition - Character.Head.CFrame.p) * Vector3.new(GetSpreadOnAxis(), GetSpreadOnAxis(), GetSpreadOnAxis())).unit * 400
		--(InitPosition - Character.Head.CFrame.p).unit * 400 * Vector3.new( GetSpreadOnAxis(), GetSpreadOnAxis(), GetSpreadOnAxis())
	)

Help would be appreciated in any capacity! If the answer is mathematical, please explain it, I’m interested in learning

2 Likes

If anyone has any information on how any other games do it too actually, that’d be fantastic

2 Likes
  1. I would use something like this article as some kind of reference.
    http://wiki.roblox.com/index.php?title=Circle
  2. I would use a temporary part to get “max spread” per angle.
  3. Do the lerp thing you do, randomize offset a bit - but not too much.

So basically, you would create a circle as a vector reference and tweak it a bit.
If it’s required, 2 circles would do it.

And since you tweaked it, it won’t be a perfect circle. it will resemble a “splat”.

1 Like

So based on what you said I’ve come up with this so far

local NewRay = Ray.new(rayInit.Origin, 1000 * rayInit.Direction.unit) 
	local _, InitPosition = workspace:FindPartOnRayWithIgnoreList(NewRay, IgnoreList) 
	
	local BaseRay = Ray.new(Character.Head.CFrame.p, (InitPosition - Character.Head.CFrame.p).unit * 400)
	local _, BasePosition = workspace:FindPartOnRayWithIgnoreList(BaseRay, IgnoreList)
	
	for i = 1, Shots do
		local OffsetDegrees = (360 / Shots) * i
		 
		local NewRayTarget = CFrame.new(BasePosition) * CFrame.Angles(math.rad(OffsetDegrees), 0, 0) * CFrame.new(0, 0, 10)
		local FinalRay = Ray.new(Character.Head.CFrame.p, (NewRayTarget.p - Character.Head.CFrame.p).unit * 400)
		local HitPart, HitPosition = workspace:FindPartOnRayWithIgnoreList(FinalRay, IgnoreList)
		
		local Part = Instance.new("Part")
		Part.Parent = workspace.BulletFilter
		Part.Size = Vector3.new(1, 1, 1)
		Part.CFrame = CFrame.new(HitPosition)
		Part.Anchored = true
	end	

I still however get very peculiar behaviour, where it is sometimes in a vertical line?

This might be a garbage idea, but if you want something that is not super accurate, but fairly simple, you could have an invisible ‘cone’ shape for the shotgun damage area, and just flash that part into the world when the gun is fired, and any collisions with players are put into a table and the cone is removed. Then for each player, fire one ray, to make sure they are not behind a wall, or behind each other, etc…, removing them from the list if they are not hit by the ray. Then, loop through the targets in order from closest to the shooter to farthest, and give a random percentage of the damage, based on how close the target is to the gun source., For instance, suppose there is a total of 30 pts of damage that can be done, like 30 pieces of buck shot. if someone is half way through the cone, they may have a chance to get hit by 60% of that 30 pts, lets say they take 10 pts of damage, well, that only leaves 20 more points left for farther away targets, but at a lesser chance of hitting, due to their distance. Sorry if this doenst make much sense, the idea just sort of jumped out at me. :stuck_out_tongue:

(Direction.Unit*5 + Vector3.new(math.random()-.5,math.random()-.5,math.random()-.5)).Unit*Distance

1 Like

This is what I use for my shotgun spread. It gives a circular pattern, although it is more dense around the centre. This is an intended feature for mine, so most pellets go where you’re aiming, but you can mess around with it yourself.

There are more efficient ways to calculate it, but I was just throwing it together and it’s not like this is running often lol.

“dir” is the initial aiming vector, and “spread” is the maximum spread size in radians.

local ang = CFrame.Angles(0,0,math.pi * 2 * math.random()) * CFrame.Angles(math.random() * spread,0,0)

dir = (CFrame.new(Vector3.new(),dir) * ang).lookVector
15 Likes