Metaverse Common
Your Industries Circle membership gives you the complete suite plus exclusive license options.
Overview
We describe here the elements of the sample which are shared between the different scenes and which seems to us to deserve an explanation
Spaces
Spaces And Matchmaking
In the metaverse sample, each places where users can go are called "Spaces". An user can either join a public space or join a private space, aka a space restricted to all user who are in the same "Group".
If too much users are in a same room for a given space/group pair, additional users will still be able to connect but will be routed to a new room.

This logic is implemented with Fusion matchmaking capabilities.
The space and group an user wants to join are described with session properties (note: a metaverse property is also used to allow more filtering if needed, but the overall logic is the same without it). Then, as no explicit room name is provided in the start game arguments given to the runner, the matchmaking system routes the user to a session matching the space id/group id filters. If the first room created is full, another room will be created and other users will join it, allowing to handle crowded spaces without prevent further users from connecting.

This logic is handled by the SpaceRoom
class, that configure the ConnexionManager
so that it uses session properties instead of a room name.
Switching Groups
SpaceRoom
also handle group switching: when the user wants to join a private group (or wants to leave a private group to join the public group), the SpaceRoom
handles it this way:
- it stores in the
Playerprefs
the requested group id - it stores the user current position in the
PlayerPrefs
(through theSceneSpawnManager
component) - it fades out the view
- it reloads the scenes
- the
SpaceRoom
provides theConnexionManager
the group id to be used as a session property in the matchmaking API - during the scene loading, the user is positioned at their previous saved position, by the
SceneSpawnManager
component
SpaceDescription
The space id can be either set on the SpaceRoom
component in the inspector, or it is possible to prepare and provide a SpaceDescription
.
The SpaceDescription
is a scriptable object, with the following fields:
spaceId
: technical data field to uniquely identify each space, and to fill in theSpaceRoom
component the session properties that will route the user to a proper Fusion room for this space.sceneName
: the Unity scene that must be loaded when a player joins this space.spaceName
: the name of the space. It is loaded on the panel of portals between spaces. It is not used as an id, just as a presentation text.spaceDescription
: a description of the space. It is loaded on the panel of portals between spaces, to explain the purpose of the target space to the user.spaceParam
: optional technical data. For future use, to provide parameters to a scene when several spaces use the same scene.spaceSecondaryParam
: optional technical data. For future use, to provide parameters to a scene when several spaces use the same scene.
Joining Another Space
Joining another Space is handled by the SpaceLoader
component. It is possible to either provide a full SpaceDescription
on it, or just a space id string for simpler needs.
To know which Unity scene to load, the SpaceLoader
can either read the scene provided in its SpaceDescription
field. Or, for simpler cases, it assumes the scene name is the same than the space id string directly provided.
When its SwitchScene()
method is called, the space change occurs, by loading the target scene. This methods:
- first shutdown the current Runner
- records the requested
SpaceDescription
in usingSpaceRoom.RegisterSpaceRequest()
. TheSpaceRoom
space description in the incoming scene is then ignored and changed to be sure that it matches the exactSpaceDescription
stored in thisSpaceLoader
that led to it. This is not needed for simple spaces configurations like the one in the current sample state, but it can enable more advanced scenarios where a single Unity scene is used for several spaces. - then load the new Unity Scene.
private async void SwitchScene()
{
...
await runner.Shutdown(true);
Debug.Log("Loading new scene " + SceneName);
SpaceRoom.RegisterSpaceRequest(spaceDescription);
SceneManager.LoadScene(SceneName, LoadSceneMode.Single);
}
Spawn Point When Coming From Another Space
The SpaceLoader
component can also provide a returnPosition
if autoRegisterAsReturnPoint
is true, to differentiate the player spawn position when the hub scene is loaded directly and when the player come back after joining another scene.
When an user is joining the current scene (associated to Space A), just after being in the space (Space B) targeted by a SpaceLoader
, the user will spawn at the returnPosition
transform associated to this SpaceLoader
leading to Space B. This is done by providing this return position associated to Space B to the SceneSpawnManager
, which handles the user spawn position.
Dynamic Audio Group
This sample provides a simple dynamic audio group feature: it allows to discuss with all the remote users around the local one, without using bandwidth or adding noise from user afar. Users beyond a defined distance are not heard. Coupled with a adapted sound spatialization and attenuation, this provides a natural listening sensation.
The current implementation is based on Photon Voice interest groups.
Each user will speak in one unique interest group, while they will be listening to several interest groups, based on their proximity to other users, limiting the needed bandwidth. Moreover, isolated people don't send their voice over the network to even further avoid consuming bandwidth.
Unlike the Expo sample chat bubbles, there is no need here of a visual cue around a single user that would creates an artificial limit.
Besides, it is not based on a common expending group for people close to each other, that might reach high user counts progressively.
Note however that as it is based on distance only, if a lot of people gather to a single place, the traffic might reach high numbers anyway: for such need, additional limitations (dynamic proximity distance decreasing with the density, hard limit to the number of followed users, ...) would be relevant.
The networked user prefab (MetaverseNetworkRig
) has the DynamicAudioGroupMember
class.
In the Spawned()
a unique audio GroupId
is computed for the player (in other words, each player has it own GroupId
).
Then, the local player Recorder audio interest group is configured with this GroupId
and the voice client is configured to listen it.
async void SpeakAndListenOnlyToGroup(byte groupId)
{
...
recorder.InterestGroup = groupId;
fusionVoiceClient.Client.OpChangeGroups(groupsToRemove: new byte[] { }, groupsToAdd: new byte[] { groupId });
...
}
Please note that the group in which the local player speaks will never be changed.
However, the list of groups he listens to is dynamically modified according to the proximity of the other players.
To do so, during the Update()
, we check if other players are within the defined distance.
private void Update()
{
CheckProximity();
}
For each player found at proximity, the local player starts to listen the GroupId
of the remote player.
private void CheckProximity()
{
...
if(DistanceSqr(member) < proximityDistanceSqr)
{
...
ListentoGroupId(member.GroupId);
}
...
}
async void ListentoGroupId(byte groupId)
{
if (!Object.HasStateAuthority) return;
while (!IsPhotonVoiceready || !isSpeakInitialized) await Task.Delay(10);
fusionVoiceClient.Client.OpChangeGroups(groupsToRemove: null, groupsToAdd: new byte[] { groupId });
}
Conversely, if a remote player is no longer in the proximity perimeter, then he is no longer listened to thanks to the StopListeningToGroupId()
method.
Because these operations are performed by all the players, a two-way conversation is possible although each player sends his voice only in his own GroupId
.
Social Distancing
To avoid two players being too close, we have implemented a "Social Distancing" feature.
To do so, the network player prefab contains a ForbiddenArea
(capsule collider). This zone is forbidden to all other remote players.
The SocialDistancing
class has been added to the network player prefab and implements the ILocomotionValidator
interface.
So, the CanMoveHeadset
method checks if the player's head is above the ForbiddenArea
by performing a raycast towards the ground.
To avoid auto collision detection, the ForbiddenArea
is deleted during the Spawned()
for the local network rig.
When a collision is detected, movement is prohibited and a visual feedback is displayed to indicate the prohibited area.
Please note that the mechanism does not prevent two players from getting closer if they move towards each other simultaneously.