HitboxClass | v1.1A | A Powerful OOP-based Hitbox Module

The connected part is to check to see if a client-sided hitbox made a connection to the client, it waits for a response back for a max of 1.5 seconds. If the client lets the server know it made the hitbox, it’ll return true, if it timed out because the client didn’t respond back in time, it returns false.

The point is to allow you to add some sort of callback if the client doesn’t respond. For instance, you could destroy the hitbox object and make a new one that’s server-sided to use instead, or just ignore the action entirely, and not apply cooldowns or whatever until a proper hitbox is made and it’s able to be used properly.

This always returns true if it’s a server-sided hitbox, since if the server wasn’t gonna respond it probably wouldn’t have ran the code to make the hitbox in the first place lol. So you don’t really need it there if it’s a server sided hitbox, just assign the object to a variable and ignore the connected boolean.

I’ll look into the delay issue when I get the time, I haven’t had any issues with it being delayed, so if you can recreate the issue in a new place and send it to me that’d be wonderful.

1 Like

It doesn’t seem like the delay is caused by your module, as it’s only delayed for about a frame or slightly more.
Also, could you help me with an error I’m facing that wasn’t present before using your module?

local Success = false
local newHitbox, connected
				
newHitbox, connected = HitboxModule.new(HitboxParams)
newHitbox:Start()
newHitbox:WeldTo(Character.HumanoidRootPart, CFrame.new(0,0,-4))
newHitbox.HitSomeone:Connect(function(hitChars)
for i,v in pairs(hitChars) do 
	local Hit = Dmg:executeDamage(v) --> returns true when damage is registered
		if Hit then 
			Success = true
			print(Success) --> prints true
		end
	end
end)
				
print(Success) --> printed true before but now prints false

Thank you!

Okay, so what I’d do first of all is set up all your connections and methods prior to starting the hitbox.

So weld and connect to the HitSomeone signal before running the Start method at the end. That might be why there’s a couple frames of delay on your server-sided hitbox.

Make sure your code is actually returning true, the signal should guarantee at least one model with a humanoid whenever it’s fired so it should definitely be running the loop just fine.

1 Like

Seems like putting the methods before starting the hitbox fixed the delay, but the real problem is that my script does return true and it even prints true for success but outside the for i,v in pairs it doesn’t. Also is it a good idea to make projectile hitboxes with this?

Very cool and thank you for sharing, but i believe a few other modules like this already exist. What are the benefits of using this over something like MuchachoHitbox or FastCast?

Looks like you’re printing right after connecting to the signal, which hasn’t been fired yet, which means Success hasn’t been changed to true yet either. Try printing Success after a delay of a few seconds and try and hit something with the hitbox within that time like so.

Here’s the code I used to test it.

local HitboxModule = require(ReplicatedStorage.HitboxClass)
local HitboxTypes = require(ReplicatedStorage.HitboxClass.Types)
local Success = false
local newHitbox, connected

local hitboxParams = {
	SizeOrPart = 5,
	DebounceTime = 1,
} :: HitboxTypes.HitboxParams

newHitbox, connected = HitboxModule.new(hitboxParams)

newHitbox:WeldTo(Character.HumanoidRootPart, CFrame.new(0,0,-4))
newHitbox.HitSomeone:Connect(function(hitChars)
	for i,v in pairs(hitChars) do 
		local Hit = true
		if Hit then 
			Success = true
			print(Success) --> prints true
		end
	end
end)

newHitbox:Start()

print(Success) --> this prints false because you print directly after connecting to the signal

task.delay(3, function()
	print(Success) --> since I delayed Success to be printed, in that span of time the signal was fired, changing this to true
end)

Sorry, I assumed you printing at the end was meant to be somewhere else printing, and not directly after haha

Yes, you can make projectile hitboxes with it just fine. I use it for ranged slashes and other projectiles like bullets (to give them more leeway than just raycasting) and some fireball-type moves, just remember if the projectile is on the server, turn off velocity prediction since you’re not attaching it to a player, which can run its own simulation of the game which would call for velocity prediction.

I’d recommend you make the projectile on the client and then just handle the hitbox on the client. When the client responds, you can take the time it took for them to respond with the time the projectile started, get the direction and speed it was going, and then use that to figure out the approximate location of the projectile in order to verify hits. You can check out the logic on my other thread here: Network Efficient and Safe Practices with Projectiles.

