I was trying to make a kick system in Roblox Studio, but I don't know a way to make a Humanoid take damage without using the .Touched event, any suggestions?
Code below:
game.ReplicatedStorage.RemoteEvent.OnServerEvent:Connect(function(Player)
local character = Player.Character or Player.CharacterAdded:Wait()
local bv = Instance.new("BodyVelocity")
bv.MaxForce = Vector3.new(math.huge, math.huge, math.huge)
bv.Velocity = character.PrimaryPart.CFrame.LookVector * 50
bv.Parent = character.PrimaryPart
local animation = Instance.new("Animation")
animation.AnimationId = "rbxassetid://10442940385"
animation.Parent = character.Humanoid
local l = character.Humanoid:LoadAnimation(animation)
l:Play()
local footparticle = game.ServerStorage.Foot:Clone()
footparticle.Parent = character.RightFoot
footparticle.Parent = character.LeftFoot
task.wait(0.2)
game.Debris:AddItem(bv, 0.00001)
footparticle.Parent = nil
footparticle.Parent = nil
l:Stop()
end)
So based on the comments, you are looking to make a damage system, understand hitboxes, and avoid using the Touched event.
A hitbox is simply a region that you define for a limited time where you look for collisions with a player's character model. And detecting collisions is a fairly simple thing; you can even simplify it down to 1 dimension :
A*----------------B*
X*
In the region between the points A and B, you would say that X is inside it because it is greater than A and less than B. In code we might express that as :
local function didHitPoint(a : number, b : number, x : number) : boolean
return x >= a and x <= b
end
Now that's all well and good for a single point in a simple region. But what if you've got something like a square? We need to check if a region overlaps with another.
Case 1) Overlap on left
A*------------B*
X*-------Y*
Case 2) Overlap on the right
A*-------------B*
X*------------Y*
Case 3) AB larger than XY
A*---------------B*
X*---Y*
Case 4) XY larger than AB
A*----B*
X*--------------Y*
In each of these cases, we would say that the two regions are overlapping. So we need to update our logic :
local function didHitRegion(a : number, b : number, x : number, y : number) : boolean
local xInside = x >= a and x <= b
local yInside = y >= a and y <= b
local xOutside = x <= a
local yOutside = y >= b
-- case 1 and 2 and 3
if xInside or yInside then
return true
end
-- case 4
if xOutside and yOutside then
return true
end
-- not overlapping in any way
return false
end
So when it comes to 2 and 3 dimensions (thinking of squares and cube collisions) we can check if each axis has an overlapping region. And if all three dimensions have overlapping regions, we know that our object has collided with the hitbox. So to do this, we need to be able to calculate an axis-aligned bounding box. These are less accurate than object-aligned bounding boxes, but they are easier to conceptualize. Zeux, the technical fellow at Roblox, has this code sample for quickly computing an AABB. I have stripped out the comments explaining the code and adapted it for single parts and undoubtedly made it worse in the process, the original is very informative about the performance implications for each line. But using this function, we can calculate a bounding box that always aligns with the XYZ axes, and easily check if we are hitting it.
-- given a part, return the coordinates representing the minimum and maximum extents of the part
local function computeAABBForPart(part)
local abs = math.abs
local inf = math.huge
local cf = part.CFrame
local size = part.Size
local sx, sy, sz = size.X, size.Y, size.Z
local x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = cf:components()
-- https://zeuxcg.org/2010/10/17/aabb-from-obb-with-component-wise-abs/
local wsx = 0.5 * (abs(R00) * sx + abs(R01) * sy + abs(R02) * sz)
local wsy = 0.5 * (abs(R10) * sx + abs(R11) * sy + abs(R12) * sz)
local wsz = 0.5 * (abs(R20) * sx + abs(R21) * sy + abs(R22) * sz)
local minx = x - wsx
local miny = y - wsy
local minz = z - wsz
local maxx = x + wsx
local maxy = y + wsy
local maxz = z + wsz
-- rather than returning a Region3, let's return the min and max so we don't have to calculate it again
return Vector3.new(minx, miny, minz), Vector3.new(maxx, maxy, maxz)
end
-- given two Parts, return whether they are touching
local function didHitBox(part1 : Part, part2 : Part) : boolean
local part1Min, part1Max = computeAABBForPart(part1)
local part2Min, part2Max = computeAABBForPart(part2)
local xOverlaps = didHitRegion(part1Min.X, part1Max.X, part2Min.X, part2Max.X)
if not xOverlaps then
return false
end
local yOverlaps = didHitRegion(part1Min.Y, part1Max.Y, part2Min.Y, part2Max.Y)
if not yOverlaps then
return false
end
local zOverlaps = didHitRegion(part1Min.Z, part1Max.Z, part2Min.Z, part2Max.Z)
if not zOverlaps then
return false
end
-- if we have not escaped by now, we must be colliding
return true
end
All of this is really a simplified version of handling collisions. A more extensive explanation can be found in the book Real-Time Collision Detection, by Christer Ericson.
So now that we can detect when two parts are overlapping, we now need a way to decide what parts to compare against our hitbox. For simplicity, we might create a Part to represent the hitbox. And to get the other part, we might loop over each player's character model and grab some part in it.
-- In a Script placed inside a large transparent Part in the Workspace...
local hitBox = script.Parent
game["Run Service"].Heartbeat:Connect(function(delta)
local charactersTouching = {}
for _, player : Player in ipairs(game.Players:GetPlayers()) do
-- if the player has a character in the game, check if they are touching the box
if player.Character and player.Character.Parent ~= nil then
for _, part in ipairs(player.Character:GetChildren()) do
-- quick escape if we've already detected a touch
if charactersTouching[player.Name] then
continue
end
if part:IsA("BasePart") then
if didHitBox(touchPart, part) then
charactersTouching[player.Name] = true
end
end
end
end
end
-- DEBUG : Turn the box green if a player is inside the box, otherwise turn red
local isTouching = next(charactersTouching) ~= nil
touchPart.Color = if isTouching then Color3.new(0, 1, 0) else Color3.new(1, 0, 0)
-- list the hit players
--print("Players hit : " .. table.concat(playersHit, ", "))
end)
You may have noticed that in the last image, the character isn't really touching the hitbox. This is due to the inaccuracies with AABB collisions. AABB collision detection tends to be relatively fast, and some strategies will use AABB as a preliminary test before doing more accurate, and more expensive checks.
Okay, all of that was to get to here. We need to ...
-- MAKE SURE TO PULL IN THE computeAABBForPart, didHitBox, and didHitRegion FUNCTIONS DEFINED ABOVE
game.ReplicatedStorage.RemoteEvent.OnServerEvent:Connect(function(player)
local character = player.Character or player.CharacterAdded:Wait()
-- play the animation
local animation = Instance.new("Animation")
animation.AnimationId = "rbxassetid://10442940385"
animation.Parent = character.Humanoid
local loadedAnimation = character.Humanoid:LoadAnimation(animation)
loadedAnimation:Play()
-- create some particles
local footparticleLeft = game.ServerStorage.Foot:Clone()
local footparticleRight = game.ServerStorage.Foot:Clone()
footparticleLeft.Parent = character.LeftFoot
footparticleRight.Parent = character.RightFoot
-- spawn the invisible hitbox
-- NOTE : adjust the size and positioning yourself
local hitbox = Instance.new("Part")
hitbox.Anchored = true
hitbox.CFrame = character:GetPrimaryPartCFrame() * CFrame.new(0, 0, -5)
hitbox.Size = Vector3.new(2, 2, 4)
hitbox.CanCollide = false
hitbox.Transparency = 1.0
hitbox.Parent = game.Workspace
-- wait a moment
task.wait(0.2)
-- check which players are in the hitbox
local hitPlayers = {} -- <name : String, player>
for _, otherPlayer in ipairs(game.Players:GetPlayers()) do
if otherPlayer ~= player then
if otherPlayer.Character and otherPlayer.Character.Parent ~= nil then
for _, part in ipairs(otherPlayer.Character:GetChildren()) do
if hitPlayers[otherPlayer.Name] then
continue
end
if part:IsA("BasePart") then
if didHitBox(hitbox, part) then
hitPlayers[otherPlayer.Name] = otherPlayer
end
end
end
end
end
end
-- deal damage to the players caught in the hitbox
local DAMAGE = 10
for name, player in pairs(hitPlayers) do
-- the player should have a character still, but be safe about it
if player.Character then
player.Character.Humanoid:TakeDamage(DAMAGE)
end
end
-- clean up
footparticleLeft:Destroy()
footparticleRight:Destroy()
loadedAnimation:Stop()
animation:Destroy()
hitbox:Destroy()
end)