Networking - replication issue

Hello,

I have Inverse Kinematics set up on my characters but for some reason the non authority clients get this weird bug :

It does not happen with the player controlled character so I am assuming this is a replication issue.

Here is how is declare the few variables I am using :



	// Kinematics
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Kinematic)
		float IKCapsuleOffset;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Kinematic)
		float IKInterpSpeed;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Kinematic)
		float IKFrontLeftOffset;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Kinematic)
		float IKFrontRightOffset;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Kinematic)
		float IKBackLeftOffset;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Kinematic)
		float IKBackRightOffset;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Kinematic)
		float IKTraceDistance;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Kinematic)
		FVector IKFrontLeftEffector;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Kinematic)
		FVector IKFrontRightEffector;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Kinematic)
		FVector IKBackLeftEffector;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Kinematic)
		FVector IKBackRightEffector;
	// End Kinematics

And the IK code :


		FVector LeftSocketLoc = GetMesh()->GetSocketLocation(LeftSocket);
		FVector RightSocketLoc = GetMesh()->GetSocketLocation(RightSocket);

		float NewOffset = 0.0f;
		if (LeftSocketLoc.Z < RightSocketLoc.Z)
			NewOffset = DistanceToGround(LeftSocket);
		else
			NewOffset = DistanceToGround(RightSocket);

		IKCapsuleOffset = FMath::FInterpTo(IKCapsuleOffset, NewOffset, Delta, IKInterpSpeed);

		GetMesh()->SetRelativeLocation(FVector(RelativeMeshLocation.X, RelativeMeshLocation.Y, RelativeMeshLocation.Z - IKCapsuleOffset));

		float LeftOffset = IKTraceFoot(LeftSocket, IKTraceDistance);
		float RightOffset = IKTraceFoot(RightSocket, IKTraceDistance);

		IKBackLeftOffset = FMath::FInterpTo(IKBackLeftOffset, LeftOffset, Delta, IKInterpSpeed);
		IKBackRightOffset = FMath::FInterpTo(IKBackRightOffset, RightOffset, Delta, IKInterpSpeed);

If I remove the GetMesh()->SetRelative… line I get a bug that happens on both the client and the server so it’s probably somewhere in this code that something gets replicated.
Anyway to prevent this replication from happening ?

Thanks,
Max

Still haven’t figured it out :frowning:

You’re not replicating your IK data, thus it is always ‘zero’ on other clients and your solver tries to reach it, which results in such weird look. :slight_smile:

But the IK data should not be replicated right ? It needs to be computed every frame to look good, or do you mean “seed” values ?

Chances are you’re using some data about the client that can not be obtained on other clients (or is not replicated). If you could provide some more information on how exactly you handle your IK setup I could point you to specific place.

Thanks, I tried to solve it but never got anywhere…

So here is more on how I handle IK :


void AGameCharacter::IKBackFeet(FName LeftSocket, FName RightSocket, float Delta)
{
	if (!GetCharacterMovement()->IsMovingOnGround()) return;

	FVector LeftSocketLoc = GetMesh()->GetSocketLocation(LeftSocket);
	FVector RightSocketLoc = GetMesh()->GetSocketLocation(RightSocket);

	float NewOffset = 0.0f;
	if (LeftSocketLoc.Z < RightSocketLoc.Z)
		NewOffset = DistanceToGround(LeftSocket);
	else
		NewOffset = DistanceToGround(RightSocket);

	CapsuleOffset = FMath::FInterpTo(CapsuleOffset, NewOffset, Delta, IKInterpSpeed);

	GetMesh()->SetRelativeLocation(FVector(RelativeMeshLocation.X, RelativeMeshLocation.Y, RelativeMeshLocation.Z - CapsuleOffset));

	float LeftOffset = IKTraceFoot(LeftSocket, IKTraceDistance);
	float RightOffset = IKTraceFoot(RightSocket, IKTraceDistance);

	IKBackLeftOffset = FMath::FInterpTo(IKBackLeftOffset, LeftOffset, Delta, IKInterpSpeed);
	IKBackRightOffset = FMath::FInterpTo(IKBackRightOffset, RightOffset, Delta, IKInterpSpeed);
}

First of all I find the foot that is the lowest then raytrace to the ground to find the distance from the ground :


