Help me make my hitbox script better!

I have been using this hitbox script for a while for personal projects. However, I believe there are more optimal ways to make a hitbox system. My goal of this code review is just learning a new way on how to make hitbox system because I’ve heard that .Touched is outdated and an optimal way of making modules.

local module = {}

module.Timer = {}

module.HitBox = function(size, damage, shape, cframe, plr, count, plrknockback, enemyknockback, up, hitvfx)
	local char = plr.Character or plr.CharacterAdded:Wait()
	local hum = char:FindFirstChild("Humanoid")
	local hrp = char:FindFirstChild("HumanoidRootPart")
	local ht = {}
	local hitBox = Instance.new("Part", workspace)
	local tz = os.time()
	hitBox.Size = size * Vector3.one
	hitBox.Shape = shape
	hitBox.CFrame = hrp.CFrame * cframe
	hitBox.Anchored = true
	hitBox.CanCollide = false
	hitBox.Transparency = 1
	
	print(count)
	
	hitBox.Touched:Connect(function(hit)
		if hit.Name == "HumanoidRootPart" and hit.Parent.Name ~= plr.Name and hit.Parent:FindFirstChild("Humanoid") then
			if table.find(ht, hit.Parent.Name) then return end
			
			local creator = Instance.new("ObjectValue")
			creator.Value = plr
			creator.Name = "creator"

			game.Debris:AddItem(creator, 0.6)
			hit.Parent:FindFirstChild("Humanoid").Died:Connect(function()
				creator.Parent = hit.Parent:WaitForChild("Humanoid")

				print(creator)
				if creator and creator.Value then
					plr.leaderstats.Kills.Value += 1
				end


			end)
			
			task.spawn(function()
				if hitvfx then
					hitvfx.CFrame = hit.Parent:FindFirstChild("HumanoidRootPart").CFrame * CFrame.new(0,math.random(0,1.5),-1)
					for i, v in pairs(hitvfx.Attachment:GetChildren()) do
						task.wait()
						v:Emit(v:GetAttribute("EmitCount"))
					end
				else
					hitvfx = nil
				end
			end)

		

			local Direction = (char:FindFirstChild("HumanoidRootPart").Position - hit.Parent:FindFirstChild("HumanoidRootPart").Position).Unit
			local dot = hit.Parent.HumanoidRootPart.CFrame.LookVector:Dot(Direction)
			if count == 4 and hit.Parent:GetAttribute("Blocking") == true then
				hit.Parent:SetAttribute("GuardBreak", true)
				print('guardbreak')
				damage = damage/2
			elseif count == 4 and hit.Parent:GetAttribute("Blocking") == false then
				task.spawn(function()
					enemyknockback = 200 -- make sure to lower the enemyknockback so that the player doesn't go flying with the enemy or just lower players knockback
					print(enemyknockback) -- oh and make sure to add like an animfreeze tech
					hit.Parent.HumanoidRootPart.AssemblyLinearVelocity = hrp.CFrame.LookVector * enemyknockback 
					task.wait(1.5)
					enemyknockback = 35
					print(enemyknockback)
				end)
			elseif count == 5 and hit.Parent:GetAttribute("Blocking") then
				damage = math.floor(damage/4)
				hit.Parent:SetAttribute("GuardBreak", true)
				print('guardBrekofdeath')
			end
			if not up then
				hit.Parent.HumanoidRootPart.AssemblyLinearVelocity = hrp.CFrame.LookVector * enemyknockback  
				hrp.AssemblyLinearVelocity = hrp.CFrame.LookVector * plrknockback
			else
				hit.Parent.HumanoidRootPart.AssemblyLinearVelocity = hrp.CFrame.UpVector * enemyknockback
			end
			
			
			if dot > 0 and hit.Parent:GetAttribute("Blocking") == true then
				print('yes')
				damage = math.floor(damage/3.5)
			end
			local eplr = game.Players:GetPlayerFromCharacter(hit.Parent)
			if eplr then
				local ehum = eplr.Character:FindFirstChild("Humanoid")
				ehum:TakeDamage(damage)
			elseif not eplr then
				local ehum = hit.Parent:FindFirstChild("Humanoid")
				ehum:TakeDamage(damage)
			end
			
			
			table.insert(ht, hit.Parent.Name)
			if not hit.Parent:GetAttribute("Blocking") then
				local target = hit.Parent
			
				print(module.Timer)
				print('Before adding Timer: ', module.Timer)
				if module.Timer[target] then  -- checks if there is another rig/player in the stun module table
					task.cancel(module.Timer[hit.Parent]) -- cancels the stun time thread
				end
		
				hit.Parent:SetAttribute("Stunned", true) -- stuns the player
				
				

				print('After adding timer: ',module.Timer)

				module.Timer[target] = task.delay(0.885, function() -- turns the stun into the stun table
					if module.Timer[target] then -- sees if there is a plr/ rig in the table
						hit.Parent:SetAttribute("Stunned", false) -- unstuns the plr/rig
						module.Timer[target] = nil -- takes out the plr/ rig out of the table
						table.remove(module.Timer, table.find(module.Timer, hit.Parent.Name))
						print('Finishing the timer: ',module.Timer)
					end

				end)	
				
			
				
			end
		end
		
	end)
	
	--[[hitBox.TouchEnded:Once(function(hit)
		if hit.Name == "HumanoidRootPart" and hit.Parent.Name ~= plr.Name and hit.Parent:FindFirstChild("Humanoid") then
			print("ended")
			task.wait(1.5)
			hit.Parent:SetAttribute("Stunned", false)
		end
	end)--]]
	
	game.Debris:AddItem(hitBox, 0.7)
	
