Broadcast Player Death without a NetMulticast

Watching an unreal stream a few months back about networking optimizations, they said that reliable **NetMulticasts **are very expensive to the network and should be avoided.

Going through the ShooterGame example I see that they indeed use a Reliable NetMulticast to broadcast a player’s death so that everyone can update their HUDs etc.

Is there a better, more network optimal way of creating this mechanic?

Event driven replication like that isn’t going to kill network performance.

Certainly not. But things do tend to pile up really quickly with networking especially when you aim for a big number of concurrent connections so I was just wondering if there was a more optimal way.

True, but honestly there are far worst things when you get in to large player counts. Epic solved or added solutions for a lot of them thanks to Fortnite though. If you’re really looking to optimize it, the absolutely cheapest way is to make a ReplicatedUsing byte and just increase it on death, then use that to trigger the died message. Course that doesn’t give any information other than “died” but it’s cheap. Once you get in to having to pass any type of information it’s either a total custom solution or just multicast. Could you save a few bytes with a custom solution sure, but the maintenance and readability will take a hit. You’re right that the real danger from a multicast is the calls getting away from the coder. But in something as rare (in relative terms) as dying it’s hardly going to be your choke point.

You don’t want to miss important events like “Player Death” so this is not where you should optimize. Things that are frequently updated like movement should however not be reliable but rather be a replicated variable that just sends the latest update if it can. Anything that only adds visual effects should also not be reliable as it doesn’t matter if it is dropped once in a while.

Thanks for that wisdom!

“Is there a better, more network optimal way of creating this mechanic?”

I agree that since death events don’t happen often at all, a reliable event is perfectly fine in terms of performance, but since you asked: another powerful and “reliable” approach is to treat an OnRep callback as an event.



UPROPERTY(ReplicatedUsing=OnRep_bIsAlive)
bool bIsAlive;

void OnRep_bIsAlive()
{
   if (!bIsAlive)
   {
      // Do your client-side PlayerDeath logic here.
   }
}


This approach works when the value doesn’t change often (and I assume here that IsAlive only ever changes once from true to false), because:

  • UE guarantees that the latest value on the server eventually arrives on the client (so its a way to implement a multicast event)
  • But on quick changes the client may not observe all values changes (its reliable only if the value doesn’t change often on the server)

So, to give a counter example of when NOT to use this. If you have a replicated integer that changes value on the server from 0 to 1, 2, 3, 4 ,5. It’s very possible that the client only observes the values 0, 2, 5 or 0, 3, 5 or 0, 5 etc because as I started only the latest value will arrive eventually unless it changes on the server before its replicated.

There are lots of potential issues by handling this via a replicated value, so I would avoid it.

  • Late-Join players will get a bunch of these notifications when they join a match. Yes you can code around it, but why bother adding complexity that will need maintaining later.
  • You can’t send any extra information about “how” they died, which is often a requirement, unless you pack it into a larger data struct. Then you’re paying the overhead to check if that struct/variable has changed every time the actor is considered for replication. By default, that’s up to 100 times a second.
  • If the player is killed in quick enough succession, or killed then immediatelly respawns, many players may miss out on the value changes.

Multicast is the best and easiest way to handle this IMO, it’s a rare one-shot event which is exactly what they are designed for.

You can always make it unreliable if you’re worried about bandwidth, but if you don’t have the bandwidth to spare for a few occasional reliable multicasts I would look elsewhere for optimisations.

Remember that the actor processing the Multicast must be network-relevant to the connection to be received by it (this never used to be the case).

I may be wrong about this, but last I knew, by default a Pawn was torn off immediately after dying, so if the client somehow missed the variable update, but then received the tearoff, I could see there possibly being a situation where the client has a living pawn that is dead everywhere else.

Thank you all for your informative replies.

There wasn’t really a bottleneck on death to begin with or a bandwith problem. I was just wondering if there was a more optimal way for this (out of paranoia if anything).

This will only happen if you handle dying by calling Destroy() on the actor as Destroy does also destroy the actor from the network.
In my case I can’t do that cause I need the player’s model to stay in the world as a corpse for some time after death so I am calling TearOff() manually when it makes sense to do so.

Fair points, we’re actually using both a replicated bIsAlive with associated OnRep_bIsAlive callback and an unreliable NetMulticast to share death details for different purposes. You’ll want a replicated bIsAlive variable anyway so that late joiners can show a player’s alive state in the HUD correctly. If you depend on a NetMulticast even to update the HUD, late joining clients will not have received that event and their HUD will be off. On the other hand, we’re using a NetMulticast event to share death information to generate a message to show in each client’s chat/combat log. That RPC is unreliable because in the rare case that the message doesn’t arrive, it just means some client didn’t print a death message to chat but the overall game state remains correct on the client.

Another relevant case, when our match ends and the final score panel is supposed to show on all clients (winning team + some match summary data that is calculated at the end), we’re spawning an actor “AGameEndinformation” that stores the match summary information as a replicated atomic struct (atomic ensuring that the struct entries are replicated at the same time). Again that’s using replicated values instead of NetMulticast, precisely so that people that enter the server after the match has ended will still have access to that information. It was relevant for us, since we have drop-in and let players reconnect if they d/c or crash.

I think this is a fair cheat sheet: [TABLE=“border: 1, cellpadding: 1, width: 500”]

Information that is…
Relevant to late joiners
Not relevant to late joiners

Crucial for game state
Replicated properties
Reliable NetMulticast call

Not crucial for game state
Replicated properties
Unreliable NetMulticast call

And then by replicated properties, I usually mean with OnRep_ callback assigned that you can trigger events from such as HUD refreshes. For example, in OnRep_IsAlive we in turn broadcast a dynamic multicast delegate, so that UI elements can subscribe to bIsAlive changes to refresh the HUD instead of checking the value every frame.

In some cases you’ll even want to use both replicated properties and RPCs, just to guarantee that every game or UI feature receives the information / doesn’t miss the information it relies on.