Simple Hitbox for melee attacks using GetPartsBoundInBox

Hello! I would like to share my hitbox script that uses GetPartsBoundInBox. It isn’t state-of-the-art and it could be coded better, but I’d still like to share it. I would suggest reading the pros and cons to see if this hitbox system fits your taste.

How it works

There are 4 Scripts:

  1. AttackInitiate(localscript)
  2. AttackVerify(serverscript)
  3. MeleeFunctions(modulescript)
  4. AttackInformationServer(modulescript)

AttackInitiate is as the name suggests, initates the attack. It’s here that the actual attack function(from MeleeFunctions) are called using UserInputService. A remote function is used to retrieve AttackInformationServer, which is in ServerScriptService; you could put it in ReplicatedStorage, but I did it this way to “hide” information although it might be a bit inefficient. A remote event is then used to send information, once certain input keys are pressed, to AttackVerify

AttackVerify just verifies the attack by comparing the hitbox data sent from AttackInitiate to the hitbox information from AttackInformationServer and if it all matches, then the attack is allowed. A spamdetect variable is used and resets once the timer is over; if the client, alters the cooldown via the local script to a shorter time, the remote event will fire even if spamdetect hasn’t reset, which will prompt a warning message about a potential exploiter(you can replace it with a kick or a ban). It also compares distances from the client’s hitbox and the client’s humanoidrootpart to each other and to their serverside counterpart in order to prevent any exploiting/ excessive latency.

MeleeFunctions is a module script in replicated storage that contains one function. The parameters are AttackName and the client’s Humanoid Root Part. This function basically returns a table of characters that are hit, if any.

AttackInformationServer is a dictionary with tables inside, each being different attacks

How to Use

Put AttackInformationServer and AttackVerify into ServerScriptService.
Put MeleeFunctions into ReplicatedStorage.
PutAttackInitiate in StarterCharacterScripts.

Create a remote function called “ReceiveScriptData” and put it into Replicated Storage
Create a remote event called “PunchEvent” and put it into Replicated Storage

Any Animations will also go into Replicated Storage and will be called inside AttackInformationServer

To make attacks:

  1. There is a template and examples in AttackInformationServer so use those for guidance.
if Character.Humanoid:GetState() ~= Enum.HumanoidStateType.Dead then
		if input.UserInputType == Enum.UserInputType.MouseButton1 and not gameProcessed and not Cooldown then  --change MouseButton1 to any input you want
			AttackFunction(AttackInformation.PunchAttack.Animation,AttackInformation.PunchAttack) -- parameter 1 calls the animation from the AttackInformationServer module script and parameter 2 is the name of the attack called from AttackInformationServer as well.
		end
	end
Code

AttackInitiate

--// Services //--

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

--// Events //--

local ReceiveScript = ReplicatedStorage:WaitForChild("ReceiveScriptData")
local ReceivedData = ReceiveScript:InvokeServer()

--// Variables //--

local AttackInformation = ReceivedData
local HitboxFunction = require(ReplicatedStorage:WaitForChild("MeleeFunctions"))
local Player = game.Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local HRP = Character:WaitForChild("HumanoidRootPart")
local PunchEvent = ReplicatedStorage:WaitForChild("PunchEvent")
local Cooldown = false

--// Functions //--

local function AttackFunction(Animation, AttackInfo)
	Cooldown = true
	local Animation = Character:WaitForChild("Humanoid"):LoadAnimation(Animation)
	Animation:Play()
	task.wait(AttackInfo.AttackChargeup)
	local HitDetected = HitboxFunction.AttackInitiated(AttackInfo, HRP)
	if	HitDetected and #HitDetected ~= 0 then
		PunchEvent:FireServer(AttackInfo, HRP, HitDetected)
	end
	task.wait(AttackInfo.Cooldown)
	Cooldown = false
end

--// 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 not Cooldown then  
			AttackFunction(AttackInformation.PunchAttack.Animation,AttackInformation.PunchAttack)
		end

		if input.UserInputType == Enum.UserInputType.MouseButton2 and not gameProcessed and not Cooldown then  
			AttackFunction(AttackInformation.BigPunchAttack.Animation,AttackInformation.BigPunchAttack)
		end]] --examples
	end

end)

AttackVerify

--// 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
local spamDetect = 0

--// Main //--

