As a Roblox developer, it’s unnecessarily difficult to deal with server-sided hit detection without making gameplay miserable for a majority of the player base. I’ll try to provide as much information as possible to explain why this WOULD be a really good feature to have, since I’ve been really looking forwards towards something like this for a while.
All of my tests will be done with the WalkSpeed of 16, although it is worth to mention that the issue gets worse the faster you go for obvious reasons. I will be utilizing a specific fast flag - DFIntS2PhysicsSenderRate - allowing me to control the physics replication frequency. It defaults to just 15 replications per second, which is really low. IncomingReplicationLag is consistently set to 0 for all the provided clips.
First, I’ll clarify what I mean. For this demonstration, I coded a very simple sword tool:
Click here to view code
--!strict
local Players = game:GetService("Players")
local tool = script.Parent
local player = script:FindFirstAncestorOfClass("Player")
local character: Model
if player then
character = player.Character
else
character = assert(script:FindFirstAncestorOfClass("Model"))
player = Players:GetPlayerFromCharacter(character)
end
local rootPart = character:FindFirstChild("HumanoidRootPart"):: Part
local humanoid = character:FindFirstChildOfClass("Humanoid"):: Humanoid
local animator = humanoid:FindFirstChildOfClass("Animator"):: Animator
local size = Vector3.new(4, 5, 6)
local overlapParams = OverlapParams.new() do
overlapParams.FilterDescendantsInstances = {character}
end
local swingAnim = animator:LoadAnimation(script.Slash)
local nextSwing = 0
local function onActivated()
local t = os.clock()
if t < nextSwing then
return
end
nextSwing = t + 1
tool.Handle.SwordSlash:Play()
swingAnim:Play()
task.wait(0.2)
for _ = 1, 15 do
local cframe = rootPart.CFrame * CFrame.new(0, 0, -(size.Z - 1) / 2)
local hitList = workspace:GetPartBoundsInBox(cframe, size, overlapParams)
local hadHit = false
for _, part in hitList do
-- extremely lazy check because this is a demo
if part.Name == "HumanoidRootPart" then
hadHit = true
break
end
end
local hitbox = Instance.new("Part")
hitbox.Material = Enum.Material.ForceField
hitbox.Transparency = hadHit and 0 or 0.5
hitbox.Color = hadHit and Color3.new(0, 1, 0) or Color3.new(1, 0, 0)
hitbox.CFrame = rootPart.CFrame * CFrame.new(0, 0, -2.5)
hitbox.CastShadow = false
hitbox.CanCollide = false
hitbox.CanTouch = false
hitbox.CanQuery = false
hitbox.Anchored = true
hitbox.Size = Vector3.new(4, 5, 6)
hitbox.Parent = workspace
task.delay(1, hitbox.Destroy, hitbox)
if hadHit then
break
end
task.wait(1/60)
end
end
tool.Activated:Connect(onActivated)
ATTACKER PERSPECTIVE
To begin, we’ll check out how replication feels for the person holding the sword.
-
Regular replication frequency
It.. kind of works, until you realize how far behind the hitbox is, which results in hits that you believe should’ve registered, missing by a large margin.
That doesn’t look fun, now does it? The hitbox is barely able to keep up with my character in studio, where you have virtually no latency.
As you can probably imagine, it only gets worse past this point, eventually leaving the hitbox literally BEHIND you.
(For reference, I average 80-100ms (60ms on a good day) in game servers) -
Same scenario as above, but the replication frequency is raised to 60
That already looks WAY better. While not perfect, which is to be expected since we lack slight velocity-based compensation, it’s a major improvement from what we’ve seen previously.
Quick comparison (before / after)
TARGET PERSPECTIVE
For this one, I coded a basic server position visualizer, which is the green part that follows my character around.
Click here to view code
--!strict
local RunService = game:GetService("RunService")
local target = workspace:WaitForChild("0h7w7")
local targetRootPart = target:WaitForChild("HumanoidRootPart"):: Part
local p = Instance.new("Part")
p.Size = Vector3.new(2, 2, 1)
p.CanCollide = false
p.CanTouch = false
p.CanQuery = false
p.CastShadow = false
p.Anchored = false
p.Material = Enum.Material.Neon
p.Transparency = 0.5
p.Color = Color3.new(0, 1, 0)
p.CFrame = targetRootPart.CFrame
p.Parent = workspace
RunService.PostSimulation:Connect(function()
p.CFrame = targetRootPart.CFrame
end)
-
Regular replication frequency
I will be running away from a really basic AI that swings the sword whenever you’re in relatively close proximity.
According to what we see, this shouldn’t have hit, yet it does because the server position lags behind. To reiterate once more, this is observed at minimal latency and WILL be way worse in-game. -
Same scenario as above, but the replication frequency is raised to 60
Quick comparison (before / after)
Yet another issue from the same category:
-
Regular replication frequency
You can clearly see how it takes quite a bit of time for the server to receive the new position, which is absolutely terrible for games with fast projectiles. -
Same scenario as above, but the replication frequency is raised to 60
Once again, noticeable improvements (which makes a huge difference when it comes to dodging)
CONCLUSION
If Roblox were to provide a simple way to change the replication frequency, it would be way more feasible to rely on server-authoritative hit detection.
In my specific case, I wouldn’t be forced to write a custom replicator just to make the gameplay feel more fluid (for additional context, there were lots and lots of complaints about people getting hit from unreasonable distances until we implemented our own replicator, which mitigated that for the most part).
While I may be capable of managing all that on my own and, obviously, specialized solutions are usually better because they’re designed with the game in mind, it’s not really beginner friendly nor convenient for quick projects - it would be way better to expose this as an engine setting.




