[Networking] Interpolating replicated movement data in Pawn, without modifying source

Hey guys,

When I play my simple space shooter with a dedicated server (or as a client connected to a listen), the desync between keypress and replicated movement info is rather jarring (if the ship is rolling, then you stop rolling, the ship will jitter into a slightly different position, but not if listen / standalone). This occurs even on the simulated UE4 editor server, and cross-PC.

When a key is pressed (for pitch, roll, yaw, and moving in all axes), the event is run on the server, which adds forces and torques to the pawn. A local copy is also run, so the client receives immediate feedback.

I’m trying to interpolate between the client’s physical model and the returning server variables with the following code, in my Ship class, which inherits from Pawn.



void AShip::Tick(float DeltaSeconds)
{
        FVector PreTickPos;
        FRotator PreTickRot;
        FVector PreTickVel;
        FVector PreTickAngVel;
 
        if (IsLocallyControlled())
        {
                PreTickPos = GetActorLocation();
                PreTickRot = GetActorRotation();
                PreTickVel = MeshComponent->GetPhysicsLinearVelocity();
                PreTickAngVel = MeshComponent->GetPhysicsAngularVelocity();
        }
 
        // Update AActor, including replication of movement.
        Super::Tick(DeltaSeconds);
 
        if (IsLocallyControlled())
        {
                // Replicated movement comes from AActor, updated on Tick(), and only replicated when "Replicate Movement" ticked.
 
                FVector ReplicatedPos = ReplicatedMovement.Location;
                FRotator ReplicatedRot = ReplicatedMovement.Rotation;
                FVector ReplicatedVelocity = ReplicatedMovement.LinearVelocity;
                FVector ReplicatedAngVelocity = ReplicatedMovement.AngularVelocity;
               
                // Current, Target, DeltaTime, Speed
                FVector LerpPos = FMath::VInterpConstantTo(PreTickPos, ReplicatedPos, DeltaSeconds, 0.001f);
                FRotator LerpRot = FMath::RInterpConstantTo(PreTickRot, ReplicatedRot, DeltaSeconds, 0.001f);
                FVector LerpVelocity = FMath::VInterpConstantTo(PreTickVel, ReplicatedVelocity, DeltaSeconds, 0.001f);
                FVector LerpAngVelocity = FMath::VInterpConstantTo(PreTickAngVel, ReplicatedAngVelocity, DeltaSeconds, 0.001f);
 
                SetActorLocation(LerpPos);
                SetActorRotation(LerpRot);
                MeshComponent->SetPhysicsLinearVelocity(LerpVelocity);
                MeshComponent->SetAllPhysicsAngularVelocity(LerpAngVelocity);
        }
}

The problem with the above is that the client will only run its physical simulation on Tick, which is also when the replication occurs, so really I’m just interpolating between two frames of server input, and totally ignoring my client.

Additionally, when playing over a LAN, the client appears to ‘fight’ with the server’s decision, where the server wants the player to be at 0,0,0, with no rotation, regardless of the physics-based events I send (or maybe the client does, and it’s overriding the server, but that shouldn’t happen).

Any insight? I know I could crack open the source and/or re-implement AActor’s replication behaviour, but I’d rather avoid that where possible, and use pre-existing functionality.

Thanks!

Okay, so I’ve added my own tickbox to the defaults tab, known as bCustomLocationReplication, which forces the normal bReplicateMovement to be false (this replaces it). This is my way of controlling when the replication occurs - I can update the client’s physics simulation of its own controlled pawn via Super::Tick (as per the above screenshots of my BP_PlayerController blueprint), and store the result, and then manually trigger the replication, and interpolate between the new result and the stored (local) result. Is this an acceptable way to approach the problem?


