The RaycastParams blacklist doesn't want to change?

So I want to create a sniper rifle that can shoot through enemies, but the way I am trying to do it doesn’t work. After some investigation, the Blacklist table actually does have instances added, but it seems like the raycastparam itself does not

local tool = script.Parent
local damage = tool:GetAttribute('Damage')

script.Parent.Shoot.OnServerEvent:Connect(function(player, mousePosition)
	local penetrationAttempts = tool:GetAttribute('Penetration')
	local Blacklist = {player.Character}

	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	raycastParams.FilterDescendantsInstances = Blacklist

	local raycastOrigin = tool.Raycast.Position
	local rayResult = game.Workspace:Raycast(raycastOrigin, (mousePosition - raycastOrigin) * 200, raycastParams)

	if rayResult then
		while penetrationAttempts > 0 do
			penetrationAttempts -= 1	
			local Enemy = rayResult.Instance.Parent

			if Enemy:FindFirstChildOfClass('Humanoid') then
				Enemy.Humanoid.Health -= damage
				table.insert(Blacklist, Enemy)
			end
			wait(2)

			raycastParams.FilterDescendantsInstances = Blacklist
			local raycastOrigin = rayResult.Position

			local rayResult = game.Workspace:Raycast(raycastOrigin, (mousePosition - raycastOrigin) * (penetrationAttempts + 1)/0.05, raycastParams)
		end
	end
end)

Is there any way I can fix that?

1 Like

I assume it’s because the character is a group, and not a part. Try looping through a character and inserting every BasePart to the array instead.

Same thing happens. I also noticed that local raycastOrigin = rayResult.Position in game.Workspace:Raycast(raycastOrigin, ...) doesn’t actually take the new result position and just takes old origin, that might be somehow connected

1 Like

You need to re-position the place you raycast the ‘penetration raycast’ from. Here’s an example I made up for a simple wall-bang system;

Here you can see some parts, one being labelled A and the others being labelled E. The parts labelled E are simply just some enemies. And A is simply just the place you shoot from.

You can also see the ‘lines’ which are either connected ‘E-E’ ( enemy to enemy ) or ‘A-E’ ( origin to enemy ). These lines are visualized raycasts.

The code is rather lengthy to get into so I’ll just provide you with the code, and maybe you’ll learn some things:

 -- variables
local a: Part = workspace.A

-- functions
local function VisualizeRaycast(raycastResult: RaycastResult, originVector: Vector3)
	local distance: number = (originVector - raycastResult.Position).Magnitude
	local thickness: number = 0.1
	
	local linearLine: Part = Instance.new("Part")
	linearLine.Anchored = true
	linearLine.CanCollide = false
	linearLine.Size = Vector3.new(thickness, thickness, distance)
	linearLine.CFrame = CFrame.lookAt(originVector, raycastResult.Position) * CFrame.new(0, 0, -(distance / 2))
	linearLine.Color = Color3.fromRGB(math.random(0, 255), math.random(0, 255), math.random(0, 255))
	linearLine.Parent = workspace
	
	return linearLine
end

local function Shoot(origin: Vector3, direction: Vector3, penetrations: number)
	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	raycastParams.FilterDescendantsInstances = {a} -- you can add whatever else you want to the blacklist
	
	local raycastResult: RaycastResult = workspace:Raycast(origin, direction, raycastParams)
	
	if (raycastResult) then
		local enemy: Part = raycastResult.Instance -- we have to remember that this is the first enemy we hit
		
		VisualizeRaycast(raycastResult, origin)
		
		-- we'll use this 'currentOrigin' variable to make sure the raycast(s) are being shot properly at each position.
		local currentOrigin: Vector3 = raycastResult.Position
		
		while (penetrations > 0) do
			penetrations -= 1
			
			local newRaycastResult: RaycastResult = workspace:Raycast(currentOrigin, direction, raycastParams)
			
			if (newRaycastResult) then
				local anotherEnemy: Part = newRaycastResult.Instance -- this is the enemy which got hit through the wall or whatever you wall banged it from
				
				VisualizeRaycast(newRaycastResult, currentOrigin)
				
				-- set the origin raycast position for the next raycast to use.
				currentOrigin = newRaycastResult.Position
			end
		end
	end
end

-- whenever you shoot the gun, you'd call this function:
Shoot(a.Position, a.CFrame.LookVector * 100, 1)

