luarobloxluau

How I can make a damage system without using Touched?


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)

Solution

  • 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 :

    Calculating Collisions

    1) One Dimension - Point

        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.

    2) One Dimension - Region

    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
    

    3) Three Dimensions - Cube

    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.


    Detecting Collisions with Character Models

    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)
    
    Not Touching Touching DEBUG - with AABB visualized
    A cube in the workspace sits near a character. The cube is red. A character stands inside the cube. The cube is green. a character stands next to a cube. But the cube is still green due to inaccuracies with AABB collisions detection

    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.


    Putting it all together

    Okay, all of that was to get to here. We need to ...

    1. play the kick animation
    2. spawn the hitbox
    3. collect a list of which players have been hit
    4. despawn the hitbox
    -- 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)