void AShip::Tick(float DeltaSeconds)
{
	// Update AActor, including replication of movement.
	Super::Tick(DeltaSeconds);

	if (Role >= ROLE_Authority)
	{
		/* Some server-tracking stuff here [not relevant]. */
	}

	if (IsLocallyControlled())
	{
		FRigidBodyState RBState;

		FVector PreTickPos;
		FRotator PreTickRot;
		FVector PreTickVel;
		FVector PreTickAngVel;

		if (bReplicates && bCustomLocationReplication)
		{
			MeshComponent->GetRigidBodyState(RBState);

			PreTickPos = RBState.Position;
			PreTickRot = RBState.Quaternion.Rotator();
			PreTickVel = RBState.LinVel;
			PreTickAngVel = RBState.AngVel;

			GatherCurrentMovement();
		}

		FVector ReplicatedPos = ReplicatedMovement.Location;
		FRotator ReplicatedRot = ReplicatedMovement.Rotation;
		FVector ReplicatedVelocity = ReplicatedMovement.LinearVelocity;
		FVector ReplicatedAngVelocity = ReplicatedMovement.AngularVelocity;

		FVector LerpPos = FMath::VInterpConstantTo(PreTickPos, ReplicatedPos, DeltaSeconds, 0.001f);
		FRotator LerpRot = FMath::RInterpConstantTo(PreTickRot, ReplicatedRot, DeltaSeconds, 0.001f);
		FVector LerpVelocity = FMath::VInterpConstantTo(PreTickVel, ReplicatedVelocity, DeltaSeconds, 0.001f);
		FVector LerpAngVelocity = FMath::VInterpConstantTo(PreTickAngVel, ReplicatedAngVelocity, DeltaSeconds, 0.001f);

		SetActorLocation(LerpPos);
		SetActorRotation(LerpRot);
		MeshComponent->SetPhysicsLinearVelocity(LerpVelocity);
		MeshComponent->SetAllPhysicsAngularVelocity(LerpAngVelocity);
	}
	else // Should run on remote clients that don't own this Ship object?
	{
		if ((bReplicates && bCustomLocationReplication) || (bReplicates && bReplicateMovement))
		{
			GatherCurrentMovement();

			SetActorLocation(ReplicatedMovement.Location);
			SetActorRotation(ReplicatedMovement.Rotation);
			MeshComponent->SetPhysicsLinearVelocity(ReplicatedMovement.LinearVelocity);
			MeshComponent->SetPhysicsAngularVelocity(ReplicatedMovement.AngularVelocity);
		}
	}
}


The client control of the Ship is fantastic and smooth, even across the LAN. I’m worried that this isn’t actually a realistic representation, and the client is really just doing its own simulation, ignoring the server.