The function ‘Shoot’ is just the function you use every time you shoot the gun. It takes in three arguments; ‘Origin Position’ which is a Vector3 type, ‘Direction’ which is a Vector3 type, ‘Penetrations’ which is how many times the function will iterate over raycasts ( similar to your system ).

Remember that the direction must be correct otherwise your gun won’t shoot in the correct direction.

Just a reminder this ‘system’ is super bare bones and nothing too insane, but maybe you’ll get something out of it.

1 Like

But what was I doing wrong? I was taking the new origin too. Also it seems like the penetration goes on only once. I mean that once I hit first enemy, the ray hits second one but doesn’t continue though I have like 5 penetration. Also it seems like the hit doesn’t register sometimes

1 Like

Nevermind… my models were kinda wacky lol. But I still don’t understand what I was doing wrong

1 Like

You never ‘re-set’ the origin position in the code you provided above. And the reason it penetrates once is because you need to set the ‘penetrate amount’ in the Shoot function as such:

Shoot(a.Position, a.CFrame.LookVector * 100, 8)

local raycastOrigin = rayResult.Position - I do that right there
as I said the problem was with my models. But apparently, what this script considers a ‘penetration’ is penetrating a single part while I have models, and I need to ignore whole model after hit

1 Like

You’re not actually re-setting it, you’re just creating a new variable with the same value. And also the reason your blacklist isn’t working is because you’re not creating a new RaycastParams. I edited your script, try this:

local tool = script.Parent
local damage = tool:GetAttribute('Damage')

script.Parent.Shoot.OnServerEvent:Connect(function(player, mousePosition)
	local penetrationAttempts = tool:GetAttribute('Penetration')
	
	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	raycastParams.FilterDescendantsInstances = {player.Character}

	local raycastOrigin = tool.Raycast.Position
	local rayResult = game.Workspace:Raycast(raycastOrigin, (mousePosition - raycastOrigin) * 200, raycastParams)

	if rayResult then
		local raycastOrigin = rayResult.Position
		
		while penetrationAttempts > 0 do
			penetrationAttempts -= 1
			
			local Enemy = rayResult.Instance.Parent
			
			local newRaycastParams = RaycastParams.new()
			newRaycastParams.FilterType = Enum.RaycastFilterType.Blacklist
			newRaycastParams.FilterDescendantsInstances = {player.Character, Enemy}
			
			if Enemy:FindFirstChildOfClass('Humanoid') then
				Enemy.Humanoid.Health -= damage
			end
			
			wait(2)
			
			local newRayResult = game.Workspace:Raycast(raycastOrigin, (mousePosition - raycastOrigin) * (penetrationAttempts + 1)/0.05, newRaycastParams)
			
			if (newRayResult) then
				raycastOrigin = newRayResult.Position
			else
				-- we're going to break out because no enemy was found
				
				break
			end
		end
	end
end)
1 Like

Hmm, I will definitely try that later. I got to go now

1 Like

Hey! That almost works perfectly fine. One thing tho is that when you click on first dummy, the ray doesn’t go further through other dummies behind it, but when I try to hit the last dummy, it penetrates all of the dummies (yeah it penetrates the amount I want). I think that’s because we use a mouse position

Alright so I changed the code a little bit, but one problem stays unfixed. It’s described in the video. Basically you can’t really penetrate unless you click on an object behind another one

local tool = script.Parent
local debris = game:GetService('Debris')

local function VisualizeRaycast(raycastResult: RaycastResult, originVector: Vector3)
	local distance: number = (originVector - raycastResult.Position).Magnitude
	local thickness: number = 0.1

	local linearLine: Part = Instance.new("Part")
	linearLine.Anchored = true
	linearLine.CanCollide = false
	linearLine.Size = Vector3.new(thickness, thickness, distance)
	linearLine.CFrame = CFrame.lookAt(originVector, raycastResult.Position) * CFrame.new(0, 0, -(distance / 2))
	linearLine.Color = Color3.fromRGB(math.random(0, 255), math.random(0, 255), math.random(0, 255))
	linearLine.Parent = workspace
	debris:AddItem(linearLine, 1)

	return linearLine
end

