Movement replication with custom Pawn

Hello everybody.

(Using 4.14.1 for future readers)

I know this has been asked many many times, but most answers are just referring to the wiki/doc, which are sometimes very confusing or lacking simple basic example.

I’m making an RTS kind of game, and right now, I have my custom Pawn, my custom PlayerController and custom GameMode.
I can move around and bend my camera, everything is fine in single player.

Thing is, in multiplayer, client can see the server moving, but no the other way around.
I’ve read lots of threads about that, plus the doc, but everything is really confusing for me, and I’ve never, ever seen a basic example of movement replication for custom Pawns.
Also, most of youtube videos are +1 year old.

But before anything, I have a few questions concerning multiplayer (In Engine) :

  • Why is my Pawn also spawning server side ? I’ve never told the server that I joined the game, why would I have a Pawn server side (even if not updated) ?
  • Why is the server automatically replicating location to the clients even though absolutly no replication option as been set on the Pawn ? What if I want the server NOT to replicate it’s location for some reason ?

Back to the main problem, I’ve seen a lot of this :


void AReplicatedActor::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
    DOREPLIFETIME(AReplicatedActor, bFlag);
}

And that :


UPROPERTY(Replicated)
   int myVar;

And finally this :


UFUNCTION(Server, Reliable, WithValidation)
void ServerChangeVar(AActor* TargetedActor, float NewValue);
virtual bool ServerChangeVar_Validate(AActor* TargetedActor, float NewValue) { return true; };
virtual void ServerChangeVar_Implementation(AActor* TargetedActor, float NewValue);

And I kind of understand what it does, but nobody seems to be doing the same thing and so I cannot find a functionnal example.
I come from a Blueprint background and this is what I was doing back in time (same thing, RTS camera with custom Pawn & stuff) :

(The name Camera refers to the Pawn, not to an actual camera. This is because the whole Pawn thing is supposed to be the RTS Camera. Read Pawn/Commander instead of Camera for less confusion :p)

And it worked like a charm.
Client called the server to execute a function, which was broadcasted to all clients, making it moves to everyone.
But now, I want to do it in C++, and I just can’t make it works.
In the picture above, inputs were handled directly in the Pawn. In my C++ project, I use the Controller for that (for learning purposes).

For now, this is what I’ve got in my code to move my Pawn forward :

CommanderController.cpp (My custom Controller class) :


void ACommanderController::SetupInputComponent() {
	Super::SetupInputComponent();
	InputComponent->BindAxis("MoveForward", this, &ACommanderController::MoveForward);
}

void ACommanderController::MoveForward(float scale) {
	auto pawn = Cast<ACommander>(this->GetPawn());
	if (pawn) {
		pawn->MoveForward(scale);
	}
}

Commander.cpp (My custom Pawn class) :


void ACommander::MoveForward(float scale) {
	AddActorLocalOffset(FVector(10 * scale, 0, 0));
}

From there, what do I have to do, if I want to do what I was doing in Blueprint (if it’s at least the way to go) that is to say :

  • Getting my input from player.
  • Sending a request to the server asking to move.
  • Server replying by moving my pawn everywhere on the network.

Thanks !

So I’ve found a way, if anyone here is having trouble with the mutliplayer stuff. This is probably super dirty but I can’t find another good way considering Movement Component are network-ready only for Characters and Vehicles.

Here is how to make Forward and Backward working across network (works with dedicated server too) :

First, the Player Controller get the input from the mapping nammed “MoveForward”.
Then, the mapping tells the game to call ACommanderController::MoveForward which itself call the ServerMoveForward method of the Pawn (Commander).

CommanderController.h


// Control inputs from the player
void SetupInputComponent();

// Called to control camera's movement
void MoveForward(float scale);

CommanderController.cpp


void ACommanderController::SetupInputComponent() {
	Super::SetupInputComponent();
	InputComponent->BindAxis("MoveForward", this, &ACommanderController::MoveForward);
}

void ACommanderController::MoveForward(float scale) {
	auto pawn = Cast<ACommander>(this->GetPawn());
	if (pawn) {
		Cast<ACommander>(pawn)->ServerMoveForward(scale);
	}
}

Commander.h


// Update camera ZS movements over network
void MoveForward(float scale);

UFUNCTION(Server, Reliable, WithValidation)
void ServerMoveForward(float scale);
void ServerMoveForward_Implementation(float scale);
bool ServerMoveForward_Validate(float scale);

UFUNCTION(NetMulticast, Reliable)
void BroadcastMoveForward(float scale);
void BroadcastMoveForward_Implementation(float scale);

Commander.cpp


void ACommander::MoveForward(float scale) {
	ServerMoveForward(scale);
}
bool ACommander::ServerMoveForward_Validate(float scale) {
	return true;
}
void ACommander::ServerMoveForward_Implementation(float scale) {
	if (Role == ROLE_Authority) {
		BroadcastMoveForward(scale);
	}
}
void ACommander::BroadcastMoveForward_Implementation(float scale) {
	AddActorLocalOffset(FVector(10 * scale, 0, 0));
}

Here is the tricky part. The MoveForward method from Commander.cpp call the ServerMoveForward which itself has NO “ACommander::ServerMoveForward” definition (don’t ask me why, it seems to work like this). Instead, the _Implementation & _Validate are called when you do that.
the ServerMoveForward_Implementation is set to be Server and Reliable, which means they are executed on SERVER and MUST & WILL be replicated CORRECTLY.
From there, the server (the Role == ROLE_Authority is overkill because we are already supposed to be on the server) will call a broadcast, that will be executed on every clients.
Every clients will then move the actor using AddActorLocalOffset.

I’ve heard about some methods made for MovementComponent, but it’s seems to be really characters/important entities related. Here, I’m just having a RTS camera that won’t even be replicated anymore in the future (I don’t recall an RTS game where you can see where a player is looking at !).

You can also store the location in the controller and use a On_Rep.
Before the multicast, the Pawns were moving EVERYWHERE except for the player who moved it. I’ve no idea why.