Efficient way to make a wallbang system?

Hello, I am looking into a way to create a WallBang system,

Currently I have the code below and it does work.

My question is, in a large server, with people constantly shooting. Would such a system create noticeable lag?

The incremental moving of the one part in my code I think would slow down the game if done many times over, however I am not certain.

Here is the code:

local mouse = game.Players.LocalPlayer:GetMouse()

local parmas = RaycastParams.new()
parmas.FilterType = Enum.RaycastFilterType.Exclude
parmas.FilterDescendantsInstances = {game.Players.LocalPlayer.Character}

local char = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()

function visualizeRay(origin, result ,position, colour)

	local distance = (origin - result.Position).Magnitude
	local p = Instance.new("Part", workspace.Visualizers)
	p.Name = 'Visualizer'
	p.Anchored = true
	p.CanCollide = false
	p.BrickColor = colour
	p.Size = Vector3.new(0.1, 0.1, distance)
	p.CFrame = CFrame.lookAt(origin, position)*CFrame.new(0, 0, -distance/2)

end

mouse.Button1Down:Connect(function()
	local screenRay = game.Workspace.CurrentCamera:ScreenPointToRay(mouse.X, mouse.Y)
	local rayResultInitial = game.Workspace:Raycast(screenRay.Origin, screenRay.Direction * 10000, parmas)
	
	if not rayResultInitial then
		return
	end
	
	visualizeRay(screenRay.Origin, rayResultInitial, rayResultInitial.Position, BrickColor.new("Really red"))
	
	local direction = (rayResultInitial.Position - char.HumanoidRootPart.Position).Unit
	
	
	local newPart = Instance.new("Part", workspace)
	newPart.Anchored = true
	newPart.Position = rayResultInitial.Position
	newPart.Transparency = 1
	newPart.Size = Vector3.new(0.01,0.01,0.01)
	newPart.CFrame = CFrame.new(rayResultInitial.Position, rayResultInitial.Position + screenRay.Direction)
	
	
	while true do
		newPart.CFrame = newPart.CFrame + newPart.CFrame.LookVector * 0.1
		local touching = newPart:GetTouchingParts()
		
		if #touching == 0 then
			
			local rayResult = game.Workspace:Raycast(newPart.Position, newPart.CFrame.LookVector * 10000, parmas)
			
			if not rayResult then
				return
			end
			
			visualizeRay(newPart.Position, rayResult, rayResult.Position, BrickColor.new("Really red"))
			
			break
		end
		
	end
	
	local penetrationDistance = (newPart.Position - rayResultInitial.Position).Magnitude
	print(penetrationDistance)
	
	local rayResultPenetration = game.Workspace:Raycast(rayResultInitial.Position, screenRay.Direction * penetrationDistance, parmas)
	visualizeRay(rayResultInitial.Position, newPart, newPart.Position, BrickColor.new("Royal blue"))
	
	newPart:Destroy()
end)

In the case that the system is inefficient I would appreciate if someone could suggest an alternative

Thanks

local function raycastPenetration(origin, direction, maxDistance, rayParams, maxPenetration)
    local totalDistance = 0
    local currentOrigin = origin
    
    while totalDistance < maxDistance do
        local ray = workspace:Raycast(currentOrigin, direction * (maxDistance - totalDistance), rayParams)
        
        if not ray then break end
        
        if totalDistance > 0 then
            return ray.Position, totalDistance
        end
        
        local nextOrigin = ray.Position + direction * 0.1
        
        local backRay = workspace:Raycast(nextOrigin, -direction * maxPenetration, rayParams)
        if backRay then
            local thickness = (nextOrigin - backRay.Position).Magnitude
            
            if thickness <= maxPenetration then
                currentOrigin = nextOrigin + direction * thickness
                totalDistance = totalDistance + thickness + 0.1
            else
                break
            end
        else
            break
        end
    end
    
    return nil, 0
end

A little sloppy, sorry about that, just blew this out.. Key point is it’s not using touched at all.
There were some others issues in your org script also.. I’ve addressed them also.

The original approach could do 1000+ GetTouchingParts() calls per bullet - with 20 players shooting 10 times per second, that’s 200,000 expensive physics calls per second. This version does 2-3 raycasts per bullet (400-600 calls per second). That would be more efficient.

Thank’s for the reply, however I do not understand how I would implement the code snipped you send into my code.

I also modified what I have so that it can penetrate multiple parts, so if you could come up with something that works for this new code I would appreciate it:

local mouse = game.Players.LocalPlayer:GetMouse()

local parmas = RaycastParams.new()
parmas.FilterType = Enum.RaycastFilterType.Exclude
parmas.FilterDescendantsInstances = {game.Players.LocalPlayer.Character, game.Workspace.Visualizers}

local char = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()


local maxPenetration = 25
local maxTotalRayDistance = 100

function bulletPenetrate()
	
	local currentDistance = 0
	local curRayOrigin
	
	local screenRay = game.Workspace.CurrentCamera:ScreenPointToRay(mouse.X, mouse.Y)
	curRayOrigin = screenRay.Origin

	local function visualizeRay(origin, result, colour)

		local distance = (origin - result).Magnitude
		local p = Instance.new("Part", workspace.Visualizers)
		p.Name = 'Visualizer'
		p.Anchored = true
		p.CanCollide = false
		p.BrickColor = colour
		p.Size = Vector3.new(0.1, 0.1, distance)
		p.CFrame = CFrame.lookAt(origin, result)*CFrame.new(0, 0, -distance/2)

	end
	
	local function penetrate(partInitHit)

		local distBeforePenStart = currentDistance

		local newPart = Instance.new("Part", workspace)
		newPart.Anchored = true
		newPart.Position = partInitHit
		newPart.Transparency = 1
		newPart.Size = Vector3.new(0.001,0.001,0.001)
		newPart.CFrame = CFrame.new(partInitHit, partInitHit + screenRay.Direction)

		while true do
			
			local touching = newPart:GetTouchingParts()
			newPart.CFrame = newPart.CFrame + newPart.CFrame.LookVector * 0.1

			local penetratedDistance = (newPart.Position -  partInitHit).Magnitude 
			currentDistance = distBeforePenStart + penetratedDistance
			
			if penetratedDistance > maxPenetration or currentDistance > maxTotalRayDistance then
				return "exceeded"
			end

			if #touching == 0 then
				return newPart
			end

		end
	end


	while currentDistance < maxTotalRayDistance do

		task.wait()
		

		local rayResult = game.Workspace:Raycast(curRayOrigin, screenRay.Direction * (maxTotalRayDistance - currentDistance), parmas)
		parmas.FilterDescendantsInstances = {game.Players.LocalPlayer.Character, game.Workspace.Visualizers}


		if rayResult then
			currentDistance += (curRayOrigin - rayResult.Position).Magnitude
			visualizeRay(curRayOrigin, rayResult.Position, BrickColor.new("Really red"))
			

			local penPart = penetrate(rayResult.Position)

			if penPart == "exceeded" then
				currentDistance = 100
			else
				visualizeRay(rayResult.Position, penPart.Position, BrickColor.new("Bright green"))
				curRayOrigin = (penPart.CFrame + penPart.CFrame.LookVector * 0.05).Position
			end

		else
			
			local endDistance = (curRayOrigin + screenRay.Direction * (maxTotalRayDistance-currentDistance))
			visualizeRay(curRayOrigin, endDistance, BrickColor.new("Really red"))

			currentDistance = 100
		end

	end
end

mouse.Button1Down:Connect(function()
	
	task.spawn(bulletPenetrate)
	
end)

Well now that I’ve had time to really look at it .. crunch. This should be lag free.

local player = game:GetService("Players").LocalPlayer
local mouse = player:GetMouse()

local params = RaycastParams.new()
params.FilterDescendantsInstances = {player.Character}

mouse.Button1Down:Connect(function()
    local ray = game.Workspace.CurrentCamera:ScreenPointToRay(mouse.X, mouse.Y)
    local firstHit = game.Workspace:Raycast(ray.Origin, ray.Direction * 1000, params)
    if not firstHit then return end

    local backHit = game.Workspace:Raycast(firstHit.Position, -ray.Direction * 10, params)
    if not backHit then return end

    local wallThickness = (firstHit.Position - backHit.Position).Magnitude
    if wallThickness > 5 then return end

    local exitPoint = backHit.Position + (ray.Direction * 0.05)
    local finalHit = game.Workspace:Raycast(exitPoint, ray.Direction * 1000, params)
    
    if finalHit then
        local humanoid = finalHit.Instance.Parent:FindFirstChildWhichIsA("Humanoid")
        if humanoid then
            humanoid:TakeDamage(10)
        end
    end
end)

It’s using a bounce-back ray after the fact to get the thickness. I think that is a sweet little trick.
It turns a complex, laggy simulation into a single question to the physics engine.

It took me a while but I figured it out.

Here is the modified code:

local mouse = game.Players.LocalPlayer:GetMouse()

local parmas = RaycastParams.new()
parmas.FilterType = Enum.RaycastFilterType.Exclude
parmas.FilterDescendantsInstances = {game.Players.LocalPlayer.Character, game.Workspace.Visualizers, game.Workspace.BulletHoles}

local char = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()


local maxPenetration = 25
local maxTotalRayDistance = 100

function bulletPenetrate()

	local currentDistance = 0
	local curRayOrigin

	local screenRay = game.Workspace.CurrentCamera:ScreenPointToRay(mouse.X, mouse.Y)
	curRayOrigin = screenRay.Origin

	local function visualizeRay(origin, result, colour)

		local distance = (origin - result).Magnitude
		local p = Instance.new("Part", workspace.Visualizers)
		p.Name = 'Visualizer'
		p.Anchored = true
		p.CanCollide = false
		p.BrickColor = colour
		p.Size = Vector3.new(0.1, 0.1, distance)
		p.CFrame = CFrame.lookAt(origin, result)*CFrame.new(0, 0, -distance/2)

	end

	local function penetrate(partInitHit, partInitInstance)

		local distBeforePenStart = currentDistance
		
		local internalParams = RaycastParams.new()
		internalParams.FilterType = Enum.RaycastFilterType.Exclude
		internalParams.FilterDescendantsInstances = {game.Players.LocalPlayer.Character, game.Workspace.Visualizers, partInitInstance, game.Workspace.BulletHoles}
		
		local internalCast = workspace:Raycast(partInitHit,screenRay.Direction * 1000, internalParams)
		local farPos = internalCast and internalCast.Position or (partInitHit + screenRay.Direction.Unit * 1000)
		--visualizeRay(partInitHit, farPos, BrickColor.new("Baby blue"))
		
		if internalCast then
			
			local rayBackParams = RaycastParams.new()
			rayBackParams.FilterType = Enum.RaycastFilterType.Include
			rayBackParams.FilterDescendantsInstances = {partInitInstance}
			
			local internalCastBack = workspace:Raycast(internalCast.Position, -screenRay.Direction * 1000, rayBackParams)
			
			--visualizeRay(internalCast.Position, internalCastBack.Position, BrickColor.new("Black"))
			
			if internalCastBack then
				
				
				local penetratedDistance = (internalCastBack.Position - partInitHit).Magnitude

				currentDistance = distBeforePenStart + penetratedDistance

				if penetratedDistance > maxPenetration or currentDistance > maxTotalRayDistance then
					return "exceeded"

				else

					local newPart = Instance.new("Part", workspace)
					newPart.CanCollide = false
					newPart.CFrame = CFrame.lookAt(internalCastBack.Position, internalCastBack.Position + screenRay.Direction)
					newPart.Size = Vector3.new(0.01,0.01,0.01)
					newPart.Anchored = true
					newPart.Transparency = 1

				
				

					local bulPart = game.ReplicatedStorage.BulletHole:Clone()
					bulPart.Parent = game.Workspace.BulletHoles

					
					local normal = internalCastBack.Normal
					local hitPos = internalCastBack.Position

					local bulPart = game.ReplicatedStorage.BulletHole:Clone()
					bulPart.Parent = game.Workspace.BulletHoles

					local OFFSET = 0.01
					bulPart.CFrame =
						CFrame.lookAt(hitPos + normal * OFFSET, hitPos + normal)

					return newPart
				end	
			end
			
		end
	end


	while currentDistance < maxTotalRayDistance do

		task.wait()


		local rayResult = game.Workspace:Raycast(curRayOrigin, screenRay.Direction * (maxTotalRayDistance - currentDistance), parmas)
		parmas.FilterDescendantsInstances = {game.Players.LocalPlayer.Character, game.Workspace.Visualizers, game.Workspace.BulletHoles}


		if rayResult then

			local hitPos = rayResult.Position
			local normal = rayResult.Normal

			local bulletHole = game.ReplicatedStorage.BulletHole:Clone()
			bulletHole.Parent = game.Workspace.BulletHoles


			local OFFSET = 0.01
			bulletHole.CFrame =
				CFrame.lookAt(hitPos + normal * OFFSET, hitPos + normal)

			currentDistance += (curRayOrigin - rayResult.Position).Magnitude
				--visualizeRay(curRayOrigin, rayResult.Position, BrickColor.new("Really red"))

			local penPart = penetrate(rayResult.Position, rayResult.Instance)

			if penPart == "exceeded" then
				currentDistance = 100
			else
				local normal = -screenRay.Direction.Unit

