This document is about: FUSION 2
SWITCH TO

Fusion VR Training

Level 4
Available in the Industries Circle
Circle

Overview

The VR Training sample is provided with full source code and demonstrates how Fusion can be used to create a multiplayer VR training application.

In order to use a concrete case for the design of this sample, several activities around a drone have been imagined (what's what, drone assembly and drone control).
However, the underlying elements can be reused or adapted to design other VR training courses.

Here are some of the most notable features:

  • users can take on the role of teacher or learner,
  • learners are provided with their own objects for each training activity when they connect to the room,
  • training activities are managed centrally to synchronize participants,
  • learners can quickly restart an activity at the touch of a button,
  • unintentional disconnections are managed to ensure that training progress is not lost,
  • each learner's progress on activities is synchronized in real time,
  • activation or deactivation of an object (or object group) for each activity status,
  • simultaneous assembly and manipulation of objects by several people,
  • visual guide to help participants assemble objects,

From a more technical point of view, the most noteworthy elements are :

  • cross-registering management
  • advanced object magnet mechanism
  • structure construction/deconstruction with objects than can be manipulated simultaneously by different players
  • disconnection management to recover authority over objects previously owned by a player

Technical Info

  • This sample uses the Shared Authority topology,
  • Builds are available for Meta Quest,
  • The project has been developed with Unity 2022.3, Fusion 2, Photon Voice 2.53,
  • 2 avatars solutions are supported (home made simple avatars & Ready Player Me avatars),

Before you start

To run the sample :

  • Create a Fusion AppId in the PhotonEngine Dashboard and paste it into the App Id Fusion field in Real Time Settings (reachable from the Fusion menu).

  • Create a Voice AppId in the PhotonEngine Dashboard and paste it into the App Id Voice field in Real Time Settings

  • Then load the AvatarSelection scene and press Play

Download

Version Release Date Download
2.0.5 Feb 27, 2025 Fusion VR Training 2.0.5 Build 792

Download APK

A demo version of VR Training is available below :

Handling Input

Meta Quest

  • Teleport : press A, B, X, Y, or any stick to display a pointer. You will teleport on any accepted target on release
  • Rotation : move the joystick left or right to rotate
  • Touch : simply put your hand over a button to toggle it
  • Grab : first put your hand over the object and grab it using controller grab button
  • Drone : use the menu button on the left controller to turn on/off the drone. Use the left and right joystick to control the drone.
  • Pen color : move the joystick up or down to change the pen color

Folder Structure

The main folder /Training contains all elements specific to this sample.

The folder /IndustriesComponents contains components shared with others industries samples.

The /Photon folder contains the Fusion and Photon Voice SDK.

The /Photon/FusionAddons folder contains the Industries Addons used in this sample.

The /Photon/FusionAddons/FusionXRShared folder contains the rig and grabbing logic coming from the VR shared sample, creating a FusionXRShared light SDK that can be shared with other projects.

The /XR folder contains configuration files for virtual reality.

Architecture overview

The VR Training sample is based on the same code base as that described in the VR Shared page, notably for the rig synchronization.

The grabbing system used here is the alternative "local rig grabbing" implementation described in the VR Shared - Local rig grabbing page.

Aside from this base, the sample, like the other Industries samples, contains some extensions to the Industries Addons, to handle some reusable features like synchronized rays, locomotion validation, touching, teleportation smoothing or a gazing system.

Training Sample

The sample is structured around the following main elements:

  • participants connected to the room. They can be either learner (default) or teacher. This is managed by the Learner class,
  • a training manager (class LearningManager), which has an overview of all participants and learning activities,
  • the various training activities, inherited from the LearnerActivityInfo class,
  • a scoreboard to display learners' progress on the various activities,
  • a stand where you can choose your role (learner or teacher) and change the status of the various activities.

Learner

When a player connects to the room, the NetworkRigVariant prefab is spawned by the ConnectionManager game object.
This prefab contains the Learner class, which :

  • manages a UserID identifier to detect unexpected disconnections,
  • registers the participant with the LearningManager (only one in this sample),
  • spawns the activity prefab once registered with the LearningManager (or recovers authority over its objects in case of reconnection).

Participant's role

In our implementation, when a participant connects, he's considered as a learner by default (IsLearning is set to true on spawn).
He has the possibility of changing role by clicking on the "Request the trainer Role" button on the console.
See Control stand for more details.

Registration

When a user connects, he must register with the LearningManager so that it can assign him a slot in the room with all the elements required for training activities.
Similarly, when the LearningManager is created at room connection time, it must search for all Learners present in the room in order to update its local cache of Learner.

As the order in which objects (Learner and LearningManager) are created is not certain, there are several risks that a simple registration process could fail:

  • the LearningManager object is not yet spawned & valid when the Learner is spawned,
  • the Learner object is not yet spawned & valid when the LearningManager is spawned,
  • the registration process cross between Learners and LearningManagers.

So, to handle the complexity of the registration process, we use the SubscriberRegistry addon :

  • the Learner class inherit from the LearnerComponent which inherit from the Subscriber addon class,
  • the LearningManager class inherit from the Registry addon class,
    See SubscriberRegistry for more details.

When the registration process is done, the Learner spawns the activity prefab which contains all the objects required for the learning activities.
This prefab is configured in the LearningManager (learnerActivitiesPrefab).
See method LearnerSlotBookedOnLearningManager() for more details.

Reconnection management

A recovery mechanism is implemented so that the progress of the training course is not lost in the event of unexpected disconnection.

When a user connects for the first time :
- a unique identifier is generated and stored in the networked variable UserId,
- this identifier is also saved in the local user's preferences,
- the UserId is stored in all user's spawned objects with the Recoverable class.
- the State Authority of the LearningManager allocates a slot for this UserId in the NetworkArray LearnerSlotInfos (StoreLearner())

In case of disconnection, ideally :
- the Learner is unregistered (method Despawned() of Subscriber class)
- the LearninManager will then free the slot in the LearnerSlotInfos array (CleanLearnerSlotPosition()),

When the user reconnects to the room :
- the Learner class checks whether a UserId has been saved in the local user's preferences,
- we can deduce that it's a reconnection if this UserId is present in the LearnerSlotInfos NetworkArray of the LearningManager (it's another room participant who has maintained the existence of the array and has the StateAuthority on it).
- in this case, the activity prefab is not respawned and the participant recovers the state authority on his objects created at his first connection (RequestAuthorityOnRecoverablesWithUserId()),
- so that users can continue their training.