2 Likes

FastCast is used for raycasting, which is great for smaller projectiles that you want to be really accurate like bullets in an FPS. But for larger things you normally see in fighting games and RPGs that include magic like fireballs, a lot of those projectiles are simply too large for raycasting to be a feasible solution, since if you’d want to cover a large projectile, you could easily start firing 80+ raycasts just to cover the impact surface. You also run into an issue where they’re too accurate, giving players some leeway in their hits enables a more responsive experience.

Again, for stuff like an FPS game where accuracy is a must, FastCast is absolutely the best choice.

MuchachoHitbox is a great resource, however, it’s not type-checked, and it doesn’t allow easy access to client-sided hitboxes like HitboxClass does. Unless you change the hitboxes on the client in some way, hitboxes are entirely synced, and all you had to do was require it, set the UseClient parameter to the player that’ll run the calculations, and then use it exactly like a normal hitbox.

HitboxClass also gives you access to a couple more methods of detection, like GetPartsInPart and magnitude. You can also easily check dot-products for magnitude via the DotProductRequirement option in the HitboxParams. You can use that to cut down your magnitude hitbox into parts of a sphere. You could, for instance, ensure magnitude hitboxes only hit in front of the player rather than behind them by checking for a dot-product of 0.

3 Likes

Awesome. Also, would it be more or less optimal to use this for stationary hitboxes than using region3s? I have a hitbox function that uses region3s for detection, and its just a simple call of :GetPartsInBoundBox(). If i were to use HitboxClass instead would it be more or less performant? I mainly use this function for repeated attacks like punches and kicks, so it needs to be able to be called multiple times in a row without performance issues.

HitboxClass’s Hitbox objects connects to Heartbeat, then the performance really depends on what detection method you use. Magnitude is the most performant, as it’s just distance checks, while GetPartsInPart is the most expensive, checking the entire geometry. GetPartBoundsInBox/Radius is kinda in the middle.

The performance gain of using your method is entirely dependent on how you use it. If you call your Region3 method exactly when you need it, and you only call it a single time, the performance is technically more optimal, since you risk less empty checks, and that also means less cycles. If you connect to heartbeat and allow the hitbox to be checking for a certain amount of time, then HitboxClass provides the exact same thing without using a deprecated method. Just use the InBox option in the SpatialOption parameter in the HitboxParams.

You can stop the Hitbox object using the Stop method, which would allow you to give the players some leeway in their hits if you allow the hitbox to be out for a certain amount of time. Like if one of your kicks checks for hits for 0.15 seconds, stop the hitbox after 0.15 seconds.

So it’s really up to you. IMHO the performance gain/take isn’t very substantial and negligible, it entirely depends on what detection method you use and whether or not you want it to be incredibly precise by checking once, or giving the players some leeway by checking for a certain amount of time. HitboxClass provides the exact same thing as Region3’s, except it’s intended to provide leeway by having you Start and Stop the hitbox when needed.

4 Likes

This method kind of worked but now the problem is that I need it to be true immediately. I tried to reduce the delay, but anything below 0.1 will be false. Is there any way to fix this.

That sounds like a logic issue, why do you need it to instantly be true? Why not spawn a separate thread, wait for it to be true, and then run your code asynchronously? Just cancel it when you don’t want it to be checking anymore.

2 Likes

Hey, are the hitboxes in your video client-sided or server-sided? How did you handle the hitbox for the projectile move? Can you add a feature that lets us detect when the hitbox hits an object itself and not necessarily a player since this feature can be used to add effects such as the object breaking or just some visual and auditory effects?

2 Likes

Sorry! Just saw this.

In the velocity prediction video, both are server-sided. For the video of my game, the melee one is client-sided since it relies on character movement, while the projectile one is server-sided.

For the projectile, I get where the player is aiming their mouse, I have a static formula that determines how long the potion will take to reach the destination, so after that certain amount of time I do the VFX and start 2 hitboxes, one for the explosion and the other for the poison floor.

Object-detection will likely come in a future update, since it shouldn’t be too troublesome to add.

1 Like

Updated HitboxClass to v1.1

  • Added a new Timer class with methods listed on the documentation!
  • Added BasePart detection!
  • Added a Debris parameter to delete hitboxes upon the built-in timer reaching 0!
  • Added a LookingFor parameter for the above BasePart detection!

