This page is a work in progress and could be pending updates.
Available in the Industries Circle

Metaverse Common

The Fusion Metaverse sample is currently only available to users with an active Photon Industries Circle subscription.

Your Industries Circle membership gives you the complete suite plus exclusive license options.


We describe here the elements of the sample which are shared between the different scenes and which seems to us to deserve an explanation

Back To Top


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.

Fusion Metaverse Spaces model

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.

Fusion Metaverse Crowded spaceexample

This logic is handled by the SpaceRoom class, that configure the ConnexionManager so that it uses session properties instead of a room name.

Back To Top

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 the SceneSpawnManager component)
  • it fades out the view
  • it reloads the scenes
  • the SpaceRoom provides the ConnexionManager 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

Back To Top


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 the SpaceRoom 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.

Back To Top

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 using SpaceRoom.RegisterSpaceRequest(). The SpaceRoom space description in the incoming scene is then ignored and changed to be sure that it matches the exact SpaceDescription stored in this SpaceLoader 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);
    SceneManager.LoadScene(SceneName, LoadSceneMode.Single);

Back To Top

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.

Back To Top

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()

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)
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.

Back To Top

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.

To Document Top