Download

Replicate a simple class that derives from AActor

Hello, i created a new project based on the third person template and i created a simple class named ATestActor that derives from AActor:



UCLASS()
class ATestActor : public AActor
{
    GENERATED_UCLASS_BODY()
 
    UPROPERTY(Replicated)
    bool bIsTest;
};
 
----------------------
 
ATestActor::ATestActor(const class FPostConstructInitializeProperties& PCIP)
    : Super(PCIP)
{
    bReplicates = true;
}
 
void ATestActor::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
 
    DOREPLIFETIME(ATestActor, bIsTest);
}


On the character class i added the following:



UPROPERTY(Transient, ReplicatedUsing=OnRep_Test)
class ATestActor * Test;
 
UFUNCTION()
void OnRep_Test();
 
----------------------------
 
void AMyCharacter::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
 
    DOREPLIFETIME(AMyCharacter, Test);
}
 
void AMyCharacter::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);
 
       // The following code is just for testing purposes
 
    if (Role == ROLE_Authority)
       Test = GetWorld()->SpawnActor<class ATestActor>(ATestActor::StaticClass());
}
 
void AMyCharacter::OnRep_Test()
{
    // It never reaches here
}


When running with 2 clients (1 listen server + 1 pure client) the Test variable is always null on the clients and instanced on the server character instances. Can you tell me what am i missing? Thank you

P.S: What does Transient do and where is it used? I read that it’s when you don’t want to save the variable to disk, but when does that happen? When saving a savegame to disk?

Don’t do it in the tick event. Hook it to an input event and then see if it works.

I’ve put it on Tick just to make sure every character server side gets an instantiated ATestActor, i could put a if(Test == NULL) instantiate, to prevent it from spamming instantiations, but the result is the same. I tried making a client to server call using key event with the following code:



void AMyCharacter::OnPressedRKey()
{
    ATestActor * Test2 = GetWorld()->SpawnActor<class ATestActor>(ATestActor::StaticClass());
    ServerSetTest(Test2);
}
 
bool AMyCharacter::ServerSetTest_Validate(class ATestActor * NewTest)
{
    return true;
}
 
void AMyCharacter::ServerSetTest_Implementation(class ATestActor * NewTest)
{
    Test = NewTest;
}


I put a breakpoint before calling the ServerSetTest and inside it. When i press the R key on the client the breakpoint is activated and i see that Test2 is instanced and when i enter ServerSetTest_Implementation NewTest is NULL, so he’s just not being able to transmit this class.

My guess would be that it’s spawning at (0,0,0) so it isn’t considered as being “relevant” for your clients and not being replicated. Three things you could do that I can think of:

  • Set the new ATestActor’s location after spawning it to be (roughly) the same as your AMyCharacter so that it’d be near enough to be considered relevant
  • Set the ATestActor’s bAlwaysRelevant = true so that it’s always replicated whether relevant or not
  • Set the ATestActor’s owner to be your AMyCharacter and set bNetUseOwnerRelevancy = true so that it’s considered relevant if the AMyCharacter is relevant

Those are really good pointers essbuh, i will try it when i get home and i will let you know. I also have another question related to replication if i’m not being annoying, how can i tell that a character is the character the player is controlling?

I’m trying the following code:



void AMyCharacter::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);

    if (Role != ROLE_Authority && Controller != NULL && Controller->IsLocalPlayerController() && PlayerState->GetOwner() != NULL)
       GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Blue, FString::Printf(TEXT("%s"), *GetName()));
}


Using 3 clients (1 listen server + 2 pure clients) it prints 2 characters, so what am i missing?

GEngine would not work in this case, until you compile your project and launch as standalone.

Regarding replication. Actors do not replicate from client to server, they replicate from server to clients. So if you want your actor to be replicated, you have to spawn it on server-side first.

The GEngine->AddOnScreenDebugMessage is just to print a string to the screen, which he does, the main problem is that he finds 2 characters that meet that criteria of the if statement instead of just the one he’s controlling. I think that the Shooter Game sample has calls from the client to the server where he passes derived classes from AActor, or am i wrong and you can only send simple types?

You can, if the Actor originated on the server and was replicated down to the client. If it’s created on the client though it won’t be replicated up and created on the server.

As for the local player - I think you need to check if the Controller’s “LocalPlayer” property is null. Not sure off the top of my head if there’s another way to see if it’s actually a locally owned controller.

If you test multiplayer in PIE, GEngine is the same for all the clients, even though each client fins only one corresponding actor, it prints both.

The only solution for this is start in standalone.

essbuh, just tried it and it was a net relevancy issue, as soon as i set the owner it all worked, thank you :slight_smile: BiggestSmile sorry for misunderstanding what you said, i will try it and see if it works.

No problems about it :slight_smile:

By the way, it would be better to mark debug messages with some prefix to determine what client is printing the message. Or use some of the AHUD functionality.

Hello again, i’m trying to send my inventory from the client to the server using the ServerSetInventory(AInventory * Inventory) function, but when it runs on the server the Inventory variable is NULL with the error:

LogNetPackageMap:Warning: Could not find Object for: NetGUID <1, Inventory_2> (and IsNetGUIDAuthority())
LogNetPackageMap:Warning: Unable to resolve static NetGUID <1> from Path: Inventory_2
LogNetPackageMap:Warning: InternalLoadObject unable to resolve reference from NetGUID <1> (received full path: 1)

At first i was getting constantly the log:

LogNet:Warning: Actor Inventory / Inventory_0 has no root component in AActor::IsNetRelevantFor. (Make bAlwaysRelevant=true?)

I added a RootComponent (although i don’t know why it needs since it’s a NotPlaceable item and should only exists virtually in the character) but it didn’t solve the problem. I added bAlwaysRelevant = true; and bNetUseOwnerRelevancy = true; and set the owner, but it did not do anything. Do you have any idea how to solve this? Thanks again :slight_smile: