Tag game hitbox

Hi im making a tag game and i wanted it so that when the player touches another player they get tagged (unlike untitled tag game where u have to click to tag another player). So I just wanted to know what was the best and most optimal way for the hitbox detection (in my case).

What ive tried:

  • raycast hitbox v4: I tried this but I had to have the .HitStart function running the whole game and considering that there will be multiple players it would be very un-optimized.

  • Region3: same thing as raycasting where I have to keep running the same function every frame which would very un-optimised again.

  • .Touched: super inconsistent and doesnt always fire when the event should fire.

None of these methods r suitable for my game.

What r some other methods that will work in my game without being un-optimised (also considering that I am a beginner).

2 Likes

I think in a situation like this I would just simply turn it into Touched Event.
I could be wrong but i’d think it’s the most simple and optimal method for detecting when one player touches another.
A code example would probably look something like this:

---Server Script
local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        local rootPart = character:WaitForChild("HumanoidRootPart")

        -- Add Touched event to the player's hitbox
        rootPart.Touched:Connect(function(hit)
            local taggedPlayer = Players:GetPlayerFromCharacter(hit.Parent)

            -- Ensure the object is another player's character
            if taggedPlayer and taggedPlayer ~= player then
                print(player.Name .. " tagged " .. taggedPlayer.Name)
                
                -- Perform your tagging logic here
                -- Example: Update scores or change roles
            end
        end)
    end)
end)

I would probably recommend aiming for an enhancement though and not just leaving it at that,
For example,
To improve the gameplay experience or prevent false positives:

  1. Add a Cooldown:
  • Prevent multiple tags from being registered immediately:
local cooldown = {}
rootPart.Touched:Connect(function(hit)
    local taggedPlayer = Players:GetPlayerFromCharacter(hit.Parent)
    if taggedPlayer and taggedPlayer ~= player and not cooldown[taggedPlayer] then
        cooldown[taggedPlayer] = true
        print(player.Name .. " tagged " .. taggedPlayer.Name)
        
        -- Reset cooldown after 1 second
        task.delay(1, function()
            cooldown[taggedPlayer] = nil
        end)
    end
end)

Ensure Valid Collisions:

  • Check the distance between players to ensure it’s a valid tag:
if (rootPart.Position - hit.Position).Magnitude < 5 then
    print(player.Name .. " tagged " .. taggedPlayer.Name)
end

Use a Tagging Role:

  • Ensure only specific players (e.g., the “it” player) can tag others:
if player.Team.Name == "It" then
    print(player.Name .. " tagged " .. taggedPlayer.Name)
end

Sorry hindsight that seems like maybe more insight than requested. I’m kind of new to trying to help others and got carried away, But I hope this helps.

2 Likes

Thanks but I forgot to mention that ive tried using the touched event previously. I found out that player ping take a big effect (even if u run it locally). Also the event doesnt always fire and overall the event is just so inconsistent. I dont think adding enhancement will stop the event from being inconsistent as the event is fire first. I appreciate u trying to help tho!

We could try this XD


Revised Solution: Distance-Based Detection

Instead of relying on the Touched event, we can periodically check the distance between players to determine when one tags another. This method avoids the issues with network latency and the inconsistency of Touched.


Server Script
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

-- Tagging settings
local tagRange = 5 -- The distance required to tag another player
local tagCooldown = 1 -- Cooldown between tags (in seconds)
local taggedPlayers = {} -- To track cooldowns for tagged players

-- Function to handle tagging
local function handleTagging()
    local allPlayers = Players:GetPlayers()

    for _, player in ipairs(allPlayers) do
        local character = player.Character
        local humanoidRootPart = character and character:FindFirstChild("HumanoidRootPart")
        
        if humanoidRootPart then
            for _, otherPlayer in ipairs(allPlayers) do
                if otherPlayer ~= player then
                    local otherCharacter = otherPlayer.Character
                    local otherHumanoidRootPart = otherCharacter and otherCharacter:FindFirstChild("HumanoidRootPart")

                    if otherHumanoidRootPart then
                        local distance = (humanoidRootPart.Position - otherHumanoidRootPart.Position).Magnitude
                        if distance <= tagRange and not taggedPlayers[otherPlayer.UserId] then
                            -- Tag the player
                            print(player.Name .. " tagged " .. otherPlayer.Name)
                            
                            -- Perform tag logic (e.g., update roles, scores)
                            
                            -- Add a cooldown for the tagged player
                            taggedPlayers[otherPlayer.UserId] = true
                            task.delay(tagCooldown, function()
                                taggedPlayers[otherPlayer.UserId] = nil
                            end)
                        end
                    end
                end
            end
        end
    end
end

-- Run the tagging check periodically
RunService.Heartbeat:Connect(function()
    handleTagging()
end)

How This Works/Should work

  1. Distance-Based Tagging:
  • The script calculates the distance between players’ HumanoidRootPart objects using Magnitude.
  • If the distance is less than or equal to the tagRange, the tag is registered.
  1. Cooldown System:
  • A taggedPlayers table tracks recently tagged players and prevents them from being tagged repeatedly within a short time.
  1. Heartbeat Loop:
  • The RunService.Heartbeat loop runs at the frame rate but avoids excessive processing by only checking the players periodically.

I could be wrong but

Why This is Better

  1. Ping-Independent:
  • Distance checks are calculated on the server, ensuring that lag or ping doesn’t affect the detection.
  1. Consistent Behavior:
  • Unlike Touched, which relies on physics and collision events, this method uses deterministic distance calculations, making it far more reliable.
  1. Customizable:
  • You can adjust the tagRange and tagCooldown to fit your game’s design and balance.
  1. Lightweight:
  • Instead of running complex frame-by-frame checks, it uses a simple loop and processes only the necessary players.

