How can I replicate Pawns between a server and multiple clients?

Hmm, I posted a reply 2 times but it didn’t post…

I’m glad it’s working. What I was trying to say was the same as the edit in my comment below to sixb0nes.

The Client connected to the Listen Server can see the replicated movement because when you are moving the pawn on the Listen Server, you are telling the server directly to move the pawn. When you move the connected Client, you are not telling the server anything about the movement. You have to implement the movement like this:

Moving Client:

Get input, send request to server.

Server:

Handle moving client request, move pawn on server, tell all clients to move pawn

All Clients:

Move Pawn from method called by server

Yep, I don’t doubt that that would work.

Initially I was going to write my own NodeJS based socket server for the game I’m working on, and I’d have had to do all that with it as well. By using the UE4 server I was trying to avoid having to write things like that. More specifically by writing it for a pawn I’m concerned about losing interpolation/extrapolation that the character has.

Perhaps the examples they give in 4.2 will shed some light on how better to do this for Pawns rather than Characters.

So I’ve found that the character replication only works when using AddMovementInput or AddControllerYawInput and replicating the rotation code I had with AddControllerYawInput is giving me an equally painful headache.

Here’s the code for doing what you’ve suggested above, unfortunately I’m getting the same problem. Even though the value of CurrentForwardSpeed is changing on both clients the connected client is only moving on it’s own screen, not on the hosting client.

UPROPERTY(Transient, Replicated)
float CurrentForwardSpeed;

UFUNCTION(Reliable, Server, WithValidation)
void ServerMoveForward(float Val);

========================
void ATestPlayerController::MoveForward(float Val)
{
	ServerMoveForward(Val);
}

bool ATestPlayerController::ServerMoveForward_Validate(float Val)
{
	return true;
}

void ATestPlayerController::ServerMoveForward_Implementation(float Val)
{
	bHasForwardInput = !FMath::IsNearlyEqual(Val, 0.f);
	float CurrentAcc = bHasForwardInput ? (Val * Acceleration) : 0.0f;

	float NewForwardSpeed = CurrentForwardSpeed + (GetWorld()->GetDeltaSeconds() * CurrentAcc);

	CurrentForwardSpeed = FMath::Clamp(NewForwardSpeed, -MaxSpeed, MaxSpeed);
	CurrentForwardValue = Val;
}

void ATestPlayerController::PlayerTick(float DeltaSeconds)
{
	const FVector LocalMove = FVector(CurrentForwardSpeed * DeltaSeconds, CurrentRightSpeed * DeltaSeconds, 0.f);

	GetPawn()->SetActorLocation(GetPawn()->GetActorLocation() + LocalMove, true);
}

This is good to let the server know but you need to tell the clients about the new value. So for CurrentForwardSpeed, you probably want to replicated it and have it call a method.

UPROPERTY(Transient, ReplicatedUsing=OnRep_UpdateSpeed)
float CurrentForwardSpeed;

and then make a method

void ATestPlayerController::OnRep_UpdateSpeed(){

    // do the things to move the player

}

this will be replicated to all the clients causing the simulated player model to move.

So you, the moving player, tell the server, “I am moving” then the server changes the value and does the move. The changing of the value of CurrentForwardSpeed is changed and causes it to replicate to the clients and call the above method to move the player model on them as well.

Edit:

I just noticed you are using the replicated variable in the Tick function. Hmm. I will look at some more. I’m busy tonight but I’ll be back tomorrow to help again.

Alright dude.

I built a test project this morning and got it working. I doubt this is exactly what you need but it does what you want. It replicates the movement of the object to all clients using the APawn class only. I have no doubt in my mind that there are better ways because this is sending a lot of data to handle movement… without a full understanding of how the engine does it with the movement components I can’t say whether or not the performance with this implementation is comparable.

I tested this using multiple clients on a dedicated server.

NOTE: This does not replicate the rotation unless you are moving forward or backward… I’ll leave that to you :wink:

Commence code dump…

ATestPawn.h

#pragma once

#include "GameFramework/Pawn.h"
#include "Net/UnrealNetwork.h"
#include "TestPawn.generated.h"

/**
 * 
 */
UCLASS()
class ATestPawn : public APawn
{
	GENERATED_UCLASS_BODY()

	virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) OVERRIDE;

	UFUNCTION()
	void MoveForward(float Value);

	UFUNCTION(Server, Reliable, WithValidation)
		void ServerMoveForward(float Value, FRotator Rotation);

	UPROPERTY(Transient, ReplicatedUsing=OnRep_PosChange)
		FVector CurrentPosition;

	UPROPERTY(Transient, ReplicatedUsing=OnRep_RotChange)
		FRotator CurrentRotation;

	UFUNCTION()
		void OnRep_PosChange();

	UFUNCTION()
		void OnRep_RotChange();

};

ATestPawn.cpp

#include "TestAActorRep.h"
#include "TestPawn.h"


ATestPawn::ATestPawn(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP) {
	bUseControllerRotationPitch = true;
	bUseControllerRotationYaw = true;
	bReplicates = true;
}

void ATestPawn::OnRep_PosChange(){
	SetActorLocation(CurrentPosition);
}

void ATestPawn::OnRep_RotChange(){
	SetActorRotation(CurrentRotation);
}


void ATestPawn::MoveForward(float Value){    
	if (!Controller || Value == 0.0f) return;    
	if (Role != ROLE_Authority && IsLocallyControlled()){
		ServerMoveForward(Value, Controller->GetControlRotation());
	}
}

bool ATestPawn::ServerMoveForward_Validate(float Value, FRotator Rotation){
	return true;
}

void ATestPawn::ServerMoveForward_Implementation(float Value, FRotator Rotation){
	SetActorRotation(Rotation);
	CurrentRotation = Rotation;
	const FVector Direction = FRotationMatrix(CurrentRotation).GetScaledAxis(EAxis::X) * Value;
	SetActorLocation(GetActorLocation() + Direction);
	CurrentPosition = GetActorLocation();
}


void ATestPawn::SetupPlayerInputComponent(class UInputComponent* InputComponent){
	check(InputComponent);
	InputComponent->BindAxis("MoveForward", this, &ATestPawn::MoveForward);
	InputComponent->BindAxis("Turn", this, &ATestPawn::AddControllerYawInput);
	InputComponent->BindAxis("LookUp", this, &ATestPawn::AddControllerPitchInput);
}


void ATestPawn::GetLifetimeReplicatedProps(TArray<FLifetimeProperty> &OutLifetimeProps) const{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(ATestPawn, CurrentPosition);
	DOREPLIFETIME(ATestPawn, CurrentRotation);
}

Also note that I do nothing on the client side. This is an implementation choice that I made to simply show the replication moving the clients, including the one you are currently controlling.

Hopefully this is enough to get you going!

You can set bReplicatesMovement to true in your pawn constructor. This automatically replicates any pawn movement happening on the server to all connected clients. To move a client pawn, and get that movement shared to all other clients/server you call an RPC on the server (like this example above). However you don’t have to do the ReplicatedUsing to get the movements back to the clients; that is handled automatically by the bReplicatesMovement flag.

No, it doesn’t. It will work for Server->Server and Server->Client, but it wont work from Client->Server->Client. AnxGotta’s answer is complete and correct.