Additionally, clients do not receive updates about other clients, despite the last ‘else’ statement above, which should run on remote clients ticking the Ship (this is how it works, right? If the ‘Replicates’ tickbox is checked, all clients will run this tick function?

Some help would be appreciated!

Thanks :slight_smile:

What you’re trying to do (simulating client movement locally AND on the server) is called client prediction, and I’ve been trying to get it working with the UE physics for a while.
The hard part is letting the client trust the server and fix their own simulation with the server’s, as that will be in the past in the client’s perspective and snap / jitter like you explained in your first post.
The common way to do it is remember and store all the movement and input that happens with timestamps, then when you receive and update from the server (with client timestamp), snap to the received state, reapply all movement and input since that time and delete all locally stored information before that state.

I gave up for a while as I couldn’t find a way to force an actor to simulate physics to reapply the movement, but if Super::Tick does what I think it does I might have to give it another shot…

Some relevant AH posts of mine…:
https://answers.unrealengine.com/questions/58920/client-prediction-reconciliation-theory-for-physic.html
https://answers.unrealengine.com/questions/64086/simulate-a-certain-time-period-of-physics-for-an-a.html

I’m not sure how effective your lerping solution is - I thought of doing that too initially, but the simulation the client runs would still be in the past compared to the player’s input, blended with the client not acknowledging server’s simulation at all. (Since that’s what a lerp is) You may want to draw a debug point on the position that the client receives from the server before you interp to it, so that you can compare them, and then test it out on a high latency connection, maybe fire some impulses on the server or client to try and desync them. An effective method will acknowledge the server’s simulation but still feel responsive to the player as if he/she were playing on the server itself.

If you’re happy to trust your players not to cheat, you could use Rama’s method of letting the clients send their location to the server, and the server trusting that, however I can’t give so much trust to my players and would rather a full authoritative approach like the rewind-replay method :stuck_out_tongue:
[Video] Player-Controlled Replicating Physics Movement, Simulating Physics! - C++ - Unreal Engine Forums!

Hey Acren,

So the part that’s frustrating me at the moment is actually figuring out how to run the local simulation, as you mentioned, and be able to manually invoke the replicated version. If I use the ‘Replicate Movement’ box, the location data in the Actor/Pawn is automatically overwritten, no? I then lose any track of my ‘previous’ location / local physics location. I feel paralyzed by a lack of documentation of how all of this actually occurs.

Yeah, I don’t use Replicate Movement. It just doesn’t work for me. Causes desync right off the bat.
So what I do is disable that and just replicate the properties I need or call RPCs.

You said clients send their input events to the server? Just do whatever the server does with them but locally when you send them.

In the client’s RPC or OnRep, if you do nothing, should just cause the client and server simulations to run completely independent of each other, which will eventually desync.

Right, but I don’t see how to intercept and utilise the physics calculations.

Imagine we’re only doing the local physics interpolation to server physics (and not keeping track of past stats and so on… that’ll come later). I definitely do send the input event to the server, but also execute it locally, so my client is definitely doing physics simulations with the same inputs. Should I then tick the Pawn on the client, to get the physics updated, and store this locally, then tick on the server to get the replicated/changed version, then next client Pawn tick, interpolate? I don’t yet know how to control who/what/when ticks the Pawn to this detail, yet. Any ideas?

I’m not sure exactly what you’re aiming for… do you want to just lerp the client towards what the server is doing?

Sorry, I mean to have my local client physics simulation for my controlled Pawn, so there’s immediate reaction to input, interpolated towards the server’s version of the physics update.

Sure, have you tried having the server constantly sending a TargetPosition, TargetVelocity etc to the client via replication or RPC, then on the client’s tick (doesn’t matter if it’s before or after physics) lerping from the current to the target with deltaTime * speed as the alpha, speed being how fast you want it to be?

It will kind of override the local prediction any further than the initial key press, but that’s how to do it I think.

Just to clarify - I control whether we’re talking client vs server tick via the if (Role < ROLE_Authority) type conditions inside the Tick method? Do all remote clients execute a tick call on each others’ pawns, too?

Yeah, I just use HasAuthority() as a shortcut but it’s the same thing. Clients should be ticking too, yes.

HasAuthority() returns true for dedicated server and for listen server, when viewing the Pawn in question? Or do clients have authority over their controlled pawn?

If all clients tick the pawns of all other clients, what do they tick inside it? Exactly the same stuff? (Obviously except for the HasAuthority sections).

HasAuthority

Yes, in all or most cases authority is just the server.
All clients tick all actors if they have ticking enabled.
They should tick exactly the same. Completely independent of each other apart from any replicating, or spawning and destroying. I think.



void AShip::Tick(float DeltaSeconds)
{
	// Update AActor, including physics.
	Super::Tick(DeltaSeconds);

	if (HasAuthority())
	{
		TargetMovementData.Location = GetActorLocation();
		TargetMovementData.Rotation = GetActorRotation();
		TargetMovementData.LinearVelocity = MeshComponent->GetPhysicsLinearVelocity();
		TargetMovementData.AngularVelocity = MeshComponent->GetPhysicsAngularVelocity();
	}
	else
	{
		if (IsLocallyControlled())
		{

			FVector LerpPos;
			FRotator LerpRot;
			FVector LerpVel;
			FVector LerpAngVel;


			LerpPos = FMath::VInterpConstantTo(GetActorLocation(), TargetMovementData.Location, DeltaSeconds, 0.01f); // have tried different values for this 'speed' argument (0.1 to 0.001). Same results.
			LerpRot = FMath::RInterpConstantTo(GetActorRotation(), TargetMovementData.Rotation, DeltaSeconds, 0.01f);
			LerpVel = FMath::VInterpConstantTo(MeshComponent->GetPhysicsLinearVelocity(), TargetMovementData.LinearVelocity, DeltaSeconds, 0.01f);
			LerpAngVel = FMath::VInterpConstantTo(MeshComponent->GetPhysicsAngularVelocity(), TargetMovementData.AngularVelocity, DeltaSeconds, 0.01f);

			SetActorLocation(LerpPos);
			SetActorRotation(LerpRot);
			MeshComponent->SetPhysicsLinearVelocity(LerpVel);
			MeshComponent->SetAllPhysicsAngularVelocity(LerpAngVel);
		}
		else
		{
			SetActorLocation(TargetMovementData.Location);
			SetActorRotation(TargetMovementData.Rotation);
			MeshComponent->SetPhysicsLinearVelocity(TargetMovementData.LinearVelocity);
			MeshComponent->SetAllPhysicsAngularVelocity(TargetMovementData.AngularVelocity);
		}
	}
}

With the above, my clients often see their own movement really slowly (they rotate much slower than they do on the screen of the listen server, and slower than they should in general), and thus desynced from the server - their ‘up’ direction is not used for physics, but rather the server’s opinion on their ‘up’ direction, and so on.

Additionally, I can’t see the rotations of other clients, and I think the location stuff is slightly out of sync, too.

I’m not sure how to correct this, or to do something like having collisions work only on the server.

Any ideas?

Try higher interp speeds maybe, 0.1 seems very low. See if 1-10 makes a difference. I assume TargetMovementData is replicated?

Correct.

Okay, so I’ve tried to hook into pre-existing methods, and it seems to work better (client feels good, but others jitter a bit), however it’s probably not efficient, and as soon as I run a dedicated server as a separate process, all ships refuse to move - they snap back to their starting positions with their starting rotations. Debugging the server suggests their positions in the TargetMovementData never change on its end.


void AShip::Tick(float DeltaSeconds)
{
	// Update AActor, including replication of movement, and physics.
	

	Super::Tick(DeltaSeconds);

	if (HasAuthority())
	{

		TargetMovementData.Location = GetActorLocation();
		TargetMovementData.Rotation = GetActorRotation();
		TargetMovementData.LinearVelocity = MeshComponent->GetPhysicsLinearVelocity();
		TargetMovementData.AngularVelocity = MeshComponent->GetPhysicsAngularVelocity();
	}
	else
	{
		if (IsLocallyControlled())
		{
			MeshComponent->RecreatePhysicsState();

			FVector LerpPos;
			FRotator LerpRot;
			FVector LerpVel;
			FVector LerpAngVel;

			LerpPos = FMath::VInterpConstantTo(GetActorLocation(), TargetMovementData.Location, DeltaSeconds, 5);
			LerpRot = FMath::RInterpConstantTo(GetActorRotation(), TargetMovementData.Rotation, DeltaSeconds, 5);
			LerpVel = FMath::VInterpConstantTo(MeshComponent->GetPhysicsLinearVelocity(), TargetMovementData.LinearVelocity, DeltaSeconds, 5);
			LerpAngVel = FMath::VInterpConstantTo(MeshComponent->GetPhysicsAngularVelocity(), TargetMovementData.AngularVelocity, DeltaSeconds, 5);

			//FRepMovement LerpMoveState; // This was used when I tried overloaded PostRecieve... and passing in this LerpMoveState instead of it using TargetMovementData. Hard to tell if it helped.
			//LerpMoveState.Location = LerpPos;
			//LerpMoveState.Rotation = LerpRot;
			//LerpMoveState.LinearVelocity = LerpVel;
			//LerpMoveState.AngularVelocity = LerpAngVel;

			SetActorLocation(LerpPos);
			SetActorRotation(LerpRot);
			MeshComponent->SetPhysicsLinearVelocity(LerpVel);
			MeshComponent->SetAllPhysicsAngularVelocity(LerpAngVel);

			PostNetReceivePhysicState();
		}
		else
		{
			MeshComponent->RecreatePhysicsState();

			SetActorLocation(TargetMovementData.Location);
			SetActorRotation(TargetMovementData.Rotation);
			MeshComponent->SetPhysicsLinearVelocity(TargetMovementData.LinearVelocity);
			MeshComponent->SetAllPhysicsAngularVelocity(TargetMovementData.AngularVelocity);

			PostNetReceivePhysicState();
		}
	}
}

Overloaded PostNetReceivePhysicState:


void AShip::PostNetReceivePhysicState()
{
	UPrimitiveComponent* RootPrimComp = Cast<UPrimitiveComponent>(RootComponent);
	if (RootPrimComp)
	{
		FRigidBodyState NewState;
		TargetMovementData.CopyTo(NewState);

		FVector DeltaPos(FVector::ZeroVector);
		RootPrimComp->ConditionalApplyRigidBodyState(NewState, GEngine->PhysicErrorCorrection, DeltaPos);
	}
}


void AShip::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const
{
	DOREPLIFETIME(AShip, TargetMovementData);
}

The other method I described above in my comments, but which I’m not using:


void AShip::PostNetReceivePhysicState(FRepMovement TargetMovementState)
{
	UPrimitiveComponent* RootPrimComp = Cast<UPrimitiveComponent>(RootComponent);
	if (RootPrimComp)
	{
		FRigidBodyState NewState;
		TargetMovementState.CopyTo(NewState);

		FVector DeltaPos(FVector::ZeroVector);
		RootPrimComp->ConditionalApplyRigidBodyState(NewState, GEngine->PhysicErrorCorrection, DeltaPos);
	}
}

Thoughts?

So now I’m trying to figure how to make it feel good for the player, in terms of controlling their own pawn, but having the server override their physics gracefully.

I was thinking I could create a buffer (a queue) of time-stamped moments, storing the physics info for the pawn, the active player inputs, and the time it occurred. I could then be compared the incoming server info with this info, and if it didn’t match, roll the physics back to that incorrect moment/tick, correct the data, then re-simulate based on the inputs stored for each successive moment/frame up until the present time/tick.

  • How can I query all inputs / event received in the current tick? (In order to store what inputs were active in each tick/moment that I’m storing in the buffer, without making my own boolean for each key/input).
  • Can the physics be ‘rolled back’ in some way?
  • Can I simulate several frames worth of physics with a single tick, short of writing my own physics calculations from scratch? (And hoping that they match the current ones).

The above seems to be the most common approach for client-side prediction, but I see no way of accomplishing some of these tasks. Does anybody have any insights?

Thanks.

This is the correct way to do client prediction, though I haven’t got it working myself.

Since you only really need to work with movement, you could store it as a vector depending on how your movement works. (A vector would allow for direction and strength)
If your movement is more complicated, you’ll need to store whatever is relevant. (but ONLY movement related input)

Yes, just set position and velocity to whatever the server gives you :stuck_out_tongue:
The issue is rolling it forward again, as it requires you iterating through all of your stored frames and simulating physics on your actor without actually progressing time… this leads to your next question.

This is basically where I gave up, although you were talking about Super::Tick() and I wondered if it could be used for this if it works.

Good luck, I’m eager to hear of any more ideas or progress.

Okay, so I’m coming at this another way for now - client-side physics for locally-controlled Ship, and replication for the others in the world around you. I know it’s vulnerable to cheating, but for now it’ll do!

Ship.h:



USTRUCT()
struct FShipPhysicsState
{
	GENERATED_USTRUCT_BODY()

	FShipPhysicsState()
	{
	}

	FShipPhysicsState(FVector NewLocation, FRotator NewRotation,
	FVector NewLinearVelocity, FVector NewAngularVelocity, float NewTimestamp)
	: Location(NewLocation), Rotation(NewRotation),
	LinearVelocity(NewLinearVelocity), AngularVelocity(NewAngularVelocity), Timestamp(NewTimestamp)
	{
	}

	UPROPERTY()
	FVector Location;
	UPROPERTY()
	FRotator Rotation;
	UPROPERTY()
	FVector LinearVelocity;
	UPROPERTY()
	FVector AngularVelocity;
	float Timestamp;
};

/////////////////////////////////////
// Inside Ship class:

/* Prepare local physics state for transmission via RPC. */
UFUNCTION(Category = Networking)
void TransmitLocalPhysicsState_Client();
 
/* Receive physics information from client about its physics state. */
UFUNCTION(Server, Unreliable, WithValidation, Category = Networking)
void ReceiveLocalPhysicsState_Server(FShipPhysicsState NewPhysicsState);
 
/* Call the function to transmit the Ship's physics info to all clients. */
UFUNCTION(Category = Networking)
void TransmitRemotePhysicsState();
 
/* Called on clients to receive the physics info of the Ship. */
UFUNCTION(NetMulticast, Unreliable, Category = Networking)
void ReceiveRemotePhysicsState(FShipPhysicsState NewPhysicsState);
 
UPROPERTY(ReplicatedUsing=OnRep_ShipRemotePhysicsState)
FShipPhysicsState ShipRemotePhysicsState;
UPROPERTY(ReplicatedUsing = OnRep_ShipLocalPhysicsState)
FShipPhysicsState ShipLocalPhysicsState; // Not used yet.
 
UFUNCTION()
void OnRep_ShipRemotePhysicsState();
UFUNCTION()
void OnRep_ShipLocalPhysicsState(); // not used - using RPC as client->server replication isn't real.


Ship.cpp:



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

	if (IsLocallyControlled())
	{
		TransmitLocalPhysicsState_Client();
	}

	//if (HasAuthority())
	//{
	//	/*TransmitRemotePhysicsState();*/
	//}

	ShipRemotePhysicsState.Location = GetActorLocation();
	ShipRemotePhysicsState.Rotation = GetActorRotation();
	ShipRemotePhysicsState.LinearVelocity = MeshComponent->GetPhysicsLinearVelocity();
	ShipRemotePhysicsState.AngularVelocity = MeshComponent->GetPhysicsAngularVelocity();
}

