Client side collision detection

Hi,

I’m working on a game where players need to avoid zombies in a maze.

I’m trying to get the best solution for collision detection between players and zombies.

At first I had server based detection, to see when a player hits a zombie. This is OK most of the time, but players with lag can be killed by a zombie that seems to be several blocks away on their screen, which does not feel good.

I’ve currently moved it over to be client based, it’s taken away the lag issues and game play is much better. However I’m worried that this would be exploitable.

I know the usual advice here is “double check on the server”, but I think often that is to stop people faking collisions that were impossible (e.g. shooting another player through a wall or hitting someone from miles away). That’s not the problem here.

I can’t see a good way to sanity check on the server for anyone who should have hit a zombie, but who somehow exploited to not.

Just to confirm, for the laggy players, I would want to give them the benefit of the doubt, so if on their screen they didn’t hit anything, I don’t want them to die. Even if the server thought that was a hit.
So the client side detection is behaving exactly as I want.
My concern is how to keep this, but prevent, as much as possible, it being exploited.

If possible Check both the Client and the server. The client can have a BoolValue thats true if the player is being touched on the client here is an example

local IsTouched = false
local RE = <Define a remoteEvent here> --Have this remote event be for ONLY zombies touching the player
local Player = game.Players.localplayer
local function IsZombie()
local Zombie = false
-- Write some code here to check if it is a zombie
return Zombie
end
Player.Character.HumanoidRootPart.Touched:Connect(function(Part)
local Hum = Part.Parent:FindFirstChild("Humanoid")
if Hum and IsZombie then -- Check if the touched part is part of a humanoid and is a zombie
IsTouched = true
end
end)
Player.Character.HumanoidRootPart.TouchEnded:Connect(function(Part)
local Hum = Part.Parent:FindFirstChild("Humanoid")
if Hum and IsZombie then -- Check if the touched part is part of a humanoid and is a zombie
IsTouched = false
end
end)

RE.OnClientEvent:Connect(function()
RE:FireServer(IsTouched)
end)

Thanks for replying!

I think my problem is, I don’t know what I’d do with that server check. My concern is that I do not want to kill laggy players, it just feels really bad gameplay when that happens. If they don’t see a collision on their screen then they should live, EVEN if the server showed there actually was a collision.
So the functionality of the client side detection is exactly what I want.

If because of lag someone gets past a couple of zombies that the server thought should have killed them, I’m fine with that.

So I know how to do a check on the server too - my problem is that if I see that, I don’t want to just kill the player. I’m not sure what I can do instead though.

As i said check on both the server and the client. If they both agree the player was touched by a zombie they should be killed. but if the server says the player was touched but the client says otherwise then dont. The script i typed out is an example of the client checking if the player was touched

I’m probably being dumb sorry… …but in this scenario can’t my exploiter still just disable the collisions on client side (I assume that they can, which is what I’m worried about)
The extra server check doesn’t seem to help this because the client says nothing touched, and they don’t die.

The only extra this gives, if I’ve understood right, is that in my current set up if the client says there is a hit but the server doesn’t, the player dies.
If I was doing a game where this was PvP hits, I’d definitely want this extra check to make sure that the client can’t fake collisions that aren’t there. In my scenario I don’t think I care about that, if the client says they die, then they die, and it doesn’t matter to me what the server thinks.

Hope that makes sense?

Ah i see. To solve the issue

You can run a loop to check if character parts just disappear on the client or if the CanTouch property is ever disabled as those are the only ways i believe you could disable client collision detection and if either of the 2 happen kick the player from the game

and if you do take the script i wrote change it to

local IsTouched = false
local RE = <Define a remoteEvent here> --Have this remote event be for ONLY zombies touching the player
local Player = game.Players.localplayer
local function IsZombie()
local Zombie = false
-- Write some code here to check if it is a zombie
return Zombie
end
Player.Character.HumanoidRootPart.Touched:Connect(function(Part)
local Hum = Part.Parent:FindFirstChild("Humanoid")
if Hum and IsZombie then -- Check if the touched part is part of a humanoid and is a zombie
IsTouched = true
RE:FireServer(IsTouched)
end
end)
Player.Character.HumanoidRootPart.TouchEnded:Connect(function(Part)
local Hum = Part.Parent:FindFirstChild("Humanoid")
if Hum and IsZombie then -- Check if the touched part is part of a humanoid and is a zombie
IsTouched = false
end
end)
1 Like

Thanks again!

