MyActor->Destroy() not working for clients

I spawn an actor on the server and then want it destroyed later. If the server does this, it works. If a client does this, it doesn’t destroy. In the World Outliner the Mesh_1 will briefly change to *(Deleted Actor) *but then reappear as *Mesh_2 *

The spawning code (CPP):


void AMyCharacter::Server_InitializeMesh_Implementation()
{
    InitializeMesh();
}

void AMyCharacter::InitializeMesh()
{
    if (GetLocalRole() == ROLE_Authority)
    {
        FActorSpawnParameters SpawnParams;
        SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
        SpawnParams.Instigator = this;

        MyMesh = GetWorld()->SpawnActor<AStaticMeshActor>(BridgeMesh, GetTransform(), SpawnParams);
    }
    else
    {
        Server_InitializeMesh();
    }
}

This is the code I’m running to destroy it (works for server player):

cpp


void AMyCharacter::Server_BuildDeselected_Implementation()
{
    BuildDeselected();
}

void AMyCharacter::BuildDeselected()
{
    if (GetLocalRole() == ROLE_Authority)
    {
        if (MyMesh)
        {
            MyMesh->Destroy();
        }
    }
    else
    {
        Server_BuildDeselected();
    }
}

I’m under the assumption that AStaticMeshActor is replicated (it shows up nicely on all the clients, so yeah) so I’m confused as to why this doesn’t work.

You shouldn’t be calling Destroy yourself (generally). Instead, just set the actor to invisible, disable it’s collision, and set “MyMesh” to null (I’m assuming that’s the UPROPERTY that is holding the reference to your instantiated mesh object). Garbage Collection will see no one is referencing the object anymore, and properly clean it up on both client/server.

1 Like

Thanks for the reply, Matt. Unfortunately that hasn’t worked either (well it works for the server player, but not the clients).

I see someone had a similar issue here in BP, but I don’t see how their solution differs:
https://forums.unrealengine.com/deve…ed-actor/page2

This was the code I tried instead of Destroy():


void AMyCharacter::DeleteMesh()
{
    if (MyMesh)
    {
        MyMesh->SetActorHiddenInGame(true);
        MyMesh = nullptr;
    }
}

You should always Spawn and Destroy a replicated Actor on the server. A client has no authority to destroy a server replicated Actor. It would be easy to cheat as a client if you could just Destroy any Actor the Server attempts to replicate.

I agree. When a client attempts to do it, you can see in the original code that it checks for authority and asks the server to perform the SpawnActor() and Destroy() functions.

(Edit: the .h file has the Server_ versions of those functions set to UFUNCTION(Server, Reliable))

The client that calls the server RPC needs to be the owner otherwise the RPC call will be dropped.

That’s interesting. I’ll look into this when I get home… Thanks for the info.

Yeah its important to check if you are the owner, and also you can try this message passing route as well:

Client Sends Death RPC to Server() -> Server executes Death(check for owner) on Server() -> Server sends the Multicast for death to be called on each client()

I’m home now, so I’ve added the Owner to the FActorSpawnParameters, and logged it out to confirm, but it still won’t destroy (or hide and nullify) the AStaticMeshActor for the client players.

The cpp code is now:


void AMyCharacter::InitializeMyMesh()
{
    if (GetLocalRole() == ROLE_Authority)
    {
        FActorSpawnParameters SpawnParams;
        SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
        SpawnParams.Instigator = this;
**        SpawnParams.Owner = this;**

        MyMesh = GetWorld()->SpawnActor<AStaticMeshActor>(Bridge, GetBuildingTransform(), SpawnParams);

        if (MyMesh)
        {
            UE_LOG(LogTemp, Warning, TEXT("I am: %s and MyMesh's Owner is: %s"), *GetName(), *MyMesh->GetOwner()->GetName())
        }
    }
    else
    {
        Server_InitializeMyMesh();
    }
}

The log shows:
LogTemp: Warning: I am: BP_MyCharacter_C_3 and MyMesh’s Owner is: BP_MyCharacter_C_3

I’m really struggling to understand what I’m missing.

When using Destroy() you can see the World Outliner switch to Actor Destroyed for a moment before replacing it with a new version, one number higher… As if the server was trying to re-sync its presence maybe? But I’m specifically telling the server to do the destroying.