void AShip::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const
{
	DOREPLIFETIME_CONDITION(AShip, ShipRemotePhysicsState, COND_SkipOwner);
	DOREPLIFETIME_CONDITION(AShip, ShipLocalPhysicsState, COND_OwnerOnly);
}

void AShip::OnRep_ShipRemotePhysicsState()
{
	TeleportTo(ShipRemotePhysicsState.Location, ShipRemotePhysicsState.Rotation, false, true);

	/*SetActorLocation(ShipRemotePhysicsState.Location);
	SetActorRotation(ShipRemotePhysicsState.Rotation);*/
	//MeshComponent->SetPhysicsLinearVelocity(ShipRemotePhysicsState.LinearVelocity);
	//MeshComponent->SetPhysicsAngularVelocity(ShipRemotePhysicsState.AngularVelocity);
}

// Will eventually have use of local queue of physics states.
void AShip::OnRep_ShipLocalPhysicsState()
{
	SetActorLocation(ShipLocalPhysicsState.Location);
	SetActorRotation(ShipLocalPhysicsState.Rotation);
	MeshComponent->SetPhysicsLinearVelocity(ShipLocalPhysicsState.LinearVelocity);
	MeshComponent->SetPhysicsAngularVelocity(ShipLocalPhysicsState.AngularVelocity);
}