script.Parent.Shoot.OnServerEvent:Connect(function(player, mousePosition)
	local penetrationAttempts = tool:GetAttribute('Penetration')
	local damage = tool:GetAttribute('Damage')
	local Enemy = nil
	local rOrigin = tool.Raycast.Position
	
	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist

	while penetrationAttempts > 0 do
		wait(0.1)
		penetrationAttempts -= 1
		
		raycastParams.FilterDescendantsInstances = {player.Character, Enemy}
		
		local rayResult = game.Workspace:Raycast(rOrigin, (mousePosition - rOrigin) * 200, raycastParams)
		
		if rayResult then
			VisualizeRaycast(rayResult, rOrigin)
			
			Enemy = rayResult.Instance.Parent
			rOrigin = rayResult.Position

			if Enemy:FindFirstChildOfClass('Humanoid') then
				Enemy.Humanoid.Health -= damage / (tool:GetAttribute('Penetration') - penetrationAttempts)
			else
				break
			end
		end
	end
end)

If I understood correctly, your script uses the penetrationAttempts variable to control the number of times the bullet will penetrate objects before stopping.

However, the script does not take into account the thickness or type of materials that the objects are made of. Therefore, the bullet may stop prematurely if it hits a thick or dense object.

One possible solution to this issue is to modify the script to calculate the remaining penetration power of the bullet based on the thickness and type of material of the objects it hits. This could be achieved by using a function that calculates the remaining penetration power based on the bullet’s velocity, the thickness, and other factors.

Yeah that is a good way to make a shooting system, but the problem is that it doesn’t work at all. I want that when I hit first dummy it should go through others too, but it only does it when I click on a dummy behind it

It could be due to how you’re handling the Enemy variable. In your current code, you’re setting Enemy to nil at the beginning of the function, and then adding it to the FilterDescendantsInstances of the raycastParams object. However, at this point Enemy is still nil, so the filter is effectively not doing anything.

Instead, you can set Enemy to game.Workspace at the beginning of the function, which will allow the raycast to hit any object in the workspace. Then, after you hit an object, you can set Enemy to the parent of that object, which will allow the raycast to hit any other objects in the same parent.

local tool = script.Parent
local debris = game:GetService('Debris')

local function VisualizeRaycast(raycastResult: RaycastResult, originVector: Vector3)
	local distance: number = (originVector - raycastResult.Position).Magnitude
	local thickness: number = 0.1

	local linearLine: Part = Instance.new("Part")
	linearLine.Anchored = true
	linearLine.CanCollide = false
	linearLine.Size = Vector3.new(thickness, thickness, distance)
	linearLine.CFrame = CFrame.lookAt(originVector, raycastResult.Position) * CFrame.new(0, 0, -(distance / 2))
	linearLine.Color = Color3.fromRGB(math.random(0, 255), math.random(0, 255), math.random(0, 255))
	linearLine.Parent = workspace
	debris:AddItem(linearLine, 1)

	return linearLine
end

script.Parent.Shoot.OnServerEvent:Connect(function(player, mousePosition)
	local penetrationAttempts = tool:GetAttribute('Penetration')
	local damage = tool:GetAttribute('Damage')
	local Enemy = game.Workspace -- set to workspace to hit any object in the workspace
	local rOrigin = tool.Raycast.Position

	while penetrationAttempts > 0 do
		wait(0.1)
		penetrationAttempts -= 1
		
		local raycastParams = RaycastParams.new()
		raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
		raycastParams.FilterDescendantsInstances = {player.Character, Enemy}
		
		local rayResult = game.Workspace:Raycast(rOrigin, (mousePosition - rOrigin) * 200, raycastParams)
		
		if rayResult then
			VisualizeRaycast(rayResult, rOrigin)
			
			Enemy = rayResult.Instance.Parent -- set to parent of hit object to hit any other objects in the same parent
			rOrigin = rayResult.Position

			if Enemy:FindFirstChildOfClass('Humanoid') then
				Enemy.Humanoid.Health -= damage
			else
				break
			end
		end
	end
end)

Let me know if this works!

Well my filter type is set to blacklist so pretty much my gun stopped shooting at all lol. My idea is that when the ray hits first enemy, the enemy goes to blacklist so it doesn’t get interacted by ray anymore because it has already been. When I click first time in the video the ray stops on the first dummy (it should go on), but when I click on dummies behind the first one everything works as I wanted to

In that case, you can try changing the FilterType of the raycastParams to Enum.RaycastFilterType.Whitelist , and instead of adding the enemies to the FilterDescendantsInstances blacklist, add them to a FilterDescendants whitelist.

1 Like
local tool = script.Parent
local debris = game:GetService('Debris')

