Gun Penetration Bug

I’m trying to add bullet penetration to my gun system so it can shoot through fence gates, but for some reason when I aim over my fence gate the bullet goes in a completely random direction.

I want it so that when I aim over the fence gate the bullet goes where my mouse is pointing.

Any help is appreciated, thanks.

Video: gun penetration bug - YouTube


Also to be noted. The bug only occurs when you shoot the fence, every other part works fine.

I’m still having some trouble with this. I appreciate any help given to me.

Can you replace this part

local targetPosition = mouse.Hit.Position
local rayDistance = (equippedWeapon.Handle.Position - targetPosition).magnitude
local aimWithSpread =*rayDistance,(equippedWeaponInfo.BulletSpread/15)*rayDistance)),(targetPosition.y)+(math.random(-(equippedWeaponInfo.BulletSpread/15)*rayDistance,(equippedWeaponInfo.BulletSpread/15)*rayDistance)),(targetPosition.z)+(math.random(-(equippedWeaponInfo.BulletSpread/15)*rayDistance,(equippedWeaponInfo.BulletSpread/15)*rayDistance)))

with this

local targetPosition = mouse.Hit.Position
local rayDistance = (equippedWeapon.Handle.Position - targetPosition).magnitude/2
local spreadDistance = equippedWeaponInfo.BulletSpread/15*rayDistance
local aimWithSpread =

Yeah I definitely could. Thank you for helping me clear up my code! Though I was wondering if you knew how to fix that raycasting bug?

The problem is with the bullet spread. What my code does is take the distance of the raycast, half it then thats the spread (Your code is the same but without the halving the spread)

Here are examples:
All examples here are assuming that the bullet spread is 15 (since your formula says BulletSpread/15)

Im not good at drawing ok

As you can see from the example in the picture, the player is shooting the wall at a distance of 30 studs, and the wall size is 15. Half of 30 is 15, so the spread of the bullet is 15 which is exactly the size of the wall

The second example:

The distance of the player to the wall is 60 studs. 60 / 2 is 30 studs. But the wall is only 15 studs, so the bullet spreads outside of the wall, as you can see from the other line.

Now, from your video:

I’m assuming that the distance is 20 studs, so half is 10. Then I’m guessing your gun’s bullet spread is 3. So 3/15 * 10 is 10/5 or 2 studs of spread.

When you aim outside of the wall:

I’m guessing that the distance between you and the mouse is 50 studs, so 3/15 * 25 is 25/5 or 5 studs. But why is the spread so far? Well, you change the target position on ALL axis.

Sorry for an even worse drawing

You see in the picture, the gray is where the mouse is pointed, red is the range of the spread, and the orange is where the rng chose the spread. The yellow is the bullet, the bullet is going to where the orange is, so it went there. But since there is no wall, it continues going until it hits something (or reaches 900 studs).

If I visualize it in your video, this is what probably happened

The rng chose to spread it up then left 4 studs, so the laser goes there. But there is no wall so the laser continues until it hits something

1 Like

Try this code

local targetPosition = mouse.Hit.Position
local charPos = Character.Torso.Position
local rayDistance = (equippedWeapon.Handle.Position - targetPosition).magnitude
local spreadDistance = equippedWeaponInfo.BulletSpread/15*rayDistance^.5

local raycastParams =
raycastParams.FilterDescendantsInstances = {Character, penetrateWhitelist}

local direction = (targetPosition - charPos).Unit
local spreadRay = workspace:Raycast(Character.Torso.Position, direction * 900, raycastParams)
if spreadRay then
	local surface = CFrame.lookAt(,spreadRay.Normal)
	local spread = surface.XVector*math.random(-spreadDistance,spreadDistance) +
	direction = (targetPosition+spread-charPos).Unit
else return
1 Like