Learning Manager

The LearningManager is a central element in the operation of the sample.

It manages :

  • learners registration,
  • learners slot allocation (a finite number of learning table positions is available in the room, and each learner need to be assigned to one of these slots),
  • learning activities registration,
  • learning activity global status,
  • events required to update the scoring board and console,

LearningManager is a networked scene object : it means that all clients will have their own copy but only the state authority can modify the networked data :

  • LearnerSlotInfos : this networked array is used to store the slot occupation. Each entry contains the player's userId & associated NetworkBehaviourId.
  • CurrentActivityStatus : this networked dictionnary is used to store the global status of activities (open or closed)

C#

    // List to save the slot occupation
    [Networked, OnChangedRender(nameof(OnLearnerSlotChange))]
    [Capacity(MaxLearner)]
    [UnitySerializeField]
    public NetworkArray<LearnerSlotInfo> LearnerSlotInfos { get; }

    // Collection of Activities Status
    [Networked, OnChangedRender(nameof(OnCurrentActivityStatus))]
    [Capacity(MaxActivities)]
    [UnitySerializeField]
    public NetworkDictionary<int, ActivityStatus> CurrentActivityStatus { get; }

    public struct LearnerSlotInfo : INetworkStruct
    {
        public NetworkBehaviourId learnerBehaviourId;
        public NetworkString<_64> userId;
    }

    public enum ActivityStatus
    {
        Open,
        Closed
    }

Learner Registration & Slot Allocation

Space in the scene being limited, the LearnerSlotInfos networked array is used to allocate slots.
It is updated when a learner register to the LearningManager (StoreLearner()).
This modification triggers the OnLearnerSlotChange() method. Then, the LearningManager ask the learner to spawn the activity prefab (prefabs and spawn position are provided by the LearningManager).

See Learner Registration for more details.

Activity Registration

The activity prefab contains three LearnerActivityInfo, one for each activity.
When the activity prefab is spawned, the activities registers on the LearningManager (OnLearningManagerAvailable()).
Each client updates their local activity list registeredLearnerActivities (this local copy is used by the ScoreBoardManager)
If this activity has not yet known, the LearningManager state authority updates the CurrentActivityStatus networked dictionary.

See Activity Info Registration for more details.

Learning Activities

Registration

Just as the LearningManager needs to know all connected Learners (to assign them a slot in the scene), the LearningManager needs to know all active activities in the scene in order to be able to act on them (for example : to open/close an activity or inform the ScoreBoardManager that a player has progressed on an activity).
To achieve this, a similar registration mechanism is set up between the LearningManager and the LearnerActivityInfo.

