Why is a fresh third person template project instancing more characters than connected clients?

Hello, i’ve created a fresh third person template project and i only added the following code:

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

	GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Blue, GetName());
}

I set the editor to use 2 clients (1 listen server + 1 client) and every frame for both clients is printing 4 different names MyCharacter_C_0, MyCharacter_C_1, MyCharacter_C_2, MyCharacter_C_4 (or other similar values, but always 4 entries). If you increase the number of clients the number of instanced characters increases exponentially. If i run one of the windows on the editor viewport the Scene Outliner only shows 2 instances. Why is he creating more character instances than clients?

This happens because each instance of the game has a replicated copy of both actors: player 1 and 2 exist on both the server and client.

So for example MyCharacter_C_0 is the character i’m controlling, MyCharacter_C_1 is the other player i’m seeing, and MyCharacter_C_2 and MyCharacter_C_3 are replicated copies of the character i’m controlling and the other player i’m seeing?

Yes exactly.

And should those copies execute the normal workflow? Tick, BeginPlay, etc? Because MyCharacter_C_3 crashes on Tick for not having a Controller on a call that i make to deproject mouse coordinates, the replicated copies should have a controller? If not then why does the other player replicated copy doesnt crash there?

MyCharacter_C_8

  •   RemoteRole	ROLE_SimulatedProxy (1)	TEnumAsByte<enum ENetRole>
    
  •   Role	ROLE_Authority (3)	TEnumAsByte<enum ENetRole>
    
  •   PlayerState	0x000000001f1c1000 (Name=0x00000000046213d8 "PlayerState"_12)	APlayerState *
    
  •   Controller	0x000000003027f200 (Name=0x0000000012394ba8 "BasePlayerController"_6)	AController *
    

MyCharacter_C_9

  •   RemoteRole	ROLE_AutonomousProxy (2)	TEnumAsByte<enum ENetRole>
    
  •   Role	ROLE_Authority (3)	TEnumAsByte<enum ENetRole>
    
  •   PlayerState	0x000000001f1c2000 (Name=0x00000000046213d8 "PlayerState"_15)	APlayerState *
    
  •   Controller	0x000000003048f900 (Name=0x0000000012394ba8 "BasePlayerController"_7)	AController *
    

MyCharacter_C_10

  •   RemoteRole	ROLE_Authority (3)	TEnumAsByte<enum ENetRole>
    
  •   Role	ROLE_AutonomousProxy (2)	TEnumAsByte<enum ENetRole>
    
  •   PlayerState	0x0000000000000000 <NULL>	APlayerState *
    
  •   Controller	0x000000001c417200 (Name=0x0000000012394ba8 "BasePlayerController"_8)	AController *
    

MyCharacter_C_11

  •   RemoteRole	ROLE_Authority (3)	TEnumAsByte<enum ENetRole>
    
  •   Role	ROLE_SimulatedProxy (1)	TEnumAsByte<enum ENetRole>
    
  •   PlayerState	0x0000000000000000 <NULL>	APlayerState *
    
  •   Controller	0x0000000000000000 <NULL>	AController *

The server has player controllers for all players, but clients only have a player controller for the local player. You should check that the player controller on a pawn is non-null AND that IsLocallyControlled() returns true before executing code intended for the local player.

Thank you very much for your help, i just have one more doubt. Is there a way to know that you are the character you are controlling? Just using IsLocallyControlled is not enough because that is only telling me if it’s being controlled by someone.

Clients will have an APlayerState for each player in the game but only one APlayerController for themselves. Servers will have an APlayerState and APlayerController for each player (human or otherwise) in the game. Listen Servers specifically will have to identify which is theirs.

The APlayerState array (PlayerArray) is on the AGameState, accessible on both client and server.

APlayerState’s owner is an APlayerController. If the owner is NULL on clients, then its not local. If the owner is not null and not IsLocalPlayerController (or IsLocalController) then its not local on the server.

Just watch out that IsLocalController isn’t used during player controller construction/destruction, because its behavior is undefined.

Sorry, I’ll add that a Pawn/Character also has a pointer to the same AController, so you can get up the chain that way as well.

Thank you for your reply. I tried this 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()));
}

And with 3 clients (1 listen server and 2 pure clients) it printed 2 characters, am i missing something?

Role != ROLE_Authority - To make sure it’s a client
Controller != NULL - To check if it has a valid controller
Controller->IsLocalPlayerController() - To check if it has a valid player controller
PlayerState->GetOwner() != NULL - To check if the player state has a valid owner