[Video] Player-Controlled Replicating Physics Movement, Simulating Physics!

Implementation Overview

Dear Everyone,

I was not going to sit here and write all of this up but due to all of your requests for more info I’m posting this just for you :slight_smile:

There are three core concepts of my multiplayer physics implementation!

  1. The use of VInterpTo to smoothly adjust the position of the non net owner, both physics position and physics linear velocity

  2. The fact that player controlled units should never be interpolated, only the non-owner proxies of that player controlled unit should get interpolated.

  3. Apply any additional forces requires to get the non owner to the owner’s position that include accounting for things like gravity.

#1 is how you ensure that the non owner proxy will always end up in the correct position, with an update speed that you can control and tailor to your needs via the interp speed. VInterpTo is also frame rate invariant which is essential for supporting different generations of hardware which will run the game at different frame rates.

#2 is about gameplay experience! You’d never want a client to feel like they had sluggish control or no control over their own player unit because the server is busy adjusting the client’s proxy to what the server thinks is correct. The local owner of the unit must have full control of the unit and update everyone else, including the server.

#3 is abount ensuring the physics replication will look good, by locally applying gravity and other forces to get the proxy back in sync with the net owner’s physics orientation


**Code Structure Sample**

.h



```


//This is basically a duplicate of the struct used by Actor.h for replication (wrote my own before I found out about FRepMovement)

/*
UPROPERTY(Transient, ReplicatedUsing=OnRep_ReplicatedMovement)
struct FRepMovement ReplicatedMovement;
*/

USTRUCT()
struct FJoyMoveRep
{ 
	GENERATED_USTRUCT_BODY()

	UPROPERTY()
	FVector_NetQuantize100 Location;
	
	UPROPERTY()
	FRotator Rotation;
	
	UPROPERTY()
	FVector_NetQuantize100 LinearVelocity;
	
	UPROPERTY()
	FVector_NetQuantize100 AngularVelocity;
	
	FJoyMoveRep()
	{
		Location = LinearVelocity = AngularVelocity = FVector::ZeroVector;
		Rotation = FRotator::ZeroRotator;
	}
	FJoyMoveRep(
		FVector_NetQuantize100 Loc, 
		FRotator Rot,
		FVector_NetQuantize100 Vel,
		FVector_NetQuantize100 Angular
	)	
		: Location(Loc)
		, Rotation(Rot)
		, LinearVelocity(Vel)
		, AngularVelocity(Angular)
		
	{}
};


UFUNCTION(Reliable,Server,WithValidation)
void SERVER_SendJoyMove(FJoyMoveRep NewMove);
bool SERVER_SendJoyMove_Validate(FJoyMoveRep NewMove);
void SERVER_SendJoyMove_Implementation(FJoyMoveRep NewMove);

//Interpolated by JoyMoveRep_Receive
UPROPERTY(Replicated)
FJoyMoveRep R_JoyMove;

//Guarantee Rep 
UPROPERTY(Replicated)
uint8 DoRep_JoyMove;

void JoyMoveReplication();
void JoyMoveRep_Send();
void JoyMoveRep_Receive();


//class

UFUNCTION(Reliable,Server,WithValidation)
void SERVER_SendJoyMove(FJoyMoveRep NewMove);
bool SERVER_SendJoyMove_Validate(FJoyMoveRep NewMove);
void SERVER_SendJoyMove_Implementation(FJoyMoveRep NewMove);

//Interpolated by JoyMoveRep_Receive
UPROPERTY(Replicated)
FJoyMoveRep R_JoyMove;

//Guarantee Rep 
UPROPERTY(Replicated)
uint8 DoRep_JoyMove;


```



CPP



void AJoyBallMovement::JoyMoveReplication()
{   
	if(IsLocallyControlled())
	{
		JoyMoveRep_Send();
	}
	else
	{
		//Update!
		JoyMoveRep_Receive();
	
	}
}
void AJoyBallMovement::JoyMoveRep_Send()
{
	SERVER_SendJoyMove(
		FJoyMoveRep(
			C_BodyLocation,
			C_BodyRotation,
			C_Velocity,
			C_AngularVelocity
		)
	);
}	
bool AJoyBallMovement::SERVER_SendJoyMove_Validate(FJoyMoveRep NewMove)
{
	return true;
}
void AJoyBallMovement::SERVER_SendJoyMove_Implementation(FJoyMoveRep NewMove)
{
	//Rep!
	R_JoyMove = NewMove;	//RepRetry
	DoRep_JoyMove++;			//Just to guarantee rep happens
}

//~~~
//~~~
//~~~

void AJoyBallMovement::JoyMoveRep_Receive()
{
	//Dont rep invalid data
	if(R_JoyMove.Location.IsZero()) return;
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	
	//implement what I outlined above, using VInterpTo to update PhysicsLinearVelocity and component location in world space

       //Also you can interpolate rotation here, these are all vars that are part of R_JoyMove
      
       //This is the part that varies from project to project, which is why I outlined the core concepts above.
	
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//							Replication List
void AJoyBallHighest::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
{ 
	Super::GetLifetimeReplicatedProps( OutLifetimeProps );
 
	//Joy Rep Physics Movement!
	DOREPLIFETIME(AJoyBallHighest, JoyRepMoveVibes);
	DOREPLIFETIME(AJoyBallHighest, DoRep_JoyRepMove);
}

void AJoyBallMovement::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	//~~~~~~~~~~~~
	
	//Dont need to replicate movement in single player games
	if(GEngine->GetNetMode(GetWorld()) != NM_Standalone)
	{
                //Local owner updates others, 
                //   others update every tick even when not getting data over network
		JoyMoveReplication();
	}
}



**Notes on My Code Structure**

**Please note I am deliberately not using ReplicatedUsing** because I need to call JoyMoveRep_Receive() every tick to apply extra forces like gravity locally and run the VInterpTo functions

If I used ReplicateUsing, the replication function would only get called when updates made it through across the real network, the infrequency of which is the precise reason I need to interpolate locally / apply physics forces locally.

Since the R_JoyMove replication var is only getting updated based on real network conditions, I can use whatever data is stored there, from the last known update, and interpolate to correct postion and also account for local forces like gravity.

**Also note that I check whether a unit is being locally controlled** to know whether it is sending updates or receiving them.



```


void AJoyBallMovement::JoyMoveReplication()
{   
	if(IsLocallyControlled())
	{
		JoyMoveRep_Send();
	}
	else
	{
		//Update!
		JoyMoveRep_Receive();
	
	}
}


```



IsLocallyControlled() is part of Pawn class



```


bool APawn::IsLocallyControlled() const
{
	return ( Controller && Controller->IsLocalController() );
}


```



Please note that this occurs every tick because I want to continue interpolating to last known network positions even when experiencing a laggy connection to the local owner. This is what VInterpTo does!

Multiplayer Rotation Interpolation

Please note I highly prefer SLERP over RInterpTo for multiplayer physics rotation, because the spherical interploation just works out so much better in all my real multiplayer tests. It’s not quite the same as Interp To but it works great for me.

The person who mentioned a ship game with capsizing ships will need this :slight_smile:

Quaternions



FQuat::Slerp(A, B,Alpha);


Rotators



static FORCEINLINE FRotator Slerp(const FRotator& A, const FRotator& B, const float& Alpha)
{
	return FQuat::Slerp(A.Quaternion(), B.Quaternion(),Alpha).Rotator();
}


I generally use an alpha / speed of 0.1 and that has worked great in all circumstances

Enjoy!