How to make proper hitboxes?

So i already have a hitbox system implemented, it just has a major issue. whenever i call the hitbox it yields the code calling it for the amount of duration, i haven’t found any workaround this because if i remove the line of code that’s yielding then it can’t find a target at all.

any help is appreciated

function module.CreateHitbox(Size, Duration, Cframe, char, isAOE)
	local connection
	local overlap = OverlapParams.new()
	overlap.MaxParts = 100
	overlap.FilterType = Enum.RaycastFilterType.Include
	overlap.FilterDescendantsInstances = workspace.Enemies:GetChildren()
	
	
	-- visual
	local hitbox = Instance.new("Part")
	hitbox.Parent = workspace
	hitbox.Size = Size
	hitbox.CFrame = Cframe
	hitbox.Anchored = true
	hitbox.Transparency = 0.5
	hitbox.BrickColor = BrickColor.new("Really red")
	hitbox.CanCollide = false
	game.Debris:AddItem(hitbox, Duration)

	local damagedTargets = {}
	local detectedTargets = {}

	connection = runservice.Heartbeat:Connect(function()
		local hitParts = workspace:GetPartBoundsInBox(Cframe, Size, overlap)
		for _, part in ipairs(hitParts) do
			if not damagedTargets[part.Parent] and part.Parent ~= char then
				damagedTargets[part.Parent] = true
				table.insert(detectedTargets, part.Parent)
				if not isAOE then
					break
				end
			end
		end
	end)
	
	local startTime = os.clock()
	while os.clock() - startTime < Duration do
		if #detectedTargets > 0 then
			break
		end
		runservice.Heartbeat:Wait()
	end

	connection:Disconnect()

	if isAOE then
		return detectedTargets
	else
		return detectedTargets[1]
	end	
end

just use this bro: Raycast Hitbox 4.01: For all your melee needs!

Yeah but I’m not making just melee, i want to have large hitboxes for specific abilities and such

The yielding is coming from this section:

	local startTime = os.clock()
	while os.clock() - startTime < Duration do
		if #detectedTargets > 0 then
			break
		end
		runservice.Heartbeat:Wait()
	end

If you dont want the function to yield, you’ll need to wrap the function’s code in a task.spawn then have the function also take a “callback” function to run on the results.

It’s not possible to have a function that returns stuff in the future not yield, so you need to have it not return and thing and instead also take a function to run on the targets or return an event and fire the event when the targets are found.

Here are those options:

function module.CreateHitbox(Size, Duration, Cframe, char, isAOE, targetsFunction)
	local connection
	local overlap = OverlapParams.new()
	overlap.MaxParts = 100
	overlap.FilterType = Enum.RaycastFilterType.Include
	overlap.FilterDescendantsInstances = workspace.Enemies:GetChildren()
	
	
	-- visual
	local hitbox = Instance.new("Part")
	hitbox.Parent = workspace
	hitbox.Size = Size
	hitbox.CFrame = Cframe
	hitbox.Anchored = true
	hitbox.Transparency = 0.5
	hitbox.BrickColor = BrickColor.new("Really red")
	hitbox.CanCollide = false
	game.Debris:AddItem(hitbox, Duration)

	local damagedTargets = {}
	local detectedTargets = {}

	connection = runservice.Heartbeat:Connect(function()
		local hitParts = workspace:GetPartBoundsInBox(Cframe, Size, overlap)
		for _, part in ipairs(hitParts) do
			if not damagedTargets[part.Parent] and part.Parent ~= char then
				damagedTargets[part.Parent] = true
				table.insert(detectedTargets, part.Parent)
				if not isAOE then
					break
				end
			end
		end
	end)
	
	task.spawn(function()
		local startTime = os.clock()
		while os.clock() - startTime < Duration do
			if #detectedTargets > 0 then
				break
			end
			runservice.Heartbeat:Wait()
		end

		connection:Disconnect()

		if isAOE then
			targetsFunction(detectedTargets)
		else
			targetsFunction({detectedTargets[1]})
		end
	end)
end

Example usage:

local function destroyTargets(targets)
	for _, target in ipairs(targets) do
		target:Destroy()
	end
end

module.CreateHitbox(Size, Duration, Cframe, char, isAOE, destroyTargets)

With an event:

function module.CreateHitbox(Size, Duration, Cframe, char, isAOE)
	local connection
	local overlap = OverlapParams.new()
	overlap.MaxParts = 100
	overlap.FilterType = Enum.RaycastFilterType.Include
	overlap.FilterDescendantsInstances = workspace.Enemies:GetChildren()
	
	
	-- visual
	local hitbox = Instance.new("Part")
	hitbox.Parent = workspace
	hitbox.Size = Size
	hitbox.CFrame = Cframe
	hitbox.Anchored = true
	hitbox.Transparency = 0.5
	hitbox.BrickColor = BrickColor.new("Really red")
	hitbox.CanCollide = false
	game.Debris:AddItem(hitbox, Duration)

	local damagedTargets = {}
	local detectedTargets = {}

	connection = runservice.Heartbeat:Connect(function()
		local hitParts = workspace:GetPartBoundsInBox(Cframe, Size, overlap)
		for _, part in ipairs(hitParts) do
			if not damagedTargets[part.Parent] and part.Parent ~= char then
				damagedTargets[part.Parent] = true
				table.insert(detectedTargets, part.Parent)
				if not isAOE then
					break
				end
			end
		end
	end)
	
	local bindable = Instance.new("BindableEvent")
	task.spawn(function()
		local startTime = os.clock()
		while os.clock() - startTime < Duration do
			if #detectedTargets > 0 then
				break
			end
			runservice.Heartbeat:Wait()
		end

		connection:Disconnect()

		if isAOE then
			bindable:Fire(detectedTargets)
		else
			bindable:Fire({detectedTargets[1]})
		end
	end)
	return bindable.Event
end

Example usage:

local gotTarget = module.CreateHitbox(Size, Duration, Cframe, char, isAOE)

gotTarget:Once(function(targets)
	for _, target in ipairs(targets) do
		target:Destroy()
	end
end)

you can also use it for large hitboxes. It would make more sense to modify a module that is already really good than to make your own.

It kind of works but like it only delays the things inside of the function rather than the entire script now

I’m confused: do you want both bits of code to not yield? I.e. change the hitbox detection so it doesn’t use a duration parameter at all? I.e. make both return instantly and find the targets instantly?

So to rephrase that, is the problem is that GetPartBoundsInBox isn’t getting the targets unless you do multiple checks over a short time?