Vive Motion Controller Multiplayer 4.13

With VR template from ue4.13 i have the problem that the motion controller actions are mirrored.
i cant find anything about it for the current version (4.13) on the net

The Solution in this thread doesnt work for me

Im trying to find a workaround but for now i cant get a proper solution

thanks in advance

You need to replicate them manually

thanks for your quick response,
im struggling arround with this for days.
im neither very experienced with mp stuff an nor that good with blueprints

It is not done by just checking the checkbox in the details tab of the BpMotioncontroller blueprint right :slight_smile: ?

I made a small example project with a host and a join funktion and the vr pawn,

I look forward to receiving continued support.

On your client pawn that is possessed by a local controller you need to pass the transforms of the motion controllers as parameters into a replicated server function that will set a repnotify variable to those transforms. Then you need to make the repnotify function set the locations of the motion controllers from that variable.

like This ?

makes no difference for the behaviour of the mition controller, but the teleport functions doesnt work correct anymore

Are you sending an RPC every Tick (UpdateMotionControllerServer)? If so, that is generally a bad practice as it will be a huge drain on bandwidth (and probably CPU, depending on UE4’s net code). To sync values that change often, you want to sync them via properties, I believe, although I’m still learning UE4’s networking code, so I might be wrong. You definitely don’t want to be sending an RPC 30-60 frames per second, though.

yes its true, but its a good event for testing and troubleshooting, because you can be sure its getting fired

You can’t sync variables from clients to the server, those are replicated Server->Client only, you have to RPC Client->Server and then replicate what the server receives to all other clients with a replicated property.

Also for the player movement components they send RPCs to the server with the recorded movements every tick or relevant change, it is how you do client authoritative replication in the engine.

Edit also shouldn’t need to set the relative location on the owning player… that should already be set by the motion controller itself.

The replication of the character movement component is done using RPCs, yes, but it also has a delay timer to ensure that the RPC is not sent too often in order to prevent connection saturation. This time fluctuates based on the strength of the connection (fewer sends on a slower connection). Looks like their min time (0.0222) would put it at about 45 sends per second when the character is moving. That’s high, but accuracy for characters is important and the packets are optimized. If you had that kind of bandwidth usage for every object in your game, you would quickly saturate your connection.

I didn’t realize that the properties are only synced in a single direction (like I said, still learning the net engine for UE4), but that makes sense.

Just as a disclaimer, everything I’m saying here comes from years of making multiplayer games and are general concerns. It’s totally possible that UE4 has a bunch of magic going on in the background to streamline writing net code so that most users don’t have to think about it. I have no idea, as I’m still learning. Please correct me if anything I’m saying sounds off.

In servers with 10 or less players the player movement defaults to 90 htz send rate, also there is significantly more information in the movement replication than in his RPCs. That being said you are correct that there is no real reason to replicate at the full tick speed and it shouldn’t be replicated at all if the change in position is below a threshold to begin with. Something like 30 htz doesn’t look that bad and any positional offsets can be corrected for by sending the controllers location at the time of important events (like grabbing something).

I wasn’t arguing against what you said about the bandwidth, I was arguing against you saying that RPCs shouldn’t be used as there isn’t really an alternative.

Its all good though.

Hopefully I wasn’t coming across as aggressive or argumentative. That was not my intent.

I definitely see your point with the RPCs now too. Unreal is structured in such a way that it forces the use of RPCs for this type of pattern. I can always throttle it myself later if it turns out to be a problem.

Thanks for your input. Definitely helpful in learning a new engine.

so any idea how we could solve this problem ?
i really need get it to work

