What Is the Most Accurate Melee Hitbox Method for AI in Roblox?

I’m currently working on a melee weapon AI. But when the AI swings its weapon, I’m not sure what kind of hitbox would be the most efficient and accurate.
What I’ve considered so far is attaching a part to the NPC’s arm and enabling the .Touched event when the NPC swings the weapon, so that it deals damage to any player who touches the part.
I’ve also considered using RaycastHitbox V4.
Which method would be the most effective and accurate, and are there any other good methods?

I personally like spartial query and magnitude

1 Like

What I’ve seen a lot of games do is create several hitboxes over a timeframe and then checking whats in those hitboxes.

So, for example, a punch creates 5 hitboxes over 0.5 seconds. Each hitbox is positioned infront of the character model, and has a set size.
The first hitbox may not hit anything, but the third hitbox could hit something given the agent rotated to face a player.

Essentially, its like a touched system, but more reliable.

I’d say this method is far more accurate than .Touched, a little less performant but overall, should be a barely noticeable difference.

An example of this would be:

local hitPlayers = {}
local hitbox = script.Punch --// we'll say this is a 3,3,3 scale sphere

for i = 1, 5 do
    local clone = hitbox:Clone()
    clone.CFrame = rootPart.CFrame + rootPart.CFrame.LookVector * 2
    clone.Parent = workspace
    local partsInHitbox = workspace:GetPartsInPart(clone, overlap)
    if #partsInHitbox > 0 then
        for _, part in partsInHitbox do
            local player = game.Players:GetPlayerFromCharacter(part.Parent)
            if player and not table.find(hitPlayers, player) then
                table.insert(hitPlayers, player)
            end
        end
    end
    clone:Destroy()
    task.wait(0.1)
end

for _, player in hitPlayers do
    hitPlayers.Character.Humanoid:TakeDamage(5)
end

^ useful

2 Likes

Personally, I prefer using spatial query when creating hitboxes because it’s one of the simplest and most effective methods. Some time ago, I wrote this module, and it’s been pretty useful for me—feel free to check it out:

local Hitbox = {}

local Overlap = OverlapParams.new()
Overlap.FilterType = Enum.RaycastFilterType.Exclude

function Hitbox.CastBox(CF, Size, MaxCharacters, Debug, ...)
	Overlap.FilterDescendantsInstances = {...}

	local CharactersOnHitbox = {}
	local NearestCharacters = {}

	local PartsOnHitbox = workspace:GetPartBoundsInBox(CF, Size, Overlap)

	for Index, Part in PartsOnHitbox do
		if CharactersOnHitbox[Part.Parent] then continue end
		if not Part:IsA("BasePart") or not Part.Parent:FindFirstChild("Humanoid") or Part.Parent.Humanoid.Health <= 0 or Part.Parent:GetAttribute("IFrame") or Part.Parent:FindFirstChild("ForceField") then continue end

		CharactersOnHitbox[Part.Parent] = (CF.Position - Part.Position).Magnitude
	end

	for key, value in CharactersOnHitbox do
		NearestCharacters[#NearestCharacters+1] = {Key = key, Value = value}
	end

	table.sort(NearestCharacters, function(a, b)
		return a.Value < b.Value
	end)

	local FinalCharacters = {}

	for Index = 1, MaxCharacters do
		if NearestCharacters[Index] then
			table.insert(FinalCharacters, NearestCharacters[Index].Key)
		end
	end

	if Debug then
		local Part = Instance.new("Part", workspace)
		Part.Name = "HitboxDebug"
		Part.Anchored = true
		Part.CanCollide = false
		Part.Size = Size
		Part.CFrame = CF
		Part.Transparency = .9
		Part.Color = Color3.fromRGB(255, 0, 0)
		Part.Material = Enum.Material.Neon

		game.Debris:AddItem(Part, 1)
	end

	if MaxCharacters == 1 then
		FinalCharacters = table.unpack(FinalCharacters)
	end

	return FinalCharacters
end

function Hitbox.CastSphere(Position, Size, MaxCharacters, Debug, ...)
	Overlap.FilterDescendantsInstances = {...}

	local CharactersOnHitbox = {}
	local NearestCharacters = {}

	local PartsOnHitbox = workspace:GetPartBoundsInRadius(Position, Size, Overlap)

	for Index, Part in PartsOnHitbox do
		if CharactersOnHitbox[Part.Parent] then continue end
		if not Part:IsA("BasePart") or not Part.Parent:FindFirstChild("Humanoid") or Part.Parent.Humanoid.Health <= 0 or Part.Parent:GetAttribute("IFrame") or Part.Parent:FindFirstChild("ForceField") then continue end

		CharactersOnHitbox[Part.Parent] = (Position - Part.Position).Magnitude
	end

	for key, value in CharactersOnHitbox do
		NearestCharacters[#NearestCharacters+1] = {Key = key, Value = value}
	end

	table.sort(NearestCharacters, function(a, b)
		return a.Value < b.Value
	end)

	local FinalCharacters = {}

	for Index = 1, MaxCharacters do
		if NearestCharacters[Index] then
			table.insert(FinalCharacters, NearestCharacters[Index].Key)
		end
	end
	
	if Debug then
		local Part = Instance.new("Part", workspace)
		Part.Name = "HitboxDebug"
		Part.Shape = "Ball"
		Part.Anchored = true
		Part.CanCollide = false
		Part.Size = Vector3.new(Size * 2, Size * 2, Size * 2)
		Part.Position = Position
		Part.Transparency = .9
		Part.Color = Color3.fromRGB(255, 0, 0)
		Part.Material = Enum.Material.Neon

		game.Debris:AddItem(Part, 10)
	end

	if MaxCharacters == 1 then
		FinalCharacters = table.unpack(FinalCharacters)
	end

	return FinalCharacters
end

return Hitbox
4 Likes

Thank you all three for your excellent suggestions. I tried out each method in my game after reading your advice. Personally, I found that using spatial queries works best for my particular setup. While it’s true that creating multiple parts can give the most accurate detection when attacking and moving (like in Forsaken), in my case, the approach below seems more suitable.

I really appreciate all your input and the time you took to help me out. Thanks again.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.