Destroying replicated actors

I am seeing some inconsistencies in my project related to destruction of replicated actors.

In order to aid with debugging whether or not the actor is being destroyed on the client and server, I added some print functions to the blueprint as follows:

I am getting different results as follows:

Case 1: Server calls SetLifespan(0.01) to immediately destroy the actor. This is all done in C++.

  if (HasAuthority()) {
    creep->OnThroneReached();
    CheckGameOver();
 }
    
    
 void ACreep::OnThroneReached() {
    RewardIncome();
    
    SetLifeSpan(0.01f);
 }

The result from the print statements is:

96846-creepsreachedthrone.png

Case 2: Creeps are killed and DeathMulticast is called to notify everyone of the units impending doom. The implementation of this multicast is to simply call a blueprint event, allowing blueprint to do some visuals and set the lifespan.

UFUNCTION(Reliable, NetMulticast)
void DeathMulticast();

void ATowerWarCharacter::DeathMulticast_Implementation() {
	PlayDeath();
}

UFUNCTION(BlueprintNativeEvent, Category = "TowerWarCharacter")
void PlayDeath();

The logic in the blueprint is

This is where we start to run into problems. The server successfully destroys the unit, but the client never does.

96849-creepsdied_setlifespan.png

I was able to make a workaround to this issue by forcing both the client and the server to call destroy directly in blueprint.

This then results in an interesting behavior where it appears that the server drops the connection and hands the client authority over the actor. I’m unable to attach another image of this, but the result is:

Server: Server Destroyed
Server: Server Destroyed
Server: Server Destroyed
Server: Server Destroyed

and

Client 1: Server Destroyed
Client 1: Server Destroyed
Client 1: Server Destroyed
Client 1: Server Destroyed

I am somewhat at a loss as to why the behavior for actor destruction is behaving so differently between these cases. Any help is greatly appreciated!

I can’t quite explain everything you are seeing here. Let me try to give you some more info to help:

AActor::TearOff is the function that lets an actor become authoritative on clients. This is usually used when an actor goes into ragdoll before dieing (we no longer need to keep the exact position in sync between client and servers). I would see if this is being called for you or not. From your snippets, I don’t think it is. But from the behavior your describe, specifically the client thinking he has authority on the actor, I think something might be.

When an actor channel is destroyed, UActorChannel::CleanUp is called. This function should either directly destroy the actor, or tear it off. Assuming you are not tearing off, I don’t see how ::Destroyed never gets called on your clients. I would try setting a breakpoint there if you are able to create stable conditions where the actor in question is the only thing being destroyed.

Setting lifespan and using multicast functions should have no effect on this stuff. Lifespan itself is not directly replicated; just the actual destruction that happens at the end of the lifespan is (assuming nothing has torn it off!).

Hope that helps, let me know what you find.

Sure enough, some really old code that we ported from one of the sample projects was setting bTearOff to true on death. Thank you for the tip, this should fix a lot of issues we were having!