O H so THAT’S how your hitboxes work. Okay yeah I have seen hitboxes like that a few times and I misunderstood what you were doing, so my apologies. I thought all along the hitboxes were something more like what I usually see, which run on either RaycastHitbox or .Touched, but now I understand.
Well, when it’s normal depends on what the weapon is and how the developer wants it to behave, but if we’re asking what is the most used hitbox method across all of Roblox, then the answer is not spatial queries.
.Touched and RaycastHitbox hitboxes (the latter being superior) are easier to script, thus making them more appealing over more complicated solutions. On the weapon behavior side of things, they’re ideal for when precision is desired, like for weapons with movesets players are meant to learn and master over time. Spatial queries are better suited for things that create hazardous regions, unlike sword blade swings and gun bullets, but very much like smash attacks and explosions. You’re using spatial queries to cover a sizeable region in front of the player, which is a perfectly valid and definitely not unheard of way to use them. Let me make it perfectly clear that I am not trying to persuade you to use a different method. I am curious what about it makes you use it instead RaycastHitbox though.
Alrighty enough rambling. I ended up writing an entire damn module while trying to figure out how to handle deleting the hitbox, which I concluded is shoving everything into a “detached” thread and constantly checking for things like the existence of the player, their character, etc. I’m certain you’ll have to modify the code below to work with your game’s specifics, but I hope this will serve as a basis.
--// MODULE SCRIPT
--// THIS USES THE INDEPENDENT THREAD SPAWNER
local SQHitbox = {}
local SpawnThread = game.ServerScriptService.SpawnIndependentThread
local wait = task.wait
type SpatialQueryHitbox = {
SetDamage: (num:number) -> (),
CreateHitbox: (CF:CFrame,Size:Vector3,SpawnDelayTime:number,AutomaticDestroyTime:number) -> ()
}
local HitboxDump = Instance.new("Folder")
HitboxDump.Name = "SQHitboxDump"
HitboxDump.Parent = workspace
function SQHitbox.New(Player:Player,Weapon:Tool): SpatialQueryHitbox
local PlrCharacter:Model = Player.Character
local DamageToInflict = 0
return {
SetDamage = function(num)
DamageToInflict = num
end,
CreateHitbox = function(CF,Size,SpawnDelayTime,AutomaticDestroyTime)
SpawnThread:Fire(function()
if PlrCharacter.Humanoid.Health <= 0 then return end
--// Get the player's tool so the script can see if it gets unequipped
--// Make sure it's the weapon tho
local EquippedTool = PlrCharacter:FindFirstChildOfClass("Tool")
if EquippedTool ~= Weapon then return end
local UnequippedConnection = nil
UnequippedConnection = Weapon.Unequipped:Connect(function()
UnequippedConnection:Disconnect()
UnequippedConnection = nil
end)
--// Wait
if SpawnDelayTime and SpawnDelayTime ~= 0 then
wait(SpawnDelayTime)
if not (Player and PlrCharacter and UnequippedConnection) then return end
end
--// Watch for the player's death
local HealthMonitorConnection = nil
HealthMonitorConnection = PlrCharacter.Humanoid:GetPropertyChangedSignal("Health"):Connect(function()
if PlrCharacter.Humanoid.Health <= 0 then
HealthMonitorConnection:Disconnect()
HealthMonitorConnection = nil
end
end)
--// Create the hitbox part
local HitboxPart = Instance.new("Part")
HitboxPart.Name = "SQHitbox"
HitboxPart.CFrame = CF
HitboxPart.Size = Size
HitboxPart.Anchored = true
HitboxPart.Transparency = 0.5
HitboxPart.Color = Color3.new(1,0,0)
HitboxPart.CastShadow = false
HitboxPart.CanCollide = false
HitboxPart.CanQuery = false
HitboxPart.CanTouch = false
HitboxPart.Parent = HitboxDump
--// Check for things hitting the hitbox
local OverParams = OverlapParams.new()
OverParams.FilterType = Enum.RaycastFilterType.Exclude
OverParams.FilterDescendantsInstances = {HitboxPart,PlrCharacter}
local TimeElapsed = 0
while TimeElapsed < AutomaticDestroyTime and UnequippedConnection and HealthMonitorConnection and Player and PlrCharacter do
local HitCharacters = {}
for i,v in pairs(workspace:GetPartsInPart(HitboxPart,OverParams)) do
if v.Parent:FindFirstChildOfClass("Humanoid") and not table.find(HitCharacters,v.Parent) then
table.insert(HitCharacters,v.Parent)
v.Parent:FindFirstChildOfClass("Humanoid"):TakeDamage(DamageToInflict)
end
end
OverParams:AddToFilter(HitCharacters)
TimeElapsed += wait()
end
--// Cleanup
if HitboxPart then
HitboxPart:Destroy()
end
if UnequippedConnection then
UnequippedConnection:Disconnect()
UnequippedConnection = nil
end
if HealthMonitorConnection then
HealthMonitorConnection:Disconnect()
HealthMonitorConnection = nil
end
end)
end
}
end
return SQHitbox
--// TOOL SCRIPT AND EXAMPLE AND MODULE DEMO
local Main = script.Parent
local Player = script.Parent.Parent.Parent --// The script starts while in the player's backpack
local SQHitbox = require(game.ServerStorage.SQHitbox).New(Player,Main) --// I named the module "SQHitbox"
local wait = task.wait
SQHitbox.SetDamage(25)
local deb = false
Main.Activated:Connect(function()
if deb then return end
deb = true
for count = 1,1000 do
if Player.Character.Humanoid.Health <= 0 or not Player.Character:FindFirstChild("HumanoidRootPart") then break end
SQHitbox.CreateHitbox(Player.Character.PrimaryPart.CFrame * CFrame.new(math.random(-35,35),math.random(-5,5),math.random(-35,35)),Vector3.new(6,6,6),0,2)
wait()
end
wait(1)
deb = false
end)
Here’s a video of what the code above creates:
I put way too much effort into these posts lol.