Make sure to check out the documentation linked at the top of the page to see what’s new!

Everything else by default is the exact same! Which means this release can just be dropped in with no issues!

If there’s any issues with the latest release, let me know!

2 Likes

Really well-made, currently using it for a project and it has been doing wonders, it has fixed nearly every issue I had with our previous hitbox system.

Fantastic resource, I look forward to future updates. :smile:

1 Like

when i use a server sided hitbox that is welded, upon 2nd use the hitbox starts to sink into the ground. any explanation for this?

local HitboxClass = require(game.ReplicatedStorage.HitboxClass)
local HitboxTypes = require(game.ReplicatedStorage.HitboxClass.Types)
local flamethrowerEvent = game.ReplicatedStorage.Attacks.Flamethrower

local flamethrowerSFX = game.SoundService.FlameThrower.FlamethrowerSFX
local burningSFX = game.SoundService.Fireball.FireballBurning
local flamethrowerFlame = game.ServerStorage.AttackParts.FlamethrowerFlame

local debounceFlamethrower = false
local highlight = Instance.new("Highlight")
highlight.DepthMode = Enum.HighlightDepthMode.Occluded
highlight.FillTransparency = 0.55
highlight.OutlineTransparency = 0.4
highlight.FillColor = Color3.new(0.670588, 0, 0)
highlight.OutlineColor = Color3.new(0.764706, 0.27451, 0.27451)