I guess I can’t reliably check if an exploiter has changed CanTouch. If they do it client side then I need to be running client side code to check it. They can, I believe, bypass or disable any checks I put in.

It does no harm to add those checks in, but it wouldn’t stop anyone who knew what they were doing.
The only safe way to check things is going to be by some sort of server sided extra check, I know that.

The suggestion of checking in both server and client is perfect for the scenario where you care about people exploiting and trying to fake a collision that should not have happened.

What I don’t know is how to do a similar, server based, check to try to detect people who are trying to hide collisions that should have happened. Without just killing someone based on a server side collision, because of the lag problems from that.

Would do this tho. If a player has been touched server sided lets say 10 times but the client said no everytime kill the player just to be safe

1 Like

You could maybe use a remote event right?

yes that the only way for the server and client to speak to one another

There’s a specific framework you’d need to adapt to for you to achieve a product where it won’t be easily exploited and will accommodate lag.


Server

The server will be handling the collisions, and how pathfinding works. Every time an NPC changes directions (not position), the server will fire to the client (via remote event) where the NPC wants to go.

local RemoteEvent = -- The remote event

ZombieA.DirectionChanged:Connect(function(goal) -- Not a real function, just for clarity
    RemoteEvent:FireAllClients(ZombieA, goal) -- "ZombieA" is a string
end)

ZombieB.DirectionChanged:Connect(function(goal) -- "goal" is the position the zombie wants to go [CFrame or Vector3]
    RemoteEvent:FireAllClients(ZombieB, goal)
end)

ZombieA.Touched:Connect(function(hit)
    -- If it hits the player, kill them
end)

Client(s)

The client will handle the server calls. When the server fires a target location to the client for an NPC, the client will now animate that NPC, with consideration for client ping.

local RemoteEvent = -- The remote event
local Ping = -- The clients ping

RemoteEvent.OnClientEvent:Connect(function(zombie, goal)
    -- Offset the zombie based off of [Ping / 2] and "goal"
    workspace[zombie]:SetTarget(goal)
end)

Notes

Let’s say ZombieA has a walk speed of 1 stud per second. They are at position Vector3.new(0, 0, 0). If the server fires to the clients and tells it that ZombieA now wants to move to Vector3.new(10, 0, 0), it would take 10 seconds to walk there (Vector3.new(0, 0, 0) is 10 studs away from Vector3.new(10, 0, 0)). But, we have to consider the ping of the client. So the client has a ping of 1000ms, which is 1 second. So we lerp the 2 vectors from that:

Vector3.new(0, 0, 0):Lerp(Vector3.new(10, 0, 0), (Ping / 1000) * ZomebieWalkspeed)
         ^                          ^                   ^               ^
Original Location            Target Location       Ping(seconds)    Walkspeed
1 Like

Thanks again. This is the best I can think of at the moment too, but I think getting the right “10 times” value may be hard.
It could still kill laggy players, but does it slower, so its still not an ideal solution.
I don’t know if there is anything better though…
I’ll do some testing with this and see if it works.

Thanks! I’ll have a look at that too, see if I can use that to correct the position of zombies based on player lag

Setting the network ownership (via :SetNetworkOwnership()) of the zombie parts to the server (or the closest player) may make the lag way less since usually replication for objects close to players goes like this (Client > server > client).

I had this problem in my game too and setting it to the server fixed the bugs.

2 Likes

Thanks, yeah I’ve got network ownership set to nil
I’m not using physics anyway, movement is all CFrame based, but its set just in case it helps.

I’ve looked at changing speeds and moving parts on the client but I’m not sure how well that will work for me. I’ll keep checking this.

I’ve looked at some sort of counts on server side checks, but thats either not detecting anything, or giving false positives, so i dont think will work well.

I’m really happy with the behaviour that the client side collision detection gives, it’s how i want the game to work.

What I’m still looking for are ways to keep this behaviour but to try to protected against at least obvious exploits.

EDIT, another thing to add on this. I’m trying to make this feel like an old-style arcade game, which means that one hit on an enemy will kill you outright. Which is another reason why getting this feeling right matters. If it just took a small chunk of health off then occasional glitches in collision detection due to lag wouldn’t matter, and I’d go back to doing everything on the server

I’ve used this approach for now, ty.
my main checking is still client side collisions.

But I also detect hits on the server and if there are “too many” of these I’ll kill the player too. This can still false positives for laggy players, but it should be possible to fine tune the counting to of how many “too many” is, to make this work ok