All of my stuff is done in C++, but I’ll give you the list of everything I did.

  • Set the motion controllers (MC) to be replicated. For Blueprint, there’s a checkbox in the default options.
  • The the motion control component to have the PlayerIndex set to -1
  • Add two RepNotify values of a Vector and Rotation and set them to COND_SkipOwner (not sure what the Blueprint equivalent is)
  • In the notify functions, call SetWorldLocation and SetRotation on the MC component
  • In the Tick, set these values to the current World Location and Rotation (Do this on both server and client)
  • Also in the Tick (and this part might be done differently in Blueprint), check if the Owner is ROLE_AutonomousProxy. If so, call a server RPC that takes the location and rotation as parameters and calls SetWorldLocationAndRotation on the MC component
  • In the player character, only spawn the controllers on the server (HasAuthority box)
  • Make the references to the controllers RepNotify
  • In the notify function, call GetPlayerPawn and check if it’s equal to this. If so, set the PlayerIndex of the MC to be 0 and the Hand to be whatever the correct hand is meant to be

I think that should be it.

Oh, right, you’ll also need to make the code change I posted here. Disclaimer on this one that I’m not entirely confident that it’s the right fix, but it works for me.

To break it down a little, we set the PlayerIndex to -1 so that they’re not tracking by default. Once we know which pawn is ours (by comparing the local pawn in the RepNotify function), we set the index back to 0 on just those controllers so that they start tracking. (In a non-local multiplayer game in Unreal, the local player is always index 0.)

We have to have the server take care of spawning the controllers so that they are properly connected in the backend. We make the controller references in the player character replicated so that the client knows which controllers belong to whom.

Finally, we sync the transforms of the MCs in two ways. From the client to the server, we use an RPC because that’s the only method available to us in Unreal. We’re checking for ROLE_AutonomousProxy because that indicates that we’re the client “owner” of that pawn (conversely, ROLE_SimulatedProxy would mean we’re a client but this pawn belongs to another player).

For the server to the clients, we just use a RepNotify variable. We set it to COND_SkipOwner because we don’t want the server to send the transform back to the owning client, stomping over his local position.

There’s a lot to absorb there, but it’s fairly straightforward. The biggest gotchas are controlling the spawning from the server and that code fix from the other thread. The rest is pretty standard replication code. That said, let me know if you have any questions. I’m certainly not an expert, but I’m understanding more and more of these systems every day.

If you want to reduce your bandwidth overhead even more you could do something like what I do in my VR plugin, I call get/set in the rep notify and prior to the rpc however it could be done in the bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) function instead to make it invisible to the end user.


USTRUCT()
struct VREXPANSIONPLUGIN_API FBPVRComponentPosRep
{
	GENERATED_BODY()
public:
	UPROPERTY()
		FVector_NetQuantize100 Position;
	UPROPERTY()
		uint32 YawPitchINT;
	UPROPERTY()
		uint8 RollBYTE;

	FORCEINLINE void SetRotation(FRotator NewRot)
	{
		YawPitchINT = (FRotator::CompressAxisToShort(NewRot.Yaw) << 16) | FRotator::CompressAxisToShort(NewRot.Pitch);
		RollBYTE = FRotator::CompressAxisToByte(NewRot.Roll);
	}

	FORCEINLINE FRotator GetRotation()
	{
		const uint16 nPitch = (YawPitchINT & 65535);
		const uint16 nYaw = (YawPitchINT >> 16);

		return FRotator(FRotator::DecompressAxisFromShort(nPitch), FRotator::DecompressAxisFromShort(nYaw), FRotator::DecompressAxisFromByte(RollBYTE));
	}
};

Thank you very much guys
we will try it out

Not OP but this just helped me tremendously. A lot of posts/tutorials have been “Follow these steps and it’ll magically work” so your thorough explanation was just what I needed, thanks.

This helped me tremendously. Not only the clean solution, but the explanation as well. Thanks

I am having this exact problem and this explanation seems to be the answer. As a noob, I am having trouble following the instructions in blue print. Can any post some screen shots or better explain the step by step? Thanks!

Hey mate did you get this sorted without any C++ “Source code” changes ?

Wondering if you had an example project to do it ?

Dunk