When a user connects, its activity prefab is spawned and all included activities (3 in this sample) will look for the LearningManager in the scene and try to register on it.

At the end of the registration process, each activity has a reference on the LearningManager, and the LearningManager of each client manage a local list of all activities in the registeredLearnerActivities list

As an example, if 4 learners are connected to the room, on each client :

  • there will be one LearningManager. As it is networked scene object, only one client will have the state authority on it, and will therefore be able to modify the networked variables (LearnerSlotInfos for example), but all clients maintains local variables (learner list, activity list, etc.),
  • the LearningManager registeredLearners list have 4 entries,
  • the LearningManager registeredLearnerActivities list will have 12 entries (3 activities per learners)
  • there will be 12 activities (LearnerActivityInfo) in the scene, but each client have the state authority only on the 3 activities associated to its learner.

Learner Activity Info

Each activity inherits from the LearnerActivityInfo.
It contains common parameters such as activity status (Disabled, ReadyToStart, Started, Succeeded, etc.) and progress

C#

    public enum LearnerActivityStatus
    {
        Disabled,
        ReadyToStart,
        Started,
        Failed,
        Succeeded,
        Finished
    }

    [Networked, OnChangedRender(nameof(OnActivityStatusOfLearnerChange))]
    public LearnerActivityStatus ActivityStatusOfLearner { get; set; }

    [Networked, OnChangedRender(nameof(OnActivityChange))]
    public float Progress { get; set; }

Both ActivityStatusOfLearner and Progress are networked so all clients are notified when an other participant makes progress in the learning path.

When the ActivityStatusOfLearner is updated by a learner (when he completed its activity for example), all clients are notified and they execute the OnActivityStatusOfLearnerChange() method.
This function is in charge to activate/deactivate game objects in the scene based on the current activity status.

When a learner's actions modify the progress of an activity, the OnActivityChange() function is called on all clients.
This results in the corresponding function being called on the LearningManager.
Please note that only the state authority can change the ActivityStatusOfLearner (for example when the activity progress equals 1)

C#

    public virtual void OnActivityChange()
    {
        if (Object.HasStateAuthority)
        {
            CheckActivityProgress();
        }
        LearningManager?.OnActivityChange(this);
    }

Then, the LearningManager send the onActivityUpdate event, which will be consumed by ScoreBoardManager and ControlStandActivityListDisplay.

Activity 1 : What is What ?

This activity involves recognizing certain drone parts.
To do this, the learner must place flags on the sockets associated with the parts.
Once all the flags have been placed correctly, the activity changed to succeeded status.
There are no failure criteria implemented in this activity, but the activity could have been considered failed if the learner made 3 errors, for example.

Magnets Between Flags and Sockets

Magnetism between flags and sockets is achieved as follows:

On the sockets :

  • they have an AttractorMagnet component configured with layer "Magnets" to attract nearby ungrabbed object with AttractableMagnet (flags).
    This mechanism is local only and there is no notion of a "link" between the attractor and the attractable.
  • to create and memorize a link between two magnetic elements, we use the MagnetAttachmentPoint class (which inherits from the AttachmentPoint class).
    When two objects are magnetized together, the link between them is stored and synchronized on the network (AttachedPoint variable) so that manipulation of one of the objects by another learner can be handled correctly.
    There is a filter here to ensure that the link can only be created between compatible attachment points : sockets have an MagnetAttachmentPoint Attachment Point Tags parameter set to "Flag" (while the flags have the MagnetAttachmentPoint Compatible Attachment Point Tags set to "Flag").
  • finally, sockets also have the NamingTag class, which identifies the flag expected on each socket.

On the flag side, there are 2 MagnetAttractable subsets: one for magnetization with the socket and another for the tray.

Concerning the one for the socket :

  • there's an AttractableMagnet for magnetization with the socket's AttractorMagnet (with "Magnets" layer)
  • and a MagnetAttachmentPoint with the "Compatible AttachmentPoint Tags" parameter set to "Flag".

With these elements, a flag is magnetized by a socket and the attachment link is propagated on the network.

