Making a Sword Hitbox (For Beginners)

I have seen beginners having a hard time making a Sword Hitbox even though its really simple. Thats why I made this tutorial
(Disclaimer: We only sanity check the Hitbox and not the debounces)

First of all. Add this to your tool
image

Then put the code

Client:

-- // variables
local Player = game:GetService("Players").LocalPlayer
local Character 

repeat
	Character = Player.Character or Player.CharacterAdded:Wait()
	task.wait()
until Character

local Humanoid = Character:WaitForChild("Humanoid")
local Handle = script.Parent:WaitForChild("Handle")
local Hit = script.Parent:WaitForChild("Hit")
local Animation = Humanoid:LoadAnimation(script.Parent:WaitForChild("Animation"))

local Touched = {}

local Swing = false
local SwingDebounce = false
local SwingTime = .3
local SwingDebounceTime = .4

-- // Connect
script.Parent.Activated:Connect(function()
	if SwingDebounce then return end
	SwingDebounce = true
	Swing = true
	Animation:Play()
	task.wait(SwingTime)
	Swing = false
	Touched = {}
	task.wait(SwingDebounceTime)
	SwingDebounce = false
end)

Handle.Touched:Connect(function(hit)
	if not (hit.Parent:FindFirstChild("Humanoid") or Swing) or Touched[hit.Parent] then
		return
	end
	-- do hit fx or smth
	Touched[hit.Parent] = true
	Hit:FireServer(hit.Parent)
end)

Server:

-- // Variables
local Hit = script.Parent:WaitForChild("Hit")

local MagRange = 5
local Damage = 10

-- // Connect
Hit.OnServerEvent:Connect(function(player, hitChar)
	local magnitude = (player.Character:FindFirstChild("HumanoidRootPart").Position - hitChar:FindFirstChild("HumanoidRootPart").Position).Magnitude
	
	if magnitude <= MagRange then
		-- confirmed hit, fire all clients fx
		hitChar:FindFirstChild("Humanoid"):TakeDamage(Damage)
	end
end)

You may have noticed that I used Touched on the Client and Magnitude on the Server

Why? Because its more smoother and fluid hitbox. Touched on the client has better responsiveness while on the Magnitude on the server we just have to check if its actually close on the enemy and deal the damage

Thats basically it. I hope you learned something on this tutorial, its actually my first tutorial on the Dev Forum, if I made any mistakes please let me know. Thank you

4 Likes

Add a debounce on your server, because a exploiter can easily spam the remote event while tping to the player.

local Hit = script.Parent:WaitForChild("Hit")

local MagRange = 5
local Damage = 10
local DebounceTime = .4

local db = {}

-- // Connect
Hit.OnServerEvent:Connect(function(player, hitChar)
    if db[player] then return end
    db[player] = true
    task.delay(DebounceTime,function() db[player] = nil end)
	local magnitude = (player.Character:FindFirstChild("HumanoidRootPart").Position - hitChar:FindFirstChild("HumanoidRootPart").Position).Magnitude
	
	if magnitude <= MagRange then
		-- confirmed hit, fire all clients fx
		hitChar:FindFirstChild("Humanoid"):TakeDamage(Damage)
	end
end)
3 Likes

If possible, the RemoteEvent should not be stored in the tool. This is because for each tool, there’s a new remote, and it’s probably not good on memory. Maybe put it in ReplicatedStorage instead!

1 Like

You don’t need to use a repeat until in there, because Player.CharacterAdded:Wait() already wait for the character to exist and the script isn’t going to continue without it.

repeat
	Character = Player.Character or Player.CharacterAdded:Wait()
	task.wait()
until Character

This isn’t a good idea to use LocalPlayer in Tool Scripts, because it will not be found if the tool is stored in Replicated Storage or Server Storage, it will work only if the tool is already in the StarterPack.

local Player = game:GetService("Players").LocalPlayer

Instead you have to set the player and character related variables to nil at the top of the script, and update them when tool is equiped.

1 Like

Check Disclaimer, but yes this is how you do it.

It will still be found on the client even if its on the replicated storage but not server storage because client scripts could not work there.