PunchEvent.OnServerEvent:Connect(function(player, AttackUsed, ClientHRP, Target)
	spamDetect = spamDetect + 1
	local AttackUsedName = AttackUsed.Name
	local ServerHRP = workspace:FindFirstChild(ClientHRP.Parent.Name):FindFirstChild("HumanoidRootPart")
	local UserHRPDistances = (ClientHRP.Position - ServerHRP.Position).Magnitude
	local MaxLatencyDistanceHitbox = AttackInformation[AttackUsedName].MaxLatencyDistanceHitbox 
	local UserHRPTargetDistance = math.abs(UserHRPDistances - AttackInformation[AttackUsedName].HitboxSize.Z) 
	for i, v in pairs(AttackUsed) do
		if v ~= AttackInformation[AttackUsedName][i] then 
			warn("AttackUsedValue differs. Possible exploiter")
			HitFail = true
			break
		end
	end
	if spamDetect == 1 then
		if not HitFail then
			if UserHRPDistances < MaxLatencyDistanceHRP and UserHRPTargetDistance < MaxLatencyDistanceHitbox then  --determines if clientside information differs by a certain amount from its serverside counterpart
				for i, v in pairs (Target) do
					v.Humanoid:TakeDamage(AttackInformation[AttackUsedName].Damage)
				end
			end
		else
			HitFail = false
		end
	else
		print("spam detected")
	end
	task.wait(AttackInformation[AttackUsedName].Cooldown) 
	spamDetect = 0
end)

MeleeFunctions

local Attack = {}

function Attack.AttackInitiated(AttackName,HRP)

	local HitDetected = nil
	local Target = {}
	local Attack = AttackName
	local Hitbox = Instance.new("Part")
	local HitboxOffset = CFrame.new(Attack.HitboxOffset)
	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.50 													--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
	local camera = workspace.CurrentCamera
	local cameraPosition = camera.CFrame.Position
	local cameraTarget = camera.CFrame.LookVector
	local x, y, z = camera.CFrame:ToOrientation()
	HRP.CFrame = CFrame.new(HRP.CFrame.Position) * CFrame.Angles(0,y,0)


	if Hitbox then
		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 not Target[v.Parent] then
					Target[v.Parent] = true
					Hitbox:Destroy()
					HitDetected = true
					Target[v.Parent] = false
				else
					task.wait()
					Hitbox:Destroy()
				end
			end
		else
			task.wait()
			Hitbox:Destroy()
		end
	end
	if HitDetected then
		local TargetNew = {}
		HitDetected = false
		for i, v in pairs (Target) do
			table.insert(TargetNew, i)
		end
		return TargetNew
	end

end

return Attack

AttackInformationServer

local Attack = {
--put attack information here
	--[[PunchAttack = { 
		
		Name = "PunchAttack",
		AttackChargeup = 0.1,
		HitboxSize = Vector3.new(4, 4.998, 3.364),
		HitboxOffset = Vector3.new(0,0,-2),
		HitboxCanCollide = false,
		HitboxAnchored = false,
		Cooldown = 0.5,
		HitboxDespawn = 0.1, -- might delete
		MaxLatencyDistanceHitbox = 5,
		Animation = game.ReplicatedStorage:WaitForChild("JabAnimation"),
		Damage = 10,
		HitboxParent = workspace

	},
	
	BigPunchAttack = {

		Name = "BigPunchAttack",
		AttackChargeup = 0.3,
		HitboxSize = Vector3.new(4, 4.998, 3.364),
		HitboxOffset = Vector3.new(0,0,-2),
		HitboxCanCollide = false,
		HitboxAnchored = false,
		Cooldown = 0.7,
		HitboxDespawn = 0.1,
		MaxLatencyDistanceHitbox = 5,
		Animation = game.ReplicatedStorage:WaitForChild("JabAnimation"),
		Damage = 30,
		HitboxParent = workspace

	}]]


}

local AttackTemplate = {
	
	Name = "Attack",
	AttackChargeup = 0.1, -- "wind-up" time for attack
	HitboxSize = Vector3.new(4, 4.998, 3.364), 
	HitboxOffset = Vector3.new(0,0,-2), -- offsets hitbox relative to character
	HitboxCanCollide = false,
	HitboxAnchored = false,
	Cooldown = 0.7, -- time before able to attack again
	HitboxDespawn = 0.1, -- not necesary/keep as is
	MaxLatencyDistanceHitbox = 5, -- latency checks between serverside info and clientside info
	Animation = game.ReplicatedStorage:WaitForChild("JabAnimation"),
	Damage = 30,
	HitboxParent = workspace -- just to visualize the hitbox

}

local function SendScript()
	return Attack
end

local ReceiveScriptEvent = game.ReplicatedStorage:WaitForChild("ReceiveScriptData")
ReceiveScriptEvent.OnServerInvoke = SendScript


return Attack

Pros/Cons

Pros:

  1. Simple
  2. Uses GetPartsBoundInBox rather than Touched
  3. Hitboxes are typically easy to understand compared to raycasts
  4. can hit multiple opponents
  5. relatively fast, I think
  6. once you understand how to use it, it seems pretty easy to make new attacks
  7. Relatively secure to exploiters changing hitboxes, I think

Cons:

  1. Hard to read code
  2. May or may not be efficient(contradicts point 5 in pro lol)
  3. not as accurate as raycasts
  4. Doesn’t have a slow down feature(slows down player when hitting)
  5. no visual effects or sound feature
  6. I don’t know how to make a downloadable service lol so set up is tedious
  7. no videos because I don’t know how to do that :frowning:

Thanks for checking it out! Let me know if there are any problems although I can’t promise I’ll be able to answer in-time or at all.

9 Likes