I could use this but the only problem is that .Magnitude generates a spherical hitbox around the player. So the hitbox isnt really that accurate and doesnt match the overall “shape of the player”. Also im am pretty sure this is ping dependant because this is running on the server and not the client (at least thats what i think, i could be wrong tho).

1 Like

Sry for the inconvenience, i appreciate the help tho.

I could be wrong also XD but I want to say—server-side checks can feel ping-dependent because the server processes the last known positions of players, which might be slightly outdated if a player has high ping. However, the actual distance calculations themselves aren’t affected by ping—it’s just the data being used (player positions) that can cause discrepancies.

To make it feel more responsive, you can run the initial detection on the client and then validate the tag on the server. This way, the tagging feels instant on the client side, while the server ensures fairness.

I want to say The distance calculations (whether via .Magnitude or bounding box checks) are lightweight mathematical operations that the server is already performing as part of its normal physics and gameplay updates.

  • So No Extra Work: By performing calculations like .Magnitude or bounding box checks, you’re simply using data that the server already has in memory.
  • No Redundant Data: The server doesn’t fetch new data or perform additional network calls for these calculations—it uses the existing CFrame or Position properties of the players’ characters.

I really should of used bounding box instead of . magnitude, magnitude is slightly better performance wise if i remember correctly but its negligible if i remember correctly so XD
No worries on the inconvenience, i’d like to give this one more shot at least hopefully this other method fits your desired goals better.

---Client Script
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TagEvent = ReplicatedStorage:WaitForChild("TagEvent") -- RemoteEvent for tagging

local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoidRootPart = character:WaitForChild("HumanoidRootPart")

local tagCheckFrequency = 0.1 -- Throttle checks to every 0.1 seconds
local hitboxPadding = Vector3.new(1, 1, 1) -- Adjust hitbox size

-- Function to check bounding box overlap
local function isOverlapping(partA, partB, padding)
    local sizeA, posA = (partA.Size + padding) / 2, partA.Position
    local sizeB, posB = (partB.Size + padding) / 2, partB.Position

    return (math.abs(posA.X - posB.X) <= sizeA.X + sizeB.X) and
           (math.abs(posA.Y - posB.Y) <= sizeA.Y + sizeB.Y) and
           (math.abs(posA.Z - posB.Z) <= sizeA.Z + sizeB.Z)
end

-- Periodic tagging checks
while true do
    for _, otherPlayer in ipairs(Players:GetPlayers()) do
        if otherPlayer ~= player and otherPlayer.Character then
            local otherHumanoidRootPart = otherPlayer.Character:FindFirstChild("HumanoidRootPart")
            if otherHumanoidRootPart and isOverlapping(humanoidRootPart, otherHumanoidRootPart, hitboxPadding) then
                -- Notify the server of a potential tag
                TagEvent:FireServer(otherPlayer)
            end
        end
    end
    task.wait(tagCheckFrequency)
end
---SeverScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local TagEvent = Instance.new("RemoteEvent", ReplicatedStorage)
TagEvent.Name = "TagEvent"

local tagCooldown = 1 -- Cooldown in seconds
local taggedPlayers = {}
local hitboxPadding = Vector3.new(1, 1, 1) -- Ensure server validation matches client padding

-- Function to check bounding box overlap
local function isOverlapping(partA, partB, padding)
    local sizeA, posA = (partA.Size + padding) / 2, partA.Position
    local sizeB, posB = (partB.Size + padding) / 2, partB.Position

    return (math.abs(posA.X - posB.X) <= sizeA.X + sizeB.X) and
           (math.abs(posA.Y - posB.Y) <= sizeA.Y + sizeB.Y) and
           (math.abs(posA.Z - posB.Z) <= sizeA.Z + sizeB.Z)
end

-- Handle tag requests
TagEvent.OnServerEvent:Connect(function(player, targetPlayer)
    if taggedPlayers[targetPlayer.UserId] then return end -- Check cooldown

    local character = player.Character
    local targetCharacter = targetPlayer.Character
    if character and targetCharacter then
        local rootPart = character:FindFirstChild("HumanoidRootPart")
        local targetRootPart = targetCharacter:FindFirstChild("HumanoidRootPart")
        if rootPart and targetRootPart and isOverlapping(rootPart, targetRootPart, hitboxPadding) then
            -- Perform tag logic
            print(player.Name .. " tagged " .. targetPlayer.Name)

            -- Add cooldown for the tagged player
            taggedPlayers[targetPlayer.UserId] = true
            task.delay(tagCooldown, function()
                taggedPlayers[targetPlayer.UserId] = nil
            end)
        end
    end
end)

Enhancements

1. Hitbox Padding

  • By adding Vector3.new(1, 1, 1) to the HumanoidRootPart size in the bounding box check, the hitbox becomes slightly larger to account for animation variations or ping-induced discrepancies.

2. Client-Side Throttling

  • The tagCheckFrequency limits the client-side loop to run every 0.1 seconds instead of every frame. This reduces the load on the client and the number of remote events fired.

3. Client-Server Hybrid Approach

  • The client handles the initial detection to make the game feel responsive, while the server validates the tag for accuracy and fairness.

4. Cooldown System

  • Prevents multiple tags from being registered in a short period, avoiding abuse or unintended behavior.

5. Server Validation

  • The server ensures that only valid tags are registered, protecting against client-side manipulation or false positives.

XD hope this does it, these new enhancements should all bet much better. I can explain why to the best of my ability if it helps.

1 Like