void AShip::TransmitLocalPhysicsState_Client()
{
	FShipPhysicsState NewState(GetActorLocation(), GetActorRotation(), MeshComponent->GetPhysicsLinearVelocity(),
		MeshComponent->GetPhysicsAngularVelocity(), 0 /* GET TIME */);

	/*GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Yellow, NewState.Location.ToString());
	GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Yellow, NewState.Rotation.ToString());
	GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Yellow, NewState.LinearVelocity.ToString());
	GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Yellow, NewState.AngularVelocity.ToString());*/

	ReceiveLocalPhysicsState_Server(NewState);
}

void AShip::ReceiveLocalPhysicsState_Server_Implementation(FShipPhysicsState NewPhysicsState)
{
	SetActorLocation(NewPhysicsState.Location);
	SetActorRotation(NewPhysicsState.Rotation);
	MeshComponent->SetPhysicsLinearVelocity(NewPhysicsState.LinearVelocity);
	MeshComponent->SetPhysicsAngularVelocity(NewPhysicsState.AngularVelocity);
}

bool AShip::ReceiveLocalPhysicsState_Server_Validate(FShipPhysicsState NewPhysicsState)
{
	return true;
}

// Broken!!! Use replication instead.
void AShip::TransmitRemotePhysicsState()
{
	FShipPhysicsState NewState(GetActorLocation(), GetActorRotation(), MeshComponent->GetPhysicsLinearVelocity(),
		MeshComponent->GetPhysicsAngularVelocity(), 0 /* GET TIME */);

	ReceiveRemotePhysicsState(NewState);
}