float AGameCharacter::DistanceToGround(FName& BoneName)
{
	FVector SocketLoc = GetMesh()->GetSocketLocation(BoneName);

	FVector StartPos(SocketLoc.X, SocketLoc.Y, SocketLoc.Z);
	FVector EndPos(SocketLoc.X, SocketLoc.Y, SocketLoc.Z - IKTraceDistance);

	FHitResult Hit;

	FCollisionQueryParams Params = FCollisionQueryParams::DefaultQueryParam;
	Params.bTraceComplex = true;
	Params.AddIgnoredActor(this);

	bool bHit = GetWorld()->LineTraceSingleByChannel(Hit, StartPos, EndPos, ECC_Visibility, Params);

	if (bHit)
	{
		StartPos -= Hit.Location;

		return StartPos.Size();
	}

	return 0.0f;
}

Once I have that I lower the mesh’ height by that offset to get the lowest foot to match the ground.

Then I trace the distance from each foot (keeping the mesh’ offset in mind) to the ground like so :


float AGameCharacter::IKTraceFoot(FName Socket, float TraceDistance)
{
	FVector SocketLoc = GetMesh()->GetSocketLocation(Socket);
	FVector Location = GetActorLocation();

	FVector StartPos(SocketLoc.X, SocketLoc.Y, Location.Z);
	FVector EndPos(SocketLoc.X, SocketLoc.Y, Location.Z - (TraceDistance + CapsuleOffset));

	FHitResult Hit;

	FCollisionQueryParams Params = FCollisionQueryParams::DefaultQueryParam;
	Params.bTraceComplex = true;
	Params.AddIgnoredActor(this);

	bool bHit = GetWorld()->LineTraceSingleByChannel(Hit, StartPos, EndPos, ECC_Visibility, Params);

	if (bHit)
	{
		EndPos -= Hit.Location;
		
		return EndPos.Size();
	}

	return 0.0f;
}

And that’s it I just use the values returned.

Thanks again for your help.

Anyone ? :frowning:

It looks like you’re doubling up on your Mesh->SetRelativeLocation(). Location and Rotation are almost always already replicated, it’s just apart of the engine setup.

Try doing



if(HasAuthority())
{
 Mesh->SetRelativeLocation();
}


This should tell only the Server to perform the rotation, which will then replicate automatically to the client.

Thanks, it does make sense.

But since it’s just a visual effect I don’t think it makes sense to be server driven, how can I make it so that every client controls the relative location of the mesh ?

Thanks again !

There are multiple functions and multiple ways to go about this, and it depends on how your entire system is setup.

Code is ran on the server and the client for various objects, but a lot of it is meant to only be ran on the server. It is up to you to determine what runs where in a basic sense. To do this, you can use things like


HasAuthority()

This returns a true/false statement if “Authority” is determined for the computer currently running this code, 98% of the time this means the Server. But Authority basically means who has full control of the object, in some rare cases a Client actually has Authority.


IsLocallyControlled() or IsLocalPlayerController or IsLocalController()

Also a true/false return. Can be used to determined if the current Pawn (IsLocallyControlled()) or Controller (IsLocalPlayerController()/IsLocalController()) is being controlled BY the person who is “possessing” this item. Quite literally, this means the player who is sitting infront of the computer.

Those are your most basic, and essential ways of determine what code can be ran where. After that, you get into RPC calls, which is a very lengthy topic, but in basics, an RPC is a FUNCTION that is TOLD to be ran by either the Client telling the Server, or the Server telling the Client.

So! With all that said. You can reverse my suggestion, and run it on the client be using an ! in front, or by adding a conditional statement of false


!HasAuthority()

or


HasAuthority() == false

However. I’m not entirely sure this will fix your problem. It’s hard to tell what’s exactly happening. Who is who in the video?

So after some trial and error I managed to get it to “work” by using HasAuthority and replicating some variables, but I don’t consider it a viable solution because IKs are not meant to be replicated, every client should compute the IKs every frame for every character. My problem remains the SetRelativeLocation because I would like every client to manage the relative location themselves and remove this attribute from the replication, don’t know how to do this though…

Thanks a lot for your help, I now have a better understanding of replication :slight_smile: