Is this way of doing hitboxes efficient/effective?

Hello, I scripted a punching system that I would appreciate feedback for(is it bad, good, could use work, etc).

It involves GetPartsBoundinBox, and most of the work is done by the client e.g. creating a hitbox, determining the parts in the hit box, and detecting a humanoid “hit”(I’ll explain right underneath).

The server’s job is to validate if the “hit” is valid. What it does is it compares the properties of the AttackFunction from the serverside module script and the clientside script(both of which are pretty much contain the same information) to see if there were changes on the clientside(e.g. hitbox size change) to prevent exploiting. It then detects if the client’s Player’s HumanoidRootPart’s position of the given and the server/workspace Player’s HumanoidRootPart’s position differ to account for latency. It also compares the player’s position and the humanoid “hit” position and see if it is more than a certain value to prevent exploiting

Overall, I would like to know if my code could cause lag, be inefficient(is there a better way?), etc

Here is the client side code(it’s in StarterCharacterScripts)

--// Services //--

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UIS = game:GetService("UserInputService")

--// Variables //--

local AttackInformation = require(ReplicatedStorage:WaitForChild("AttackInformationClient"))
local Player = game.Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local HRP = Character:WaitForChild("HumanoidRootPart")
local PunchEvent = ReplicatedStorage:WaitForChild("PunchEvent")
local MoveEquipped = false

--// Main //--

UIS.InputBegan:Connect(function(input, gameProcessed)
	if Character.Humanoid:GetState() ~= Enum.HumanoidStateType.Dead then
		if input.UserInputType == Enum.UserInputType.MouseButton1 and not gameProcessed and MoveEquipped == false then  
			local HitDetected = AttackInformation.AttackInitiated(AttackInformation.PunchAttack,HRP)
			if HitDetected then
				PunchEvent:FireServer(AttackInformation.PunchAttack, HRP, HitDetected)
			end
		end
	end
end)

Serverside hit confirmation

--// Services //--

local ServerScriptService = game:GetService("ServerScriptService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

--// Variables //--

local AttackInformation = require(ServerScriptService.AttackInformationServer)
local PunchEvent = ReplicatedStorage:WaitForChild("PunchEvent")
local HitFail = false
local MaxLatencyDistanceHRP = 1 --maximum distance between client HRP and server HRP allowed due to latency

--// Main //--

PunchEvent.OnServerEvent:Connect(function(player, AttackUsed, ClientHRP, Target)
	local ServerHRP = workspace:FindFirstChild(ClientHRP.Parent.Name):FindFirstChild("HumanoidRootPart")
	local UserHRPDistances = (ClientHRP.Position - ServerHRP.Position).Magnitude
	local MaxLatencyDistanceHitbox = AttackInformation.PunchAttack.MaxLatencyDistanceHitbox --changes between Attacks
	local UserHRPTargetDistance = math.abs(UserHRPDistances - AttackInformation.PunchAttack.HitboxSize.Z) --changes between attacks
	for i, v in pairs(AttackUsed) do
		if v ~= AttackInformation.PunchAttack[i] then --changes between attacks
			warn("AttackUsedValue differs. Possible exploiter")
			HitFail = true
			break
		end
	end
	if not HitFail then
		if UserHRPDistances < MaxLatencyDistanceHRP and UserHRPTargetDistance < MaxLatencyDistanceHitbox then
			Target.Parent.Humanoid.Health = Target.Parent.Humanoid.Health - 10
		end
	else
		HitFail = false
	end


end)

clientside module script with attack properties

local Attack = {

	PunchAttack = {

		HitboxSize = Vector3.new(4, 4.998, 3.364),
		HitboxOffset = Vector3.new(0,0,-2),
		HitboxCanCollide = false,
		HitboxAnchored = false,
		HitboxTransparency = 1,
		Cooldown = 1,
		HitboxDespawn = 0.1,
		HitboxParent = workspace

	}


}

local AttackTemplate = {

	HitboxSize = Vector3.new(4, 4.998, 3.364),
	HitboxOffset = Vector3.new(0,0,-2),
	HitboxCanCollide = false,
	HitboxAnchored = false,
	HitboxTransparency = 1,
	Cooldown = 1,
	HitboxDespawn = 0.1,
	HitboxParent = workspace -- just to visualize the hitbox

}

function Attack.AttackInitiated(AttackName,HRP)
	local debounce = false
	local HitDetected = nil
	local Target
	local Attack = AttackName
	local Hitbox = Instance.new("Part")
	local HitboxDespawn = Attack.HitboxDespawn
	local HitboxOffset = CFrame.new(Attack.HitboxOffset)
	local Cooldown = Attack.Cooldown
	local HitboxParams = OverlapParams.new()
	HitboxParams.FilterDescendantsInstances = {HRP.Parent:GetChildren(), Hitbox}
	HitboxParams.FilterType = Enum.RaycastFilterType.Exclude
	Hitbox.Size = Attack.HitboxSize
	Hitbox.CanCollide = Attack.HitboxCanCollide
	Hitbox.Anchored = Attack.HitboxAnchored                                                
	Hitbox.Transparency = 0.5 													--make sure to change
	local Weld = Instance.new("Weld")
	Weld.Part0 = HRP
	Weld.Part1 = Hitbox
	Weld.C1 = CFrame.new(0,0,2)														--change
	Weld.Parent = HRP
	Hitbox.Parent = Attack.HitboxParent

	if Hitbox then
		while true do
			local HitboxObjects = workspace:GetPartBoundsInBox(Hitbox.CFrame, Hitbox.Size, HitboxParams)
			if #HitboxObjects ~= 0 then
				for i, v in pairs (HitboxObjects) do
					if v.Parent:FindFirstChild("Humanoid") and v.Parent:FindFirstChild("Humanoid").Health ~= 0 and debounce == false then
						debounce = true
						task.wait(HitboxDespawn)
						Hitbox:Destroy()
						debounce = false
						HitDetected = true
						Target = v.Parent:FindFirstChild("HumanoidRootPart")
						break
					else
						task.wait(HitboxDespawn)
						Hitbox:Destroy()
						break
					end
				end
			else
				task.wait(HitboxDespawn)
				Hitbox:Destroy()
			end
			task.wait(HitboxDespawn)
			break
		end
	end
	if HitDetected then
		HitDetected = false
		return Target
	end

end

return Attack

serverside module script with attack properties

local Attack = {

	PunchAttack = {

		HitboxSize = Vector3.new(4, 4.998, 3.364),
		HitboxOffset = Vector3.new(0,0,-2),
		HitboxCanCollide = false,
		HitboxAnchored = false,
		HitboxTransparency = 1,
		Cooldown = 1,
		HitboxDespawn = 0.1,
		MaxLatencyDistanceHitbox = 5,
		HitboxParent = workspace

	}


}

local AttackTemplate = {

	HitboxSize = Vector3.new(4, 4.998, 3.364),
	HitboxOffset = Vector3.new(0,0,-2),
	HitboxCanCollide = false,
	HitboxAnchored = false,
	HitboxTransparency = 1,
	Cooldown = 1,
	HitboxDespawn = 0.1,
	MaxLatencyDistanceHitbox = 5,
	HitboxParent = workspace -- just to visualize the hitbox

}

return Attack

1 Like

Usually GetPartsBoundInBox is not the most efficient way to detect something like this no. But is it bad in your case? It’s for you to test that out.

When I ran scripts using GetPartsBoundInBox, the activity of the script increased a lot, meaning it takes a lot of energy to do calculations like those, especially if you spam it by binding it to some loop or if the box is big. Though this was on the server and not on the client so results may differ.

I believe your way should be efficient enough, but benchmark anyway, here are some steps to benchmark this:

  1. First off, open up the activity tab to see how much energy the script is using normally, is the percentage high? Is it appropriate?
  2. Try spamming it, use an autoclicker and see how that affects the lag, if you included a proper wait it should be fine.
  3. Try attacking multiple things at once, what if multiple enemies are in the box? Maybe that will cause more lag.

Alternatives:

  1. You could bind a TouchedEvent and smart calculations and checks instead of using GetPartsBoundInBox.
  2. In certain cases a small raycast would be a less expensive and more appropriate method, however this would ignore the purpose of the hitbox and if your punch isn’t going in a straight line this would make no sense.

But before doing that check if your method is efficient so you don’t have to restart.

It’s really up to you to see if your script is efficient enough!

3 Likes

Thank you! The reason I used GetPartsBoundInBox was because I heard it was “better” than the Touched event—something about the touched event having latency or something.

1 Like

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