flamethrowerEvent.OnServerEvent:Connect(function(Player)
	local character = Player.Character
	local hitPlayers = {}
	
	local FlamethrowerAnim = character.Humanoid.Animator:LoadAnimation(game.ReplicatedStorage.Animations.Flamethrower)
	FlamethrowerAnim:Play()
	task.wait(0.4)
	local flamethrowerSFXC = flamethrowerSFX:Clone()
	flamethrowerSFXC.Parent = character.HumanoidRootPart
	flamethrowerSFXC:Play()	
	
	local flamethrowerFlameC = flamethrowerFlame:Clone()
	flamethrowerFlameC.Parent = workspace
	flamethrowerFlameC.CFrame = character.HumanoidRootPart.CFrame * CFrame.new(0, 0 ,-2)
	local weld = Instance.new("WeldConstraint", flamethrowerFlameC)
	weld.Part0 = flamethrowerFlameC
	weld.Part1 = character.HumanoidRootPart
	
	local guidePart = Instance.new("Part")
	guidePart.CFrame = character.HumanoidRootPart.CFrame * CFrame.new(0, 0 , -6.9)
	local weld2 = Instance.new("WeldConstraint", flamethrowerFlameC)
	weld2.Part0 = guidePart
	weld2.Part1 = character.HumanoidRootPart
	guidePart.Transparency = 1
	guidePart.Size = Vector3.new(4, 5.256, 13.713)
	guidePart.Parent = workspace
	guidePart.Massless = true
	guidePart.Anchored = false
	guidePart.CanCollide = false
	
	local hitboxParams = {SizeOrPart = guidePart, DebounceTime = 1, Debug = true} :: HitboxTypes.HitboxParams

	local newHitbox, connected = HitboxClass.new(hitboxParams)
	newHitbox:WeldTo(guidePart)
	newHitbox:SetVelocityPrediction(false)
	
	newHitbox.HitSomeone:Connect(function(hitCharsTable)
		for _, hitChar in pairs(hitCharsTable) do
			local player = game.Players:GetPlayerFromCharacter(hitChar)

			if player and player ~= Player then
				if not hitPlayers[player] and debounceFlamethrower == false then
					debounceFlamethrower = true
					hitPlayers[player] = true

					local isRolling = hitChar.Humanoid.isRolling 

					if hitChar:WaitForChild("Humanoid") and isRolling.Value == false and hitChar:WaitForChild("Humanoid").Parent ~= character then
						hitChar.Humanoid:TakeDamage(3)
						hitChar.Humanoid.rollingEnabled.Value = false
						hitChar.Humanoid.WalkSpeed = 6
						local highlightC = highlight:Clone()
						highlightC.Parent = hitChar
						task.wait(0.5)
						hitChar.Humanoid.rollingEnabled.Value = true
						highlightC:Destroy()
						hitChar.Humanoid.WalkSpeed = 16
						if hitChar:WaitForChild("Humanoid") and isRolling.Value == false and hitChar:WaitForChild("Humanoid").Parent ~= character then
							hitChar.Humanoid:TakeDamage(3)
							hitChar.Humanoid.rollingEnabled.Value = false
							hitChar.Humanoid.WalkSpeed = 6
							local highlightC = highlight:Clone()
							highlightC.Parent = hitChar
							task.wait(0.5)
							hitChar.Humanoid.rollingEnabled.Value = true
							highlightC:Destroy()
							hitChar.Humanoid.WalkSpeed = 16
							if hitChar:WaitForChild("Humanoid") and isRolling.Value == false and hitChar:WaitForChild("Humanoid").Parent ~= character then
								hitChar.Humanoid:TakeDamage(3)
								hitChar.Humanoid.rollingEnabled.Value = false
								hitChar.Humanoid.WalkSpeed = 6
								local highlightC = highlight:Clone()
								highlightC.Parent = hitChar
								task.wait(0.5)
								hitChar.Humanoid.rollingEnabled.Value = true
								highlightC:Destroy()
								hitChar.Humanoid.WalkSpeed = 16
								if hitChar:WaitForChild("Humanoid") and isRolling.Value == false and hitChar:WaitForChild("Humanoid").Parent ~= character then
									hitChar.Humanoid:TakeDamage(3)
									hitChar.Humanoid.rollingEnabled.Value = false
									hitChar.Humanoid.WalkSpeed = 6
									local highlightC = highlight:Clone()
									highlightC.Parent = hitChar
									task.wait(0.5)
									hitChar.Humanoid.rollingEnabled.Value = true
									highlightC:Destroy()
									hitChar.Humanoid.WalkSpeed = 16
									if hitChar:WaitForChild("Humanoid") and isRolling.Value == false and hitChar:WaitForChild("Humanoid").Parent ~= character then
										hitChar.Humanoid:TakeDamage(3)
										hitChar.Humanoid.rollingEnabled.Value = false
										hitChar.Humanoid.WalkSpeed = 6
										local highlightC = highlight:Clone()
										highlightC.Parent = hitChar
										task.wait(0.5)
										hitChar.Humanoid.rollingEnabled.Value = true
										highlightC:Destroy()
										hitChar.Humanoid.WalkSpeed = 16
										if hitChar:WaitForChild("Humanoid") and isRolling.Value == false and hitChar:WaitForChild("Humanoid").Parent ~= character then
											hitChar.Humanoid:TakeDamage(3)
											hitChar.Humanoid.rollingEnabled.Value = false
											hitChar.Humanoid.WalkSpeed = 6
											local highlightC = highlight:Clone()
											highlightC.Parent = hitChar
											task.wait(0.5)
											hitChar.Humanoid.rollingEnabled.Value = true
											highlightC:Destroy()
											hitChar.Humanoid.WalkSpeed = 16
										end
									end
								end
							end
						end
					end
				end
				debounceFlamethrower = false
			end
		end
	end)
	
	newHitbox:Start()
	task.wait(3)
	flamethrowerFlameC.Attachment.FlameParticles.Enabled = false
	newHitbox:Stop()
	guidePart:Destroy()
	flamethrowerSFXC:Destroy()
	task.wait(1)
	flamethrowerFlameC:Destroy()
end)

2 Likes

Is there a way to customize the Shape of the hitbox using the module

Do you require to call stop() or have a debris if the hitbox part instance is destroyed?

You can pass in any BasePart into the SizeOrPart parameter and use the InPart SpatialOption if you want to have a custom hitbox with a pre-determined shape. You can also cut spheres into sections via the DotProductRequirement parameter using Magnitude mode by leaving the SpatialOption blank and just passing in a number in the SizeOrPart parameter. HitboxClass also automatically creates areas/parts using the size given when a vector3 is passed in. You can find all its interactions on the documentation.

However, once the hitbox is made, the shape is immutable unless you wish to risk changing properties of the hitbox manually without using setters.

Yes, you’re required to use the Stop() method before the hitbox part instance is destroyed if you’re using the InPart option, because that part is required to perform a spatial query. It would likely cause errors if it doesn’t have a part to work with.

If you destroy the hitbox itself instead of the part, the hitbox will automatically destroy the part as well, so you could do that instead. Handling the hitbox through the Debris parameter automatically does this as well.