end

return module

1 Like

I’ll just go over the hitBox.Touched:Connect() since I honestly don’t wanna go through all the code (sorry :d)

And luckily you don’t really need to change much either.

Since you’ve already made a part to detect what is being hit you can use workspace:GetPartsInPart(). What it does is returns a table of parts inside a part, pretty self explanatory. You can further boost the speed of it by using a task.spawn()

How would you use it?

local aParams = OverlapParams.new()
aParams.FilterType = Enum.RaycastFilterType.Exclude
aParams.FilterDescendantsInstances = {char}

for _,obj in workspace:GetPartsInPart(hitBox,aParams) do
	task.spawn(function()
		if obj.Name == "HumanoidRootPart" then
			-- Continue with your old code
		end
	end)
end

First let’s talk about the aParams (OverlapParams), its basically a table of things to include or exclude from the area detection depending on FilterType being used. For this example, I set it to Exclude (aParams.FilterType = Enum.RaycastFilterType.Exclude). The default I believe is Include.

Now how do you what it’ll exclude? By the FilterDescendantsInstances (aParams.FilterDescendantsInstances = {char}) In this example, I decided to exclude the char from the area detection. There’s a few other attributes for the params but you will mostly use the the 2 I’ve talked about. And feel free to add other parts in here, like a map.
Example:

aParams.FilterDescendantsInstances = {char,workspace.Map1,workspace.Map2}

The less parts, you’re detecting, the faster the script can run attacks.

Now what’s with the for loop?
Well as stated before, workspace:GetPartsInPart() returns a table of parts that is within a part. so to get all the parts inside it, you have to make a for loop.

What’s with the task.spawn()? Well if you don’t know what it does, its basically like a script within a script. When you do task.spawn() whatever is in the task.spawn(), will run and not pause the, I guess, leading script. This is important as, you’re looping through possibly a lot of parts, and if you manage to hit 2 HumanoidRootParts, the script doesn’t pause to attack Char 1, finish attacking Char 1, then attack Char 2.

.Touched work fine in most cases, but when the player is lagging or their character is moving at a super fast speed, .Touched can be inaccurate. Spartial query(ex: GetPartsInPart), Raycast and Magnitude are alternative methods to make hitbox.

Existing implementations:
HitboxClass | v1.1B | A Powerful OOP-based Hitbox Module (Spartial Query)
Raycast Hitbox 4.01: For all your melee needs! (Raycast)

Magnitude is limited to sphere or cone shaped hitboxes, and requires a known target, not as popular.

In general, it’s better to give your variables human-readable names instead of this. It’ll help you later on when you look at the script again and don’t remember how it actually works anymore.

So instead of variables like hum I should change it into something like Humanoid = blah blah or hrp into HumanoidRootPart. So for example,

local Humanoid = char:FindFirstChild("Humanoid")
local HumanoidRootPart = char:FindFirstChild("HumanoidRootPart")
local hitTable = {}
local hitBox = Instance.new("Part", workspace)
local timezone = os.time()

Also lets say for example, I have an animation using punches or an animation using a sword or a tool. Which type of hitbox would be good for each one? Would I use spatial query for the animation with punches and raycast for the animation with a sword/tool or can I implement a same type of hitbox for both of those animations?

Yes, when I was a beginner I also made this mistake. Caused lots of problems down the line. You have autocomplete for variable names after all, so no need to make the names as short as possible.