How to keep health impact and hit effect synchronous?

In a different thread it was mentioned that ghosts transferred from server to client are part of snapshots:

So it may be possible that only certain ghost entities are synchronized to the client first in a “partial snapshot” and other entities may follow in another snapshot at a later time. But this may cause some trouble. Imagine the following scenario:

8981188--1235641--upload_2023-4-29_10-51-19.png

We are viewing the scene as Player C observing Player A shooting successfully at Player B. The Shot is an instant hit without any delays after firing. The two ghost entities of interesst are Player B and the Shot caused by Player A. The entities are both interpolated. The Player B ghost has a higher importance than the Shot ghost. As far as I understand this may lead to the effect, that the Health Component of Player B is updated and sent from Server to the Client of Player C before the Shot entitiy (which has just spawned) has also been transferred to the Client of Player C. In this case this would lead to a decrease of the Health of Player B and thereafter the Shot effect is received and rendered for Player C.

In this situation it is most desirable to have the Player B entity and the Shot entity in the same snapshot updated or find some other synchronize mechanism to keep it look consistent for Player C. What is the proper approach to solve this issue?

Hello! Out of curiosity, is this an issue you’ve observed in testing?

This isn’t typically an issue that you need to solve manually, for the following reasons:

  • Even if the shot and the hp reduction are not sent in the same frame, they’re very likely to be sent within a few frames of each other (assuming 60Hz snapshot send rate, via ClientServerTickRate.NetworkTickRate) due to importance. Obviously, this is game configuration dependent.
  • The lifetime of the animated VFX (shot particles, blood etc) is likely going to be quite long (e.g. 0.5s = 30 frames at 60Hz), so a small disparity of when those VFX spawn will likely be imperceptible.
  • Interpolated ghosts are queued and buffered over x milliseconds (via ClientTickRate.InterpolationTimeMs) (or frames, via ClientTickRate.InterpolationTimeNetTicks), meaning that the two events are likely to both be applied on the same frame for the observer C, who is likely to already have full information about those state changes for both ghosts, by the time they’re actually applied.

I’d recommend testing this and capturing a video or gif. You can force snapshots GhostSendSystemData.MaxSendChunks to be 1 for the test, ensuring that the shot will be sent in a different snapshot than the hp reduction.

I ran a test on the Asteroids sample, and got this result when observing a ThinClient ship shooting an Asteroid.
Captured with Windows ScreenToGif at 60hz and slowed.
8981260--1235653--Observing360msConnection2.gif
8981260--1235650--Observing360msConnection.gif
You can see how the (forced interpolated for this test) bullet and the Asteroid (also force interpolated) are destroyed on exactly the same frame. It’s not your exact scenario, but interpolated despawns are queued in a similar way to how component updates are queued.

I modified the sample to include a HP value on the StaticAsteroid, and observed the correct behaviour there too. A GhostField’s SmoothingAction will determine how the HP value will change. Imagine the HP goes from 100 to 50 after the shot:

  • Clamp will simply change the value from 100 to 50 on the correct timeline (the interpolated ghosts timeline). I.e. It’ll sync perfectly with the projectile being despawned (or in your case, the frame the hitscan weapon was fired).
  • Interpolate will linearly interpolate from the last snapshot value (100) to the new snapshot value (50) at the rate at which the snapshots are received.
  • InterpolateAndExtrapolate will do the same as Interpolate, except that, if it is missing snapshot data for the next frame, it’ll continue the linear extrapolation, guessing a future value.

Clamp is suitable for HP values, as HP value changes tend to be discrete events.

1 Like

Thank you for the detailed explanation on the handling regarding to this issue. Indeed in the gifs you posted there is nothing odd to be seen still being somewhat similar case than the stated scenario. So overall this should not really be an issue, I understood.

No, I did not implemented it yet so have no objections. Only thought about the “partial snapshot” handling which CMarastoni mentioned and whether or not it has to be considered for implementing the stated scenario. So I will implement it without any special handling regarding the “partial snapshots” and make then some tests especially with the setting GhostSendSystemData.MaxSendChunks to 1 to check for any troubling effects. Thanks again.

Just a couple of extra details that are really important to have a full picture:

  • In general, latency in itself is not the problem for interpolated ghosts. packet loss and jitter are. High latency does not affect the interpolation playout.
  • New spawned entities get prioritised a little bit (depend on the setting FirstSendImportanceMultiplier). So it is possible that the Shoot entity is actually sent before the PlayerB (depending on the relative priorit, chunk scaling and the last time the PlayerB has been sent).
  • Also worth noticing that asteroids, as scenario, but in general ghosts despawns are a little special. Ghost despawns are sent always immediately, regardless of the ghost priority. And, unless there are a tons of them (about 100), the despawn events for the same tick are pretty much guaranteed to be sent together. In asteroids, the bullet and the asteroid are destroyed on the server at the same tick, and very likely applied together on the clients.
2 Likes

This info about despawning (and further info if there’s difference between static and dynamic f.i.) needs to be in the manual :slight_smile:

1 Like