For more details, see (Structure cohesion add-on - Stand alone usage of AttachmentPoint)[~~~/industries-samples/industries-addons/fusion-industries-addons-structure-cohesion#stand-alone-usage-of-attachmentpoint]

Magnets Between Flags and Tray

The flags on the tray illustrate a more advanced use of magnets, in the context of a structure: the tray and the flags form a structure, that remains coherent when you grab the tray, but breaks at the level of a flag when you grab it.

The idea here is :

  • to be able to magnetize the flags on a secondary object. There is therefore a second MagnetAttractable subset on each flag with a MagnetStructureAttachmentPoint component configured with the "Tray" tag in the "Compatible AttachmentPoint Tags" parameter,
  • to magnetize several flags on the same target object: there are as many AttractorMagnet & MagnetStructureAttachmentPoint components on the tray as there are flags,
  • to magnetize an MagnetAttractable object without repositioning it to the same position than the AttractorMagnet. To do this, the tray's AttractorMagnet component is set to "Attract Only On Aligment Axis", unlike the sockets, which are set to "Match Attracting Magnet Position",
  • to make the flags follow the tray if it is moved. To do this, we use the StructureCohesion addon : both flags and tray have the GrabbableStructurePart component (which inherits from StructurePart).
    Also, the MagnetAttachmentPoint used for magnetization with sockets is replaced here by a MagnetStructureAttachmentPoint. In this way, objects are grouped together within the same structure if they become attached with AttachmentPoint.
  • to break the structure when you grab a flag, the flag has its structuralCohesionMode set to StructuralCohesionMode.WeightBasedCohesion (that allows to break a structure when only one light-weight object is grabbed), and its partWeight is set to a low weight.

For more details on the weight base splitting of a structure, see (Structure cohesion add-on - Attachment deletion when 1 "lightweight" part is moving in a structure)[~~~/industries-samples/industries-addons/fusion-industries-addons-structure-cohesion#attachment-deletion-when-1-lightweight-part-is-moving-in-a-structure]

Flags

Overall functioning of flags is managed by the NamingFlag class located on each flag.
Flags have a FlagStatus synchronized on the network.

C#

    public enum FlagStatus
    {
        goodPosition,
        badPosition,
        notDefined
    }

    [SerializeField]
    [Networked, OnChangedRender(nameof(OnFlagStatusChanged))]
    public FlagStatus flagStatus { get; set; } = FlagStatus.notDefined;

The NamingFlag class listens to the AttachmentPoint to be notified when a flag is magnetized,

C#

    void Start()
    {
        if (attachmentPoint)
        {
            attachmentPoint.onRegisterAttachment.AddListener(OnSnap);
            attachmentPoint.onUnregisterAttachment.AddListener(OnUnSnap);
        }
    }

The OnSnap() function checks if the attached point contains a NamingTag component, and if the tag match the expected tag namingTag configured on the flag.
The flagStatus is then updated and synchronized on the network. This triggers the onFlagStatusChanged event and the update of the visual feedback on all clients with the UpdateFlagRenderer() method.

Progress

The activity progress is updated by the ActivityNaming class (which inherits from the LearnerActivityInfo class).
At start, the ActivityNaming class looks for all flags and add a listener on the onFlagStatusChanged event for each of them.
In this way, it is notified each time a flag is snapped/unsnapped from a socket, and a new progress is computed.
Then, when the Progress networked variable is modified, the LearnerActivityInfo OnActivityChange() function is called on all clients.
This results in the corresponding function being called on the LearningManager, then the scoreBoardManager is notified with the onActivityUpdate event.

Reset Position And Flag

A reset button makes it easy to restart the activity.
If a participant touches the button, this triggers a call to the OnReset() functions of the parent class ResetActivity.
This parent class resets all networked objects to their original positions and configures the ActivityStatusOfLearner to ReadyToStart status.
However, the specific actions to be taken when this activity is reset are handled by the ResetNamingActivity child class.
Thus, RestoreFlagStatus() will reset flags to the "not defined" state and RestoreAttachmentPoints() will reset MagnetStructureAttachmentPoint to their initial configuration.

Activity 2 : Drone Assembly

Here, the aim is to build the drone by assembling its various parts.
The learner is provided with an assembly plan. Also, when he grabs a part of the drone, a visual guide allows him to see with which other parts of the drone it can be assembled.
Several people can handle and assemble the drone simultaneously.
The activity changed to succeeded status when all parts are assembled.

Magnets

Like the magnets for the flags and the tray, each part of the drone requires magnets with the following components :

  • AttractorMagnet and AttractableMagnet components for the local snapping effect,
  • a MagnetStructureAttachmentPoint with specific Attachment Point Tags and Compatible Attachment Point Tags parameters to link drone parts together,
  • GrabbableStructurePart component from StructureCohesion addon to make sure that linked parts move together

To help learners to see the magnets on drone parts, the MagnetPointVisual class is in charge to display the magnet's elements. It provides a visual representation for AttractorMagnet, AttractableMagnet and the radius of attraction ("magnet power").It listens to the magnet StructurePart in order to update the magnet visual (for example, hide the magnet visual when the part is snapped with an other part).

Also, to assist the player in assembling the drone, the StructurePartVisualGuide class analyses in real time the parts compatible with the part held in the learner's hand.
To do this, it compares the AttachmentPoint tags of the drone part held by the user with the parts registered in the structurePartsManager, and displays a line if :

  • the tags match,
  • the compatible magnet is not already connected to another magnet,
  • if it the closest compatible magnet,
Progress

The activity progress is updated by the ActivityDroneAssembly class (which inherits from the LearnerActivityInfo class).
At start, the ActivityNaming class looks for all MagnetStructureAttachmentPoint
It also implements the IAttachmentListener interface. It will therefore be notified each time a AttachmentPoint is attached/detached (i.e. a magnet is snapped/unsnapped), and a new progress is computed.
Then, when the Progress networked variable is modified, the LearnerActivityInfo OnActivityChange() function is called on all clients.
This results in the corresponding function being called on the LearningManager, then the scoreBoardManager is notified with the onActivityUpdate event.

Reset Position And Drone Structure

A reset button makes it easy to restart the activity.
If a participant touches the button, this triggers a call to the OnReset() functions of the parent class ResetActivity.
This parent class resets all networked objects to their original positions and configures the ActivityStatusOfLearner to ReadyToStart status.
However, the specific actions to be taken when this activity is reset are handled by the ResetDroneAssemblyActivity child class.
Thus, RestoreMagnetVisual() will restore all MagnetPointVisual of drone parts and RestoreStructureAttachment() will delete all attachements of drone parts.

Activity 3 : Drone Control

The final activity involves piloting a drone to land safely on a target position.

Controls

When the activity is open, each learner can control his or her own drone using Meta Quest controllers.

The drone is managed by the DroneControl class.
The drone's position and inclination are synchronized by Network Transform (so there's no need to synchronize joystick inputs).

In addition, to provide appropriate visual and sound effects for other players, we have to synchronize the status of the UAVStatus drone (ReadyToFly, Flying, etc.) and its DroneRemoteControlStatus on the network.

C#


    [Networked]
    [SerializeField] public UAVStatus DroneStatus { get; set; } = UAVStatus.NotYetInitialized;

    [Networked]
    [SerializeField] private DroneRemoteControlStatus RemoteControlStatus { get; set; } = DroneRemoteControlStatus.SwitchedOff;

   public enum DroneRemoteControlStatus
    {
        SwitchedOff,
        SwitchedOn
    }

    [System.Serializable]
    public enum UAVStatus
    {
        NotYetInitialized,
        ReadyToFly,
        LimitedFlyingMode,      
        Flying,
        AutoLanding,
        Landed                 
    }
Progress

The activity progress is updated by the ActivityDroneControl class (which inherits from the LearnerActivityInfo class).

It consists of calculating the distance between the drone's position and that of the target.
To avoid unnecessary bandwidth consumption, the Progress network variable is only modified if the distance changes by plus or minus 10% (unless the drone is close to the target).
Then, when the Progress networked variable is modified, the LearnerActivityInfo OnActivityChange() function is called on all clients.
This results in the corresponding function being called on the LearningManager, then the scoreBoardManager is notified with the onActivityUpdate event.

Control stand

The control stand is used to :
- select the partcipant role (teacher or learner)
- see and change the activities status (open or closed)

Participant's role

Participants connected to the room can be either learner (by default) or teacher.

The user's interaction with the button on the console to change the role is detected only the user's client.
Buttons visibiliy is managed by ControlStandUserRoleButtonManager.

Buttons calls the RequestTrainerRole() (or RequestLearnerRole()) method of the PlayerRoleSelection class, which changes the IsLearning bool.
Because the bool is networked, all clients will be notified and so can update the player's cap material.

C#

    [Networked, OnChangedRender(nameof(OnIsLearningChange))]
    public NetworkBool IsLearning { get; set; }

    void OnIsLearningChange()
    {
        UpdateLearnerCap();
    }

If a participant request to be a learner, the slot and associated objects are released to make room for another learner.

Activity status

The control stand is also used to display and change the activities status.
The ControlStandActivityListDisplay listen to the LearningManger onActivityListUpdate & onActivityUpdate events to update the UI.

C#

    private void Start()
    {
        ...
        learningManager.onActivityListUpdate.AddListener(UpdateUIForActivities);
        learningManager.onActivityUpdate.AddListener(UpdateUIForActivityUpdate);
    }

onActivityListUpdate event is triggered when the LearningManager networked dictionary CurrentActivityStatus is updated (ie when a new activity is registered/unregister or when an activity status changed).

When a participant clicks on a button to change the activity status, it triggers learningManager.ToggleActivityStatus(currentIndex) which set the new status in CurrentActivityStatus.
On CurrentActivityStatus modification, ActivityListUpdateEvent() is called and trigger the onActivityListUpdate event.

C#

    public void OnCurrentActivityStatus(NetworkBehaviourBuffer previousBuffer)
    {
        ActivityListUpdateEvent();
        InformLearnerActivityInfo(previousBuffer);
    }

    private void ActivityListUpdateEvent()
    {
        if (onActivityListUpdate != null)
        {
            onActivityListUpdate.Invoke(this);
        }
    }

In addition to ActivityListUpdateEvent(), the method InformLearnerActivityInfo() is also called when a participant touch the button : it checks which value (activity) has been updated and calls OnActivityUpdate(activityId) which update all the registered activities with this id.

Then, in the OnLearningManagerActivityUpdate() function, the state authority changes the networked LearnerActivityStatus ActivityStatusOfLearner.
So all client updates the activated objects based on the last status (UpdateActivatedObjectsBasedOnStatus()).

Score board

The ScoreBoardManager is in charge to update the score board when it is required :
- a learner join or left the room,
- a learner's progress on an activity has changed,

To do that, the ScoreBoardManager listen to the LearningManager events :

C#

private void Start()
    {
        ...
        learningManager.onNewLearner.AddListener(OnNewLearner);
        learningManager.onDeletedLearner.AddListener(OnDeletedLearner);
        learningManager.onActivityUpdate.AddListener(UpdateUIForActivity);
    }

Depending on events and modifications made, ScoreBoardManager will :

  • add/delete lines on the score board,
  • update the activity progress bars,

Used XR Addons & Industries Addons

To make it easy for everyone to get started with their 3D/XR project prototyping, we provide a few free addons.
See XR Addons for more details.
Also, we provide to our Industries Circle members a comprehensive list of reusable addons.
See Industries Addons for more details.

Here are the addons we've used in this sample.

XRShared

XRShared addon provides the base components to create a XR experience compatible with Fusion.
It is in charge of the players' rig parts synchronization, and provides simple features such as grabbing and teleport.

See XRShared for more details.

Connection Manager

We use the ConnectionManager addon to manage the connection launch and spawn the user representation.

See ConnectionManager Addons for more details.

Subscriber Registry

We use the SubscriberRegistry addon to handle the registration process between the LearningManager and Learner/LearnerActivityInfo classes.

See SubscriberRegistry Addons for more details.

Reconnection

We use the Reconnection addon to handle disconnect and reconnection of an user, to be notified of this event, and to recover the authority on Recoverable that remained in the scene.

See Reconnection for more details.

Magnets

This addon is used to snap objects together.

See Magnet Addons for more details.

Structure Cohesion

Structure Cohesion allows objects to be assembled together within the same structure. This allows all objects attached together to be moved when the user moves one of them.

See Structure Cohesion for more details.

Locomotion validation

We use the locomotion validation addon to limit the player's movements (stay in the room, avoid furniture, etc.).

See Locomotion validation Industries Addons for more details.

Dynamic Audio group

We use the dynamic audio group addon to enable users to chat together, while taking into account the distance between users to optimize comfort and bandwidth consumption.

See Dynamic Audio group Industries Addons for more details.

Drawing

The room contains whiteboards with 2D pens. When the drawing is complete (i.e. when the user releases the "trigger" button), a handle is displayed. This allows the user to move 2D.

See 3D & 2D drawing Industries Addons for more details.

Data Sync Helpers

This addon is used here to synchronize the 2D drawing points.

See Data Sync Helpers Industries Addons for more details.

Blocking contact

We use this addon to block 2D pens and drawing' pin on whiteboard surfaces.

See Blocking contact Industries Addons for more details.

Feedback

We use the Feedback addon to centralize sounds used in the application and to manage haptic & audio feedbacks.

See Feedback Addons for more details.

3rd Party Assets and Attributions

The Fusion VR Training sample is built around several awesome third party assets:

Back to top