Tip: How to shift a synced World position by a different amount on each Client (aka, use it as Local)

Reposting this question from Discord. User @Thundernerd asked:

My question is: Is there a way to sync the position and treat it as a local position? Let’s say I have three players and they all have their own playing field. I want the local player’s playing field to be in the center, and the playing fields of the other two players to be on the left and right side. Is that possible?
An example would be: Tetris. Imagine playing Tetris with two of your friends, every player is playing on their own board. Obviously you want your own to be in the center of the screen and your friends’ boards to the side

Maybe this can help:

For starters, in coherence there is no way to disable the syncing of an object’s position (as of now).

There are however ways to “cheat” to achieve this. One way for instance is to use another network entity with a complex hierarchy (1 child is ok) to act as an intermediate, non-synced parent, to enable the per-Client shifting.

Like in this video:

The Player characters are initially on the root. Their position is synced (obviously) and they have a CoherenceNode component in order to be parented in a complex hierarchy.

An object called Container is spawned, with a child called Offset. We don’t sync Offset’s position.

If the Player is parented to its own object (so, both authority side), on the non-authority side the Client can shift the intermediate child, effectively seeing the remote player in a different position.

This is a sort of a hack, but it should work reliably if the game doesn’t use limited range LiveQueries… which would lead to entities disappearing for seemingly no reason.

Another simple idea is to render the objects shifted on each Client.

Either by using multiple cameras (in built-in pipeline), or by using URP’s Renderer Features. Also a tutorial.

Using a “Render Objects” pass and its Camera > Position Offset, one can render a series of objects as if they were somewhere else in the world.

This trick can be also effective, although has some downsides:

  • It might be costly, as rendering multiple passes is in any case heavier than doing just one. How much, it depends on the game!
  • It might introduce visual artifacts (especially the interaction of shadows from different passes)
  • It might be unwieldy, as one would need to ensure that all objects to be shifted need to be on a specific Layer, which migth make it incompatible with other layer-ordering.

There is yet one more way, which is not immediately available as of now (see below) but will be in the near future.

One way to shift positions (or any other value, really) is to enable Prediction for that binding in the Config window:

Enabling Prediction means that coherence won’t just apply that value every time it receives an update, but it will send a callback to a registered script, and provide them with the value.

So one could write a simple listener as described on this page and, when the position value arrives, apply it by adding an offset.

public Vector3 offset;

private void Awake()
{
    var positionBinding = GetComponent<CoherenceSync>().Bindings.FirstOrDefault(c => c.Name == "position");
    positionBinding.OnNetworkSampleReceived += ApplyWithOffset;
}

private void ApplyWithOffset(object sampleData, long simulationFrame)
{
    var networkPosition = (Vector3)sampleData;
    transform.position = networkPosition + offset;
}

This is a simple, performant, and very powerful solution, because you can manipulate the data in many creative ways (for instance, scale or stretch the position, rotate it, quantize it, etc.).

Caveat
Enabling Prediction as of the moment of writing (coherence 1.0.5) only works with objects that have a server-authoritative setup, that is they use the CoherenceInput component to send inputs to the Simulator and receive state back.
We will enable Prediction and reconciliation as shown above for all bindings in a soon-to-come-out version of coherence.

Thank you for the detailed explanation, very nice to read!

I myself went for option one and it seems to be working as I expected, that is to say, without issues :slight_smile:

2 Likes

Another way to achieve the same result is to set Interpolate On to Nothing on the playing field prefab. This way, player positions are applied as local positions but the playing fields can still be moved around freely.

Note: This disables all interpolated bindings on the playing field, not just the position, so keep this in mind if you intend to also sync things like scale or rotation.

1 Like

Hello! I’ve tried to replicate this example using the First Steps project and I haven’t been able to make it work. For some reason, parenting the Player to the Offset (the child of the Container) is not done automatically on both clients, I have to redo it manually in the other one, and then changing the Transform values of the Offset Game Object does not really affect the networked player transform, it’s like something is making them return to their initial position. I am posting a video to show the steps I’ve followed. Any ideas what I might be doing wrong? Thank you in advance!

Oh, apparently the Player object needs to also have a Coherence node component attached to it aside the Coherence Sync. That solved the issue! Could you kindly explain why that is though?

Hey Elli! Yes, a CoherenceNode component is necessary if the prefab is not a direct child. The component tracks where the child is in the hierarchy, so that if the object is parented/unpatented or moved, its new parent is sent over the network and all the positioning etc. is resolved by coherence without you having to do anything.

You can read more about it here: Deep Child CoherenceSyncs - Unity Multiplayer SDK Documentation | coherence

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.