// Broken!!! Use replication instead.
void AShip::ReceiveRemotePhysicsState_Implementation(FShipPhysicsState NewPhysicsState)
{
	if (!IsLocallyControlled())
	{
		SetActorLocation(NewPhysicsState.Location);
		SetActorRotation(NewPhysicsState.Rotation);
		MeshComponent->SetPhysicsLinearVelocity(NewPhysicsState.LinearVelocity);
		MeshComponent->SetPhysicsAngularVelocity(NewPhysicsState.AngularVelocity);
	}

	/*RemotePhysicsBuffer.Pop(false);
	RemotePhysicsBuffer.Add(NewPhysicsState);*/
}


This worked until I added the TeleportTo line when receiving the replication of ShipRemotePhysicsState. I also tried without that, but instead only SetActorLocation and SetActorRotation (I was basically trying to avoid using velocity - the lerping should take care of that once I add it in), but for some reason, if I don’t use the velocities, clients don’t appear to move in each others’ worlds.

This has only been tested on a virtual server, and when I had the velocities included, things moved smoothly, even at high speed, but weren’t perfectly synced to the listen server (slightly different rotations, etc, and thus why I want to just straight-set location and rotation).

Additionally, I have disabled collisions for the ships on clients for now. I don’t know how I will resolve the collision issues, considering that everyone will see slightly different events, and thus their own collision result may differ to what it looks like it should’ve been from the perspective of the other players (especially when two ships collide in space).

The above are great arguments for server-side physics calculations, but that will come in time. I need to smash out this part for a playable prototype pretty **** soon. (I hope the code above is sensible - I’m grabbing it out of a very WIP file, naturally).

Any thoughts?

This is still a problem :frowning:

If I send local physics info to server via RPC and then replicate that out to everyone else (using COND_SkipOwner so the local sender doesn’t get it back at themselves), using either SetLocation/SetRotation or TeleportTo on the client, inside the OnRep for this remote physics information, results in 0 change. Is there something built-in that overrides this behaviour on replicated actors?