Fusion VR Training



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 SettingsThen load the
AvatarSelection
scene and pressPlay
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 theLearner
is spawned, - the
Learner
object is not yet spawned & valid when theLearningManager
is spawned, - the registration process cross between
Learners
andLearningManagers
.
So, to handle the complexity of the registration process, we use the SubscriberRegistry
addon :
- the
Learner
class inherit from theLearnerComponent
which inherit from theSubscriber
addon class, - the
LearningManager
class inherit from theRegistry
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'suserId
& associatedNetworkBehaviourId
.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 withAttractableMagnet
(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 theAttachmentPoint
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 anMagnetAttachmentPoint
Attachment Point Tags
parameter set to "Flag" (while the flags have theMagnetAttachmentPoint
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'sAttractorMagnet
(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 aMagnetStructureAttachmentPoint
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 theAttractorMagnet
. To do this, the tray'sAttractorMagnet
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 theGrabbableStructurePart
component (which inherits fromStructurePart
).
Also, theMagnetAttachmentPoint
used for magnetization with sockets is replaced here by aMagnetStructureAttachmentPoint
. In this way, objects are grouped together within the same structure if they become attached withAttachmentPoint
. - to break the structure when you grab a flag, the flag has its
structuralCohesionMode
set toStructuralCohesionMode.WeightBasedCohesion
(that allows to break a structure when only one light-weight object is grabbed), and itspartWeight
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
andAttractableMagnet
components for the local snapping effect,- a
MagnetStructureAttachmentPoint
with specificAttachment Point Tags
andCompatible Attachment Point Tags
parameters to link drone parts together, GrabbableStructurePart
component fromStructureCohesion
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:
- Oculus Integration
- Oculus Lipsync
- Oculus Sample Framework hands
- Sounds
- nathangibson-Universal UI Soundpack
- obsydianx-interface-sfx-pack-1
- Pixabay
- Jingle_Win_Synth_03.wav by LittleRobotSoundFactory License: Attribution 4.0
- Applause by Kubuzz License: Creative Commons 0
- drone DJI.wav by bruno.auzet License: Creative Commons 0
- BEEP - 1 by SamuelGremaud License: Creative Commons 0
- 3D models
- Drone by Hail Godzilla License: CC Attribution