Raycast random spread for gun

I’ve already got a functioning automatic gun. I now just want it to have a bit of a random spread. I did find a similar thread with a very similar question but the solution wasn’t really what I was looking for. I want the spread to be like a cone shape and change depending on how close/far you are from whatever you’re shooting.

This is the current code (using the solution I found on another thread).

local function RayCast(Position, Direction, MaxDistance, IgnoreList)
	local spreadAngle = 1
	ray = Ray.new(Position, (Direction.unit * MaxDistance), IgnoreList)
	local spreadRay = Ray.new(ray.Origin, CFrame.Angles(math.rad(math.random(-spreadAngle, spreadAngle)), math.rad(math.random(-spreadAngle, spreadAngle)), math.rad(math.random(-spreadAngle, spreadAngle))) * ray.Direction)
	return game.Workspace:FindPartOnRayWithIgnoreList(spreadRay, IgnoreList)
end

This code as is does work, but like I said before I don’t like the result:

As you can see: the bullets do spread but there’s only 9 positions that slowly spread apart as you’re further away, and it’s in a square. Overall this is better than nothing, but I’m looking for something better.

3 Likes

The reason why the spread angle forms a box is because of the way math.random works with -1 and 1: you’ll only ever get -1, 0, or 1, and never any float values.

In your sample, you call CFrame.Angles. Consider creating the random values like this:

CFrame.Angles(
    math.rad(spreadAngle * (math.random() * 2 - 1)),
    math.rad(spreadAngle * (math.random() * 2 - 1)),
    math.rad(spreadAngle * (math.random() * 2 - 1))
)

math.random() returns a value from 0 to 1 when called without arguments. math.random() * 2 - 1 gives a floating value from -1 to 1, instead of an integer. This will give you some shots landing between your 9 in your screenshot.

If you wanted to do a conic-type spread, this is more involved and requires some trigonometry. Try out that simple solution and see if it works for your game first.

1 Like

Thanks for the reply. I got what you suggested working well, and the new code is like this:

local function RayCast(Position, Direction, MaxDistance, IgnoreList)
	local spreadAngle = .5
	local ray = Ray.new(Position, (Direction.unit * MaxDistance), IgnoreList)
	local spreadRay = Ray.new(ray.Origin,
		CFrame.Angles(
    	math.rad(spreadAngle * (math.random() * 2 - 1)),
    	math.rad(spreadAngle * (math.random() * 2 - 1)),
    	math.rad(spreadAngle * (math.random() * 2 - 1)))* ray.Direction)
	return game.Workspace:FindPartOnRayWithIgnoreList(spreadRay, IgnoreList)
end

I’ve got no idea how to turn it into a cone tbh, but this solution is pretty good too even by itself.

1 Like

If you’re choosing the X and Y coordinates independently, it’s always going to be in the shape of a box. If you want it to be in a cone/circle, the two coordinates are dependent. If the X has been chosen to be to the left, then there’s a limit to how low/high the Y coordinate can be.

You can choose a random point inside a circle, and use that in your RayCast function, like this:

local random = Random.new()

local function randomPointInCircle( radius )
	local angle = random:NextNumber(0, math.pi*2)
	return Vector2.new(
		math.cos(angle),
		math.sin(angle)
	)
end

local function RayCast(Position, Direction, MaxDistance, IgnoreList)
	local maxSpreadAngle = .5
	local spreadAngles = randomPointInCircle( math.rad(maxSpreadAngle) ) * random:NextNumber(0, 1)
	local spreadRay = Ray.new(Position,
		CFrame.Angles(
    		spreadAngles.X,
    		spreadAngles.Y,
    		0 --Why rotate the ray around the Z axis?
		) * ray.Direction
	)
	return game.Workspace:FindPartOnRayWithIgnoreList(spreadRay, IgnoreList)
end

print(randomPointInCircle(1))
3 Likes

Using your code as is seems to give me the error “Vector3 expected, got CFrame” on the spreadRay line. I’m probably just missing something, but I can’t figure out how to fix it. I think I got the same earlier from the code ozzypig suggested but adding the little *ray.Direction on the end fixed it. Unfortunately that doesn’t seem to do it this time :frowning:

Whoops. I think I fixed it now :+1:

Thanks. There is one more problem on the

local angle = random:NextNumber(math.pi*2)

“NextNumber requires 0 or 2 arguments”

I’m not familiar with using that but I tried it without anything inside and adding a random number to go with it but both seem to just make bullets go all over the place lol

Lol guess I should have tested it before posting :stuck_out_tongue: Fixed it (hopefully)

Well it seems no matter what I put in the maxSpreadAngle .001 to 1000+ the bullets just kinda seem to go in the general direction the player is looking maybe as if the radius is way bigger then it should be? idk sorry for all the trouble

+1 for using the builtin Random object :slight_smile:

2 Likes