--				visualizeRay(rayResult.Position, penPart.Position, BrickColor.new("Bright green"))
				if penPart then
					if penPart.CFrame then
						curRayOrigin = (penPart.CFrame + penPart.CFrame.LookVector * 0.05).Position
					end	
				end
				
			
			end

		else

			local endDistance = (curRayOrigin + screenRay.Direction * (maxTotalRayDistance-currentDistance))
			--visualizeRay(curRayOrigin, endDistance, BrickColor.new("Really red"))

			currentDistance = 100
		end

	end
end

mouse.Button1Down:Connect(function()

	task.spawn(bulletPenetrate)

end)


1 Like

Nice .. good luck with your game.

Umm..
You understood the bounce-back trick, which is great. But you rebuild your laggy loop with a task.wait() instead of removing it. The loop itself is the problem. A wallbang check should be three raycasts and finish, not start a lifelong loop for every bullet. My script is the direct application of this, without the over-engineering.

Based off what you just posted..

LocalScript
local player = game:GetService("Players").LocalPlayer
local mouse = player:GetMouse()

local params = RaycastParams.new()
params.FilterDescendantsInstances = {player.Character}

mouse.Button1Down:Connect(function()
    local ray = game.Workspace.CurrentCamera:ScreenPointToRay(mouse.X, mouse.Y)
    local firstHit = game.Workspace:Raycast(ray.Origin, ray.Direction * 1000, params)
    if not firstHit then return end

    local backHit = game.Workspace:Raycast(firstHit.Position, -ray.Direction * 10, params)
    if not backHit then return end

    local wallThickness = (firstHit.Position - backHit.Position).Magnitude
    if wallThickness > 5 then return end

    local exitPoint = backHit.Position + (ray.Direction * 0.05)
    local finalHit = game.Workspace:Raycast(exitPoint, ray.Direction * 1000, params)
    
    if finalHit then
        local humanoid = finalHit.Instance.Parent:FindFirstChildWhichIsA("Humanoid")
        if humanoid then
            humanoid:TakeDamage(10)
        end
    end
end)

But still, good luck with your game.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.