local function VisualizeRaycast(raycastResult: RaycastResult, originVector: Vector3)
	local distance: number = (originVector - raycastResult.Position).Magnitude
	local thickness: number = 0.1

	local linearLine: Part = Instance.new("Part")
	linearLine.Anchored = true
	linearLine.CanCollide = false
	linearLine.Size = Vector3.new(thickness, thickness, distance)
	linearLine.CFrame = CFrame.lookAt(originVector, raycastResult.Position) * CFrame.new(0, 0, -(distance / 2))
	linearLine.Color = Color3.fromRGB(math.random(0, 255), math.random(0, 255), math.random(0, 255))
	linearLine.Parent = workspace
	debris:AddItem(linearLine, 1)

	return linearLine
end

script.Parent.Shoot.OnServerEvent:Connect(function(player, mousePosition)
	local penetrationAttempts = tool:GetAttribute('Penetration')
	local damage = tool:GetAttribute('Damage')
	local Enemy = nil
	local rOrigin = tool.Raycast.Position
	
	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	raycastParams.FilterDescendantsInstances = {player.Character}

	local rayResult = game.Workspace:Raycast(rOrigin, (mousePosition - rOrigin) * 200, raycastParams)
	
	if rayResult then
		VisualizeRaycast(rayResult, rOrigin)
		
		raycastParams.FilterType = Enum.RaycastFilterType.Whitelist
		
		while penetrationAttempts > 0 do
			wait(0.1)
			penetrationAttempts -= 1

			raycastParams.FilterDescendantsInstances = {Enemy}

			rayResult = game.Workspace:Raycast(rOrigin, (mousePosition - rOrigin) * 200, raycastParams)

			if rayResult then
				VisualizeRaycast(rayResult, rOrigin)

				Enemy = rayResult.Instance.Parent
				rOrigin = rayResult.Position

				if Enemy:FindFirstChildOfClass('Humanoid') then
					Enemy.Humanoid.Health -= damage
				else
					break
				end
			end
		end
	end
end)

Now it doesn’t penetrate anything

Oops, I spot my error, one second-

local tool = script.Parent
local debris = game:GetService('Debris')

local function VisualizeRaycast(raycastResult: RaycastResult, originVector: Vector3)
	local distance: number = (originVector - raycastResult.Position).Magnitude
	local thickness: number = 0.1

	local linearLine: Part = Instance.new("Part")
	linearLine.Anchored = true
	linearLine.CanCollide = false
	linearLine.Size = Vector3.new(thickness, thickness, distance)
	linearLine.CFrame = CFrame.lookAt(originVector, raycastResult.Position) * CFrame.new(0, 0, -(distance / 2))
	linearLine.Color = Color3.fromRGB(math.random(0, 255), math.random(0, 255), math.random(0, 255))
	linearLine.Parent = workspace
	debris:AddItem(linearLine, 1)

	return linearLine
end

script.Parent.Shoot.OnServerEvent:Connect(function(player, mousePosition)
	local penetrationAttempts = tool:GetAttribute('Penetration')
	local damage = tool:GetAttribute('Damage')
	local Enemy = nil
	local rOrigin = tool.Raycast.Position
	
	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	raycastParams.FilterDescendantsInstances = {player.Character}

	local rayResult = game.Workspace:Raycast(rOrigin, (mousePosition - rOrigin) * 200, raycastParams)
	
	if rayResult then
		VisualizeRaycast(rayResult, rOrigin)
		
		raycastParams.FilterType = Enum.RaycastFilterType.Whitelist
		
		while penetrationAttempts > 0 do
			task.wait(0.1)
			penetrationAttempts -= 1

			raycastParams.FilterDescendantsInstances = {Enemy, player.Character}

			rayResult = game.Workspace:Raycast(rOrigin, (mousePosition - rOrigin) * 200, raycastParams)

			if rayResult then
				VisualizeRaycast(rayResult, rOrigin)

				Enemy = rayResult.Instance.Parent
				rOrigin = rayResult.Position

				if Enemy:FindFirstChildOfClass('Humanoid') then
					Enemy.Humanoid.Health -= damage
				end
			else
				break
			end
		end
	end
end)

The main difference is that the raycastParams.FilterDescendantsInstances is now set to include both the Enemy and player.Character instances in the whitelist. This allows the ray to pass through the Enemy instances, while still being blocked by the player’s own character and other objects.

I also removed the else clause that was previously breaking the loop if the Enemy instance did not have a Humanoid. This is because I assume you want the loop to continue even if the hit object is not a Humanoid, as we may still want to penetrate through it to hit other objects behind it.

Let me know if this works better for you!

I don’t quite think that’s how it works. It not only doesn’t damage dummies now. I can kill myself instead xd