Advertisement

Predicting Object Spawns

Started by January 12, 2023 05:56 AM
3 comments, last by Kylotan 1 year, 11 months ago

So let's say we have a client with a rocket launcher. The client can predictively shoot this rocket, but how does he communicate it with the server? Let's put together a scenario.

  • Client shoots his rocket and predicts it fine. Server receives input and shoots the rocket fine. The only problem is that we don't have a ID mapped to this rocket yet to communicate rollback information to the client. How should this be synchronized? So that the client/server know that they're both talking about the same object?

Question #2: How do we rollback to a state where the predicted object didn't exist yet? If we receive a state update on the client, and one of our objects isn't in there. Then we just delete the object?

Question #3: How do we rollback to a state where the object exists on the server, but not on the client? Let's say the client mis-predicts shooting his rocket launcher and doesn't shoot anything, but the server receives that input and shoots it just fine. Now in our received state update on the client, we have an object that doesn't even exist yet. The way my netcode works is: whenever a state update comes in, the client reads into the first integer (networked object ID), pulls the object from memory, and feeds the data stream into that object, so the object can deserialize data directly into itself. If the object doesn't exist yet, this becomes very problematic.

For the first problem, there are various options, and each will work - you can choose based on what's easiest to implement:

  • Clients can allocate a temporary ID to the local entity and the server can reply with an authoritative ID, which the client can then replace it with
  • IDs can be comprised of of “process ID” + “local auto-increment” so the client creates the authoritative ID, which the server receives and replicates to everyone
  • Clients can replace their locally-controlled rocket entity with the server's provided rocket entity

It's rare you'd actually need or want to roll-back an entity you just created. There might be cases when you have to completely undo the firing (e.g. the player was killed on the server before the rocket was fired) in which case you just delete the whole thing. But the position and other state of that rocket is unlikely to be something you'd need to otherwise correct.

Question 2 - this depends on what exactly you're doing, but if you need to re-run physics simulations then yes, you'll need to be able to quickly revert to a state where the object doesn't exist at all. If it helps, you usually want to think of this as rolling back a simulation state and not rolling back your ‘scene’. You don't want to be deleting whole complex objects from the world and re-creating them during this process. You may need extra separation between your simulated world state, which is likely just physics primitives, and your more complex in-world state, with the various 3D models etc.

Question 3 - it's hard for me to imagine how that problem would happen. You probably want to change the system so that most things like this can't happen. Failure to create the object on the client side sounds like a bug, and therefore receiving a state update for a missing object needs to be handled as an error condition. If you can explain how this could possibly be valid, I might have a better suggestion.

Advertisement

@Kylotan So I assume the client's predicted spawn ID for the rockets (or other stuff) should be placed in the input packet right? And then should we also bundle an asset identifier with each predicted ID? Otherwise if the client somehow spawns multiple different predicted objects in a single tick (dropping different things from your inventory: bullets, guns, health packs), the server might get confused which ID belongs to which type of object.

As for the confusion on question #3, the way I deal with this normally on non-predicted spawns: Whenever the server sends a spawn RPC to each client, the clients have to ACK that spawn command. So the server only starts including state updates for that object for each player, when it knows they've acknowledged it.

So if the client (somehow, 0.001% chance) fails to properly predict the rocket launch (out of sync timer or something), but the server sees that it can actually launch the predicted rocket on that tick, but it ALSO sees that the client never sent any predicted IDs, should the server just ignore that then? Or rather, do a non-predicted spawn command to each client, including the player that shot it?

OR if the player predicts the rocket hit a moving player, but it actually didn't hit it on the server. Now we have another case of the object existing on the server, but not on the client. I assume like you said earlier that instead of deleting predicted objects, we should just put them in a “disabled” state until the server actually verifies that the objects have been deleted?

You're mixing a lot of low level implementation detail in with the high level concepts here, which makes it harder to understand. Forget about asset IDs, packets, ticks, and any other low level stuff. Solve the problem purely in terms of abstract information exchange first. Then, write the code to implement that.

To answer your first paragraph in these more abstract terms - the client needs to send enough information to the server for the server to be able to meaningfully process it, no more, no less. That will probably include an ID so that the server can respond and say “that thing you were calling ‘TempEntity123’, is really called ‘Entity456’ ”. Obviously if it's a per-process ID, the server doesn't need to tell the client of a replacement ID, it just tells it that the entity is confirmed to be created.

As for the rest… it's still not clear why a client should ever fail to ‘predict’ a rocket that it, itself, has launched. This isn't about how small the chance is - it should never happen. If it does, it's a bug, and all you have to do is decide how to report the bug so that you can fix it.

Unless you mean something different by “properly predict”. Usually, ‘prediction’ in most game networking is just a fancy way of saying ‘executed locally before the server had a chance to verify’. And this should be restricted to only what is essential for responsive gameplay.

I can't fathom a situation where the server somehow knows that it needs to spawn an entity based on client input when that client itself failed to spawn the entity. Perform whatever client-side validation you need to do first, then send the necessary information to the server about it.

allencook said:
if the player predicts the rocket hit a moving player, but it actually didn't hit it on the server

This should almost never happen, because the state of the rocket and the remote player are both being sent by the server and the client is only rendering what is being sent.

There are only really 2 situations where this isn't the case:

  • the initial ‘predicted’ phase where the server hasn't yet responded to confirm the rocket's existence
  • if/when you're doing some extrapolation of the remotely-controlled objects beyond the state that you've been sent.

In both these situations, you're only doing this for visual purposes and to reduce apparent latency, so you shouldn't be basing any actual game logic on it. No objects should be getting killed or despawned, apart from purely visual ones. e.g. You might predict that you need explosion visuals - but you shouldn't create a physics-based explosion client-side.

allencook said:
we should just put them in a “disabled” state

If you really need to be able to remove things from play based on the client's decision making, without waiting for the server, then a temporary ‘client-disabled’ state is a possibility. But you're mostly just opening yourself up to even more complexity, especially since you now have the potential for the rest of the game to get out of sync because you've disabled an object prematurely.

The simplest and best answer is to make no significant changes to the client-side simulation until the server requests them, except what is absolutely necessary for responsiveness.

This topic is closed to new replies.

Advertisement