Best way to handle visible bullets in server authoritative multiplayer (UNet)

Hey guys,

I am working on implementing a 2D real time multiplayer game with server authority. I am still new in this whole networking and server authority stuff. However, so far I got movement, melee attacks and ranged attacks to work fine. I have hit a dilemma with bullets in ranged attacks.

Based on my research I found the below methods but I am not sure which is proper for server authority to prevent cheating and not strain the server at the same time:

  • Basically spawn the bullet on the server, give it velocity (it has RB2D) and then use NetworkServer.Spawn(). Bullet has a NetworkTransform on it with send rate = 0. Bullet has an OnCollisionEnter2D check. The check contains a [Server] method that checks if this is an enemy and then does the health calculation. To my understanding, this will only run collisions on the server and nothing will happen on the client until the server sends to the client that a collision took place. To circumvent lag, I am guessing I could just act upon the collision on the client too until server sends in information. However I am not to sure how to do reconciliation if no collision occurred but the client registered a hit by mistake. Until the bullet dies/collides with something the client knows nothing about the server.
  • Basically do the same as the above till I spawn the bullet. Once spawned I disable the bullet on the server and then wait till the client registers a hit or a bullet destruction. Once that happens, I simply reenable the bullet. Do a linear equation test that checks if there was a collision at the time of destruction/collision on the server. If there was one, I give the green light to the client, if not, I reconcile to the result the server has. To my understanding this saves calculation on the server but might be easily cheatable with clipping bullets or some such.
  • I forgo full physics for the bullet and use transform.Translate to move the bullet. Do the same as option 1. Then in FixedUpdate I fire a raycast that checks collision every Fixed Update loop with a very small distance. If there is a hit I send that hit to the client at the end of the loop (so by the time of the next loop, the bullet would have reached the location it saw the collision on). I don’t know if this is less strain than option 1 with RB2D and what not or not. But it crossed my mind.

Is there another better option? Can you explain it? If not, which of the options above do you think works best to prevent cheating AND to put less strain on the server? Why?

Note: the reason I am not just raycasting bullets and not actually firing them is that I have dodge in the game. So projectiles need to be visible so players can actually dodge them and raycasting will not work (at least I believe so).

Thanks for your advice in advance!

1 Like

I think the best way to go, is to allow players to shoot on both server and client at the same time, but calculate hits and apply damage only on server. That way you don’t need to do any prediction/reconciliation untill you’ve got aproximately same gun rotation/position.

Sure, sometimes there will be instances where bullets that wouldn’t hit a player would hit him, but it’s not that bad, up until 200ms. But in any case it’s not playable for a shooter far beyond that point.

Also, don’t use OnCollisionEnter, it’s bad. Use raycasts.

4 Likes

So let me see if I get this straight – this is basically option number 3? Fire the bullet on both server and client using NetworkServer.Spawn(). Then disable collisions (won’t use collider on the bullet and ignore OnCollisionEnter2D) but then fire a raycast in Fixed Update on the server till the bullet hits something or dies. If the bullet hits something I then send that information to the clients. Am I correct?

Would that strain the server when there are like 20 players and they are all shooting stuff? Moreover can you please explain why OnCollisionEnter2D is bad (I just want to understand that is all)?

2 Likes

Yep you’ve understood correctly. It’s simple as that. Servers are designed to be powerfull. A bunch of raycasts won’t slow it down that much.

Also, you don’t need to “wait” for the server to calculate/receive hit.
Just fire on both client and server.
If it hits on the client → hide projectile.
If it hits on server - do what you usually do - apply damage, etc.

That way it looks more “natural” rather than bullets flying through people.
Games are just smoke and mirrors. They don’t need to be 100% accurate representation of what’s going on.
That being said, if it’s a REALLY slow moving projectile, I would rather allow it to pass through. It depends on the game.

OnCollisionEnter is bad because it doesn’t always register collisions. Simple as that. Same applies to the OnTriggerX methods. That’s why raycasting/spherecasting/overlapcasting is better option. Because it works 100% of the time.

5 Likes

Gotcha, so the only reason behind the raycast vs OnCollisionEnter is registration of said event rather than performance right? No performance difference I am guessing between the two?

Also the projectile is an arrow or a bullet so it is slow enough to dodge but fast to look natural.

Thank you very much for your explanation and suggestion :slight_smile: I really appreciate it.

2 Likes

I know this is an old thread, but I’m also having trouble with bullet collision and I have a question about this. With a raycast, the collision happens instantly, no matter how far away the hit is happening. If OnCollisionEnter / physics collision is bad, what can be done to make it feel more real? What about gravity affecting the bullets? What about the time difference between firing the shot and arriving at it’s destination? Am I thinking too complicated or not complicated enough?

Specify distance to the raycast, and it will cast only for a certain distance. (E.g. travel distance per frame)
To make it use “gravity” shift raycast origin / direction each frame, and it will have that effect.

More compicated question is how to sync it.
Ideally you’d want to simulate on both server and client, and accept registered hits from the server.

My post above should be valid, unless you’d want to do something else on the client / handle desync cases otherwise.

1 Like

Haha I really didn’t expect a reply at all and certainly not that soon xD Thank you! So when people say Raycast instead of physics collision, they are not talking about one ray, but multiple rays over time. I think I get it, but let’s see what I can do with it. I find it really hard wrapping my head around client / server synchronization, especially when it comes to authority and communication, but let’s see. Thanks again for the quick reply, means a lot! :slight_smile:

I have a follow-up question :wink: You wrote that the more complicated part is synching the hit / collision. Do you mean synching the visuals or synching the collision check itself? And is the reason for wanting to not just simulating on the server so it looks better on the client side? At the moment I’m doing the raycasts only on the server and the clients just spawn visuals that are somewhat aligned and it looks decent enough for a first try, but I only tested it on my local machine without latency, so maybe it’s deceiving ^^

Its everything at the same time. Including latency and packet drops, desync, and rollback.
Plus making it look nice and smooth on the client. Plus making it cheatproof.

Most of the time its near impossible to achieve everything being indie.
Sacrifices has to be made, so do whatever works, until it breaks.

At least you should test on latency that somewhat resembles average on your target platform.

That’s why I don’t do multiplayer games anymore, its too time consuming for the single person or small teams.
Future netcode for DOTS looks promising, but then again, HLAPI looked the same. Time will tell.

1 Like

Especially when one is a self-taught nut like me and I’m very thankful for people like you taking the time to explain things in a way that is understandable for non-professionals.

I’m using Mirror and I let it do all the heavy lifiting, but that also means that I’m not yet familiar with what happens behind the curtains. Looking forward to what Unity will do Multiplayer-wise with the MLAPI team since I haven’t gotten around to learn DOTS/ECS in any meaningful way, even though it feels like that’s what I should be doing. Well, I digress xD

Thanks again, you really helped me.

1 Like

MentalGames I’m trying to figure out the same thing as you. This post has been really helpful! My plan is to have the client tell the server that he’s shooting in a direction, and then the server would spawn a bullet with a parametric movement function as a function of time, the spawn position, and the fire direction:
bulletposition = f(t, x, v).
This way I’m minimizing network traffic for lots of bullets.

The tip about raycasts is super valuable. I think the way I’m going to interpet this is every frame I raycast the bullet and check if it hits the player within the distance the bullet would travel within a frame or two. If the raycast would intersect the player with the bullet’s current velocity, then I call the do damage function.

Here’s MLAPI’s lag compensation docs. I’m currently working with MLAPI. Its got a good deal of control over this type of stuff. I’ll let you know how it goes!
https://mlapi.network/wiki/lag-compensation/