Thank you very much for this explanation! I tested out the code but it seemed to produce similar results; likely due to my poor implementation. Also in the video the gun I use has the spread value set to 0, and after testing with a traditional no spread raycasting setup still produces similar results. This is a very strange problem because when printing the directional vector it doesnt seem to change much from the different surfaces.

Here’s an updated version of my code which doesn’t use the aim with spread function

Oh, I think (hopefully) I see the problem. So I noticed a problem in your code, and I believe that is what is causing the issue.

local origin = overrideOrigin or equippedWeapon:FindFirstChild(equippedWeaponInfo.BulletOrigin).Position
local raycastResult = nil or raycast(targetPosition, origin, penetrateWhitelist)

As you can see from your code, origin is equal to the BulletOrigin, meaning that is where the bullet spawns. Then you raycast with the origin. But in the raycast function

return workspace:Raycast(Character.Torso.Position, (aimNoSpread - Character.Torso.CFrame.p).unit * 900, raycastParams)

You never used the origin, instead you used the position of the character. What happens is that you take the direction starting from your torso, to the targetPosition, then use that direction to move the laser.
In this image, they look different direction, but its just because of the camera angle. In my theory, they should be the same direction.

Heres my terrible drawing again.


The red cross is where the mouse is pointing, or the targetPosition. So the code raycasts from the character, takes the direction, then uses it on the BulletOrigin

HOWEVER, this part of the code literally contradicts my direction theory

local hitPosition = raycastResult.Position
local visualRay = visualizeRay(hitPosition, nil, origin)

It casts a visual ray from the hitPosition, to the origin which is the BulletOrigin. Meaning, it should have visualized a ray from BulletOrigin to where the mouse is pointed. In your video, it wasn’t doing it (unless it has different result from the current script).

But just to test, can try this fix for the raycast function. I used the origin parameter of the function insted of the Torso.Position

function raycast(targetPosition, origin, ignoreList)
	local rayDistance = (origin - targetPosition).magnitude
	local spreadDistance = equippedWeaponInfo.BulletSpread/15*rayDistance^.5
	local aimWithSpread =
	local aimNoSpread = origin + (targetPosition - origin).Unit*900 -- aim no spread
	local raycastParams =
	raycastParams.FilterDescendantsInstances = {Character, ignoreList, visualizeVector(targetPosition, 0.1)}
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	print((aimNoSpread - origin).unit * 900)
	return workspace:Raycast(origin, (aimNoSpread - origin).unit * 900, raycastParams)
1 Like

Thank you so much for the help! That seems to more or less solve my problem. It does introduce a bug that lets players shoot through walls but I’m sure I can fix that myself.

By the way, I was wondering if it is possible to have a mouse position ignore the part as well. Like add penetrable parts to the mouse raycasts ignore list so that there aren’t any directional vector problems close range. close range gun problem - YouTube

Well, you would make a custom hit detection. It’s pretty simple, but it only ignores one part

mouse.Target = BasePart

If you need multiple parts to ignore, then:

local mouseParams =
mouseParams.FilterType = Enum.RaycastFilterType.Whitelist -- Whitelist only checks for parts in the table, Blacklist checks every part except the ones in the table
mouseParams.FilterDescendantsInstances = {}

local function MouseHit():{Hit:Vector3,Target:BasePart?,Distance:number}
	local origin = mouse.Origin
	local ray = workspace:Raycast(origin.p,origin.LookVector*1000,mouseParams) -- 1000 is the max distance according to roblox
	if ray then
		return {Hit = ray.Position, Target = ray.Instance, Distance = ray.Distance}
		return {Hit = origin.p+origin.LookVector*1000, Target = nil, Distance = 100}
1 Like

Quick question, what exactly is this MouseHit():{Hit:Vector3,Target:BasePart?,Distance:number} ? I have never seen this type of function before.

local function MouseHit()

is where you declare the function


is for type checking, and autocomplete. Without that, it may not autocomplete.

Oh, that function is a custom function I made.

1 Like