Creating massive multiplayer worlds
Unity has a well-known limitation of offering high precision positioning only within a few kilometers from the center of the world. A common technique to get around this limitation is to move the whole world underneath the player. This is called floating origin. Here's how you can use floating origin with coherence.
Unity uses 32-bit floating-point numbers to represent the world position of game objects in memory. While this format can represent numbers up to , its precision decreases as the number gets larger. You can use this site to see that already around the distance of meters the precision of a 32-bit float is 1 meter, which means that the position can only be represented in steps of one meter or more. So if your Game Object moves away from the origin by 1000 kilometers it can be only positioned with the accuracy of 1000km and one meter, or at 1000km and two meters, but not in between. As a result, usable virtual worlds can be limited to a range of as little as 5km, depending on how precisely GameObjects like bullets need to be tracked.
Having a single floating world origin as used in single player games is not sufficient for multiplayer games since each player can be located in different parts of the virtual world. For that reason, in coherence, all positions on the Replication Server are stored in absolute coordinates, while each Client has its own floating origin position, to which all of their game object positions are relative.
To represent the absolute position of Game Objects on the Replication Server, we use the 64-bit floating-point format. This format allows for sub-1mm precision out to distances of 5 billion kilometers. To keep the implementation simple, floating origin position and any Game Object's absolute position is limited to the 32-bit float range, but because of the Floating Origin, it will have precision of a 64-bit float when networked with other Clients.
Here is a simple example how the floating origin could be used. We will create a script that is attached to the player Prefab and is active only on the Client with authority.
Calling the CoherenceManager.TranslateFloatingOrigin
will shift all CoherenceSync objects by the translated vector, but you have to shift other non-networked objects by yourself. We will create another script which takes care of this.
When your floating origin changes, the CoherenceManager.OnFloatingOriginShifted
event is invoked. It contains arguments such as the last floating origin, the new one, and the delta between them. We use the delta to shift back all non-networked game objects ourselves. Since the floating origin is Vector3
of doubles we need to use ToUnityVector3
method to convert it to Vector3
of floats.
To control what happens to your entities when you change your floating origin, you can use CoherenceSync's floatingOriginMode
and floatingOriginParentedMode
fields. Both are accessible from the inspector under Advanced Settings.
Available options for both fields are:
MoveWithFloatingOrigin
- when you change your floating origin, the Entity is moved with it, so its relative position is the same and absolute position is shifted.
DontMoveWithFloatingOrigin
- when you change your floating origin, the Entity is left behind, so its absolute position is the same and relative position is shifted.
Floating Origin Mode dictates what happens to the Entity when it is a Root Object in the scene hierarchy, and Floating Origin Parented Mode dictates what happens to it when its parented under another non-synced Game Object.
If the Entity is parented under another CoherenceSync Object (even using CoherenceNode), its local position will never be changed, since it will always be relative to the parent.
If you are using Cinemachine for your cameras, you'll need to call OnTargetObjectWarped to notify them that the camera target has moved when you shift the floating origin.