So, I am not posting this as a question, but more as a c++ developer, in order to solve some things I saw done regarding pawn movement matching (replication) in Top-Down projects networking.
I would like to say that:
I started with the idea that, in a network environment, replication actually has to mean we mimic what happens in the client movement on the server, not backwards.
This is because I saw a lot of people seem to forget the very basics of what should happen: client asks, server validates and sends back.
It is always the client that calls the action, and always the server that should validate it and say it’s fine or not. If it is, the server is not supposed to manage the whole thing and send it to the client, but rather let the client do it’s thing and replicate what happens in the client server-side. Why? Because the client will never lag and we simply remove the whole time and processing between the client request and the server part this way. The server does it’s thing on it’s part, after receiving the request, which is also done with no time delays. The only possible time delay is that in which the server receives and validates the client request.
So, keeping that in mind, I really wanted to solve the whole problem with having to create a proxy character with an AI class because some people say using the default “UAIBlueprintHelperLibrary::SimpleMoveToLocation” from the APlayerController base isn’t working in networking to allow proper pawn movement visualization (location + orientation change).
I ended up with this:
- We make a function called RequestSetNewMoveDestination, which is called from both the client and the server.
- Two new functions are needed, one that is called from the client and one from the server. We need to split them to ensure they are called specifically from these places, because, as I said, the client asks to move first, and the server validates and replicates the client movement. I called these ClientSetNewMoveDestination / ServerSetNewMoveDestination.
- A common function is needed, to be called from each of the two above, and handle the actual movement part we do in the client and server. This is actually the old SetNewMoveDestination, as it does what the original did.
- We swap calling SetNewMoveDestination to calling RequestSetNewMoveDestination in MoveToMouseCursor.
Here is the result in PlayerController, as code:
.h:
/** Common request to nagivate player to the given world location. */
void RequestSetNewMoveDestination(const FVector DestLocation);
/** Call navigate player to the given world location (Client Version). */
UFUNCTION(Reliable, Client)
void ClientSetNewMoveDestination(const FVector DestLocation);
/** Call navigate player to the given world location (Server Version). */
UFUNCTION(Reliable, Server)
void ServerSetNewMoveDestination(const FVector DestLocation);
/** Navigate player to the given world location. */
void SetNewMoveDestination(const FVector DestLocation);
.cpp:
// Requests a destination set for the client and server.
void ATopDownPlayerController::RequestSetNewMoveDestination(const FVector DestLocation)
{
ClientSetNewMoveDestination(DestLocation);
ServerSetNewMoveDestination(DestLocation);
}
// Requests a destination set for the client (Comes first, since client calls it by clicking).
void ATopDownPlayerController::ClientSetNewMoveDestination_Implementation(const FVector DestLocation)
{
SetNewMoveDestination(DestLocation);
}
// Requests a destination set for the server (Comes after, to replicate client movement server-side).
void ATopDownPlayerController::ServerSetNewMoveDestination_Implementation(const FVector DestLocation)
{
SetNewMoveDestination(DestLocation);
}
// Common destination setting and movement implementation.
void ATopDownPlayerController::SetNewMoveDestination(const FVector DestLocation)
{
if (APawn* const MyPawn = GetPawn())
{
float const Distance = FVector::Dist(DestLocation, MyPawn->GetActorLocation());
// We need to issue move command only if far enough in order for walk animation to play correctly.
if (Distance > 120.0f)
UAIBlueprintHelperLibrary::SimpleMoveToLocation(this, DestLocation);
}
}
Very important: don’t forget to set forget to set “Allow Client Side Navigation” to true in the Project Settings, else it will not work properly. The client must be allowed to do it’s movement.
Nothing else is needed, and the client and the server will now both move properly and do their part (both move and orient to movement). Please take it for a test :).
As you see, the client calls for moving to a spot, we process the request (here we can validate it if we want), we send the client moving and the server to replicate that movement.
I am happy to share this with the community in the hope it will bring to light a better, easier and stutter-free way of pawn movement sync in this project type when implementing multiplayer, without crazy additional proxy classes, AI controllers, heavy updates per tick for solving orientation in the client or whatsoever.
Awaiting thoughts, issues debates and constructive criticism.
Have a great day and happy coding!