Add ultimate xr

This commit is contained in:
2024-08-06 21:58:35 +02:00
parent 864033bf10
commit 7165bacd9d
3952 changed files with 2162037 additions and 35 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3ab76a24169150d4eb81eab0f4d6409a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 75d39a48e6630cf46aafb8a01a2a1b39
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ec5835b639bf45b4b95e91375e169613
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
{
"reference": "GUID:7c88a4a7926ee5145ad2dfa06f454c67"
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 05341feb6ff954c46826779cf33502be
AssemblyDefinitionReferenceImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4fe2525dafa0a5246ad16c39abb7544a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,348 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFishNetAvatar.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
#if ULTIMATEXR_USE_FISHNET_SDK
using System;
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Settings;
using UltimateXR.Core.StateSave;
using UltimateXR.Core.StateSync;
using UltimateXR.Extensions.System;
using UltimateXR.Extensions.System.Collections;
using FishNet.Connection;
using FishNet.Object;
using UltimateXR.Core.Instantiation;
#endif
namespace UltimateXR.Networking.Integrations.Net.FishNet
{
#if ULTIMATEXR_USE_FISHNET_SDK
public class UxrFishNetAvatar : NetworkBehaviour, IUxrNetworkAvatar
{
#region Inspector Properties/Serialized Fields
[Tooltip("List of objects that will be disabled when the avatar is in local mode, to avoid intersections with the camera for example")] [SerializeField] private List<GameObject> _localDisabledGameObjects;
#endregion
#region Implicit IUxrNetworkAvatar
/// <inheritdoc />
public IList<GameObject> LocalDisabledGameObjects => _localDisabledGameObjects;
/// <inheritdoc />
public bool IsLocal { get; private set; }
/// <inheritdoc />
public UxrAvatar Avatar { get; private set; }
/// <inheritdoc />
public string AvatarName
{
get => _avatarName;
set
{
_avatarName = value;
if (Avatar != null)
{
Avatar.name = value;
}
}
}
/// <inheritdoc />
public event Action AvatarSpawned;
/// <inheritdoc />
public event Action AvatarDespawned;
/// <inheritdoc />
public void InitializeNetworkAvatar(UxrAvatar avatar, bool isLocal, string uniqueId, string avatarName)
{
IsLocal = isLocal;
AvatarName = avatarName;
avatar.AvatarMode = isLocal ? UxrAvatarMode.Local : UxrAvatarMode.UpdateExternally;
if (isLocal)
{
LocalDisabledGameObjects.ForEach(o => o.SetActive(false));
}
avatar.CombineUniqueId(uniqueId.GetGuid(), true);
}
#endregion
#region Public Methods
/// <summary>
/// Request authority of the local avatar over an object.
/// </summary>
/// <param name="networkObject">The object to get authority over</param>
public void RequestAuthority(NetworkObject networkObject)
{
RequestAuthorityServerRpc(networkObject);
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when a component in UltimateXR had a state change.
/// </summary>
/// <param name="component">Component</param>
/// <param name="eventArgs">Event parameters</param>
private void UxrManager_ComponentStateChanged(IUxrStateSync component, UxrSyncEventArgs eventArgs)
{
if (!IsOwner)
{
return;
}
if (eventArgs.Options.HasFlag(UxrStateSyncOptions.Network))
{
byte[] serializedEvent = eventArgs.SerializeEventBinary(component);
if (serializedEvent != null)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Sending {serializedEvent.Length} bytes from {component.Component.name} ({component.UniqueId}) {eventArgs}");
}
ComponentStateChangedServerRpc(serializedEvent);
}
}
}
#endregion
#region Event Trigger Methods
/// <inheritdoc />
public override void OnStartClient()
{
base.OnStartClient();
Avatar = GetComponent<UxrAvatar>();
InitializeNetworkAvatar(Avatar, IsOwner, OwnerId.ToString(), $"Player {OwnerId} ({(IsOwner ? "Local" : "External")})");
if (IsOwner)
{
UxrManager.ComponentStateChanged += UxrManager_ComponentStateChanged;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrFishNetAvatar)}.{nameof(OnStartClient)}: Is Local? {IsLocal}, Name: {AvatarName}. OwnerId: {OwnerId}, UniqueId: {Avatar.UniqueId}.");
}
AvatarSpawned?.Invoke();
if (UxrInstanceManager.HasInstance)
{
UxrInstanceManager.Instance.NotifyNetworkSpawn(Avatar.gameObject);
}
if (IsOwner)
{
if (!IsServer)
{
byte[] localAvatarState = UxrManager.Instance.SaveStateChanges(new List<GameObject> { Avatar.gameObject }, null, UxrStateSaveLevel.ChangesSinceBeginning, UxrGlobalSettings.Instance.NetFormatInitialState);
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} Requesting global state and sending local avatar state in {localAvatarState.Length} bytes.");
}
// Send the initial avatar state to the server and request the current scene state.
// Call after AvatarSpawned() in case any event handler changes the avatar state.
NewAvatarJoinedServerRpc(localAvatarState);
}
else
{
// Server creates the session and doesn't need to send the initial state.
s_initialStateLoaded = true;
}
}
}
/// <inheritdoc />
public override void OnStopClient()
{
base.OnStopClient();
if (Avatar && IsOwner)
{
UxrManager.ComponentStateChanged -= UxrManager_ComponentStateChanged;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrFishNetAvatar)}.{nameof(OnStopClient)}: Is Local? {IsLocal}, Name: {AvatarName}");
}
if (UxrInstanceManager.HasInstance)
{
UxrInstanceManager.Instance.NotifyNetworkDespawn(Avatar.gameObject);
}
AvatarDespawned?.Invoke();
}
#endregion
#region Private Methods
/// <summary>
/// Server RPC to request the current global state upon joining.
/// </summary>
/// <param name="avatarState">The initial state of the avatar that joined</param>
/// <param name="conn">Filled by FishNet with info</param>
[ServerRpc]
private void NewAvatarJoinedServerRpc(byte[] avatarState, NetworkConnection conn = null)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Received request for global state from client {conn.ClientId}.");
}
// First load the avatar state
UxrManager.Instance.LoadStateChanges(avatarState);
// Now export the scenario state, except for the new avatar, and send it back
byte[] serializedState = UxrManager.Instance.SaveStateChanges(null, new List<GameObject> { gameObject }, UxrStateSaveLevel.ChangesSinceBeginning, UxrGlobalSettings.Instance.NetFormatInitialState);
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Sending global state in {serializedState.Length} bytes to client {conn.ClientId}. Broadcasting {avatarState.Length} bytes to sync new avatar.");
}
// Send global state to new user.
LoadGlobalStateTargetRpc(conn, serializedState);
// Broadcast initial state of new avatar.
LoadAvatarStateClientRpc(avatarState);
}
/// <summary>
/// Server RPC call to propagate state change events to all other clients.
/// </summary>
/// <param name="serializedEventData">The serialized state change data</param>
[ServerRpc]
private void ComponentStateChangedServerRpc(byte[] serializedEventData)
{
ComponentStateChangedClientRpc(serializedEventData);
}
/// <summary>
/// Server RPC requesting authority over an object.
/// </summary>
/// <param name="networkObject">Object to get authority over</param>
/// <param name="conn">Filled by FishNet with info</param>
[ServerRpc]
private void RequestAuthorityServerRpc(NetworkObject networkObject, NetworkConnection conn = null)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Granting authority to owner {OwnerId} over network object {networkObject.name}.");
}
NetworkObject[] networkObjects = networkObject.GetComponentsInChildren<NetworkObject>();
foreach (NetworkObject no in networkObjects)
{
no.GiveOwnership(conn);
}
}
/// <summary>
/// Targeted client RPC to client that joined to sync to the current state.
/// </summary>
/// <param name="serializedStateData">The serialized state data</param>
/// <param name="clientRpcParams">Target of the RPC</param>
[TargetRpc]
private void LoadGlobalStateTargetRpc(NetworkConnection conn, byte[] serializedStateData)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Receiving {serializedStateData.Length} bytes of global state data.");
}
UxrManager.Instance.LoadStateChanges(serializedStateData);
s_initialStateLoaded = true;
}
/// <summary>
/// Client RPC to sync the state of a new avatar that joined.
/// </summary>
/// <param name="serializedStateData">The serialized state data</param>
[ObserversRpc]
private void LoadAvatarStateClientRpc(byte[] serializedStateData)
{
if (IsOwner)
{
// Don't execute on the source of the event, we don't want to load our own avatar data.
return;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Receiving {serializedStateData.Length} bytes of avatar state data.");
}
UxrManager.Instance.LoadStateChanges(serializedStateData);
}
/// <summary>
/// Client RPC call to execute a state change event. It will execute on all clients except the one that generated it,
/// which can be identified because it's the one with ownership.
/// </summary>
/// <param name="serializedEventData">The serialized state change data</param>
[ObserversRpc]
private void ComponentStateChangedClientRpc(byte[] serializedEventData)
{
if (IsOwner)
{
// Don't execute on the source of the event.
return;
}
if (s_initialStateLoaded == false)
{
// Ignore sync events until the initial state is sent, to make sure the syncs are only processed after the initial state.
return;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Receiving {serializedEventData.Length} bytes of data. Base64: {Convert.ToBase64String(serializedEventData)}");
}
UxrStateSyncResult result = UxrManager.Instance.ExecuteStateSyncEvent(serializedEventData);
}
#endregion
#region Private Types & Data
private static bool s_initialStateLoaded;
private string _avatarName;
#endregion
}
#else
public class UxrFishNetAvatar : MonoBehaviour
{
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 230855043fe1c234c97a316567668d55
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,417 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFishNetNetwork.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UnityEngine;
#if ULTIMATEXR_USE_FISHNET_SDK && UNITY_EDITOR
using UnityEditor;
#endif
#if ULTIMATEXR_USE_FISHNET_SDK
using System.Linq;
using FishNet.Component.Spawning;
using FishNet.Component.Transforming;
using FishNet.Managing;
using FishNet.Managing.Object;
using FishNet.Managing.Observing;
using FishNet.Object;
using UltimateXR.Extensions.System.Collections;
using UltimateXR.Extensions.Unity;
using UltimateXR.Manipulation;
#endif
#pragma warning disable 414 // Disable warnings due to unused values
namespace UltimateXR.Networking.Integrations.Net.FishNet
{
/// <summary>
/// Implementation of networking support using FishNet.
/// </summary>
public class UxrFishNetNetwork : UxrNetworkImplementation
{
#region Inspector Properties/Serialized Fields
[Tooltip("Show a UI during play mode with connection options to quickly prototype networking functionality")] [SerializeField] private bool _usePrototypingUI = true;
#endregion
#region Public Overrides UxrNetworkImplementation
/// <inheritdoc />
public override string SdkName => UxrConstants.SdkFishNet;
/// <inheritdoc />
public override bool IsServer
{
get
{
#if ULTIMATEXR_USE_FISHNET_SDK
return _networkManager != null && _networkManager.IsServer;
#else
return false;
#endif
}
}
/// <inheritdoc />
public override bool IsClient
{
get
{
#if ULTIMATEXR_USE_FISHNET_SDK
return _networkManager != null && _networkManager.IsClient;
#else
return false;
#endif
}
}
/// <inheritdoc />
public override UxrNetworkCapabilities Capabilities => UxrNetworkCapabilities.NetworkTransform | UxrNetworkCapabilities.NetworkRigidbody;
/// <inheritdoc />
//public override string NetworkRigidbodyWarning => $"{UxrConstants.SdkFishNet} does not use Rigidbodies";
/// <inheritdoc />
public override void SetupGlobal(UxrNetworkManager networkManager, out List<GameObject> newGameObjects, out List<Component> newComponents)
{
newGameObjects = new List<GameObject>();
newComponents = new List<Component>();
#if ULTIMATEXR_USE_FISHNET_SDK && UNITY_EDITOR
GameObject networkManagerGo = new GameObject("FishNetNetworkManager");
Undo.RegisterCreatedObjectUndo(networkManagerGo, "Create FishNet Network Manager");
networkManagerGo.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
networkManagerGo.transform.SetSiblingIndex(networkManager.transform.GetSiblingIndex() + 1);
NetworkManager fishNetNetworkManager = networkManagerGo.GetOrAddComponent<NetworkManager>();
ObserverManager observerManager = networkManagerGo.GetOrAddComponent<ObserverManager>();
PlayerSpawner playerSpawner = networkManagerGo.GetOrAddComponent<PlayerSpawner>();
// Try to assign DefaultPrefabObjects asset to NetworkManager. It will still throw an unassigned error,
// adding the component triggers an internal check before we can assign anything.
string[] guids = AssetDatabase.FindAssets("t:DefaultPrefabObjects", new[] { "Assets/" });
if (guids.Any() && fishNetNetworkManager.SpawnablePrefabs == null)
{
fishNetNetworkManager.SpawnablePrefabs = AssetDatabase.LoadAssetAtPath<DefaultPrefabObjects>(AssetDatabase.GUIDToAssetPath(guids.First()));
}
Undo.RegisterFullObjectHierarchyUndo(networkManager.gameObject, "Setup FishNet NetworkManager");
newGameObjects.Add(networkManagerGo);
newComponents.Add(fishNetNetworkManager);
newComponents.Add(observerManager);
newComponents.Add(playerSpawner);
#endif
}
/// <inheritdoc />
public override void SetupAvatar(UxrAvatar avatar, out List<GameObject> newGameObjects, out List<Component> newComponents)
{
newGameObjects = new List<GameObject>();
newComponents = new List<Component>();
#if ULTIMATEXR_USE_FISHNET_SDK && UNITY_EDITOR
if (avatar == null)
{
return;
}
NetworkObject avatarNetworkIdentity = avatar.gameObject.GetOrAddComponent<NetworkObject>();
newComponents.Add(avatarNetworkIdentity);
UxrFishNetAvatar fishNetAvatar = avatar.GetOrAddComponent<UxrFishNetAvatar>();
newComponents.Add(fishNetAvatar);
IEnumerable<Behaviour> avatarComponents = SetupNetworkTransform(avatar.gameObject, true, UxrNetworkTransformFlags.ChildAll);
IEnumerable<Behaviour> cameraComponents = SetupNetworkTransform(avatar.CameraComponent.gameObject, true, UxrNetworkTransformFlags.ChildPositionAndRotation);
IEnumerable<Behaviour> leftHandComponents = SetupNetworkTransform(avatar.GetHand(UxrHandSide.Left).Wrist.gameObject, true, UxrNetworkTransformFlags.ChildTransform);
IEnumerable<Behaviour> rightHandComponents = SetupNetworkTransform(avatar.GetHand(UxrHandSide.Right).Wrist.gameObject, true, UxrNetworkTransformFlags.ChildTransform);
newComponents.AddRange(avatarComponents.ToList().Concat(cameraComponents).Concat(leftHandComponents).Concat(rightHandComponents));
Undo.RegisterFullObjectHierarchyUndo(avatar.gameObject, "Setup FishNet Avatar");
#endif
}
/// <inheritdoc />
public override void SetupPostProcess(IEnumerable<UxrAvatar> avatarPrefabs)
{
#if ULTIMATEXR_USE_FISHNET_SDK && UNITY_EDITOR
// Find the player spawner and assign the first avatar in the list as the spawnable avatar.
PlayerSpawner fishNetPlayerSpawner = FindObjectOfType<PlayerSpawner>();
if (fishNetPlayerSpawner != null)
{
SerializedObject so = new SerializedObject(fishNetPlayerSpawner);
if (so != null)
{
so.Update();
SerializedProperty sp = so.FindProperty("_playerPrefab");
if (sp != null && avatarPrefabs.Any() && sp.objectReferenceValue == null)
{
// If there is no avatar assigned and there is an avatar prefab available, assign the first.
sp.objectReferenceValue = avatarPrefabs.First().GetComponent<NetworkObject>();
so.ApplyModifiedProperties();
}
}
}
#endif
}
/// <inheritdoc />
public override IEnumerable<Behaviour> AddNetworkTransform(GameObject gameObject, bool worldSpace, UxrNetworkTransformFlags networkTransformFlags)
{
#if ULTIMATEXR_USE_FISHNET_SDK && UNITY_EDITOR
if (networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ChildTransform) == false)
{
NetworkObject networkObject = gameObject.GetOrAddComponent<NetworkObject>();
yield return networkObject;
}
UxrFishNetworkTransform networkTransform = gameObject.GetOrAddComponent<UxrFishNetworkTransform>();
networkTransform.useWorldSpace = worldSpace;
yield return networkTransform;
#else
yield break;
#endif
}
/// <inheritdoc />
public override IEnumerable<Behaviour> AddNetworkRigidbody(GameObject gameObject, bool worldSpace, UxrNetworkRigidbodyFlags networkRigidbodyFlags)
{
// FishNet does not use NetworkRigidbody.
// We can just use the NetworkTransform if the Rigidbody is kinematic.
// Source: https://www.reddit.com/r/Unity3D/comments/vr4du5/comment/iettaaw/
return AddNetworkTransform(gameObject, worldSpace, UxrNetworkTransformFlags.All);
}
/// <inheritdoc />
public override void EnableNetworkTransform(GameObject gameObject, bool enable)
{
#if ULTIMATEXR_USE_FISHNET_SDK
UxrFishNetworkTransform[] networkTransforms = gameObject.GetComponentsInChildren<UxrFishNetworkTransform>();
networkTransforms.ForEach(nt => nt.SetEnabled(enable));
#endif
}
/// <inheritdoc />
public override void EnableNetworkRigidbody(GameObject gameObject, bool enable)
{
// FishNet does not use NetworkRigidbody.
// We can just use the NetworkTransform if the Rigidbody is kinematic.
// Source: https://www.reddit.com/r/Unity3D/comments/vr4du5/comment/iettaaw/
EnableNetworkTransform(gameObject, enable);
}
/// <inheritdoc />
public override bool HasAuthority(GameObject gameObject)
{
#if ULTIMATEXR_USE_FISHNET_SDK
NetworkObject networkObject = gameObject.GetComponent<NetworkObject>();
return networkObject != null && networkObject.IsOwner;
#else
return false;
#endif
}
/// <inheritdoc />
public override void RequestAuthority(GameObject gameObject)
{
#if ULTIMATEXR_USE_FISHNET_SDK
if (gameObject == null)
{
return;
}
UxrFishNetAvatar fishNetAvatar = UxrAvatar.LocalAvatar.GetComponentInChildren<UxrFishNetAvatar>();
NetworkObject networkObject = gameObject.GetComponent<NetworkObject>();
if (networkObject != null && fishNetAvatar != null)
{
fishNetAvatar.RequestAuthority(networkObject);
}
#endif
}
/// <inheritdoc />
public override void CheckReassignGrabAuthority(GameObject gameObject)
{
#if ULTIMATEXR_USE_FISHNET_SDK
UxrGrabbableObject grabbableObject = gameObject.GetComponent<UxrGrabbableObject>();
NetworkObject networkObject = gameObject.GetComponent<NetworkObject>();
if (networkObject != null && grabbableObject != null)
{
UxrAvatar avatarAuthority = UxrAvatar.EnabledComponents.FirstOrDefault(a => a.GetComponent<NetworkObject>() != null && a.GetComponent<NetworkObject>().OwnerId == networkObject.OwnerId);
if (avatarAuthority == null || !UxrGrabManager.Instance.IsBeingGrabbedBy(grabbableObject, avatarAuthority))
{
// No avatar has authority or the avatar that grabbed it doesn't have it anymore. Change authority to first one.
UxrAvatar firstAvatar = UxrGrabManager.Instance.GetGrabbingHands(grabbableObject).First().Avatar;
if (firstAvatar == UxrAvatar.LocalAvatar)
{
UxrNetworkManager.Instance.RequestAuthority(gameObject);
}
}
}
#endif
}
/// <inheritdoc />
public override bool HasNetworkTransformSyncComponents(GameObject gameObject)
{
#if ULTIMATEXR_USE_FISHNET_SDK
return gameObject.GetComponent<UxrFishNetworkTransform>() != null;
#else
return false;
#endif
}
#endregion
#if ULTIMATEXR_USE_FISHNET_SDK
#region Unity
/// <summary>
/// Unity OnGUI call that will draw the prototyping UI if it's enabled.
/// </summary>
private void OnGUI()
{
if (!_usePrototypingUI || !NetworkManager.Instances.First())
{
return;
}
PosY = 0;
GUI.Box(new Rect(0, 0, Screen.width, Screen.height), string.Empty);
GUI.Box(new Rect(0, PosY, ButtonWidth, ButtonHeight), "UltimateXR Fish Networking");
PosY += ButtonHeight;
if (_networkManager != null)
{
if (_networkManager.IsServer || _networkManager.IsClient)
{
if (_networkManager.IsServerOnly)
{
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Stop Server"))
{
_networkManager.ServerManager.StopConnection(true);
}
PosY += ButtonHeight;
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Start Client"))
{
_networkManager.ClientManager.StartConnection("localhost", NetworkPort);
}
}
else if (_networkManager.IsClientOnly)
{
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Disconnect Client"))
{
_networkManager.ClientManager.StopConnection();
}
}
else if (_networkManager.IsServer && _networkManager.IsClient)
{
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Stop Server"))
{
_networkManager.ServerManager.StopConnection(true);
}
PosY += ButtonHeight;
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Stop Client"))
{
_networkManager.ClientManager.StopConnection();
}
}
return;
}
}
else
{
_networkManager = NetworkManager.Instances.First();
}
GUI.Box(new Rect(0, PosY, ButtonWidth, LabelHeight), "Network Address:");
GUI.Box(new Rect(ButtonWidth + 10, PosY, ButtonWidth / 2, LabelHeight), "Port:");
PosY += LabelHeight;
_networkAddress = GUI.TextField(new Rect(0, PosY, ButtonWidth, LabelHeight), _networkAddress);
_networkPort = GUI.TextField(new Rect(ButtonWidth + 10, PosY, ButtonWidth / 2, LabelHeight), _networkPort);
PosY += ButtonHeight;
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Start Host"))
{
_networkManager.ServerManager.StartConnection(NetworkPort);
_networkManager.ClientManager.StartConnection(_networkAddress, NetworkPort);
}
PosY += ButtonHeight;
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Start Server"))
{
_networkManager.ServerManager.StartConnection(NetworkPort);
}
PosY += ButtonHeight;
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Start Client"))
{
_networkManager.ClientManager.StartConnection(_networkAddress, NetworkPort);
}
}
/// <summary>
/// Gets the network port specified by the user through the UI.
/// </summary>
private ushort NetworkPort => ushort.Parse(_networkPort);
#endregion
#region Private Types & Data
private int PosY { get; set; }
private const int LabelHeight = 25;
private const int ButtonWidth = 200;
private const int ButtonHeight = 40;
private NetworkManager _networkManager;
private string _networkAddress = "localhost";
private string _networkPort = "7777";
#endregion
#endif
}
}
#pragma warning restore 414

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 81f68be0652d69e4b82f09943363c604
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2cd6d50a617b3d648b98f9d69964c52a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,327 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrMirrorAvatar.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
#if ULTIMATEXR_USE_MIRROR_SDK
using System;
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Settings;
using UltimateXR.Core.StateSave;
using UltimateXR.Core.StateSync;
using UltimateXR.Extensions.System;
using UltimateXR.Extensions.System.Collections;
using UltimateXR.Core.Instantiation;
using Mirror;
#endif
namespace UltimateXR.Networking.Integrations.Net.Mirror
{
#if ULTIMATEXR_USE_MIRROR_SDK
public class UxrMirrorAvatar : NetworkBehaviour, IUxrNetworkAvatar
{
#region Inspector Properties/Serialized Fields
[Tooltip("List of objects that will be disabled when the avatar is in local mode, to avoid intersections with the camera for example")] [SerializeField] private List<GameObject> _localDisabledGameObjects;
#endregion
#region Implicit IUxrNetworkAvatar
/// <inheritdoc />
public IList<GameObject> LocalDisabledGameObjects => _localDisabledGameObjects;
/// <inheritdoc />
public bool IsLocal { get; private set; }
/// <inheritdoc />
public UxrAvatar Avatar { get; private set; }
/// <inheritdoc />
public string AvatarName
{
get => _avatarName;
set
{
_avatarName = value;
if (Avatar != null)
{
Avatar.name = value;
}
}
}
/// <inheritdoc />
public event Action AvatarSpawned;
/// <inheritdoc />
public event Action AvatarDespawned;
/// <inheritdoc />
public void InitializeNetworkAvatar(UxrAvatar avatar, bool isLocal, string uniqueId, string avatarName)
{
IsLocal = isLocal;
AvatarName = avatarName;
avatar.AvatarMode = isLocal ? UxrAvatarMode.Local : UxrAvatarMode.UpdateExternally;
if (isLocal)
{
LocalDisabledGameObjects.ForEach(o => o.SetActive(false));
}
avatar.CombineUniqueId(uniqueId.GetGuid(), true);
}
#endregion
#region Public Methods
/// <summary>
/// Request authority of the local avatar over an object.
/// </summary>
/// <param name="networkIdentity">The object to get authority over</param>
public void RequestAuthority(NetworkIdentity networkIdentity)
{
CmdRequestAuthority(networkIdentity);
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when a component in UltimateXR had a state change.
/// </summary>
/// <param name="component">Component</param>
/// <param name="eventArgs">Event parameters</param>
private void UxrManager_ComponentStateChanged(IUxrStateSync component, UxrSyncEventArgs eventArgs)
{
if (!netIdentity.isOwned)
{
return;
}
if (eventArgs.Options.HasFlag(UxrStateSyncOptions.Network))
{
byte[] serializedEvent = eventArgs.SerializeEventBinary(component);
if (serializedEvent != null)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Sending {serializedEvent.Length} bytes from {component.Component.name} ({component.UniqueId}) {eventArgs}");
}
CmdComponentStateChanged(serializedEvent);
}
}
}
#endregion
#region Event Trigger Methods
/// <inheritdoc />
public override void OnStartClient()
{
Avatar = GetComponent<UxrAvatar>();
InitializeNetworkAvatar(Avatar, netIdentity.isOwned, netId.ToString(), $"Player {netId} ({(netIdentity.isOwned ? "Local" : "External")})");
if (netIdentity.isOwned)
{
UxrManager.ComponentStateChanged += UxrManager_ComponentStateChanged;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrMirrorAvatar)}.{nameof(OnStartClient)}: Is Local? {IsLocal}, Name: {AvatarName}. NetId: {netId}, UniqueId: {Avatar.UniqueId}.");
}
AvatarSpawned?.Invoke();
if (UxrInstanceManager.HasInstance)
{
UxrInstanceManager.Instance.NotifyNetworkSpawn(Avatar.gameObject);
}
if (netIdentity.isOwned)
{
if (!netIdentity.isServer)
{
byte[] localAvatarState = UxrManager.Instance.SaveStateChanges(new List<GameObject> { Avatar.gameObject }, null, UxrStateSaveLevel.ChangesSinceBeginning, UxrGlobalSettings.Instance.NetFormatInitialState);
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} Requesting global state and sending local avatar state in {localAvatarState.Length} bytes.");
}
// Send the initial avatar state to the server and request the current scene state.
// Call after AvatarSpawned() in case any event handler changes the avatar state.
CmdNewAvatarJoined(localAvatarState);
}
else
{
// Server creates the session and doesn't need to send the initial state.
s_initialStateLoaded = true;
}
}
}
/// <inheritdoc />
public override void OnStopClient()
{
if (Avatar && netIdentity.isOwned)
{
UxrManager.ComponentStateChanged -= UxrManager_ComponentStateChanged;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrMirrorAvatar)}.{nameof(OnStopClient)}: Is Local? {IsLocal}, Name: {AvatarName}");
}
AvatarDespawned?.Invoke();
}
#endregion
#region Private Methods
/// <summary>
/// Server RPC to request the current global state upon joining.
/// </summary>
/// <param name="avatarState">The initial state of the avatar that joined</param>
/// <param name="sender">Information filled by Mirror with information about the sender</param>
[Command]
private void CmdNewAvatarJoined(byte[] avatarState, NetworkConnectionToClient sender = null)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Received request for global state from client {sender.identity.netId.ToString()}.");
}
// First load the avatar state
UxrManager.Instance.LoadStateChanges(avatarState);
// Now export the scenario state, except for the new avatar, and send it back
byte[] serializedState = UxrManager.Instance.SaveStateChanges(null, new List<GameObject> { gameObject }, UxrStateSaveLevel.ChangesSinceBeginning, UxrGlobalSettings.Instance.NetFormatInitialState);
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Sending global state in {serializedState.Length} bytes to client {sender.identity.netId.ToString()}. Broadcasting {avatarState.Length} bytes to sync new avatar.");
}
// Send global state to new user.
TargetLoadGlobalState(sender, serializedState);
// Broadcast initial state of new avatar.
RpcLoadAvatarState(avatarState);
}
/// <summary>
/// Server RPC to propagate state change events to all other clients.
/// </summary>
/// <param name="serializedEventData">The serialized state change data</param>
[Command]
private void CmdComponentStateChanged(byte[] serializedEventData)
{
RpcComponentStateChanged(serializedEventData);
}
/// <summary>
/// Server RPC requesting authority over an object.
/// </summary>
/// <param name="networkIdentity">Object to get authority over</param>
[Command]
private void CmdRequestAuthority(NetworkIdentity networkIdentity)
{
networkIdentity.AssignClientAuthority(netIdentity.connectionToClient);
}
/// <summary>
/// Targeted client RPC to client that joined to sync to the current state.
/// </summary>
/// <param name="target">Target of the RPC</param>
/// <param name="serializedStateData">The serialized state data</param>
[TargetRpc]
private void TargetLoadGlobalState(NetworkConnectionToClient target, byte[] serializedStateData)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Receiving {serializedStateData.Length} bytes of global state data.");
}
UxrManager.Instance.LoadStateChanges(serializedStateData);
s_initialStateLoaded = true;
}
/// <summary>
/// Client RPC to sync the state of a new avatar that joined.
/// </summary>
/// <param name="serializedStateData">The serialized state data</param>
[ClientRpc]
private void RpcLoadAvatarState(byte[] serializedStateData)
{
if (netIdentity.isOwned)
{
// Don't execute on the source of the event, we don't want to load our own avatar data.
return;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Receiving {serializedStateData.Length} bytes of avatar state data.");
}
UxrManager.Instance.LoadStateChanges(serializedStateData);
}
/// <summary>
/// Client RPC to execute a state change event. It will execute on all clients except the one that generated it,
/// which can be identified because it's the one with ownership.
/// </summary>
/// <param name="serializedEventData">The serialized state change data</param>
[ClientRpc]
private void RpcComponentStateChanged(byte[] serializedEventData)
{
if (netIdentity.isOwned)
{
// Don't execute on the source of the event.
return;
}
if (s_initialStateLoaded == false)
{
// Ignore sync events until the initial state is sent, to make sure the syncs are only processed after the initial state.
return;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Receiving {serializedEventData.Length} bytes of data. Base64: {Convert.ToBase64String(serializedEventData)}");
}
UxrManager.Instance.ExecuteStateSyncEvent(serializedEventData);
}
#endregion
#region Private Types & Data
private static bool s_initialStateLoaded;
private string _avatarName;
#endregion
}
#else
public class UxrMirrorAvatar : MonoBehaviour
{
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 93acbfc66a5bc4e4aa5167ba1b96dc6b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,452 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrMirrorNetwork.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if ULTIMATEXR_USE_MIRROR_SDK
using System.Linq;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.System.Collections;
using UltimateXR.Extensions.Unity;
using UltimateXR.Manipulation;
using Mirror;
using kcp2k;
#endif
#pragma warning disable 414 // Disable warnings due to unused values
namespace UltimateXR.Networking.Integrations.Net.Mirror
{
/// <summary>
/// Implementation of networking support using Mirror.
/// </summary>
public class UxrMirrorNetwork : UxrNetworkImplementation
{
#region Inspector Properties/Serialized Fields
[Tooltip("Show a UI during play mode with connection options to quickly prototype networking functionality")] [SerializeField] private bool _usePrototypingUI = true;
#endregion
#region Public Overrides UxrNetworkImplementation
/// <inheritdoc />
public override string SdkName => UxrConstants.SdkMirror;
/// <inheritdoc />
public override bool IsServer
{
get
{
#if ULTIMATEXR_USE_MIRROR_SDK
return NetworkManager.singleton.isNetworkActive && (NetworkManager.singleton.mode == NetworkManagerMode.Host || NetworkManager.singleton.mode == NetworkManagerMode.ServerOnly);
#else
return false;
#endif
}
}
/// <inheritdoc />
public override bool IsClient
{
get
{
#if ULTIMATEXR_USE_MIRROR_SDK
return NetworkManager.singleton.isNetworkActive && (NetworkManager.singleton.mode == NetworkManagerMode.Host || NetworkManager.singleton.mode == NetworkManagerMode.ClientOnly);
#else
return false;
#endif
}
}
/// <inheritdoc />
public override string NetworkRigidbodyWarning => "Mirror doesn't support nested NetworkIdentity components, which is required when using grabbable rigidbodies with other grabbable rigidbodies attached. If you're using nested grabbable rigidbodies, do not set up NetworkRigidbody components here. Don't worry! UltimateXR will still synchronize them using RPC calls to try to keep the same position/velocity on all clients.";
/// <inheritdoc />
public override UxrNetworkCapabilities Capabilities => UxrNetworkCapabilities.NetworkTransform | UxrNetworkCapabilities.NetworkRigidbody;
/// <inheritdoc />
public override void SetupGlobal(UxrNetworkManager networkManager, out List<GameObject> newGameObjects, out List<Component> newComponents)
{
newGameObjects = new List<GameObject>();
newComponents = new List<Component>();
#if ULTIMATEXR_USE_MIRROR_SDK && UNITY_EDITOR
GameObject networkManagerGo = new GameObject("MirrorNetworkManager");
Undo.RegisterCreatedObjectUndo(networkManagerGo, "Create Mirror Network Manager");
networkManagerGo.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
networkManagerGo.transform.SetSiblingIndex(networkManager.transform.GetSiblingIndex() + 1);
NetworkManager mirrorNetworkManager = networkManagerGo.GetOrAddComponent<NetworkManager>();
KcpTransport mirrorTransport = networkManagerGo.GetOrAddComponent<KcpTransport>();
mirrorNetworkManager.transport = mirrorTransport;
Undo.RegisterFullObjectHierarchyUndo(networkManager.gameObject, "Setup Mirror NetworkManager");
newComponents.Add(mirrorTransport);
newComponents.Add(mirrorNetworkManager);
newGameObjects.Add(networkManagerGo);
#endif
}
/// <inheritdoc />
public override void SetupAvatar(UxrAvatar avatar, out List<GameObject> newGameObjects, out List<Component> newComponents)
{
newGameObjects = new List<GameObject>();
newComponents = new List<Component>();
#if ULTIMATEXR_USE_MIRROR_SDK && UNITY_EDITOR
if (avatar == null)
{
return;
}
NetworkIdentity avatarNetworkIdentity = avatar.gameObject.GetOrAddComponent<NetworkIdentity>();
UxrMirrorAvatar mirrorAvatar = avatar.GetOrAddComponent<UxrMirrorAvatar>();
newComponents.Add(mirrorAvatar);
IEnumerable<Behaviour> avatarComponents = SetupNetworkTransform(avatar.gameObject, true, UxrNetworkTransformFlags.ChildAll);
IEnumerable<Behaviour> cameraComponents = SetupNetworkTransform(avatar.CameraComponent.gameObject, true, UxrNetworkTransformFlags.ChildPositionAndRotation);
IEnumerable<Behaviour> leftHandComponents = SetupNetworkTransform(avatar.GetHand(UxrHandSide.Left).Wrist.gameObject, true, UxrNetworkTransformFlags.ChildPositionAndRotation);
IEnumerable<Behaviour> rightHandComponents = SetupNetworkTransform(avatar.GetHand(UxrHandSide.Right).Wrist.gameObject, true, UxrNetworkTransformFlags.ChildPositionAndRotation);
newComponents.AddRange(avatarComponents.ToList().Concat(cameraComponents).Concat(leftHandComponents).Concat(rightHandComponents));
newComponents.Add(avatarNetworkIdentity);
foreach (Behaviour behaviour in newComponents)
{
if (behaviour is NetworkTransformUnreliable networkTransformUnreliable)
{
networkTransformUnreliable.syncDirection = SyncDirection.ClientToServer;
}
}
Undo.RegisterFullObjectHierarchyUndo(avatar.gameObject, "Setup Mirror Avatar");
#endif
}
/// <inheritdoc />
public override void SetupPostProcess(IEnumerable<UxrAvatar> avatarPrefabs)
{
#if ULTIMATEXR_USE_MIRROR_SDK && UNITY_EDITOR
NetworkManager mirrorNetworkManager = FindObjectOfType<NetworkManager>();
if (mirrorNetworkManager != null)
{
mirrorNetworkManager.playerPrefab = avatarPrefabs.Any() ? avatarPrefabs.First().gameObject : null;
Undo.RegisterCompleteObjectUndo(mirrorNetworkManager, "Setup Mirror Avatar");
}
#endif
}
/// <inheritdoc />
public override IEnumerable<Behaviour> AddNetworkTransform(GameObject gameObject, bool worldSpace, UxrNetworkTransformFlags networkTransformFlags)
{
#if ULTIMATEXR_USE_MIRROR_SDK && UNITY_EDITOR
NetworkIdentity networkIdentity = null;
if (networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ChildTransform) == false)
{
networkIdentity = gameObject.GetOrAddComponent<NetworkIdentity>();
}
NetworkTransformUnreliable networkTransform = gameObject.GetOrAddComponent<NetworkTransformUnreliable>();
networkTransform.coordinateSpace = worldSpace ? CoordinateSpace.World : CoordinateSpace.Local;
networkTransform.syncPosition = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.PositionX) | networkTransformFlags.HasFlag(UxrNetworkTransformFlags.PositionY) | networkTransformFlags.HasFlag(UxrNetworkTransformFlags.PositionZ);
networkTransform.syncRotation = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.RotationX) | networkTransformFlags.HasFlag(UxrNetworkTransformFlags.RotationY) | networkTransformFlags.HasFlag(UxrNetworkTransformFlags.RotationZ);
networkTransform.syncScale = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ScaleX) | networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ScaleY) | networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ScaleZ);
yield return networkTransform;
if (networkIdentity)
{
// return after the transform, so that when they are removed, the transform is removed before. Otherwise Mirror complains about transform requiring identity.
yield return networkIdentity;
}
#else
yield break;
#endif
}
/// <inheritdoc />
public override IEnumerable<Behaviour> AddNetworkRigidbody(GameObject gameObject, bool worldSpace, UxrNetworkRigidbodyFlags networkRigidbodyFlags)
{
#if ULTIMATEXR_USE_MIRROR_SDK && UNITY_EDITOR
UxrGrabbableObject grabbableObject = gameObject.GetComponent<UxrGrabbableObject>();
if (grabbableObject != null && grabbableObject.RigidBodySource != null && grabbableObject.RigidBodyDynamicOnRelease && grabbableObject.transform.parent != null)
{
UxrGrabbableObject[] parentGrabbableObjects = grabbableObject.transform.parent.GetComponentsInParent<UxrGrabbableObject>(true);
UxrGrabbableObject physicsDrivenParent = parentGrabbableObjects.FirstOrDefault(g => g.RigidBodySource != null && g.RigidBodyDynamicOnRelease);
if (physicsDrivenParent != null)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.NetworkingModule} Ignoring physics-driven grabbable object {grabbableObject.GetPathUnderScene()} because there is already a parent physics-driven grabbable object ({physicsDrivenParent.GetPathUnderScene()}) and Mirror doesn't support nested NetworkIdentity components. UltimateXR will sync the rigidbody using RPC calls.");
}
yield break;
}
}
// Building list forces evaluation of AddNetworkTransform IEnumerable and creates the components
List<Behaviour> networkTransformComponents = new List<Behaviour>(AddNetworkTransform(gameObject, worldSpace, UxrNetworkTransformFlags.All));
NetworkRigidbodyUnreliable networkRigidbody = gameObject.GetOrAddComponent<NetworkRigidbodyUnreliable>();
yield return networkRigidbody;
// Return transform components after, so that when removing the components the NetworkRigidbody is removed before the identity. Otherwise Mirror will complain.
foreach (Behaviour newBehaviour in networkTransformComponents)
{
yield return newBehaviour;
}
#else
yield break;
#endif
}
/// <inheritdoc />
public override void EnableNetworkTransform(GameObject gameObject, bool enable)
{
#if ULTIMATEXR_USE_MIRROR_SDK
NetworkTransformUnreliable[] networkTransforms = gameObject.GetComponentsInChildren<NetworkTransformUnreliable>();
networkTransforms.ForEach(nt => nt.SetEnabled(enable));
#endif
}
/// <inheritdoc />
public override void EnableNetworkRigidbody(GameObject gameObject, bool enable)
{
#if ULTIMATEXR_USE_MIRROR_SDK
EnableNetworkTransform(gameObject, enabled);
NetworkRigidbodyUnreliable[] networkRigidbodies = gameObject.GetComponentsInChildren<NetworkRigidbodyUnreliable>();
networkRigidbodies.ForEach(nrb => nrb.SetEnabled(enable));
#endif
}
/// <inheritdoc />
public override bool HasAuthority(GameObject gameObject)
{
#if ULTIMATEXR_USE_MIRROR_SDK
NetworkIdentity networkIdentity = gameObject.GetComponent<NetworkIdentity>();
if (networkIdentity == null)
{
return false;
}
return networkIdentity.isOwned;
#else
return false;
#endif
}
/// <inheritdoc />
public override void RequestAuthority(GameObject gameObject)
{
#if ULTIMATEXR_USE_MIRROR_SDK
if (gameObject == null)
{
return;
}
UxrMirrorAvatar mirrorAvatar = UxrAvatar.LocalAvatar.GetComponentInChildren<UxrMirrorAvatar>();
NetworkIdentity networkIdentity = gameObject.GetComponent<NetworkIdentity>();
if (mirrorAvatar != null && networkIdentity != null)
{
mirrorAvatar.RequestAuthority(networkIdentity);
}
#endif
}
/// <inheritdoc />
public override void CheckReassignGrabAuthority(GameObject gameObject)
{
#if ULTIMATEXR_USE_MIRROR_SDK
UxrGrabbableObject grabbableObject = gameObject.GetComponent<UxrGrabbableObject>();
NetworkIdentity networkIdentity = gameObject.GetComponent<NetworkIdentity>();
if (networkIdentity != null && grabbableObject != null)
{
UxrAvatar avatarAuthority = UxrAvatar.EnabledComponents.FirstOrDefault(a => a.GetComponent<NetworkIdentity>() != null && a.GetComponent<NetworkIdentity>().connectionToClient == networkIdentity.connectionToClient);
if (avatarAuthority == null || !UxrGrabManager.Instance.IsBeingGrabbedBy(grabbableObject, avatarAuthority))
{
// No avatar has authority or the avatar that grabbed it doesn't have it anymore. Change authority to first one.
UxrAvatar firstAvatar = UxrGrabManager.Instance.GetGrabbingHands(grabbableObject).First().Avatar;
if (firstAvatar == UxrAvatar.LocalAvatar)
{
UxrNetworkManager.Instance.RequestAuthority(gameObject);
}
}
}
#endif
}
/// <inheritdoc />
public override bool HasNetworkTransformSyncComponents(GameObject gameObject)
{
#if ULTIMATEXR_USE_MIRROR_SDK
return gameObject.GetComponent<NetworkTransformUnreliable>() != null || gameObject.GetComponent<NetworkRigidbodyUnreliable>() != null;
#else
return false;
#endif
}
#endregion
#if ULTIMATEXR_USE_MIRROR_SDK
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Start()
{
base.Start();
_networkAddress = NetworkManager.singleton?.networkAddress;
if (Transport.active != null && Transport.active is KcpTransport kcpTransport)
{
_networkPort = kcpTransport.Port.ToString();
}
}
/// <summary>
/// Shows the connection UI if its enabled.
/// </summary>
private void OnGUI()
{
if (!_usePrototypingUI || NetworkManager.singleton == null)
{
return;
}
PosY = 0;
GUI.Box(new Rect(0, 0, Screen.width, Screen.height), string.Empty);
GUI.Box(new Rect(0, PosY, ButtonWidth, ButtonHeight), "UltimateXR Unity Mirror");
PosY += ButtonHeight;
if (NetworkManager.singleton.isNetworkActive)
{
if (NetworkManager.singleton.mode == NetworkManagerMode.Host)
{
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Stop Host"))
{
NetworkManager.singleton.StopHost();
}
}
else if (NetworkManager.singleton.mode == NetworkManagerMode.ServerOnly)
{
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Stop Server"))
{
NetworkManager.singleton.StopServer();
}
}
else if (NetworkManager.singleton.mode == NetworkManagerMode.ClientOnly)
{
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Disconnect Client"))
{
NetworkManager.singleton.StopClient();
}
}
return;
}
GUI.Box(new Rect(0, PosY, ButtonWidth, LabelHeight), "Network Address:");
GUI.Box(new Rect(ButtonWidth + 10, PosY, ButtonWidth / 2, LabelHeight), "Port:");
PosY += LabelHeight;
_networkAddress = GUI.TextField(new Rect(0, PosY, ButtonWidth, LabelHeight), _networkAddress);
_networkPort = GUI.TextField(new Rect(ButtonWidth + 10, PosY, ButtonWidth / 2, LabelHeight), _networkPort);
PosY += ButtonHeight;
if (NetworkManager.singleton != null)
{
NetworkManager.singleton.networkAddress = string.IsNullOrEmpty(_networkAddress) ? DefaultNetworkAddress : _networkAddress;
}
if (Transport.active != null && Transport.active is KcpTransport kcpTransport)
{
if (int.TryParse(string.IsNullOrEmpty(_networkPort) ? DefaultNetworkPort : _networkPort, out int port))
{
kcpTransport.Port = (ushort)port;
}
}
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Start Host"))
{
NetworkManager.singleton.StartHost();
}
PosY += ButtonHeight;
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Start Server"))
{
NetworkManager.singleton.StartServer();
}
PosY += ButtonHeight;
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Start Client"))
{
NetworkManager.singleton.StartClient();
}
}
#endregion
#region Private Data
private const string DefaultNetworkAddress = "localhost";
private const string DefaultNetworkPort = "7777";
private const int LabelHeight = 25;
private const int ButtonWidth = 200;
private const int ButtonHeight = 40;
private int PosY { get; set; }
private string _networkAddress = DefaultNetworkAddress;
private string _networkPort = DefaultNetworkPort;
#endregion
#endif
}
}
#pragma warning restore 414

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 65af5fca2bde7184d8ac076e99570fd0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ee77d08b9af636c40bddc2a88b75faf8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,430 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrPhotonFusionAvatar.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
using System;
using System.Collections.Generic;
using UltimateXR.Attributes;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Settings;
using UltimateXR.Core.StateSave;
using UltimateXR.Core.StateSync;
using UltimateXR.Extensions.System;
using UltimateXR.Extensions.System.Collections;
using UltimateXR.Core.Instantiation;
using Fusion;
#endif
namespace UltimateXR.Networking.Integrations.Net.PhotonFusion
{
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
[OrderAfter(typeof(NetworkTransform), typeof(NetworkRigidbody))]
public class UxrPhotonFusionAvatar : NetworkBehaviour, IUxrNetworkAvatar
{
#region Inspector Properties/Serialized Fields
[Tooltip("List of objects that will be disabled when the avatar is in local mode, to avoid intersections with the camera for example")] [SerializeField] private List<GameObject> _localDisabledGameObjects;
[SerializeField] [ReadOnly] private NetworkTransform _networkTransformRigAvatar;
[SerializeField] [ReadOnly] private NetworkTransform _networkTransformRigCamera;
[SerializeField] [ReadOnly] private NetworkTransform _networkTransformRigHandLeft;
[SerializeField] [ReadOnly] private NetworkTransform _networkTransformRigHandRight;
#endregion
#region Implicit IUxrNetworkAvatar
/// <inheritdoc />
public IList<GameObject> LocalDisabledGameObjects => _localDisabledGameObjects;
/// <inheritdoc />
public bool IsLocal { get; private set; }
/// <inheritdoc />
public UxrAvatar Avatar { get; private set; }
/// <inheritdoc />
public string AvatarName
{
get => _avatarName;
set
{
_avatarName = value;
if (Avatar != null)
{
Avatar.name = value;
}
}
}
/// <inheritdoc />
public event Action AvatarSpawned;
/// <inheritdoc />
public event Action AvatarDespawned;
/// <inheritdoc />
public void InitializeNetworkAvatar(UxrAvatar avatar, bool isLocal, string uniqueId, string avatarName)
{
IsLocal = isLocal;
AvatarName = avatarName;
avatar.AvatarMode = isLocal ? UxrAvatarMode.Local : UxrAvatarMode.UpdateExternally;
if (isLocal)
{
LocalDisabledGameObjects.ForEach(o => o.SetActive(false));
}
avatar.CombineUniqueId(uniqueId.GetGuid(), true);
}
#endregion
#region Public Overrides NetworkBehaviour
/// <inheritdoc />
public override void Spawned()
{
base.Spawned();
Avatar = GetComponent<UxrAvatar>();
InitializeNetworkAvatar(Avatar, Object.HasInputAuthority, Object.Id.ToString(), $"Player {Object.InputAuthority.PlayerId} ({(Object.HasInputAuthority ? "Local" : "External")})");
if (Object.HasInputAuthority)
{
UxrManager.ComponentStateChanged += UxrManager_ComponentStateChanged;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionAvatar)}.{nameof(Spawned)}: Is Local? {IsLocal}, Name: {AvatarName}. ObjectId: {Object.Id.ToString()}, UniqueId: {Avatar.UniqueId}.");
}
AvatarSpawned?.Invoke();
if (UxrInstanceManager.HasInstance)
{
UxrInstanceManager.Instance.NotifyNetworkSpawn(Avatar.gameObject);
}
if (Object.HasInputAuthority)
{
byte[] localAvatarState = UxrManager.Instance.SaveStateChanges(new List<GameObject> { Avatar.gameObject }, null, UxrStateSaveLevel.ChangesSinceBeginning, UxrGlobalSettings.Instance.NetFormatInitialState);
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} Requesting global state and sending local avatar state in {localAvatarState.Length} bytes.");
}
// Call after AvatarSpawned() in case any event handler changes the avatar state
RPC_NewAvatarJoined(localAvatarState);
}
}
/// <inheritdoc />
public override void Despawned(NetworkRunner runner, bool hasState)
{
base.Despawned(runner, hasState);
if (Avatar && Object.HasInputAuthority)
{
UxrManager.ComponentStateChanged -= UxrManager_ComponentStateChanged;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionAvatar)}.{nameof(Despawned)}: Is Local? {IsLocal}, Name: {AvatarName}");
}
AvatarDespawned?.Invoke();
}
/// <inheritdoc />
public override void FixedUpdateNetwork()
{
base.FixedUpdateNetwork();
// update the rig at each network tick
if (GetInput<UxrPhotonFusionNetwork.RigInput>(out var input))
{
_networkTransformRigAvatar.transform.position = input.avatarPosition;
_networkTransformRigAvatar.transform.rotation = input.avatarRotation;
_networkTransformRigAvatar.transform.localScale = input.avatarScale;
_networkTransformRigCamera.transform.position = input.cameraPosition;
_networkTransformRigCamera.transform.rotation = input.cameraRotation;
_networkTransformRigHandLeft.transform.position = input.leftHandPosition;
_networkTransformRigHandLeft.transform.rotation = input.leftHandRotation;
_networkTransformRigHandRight.transform.position = input.rightHandPosition;
_networkTransformRigHandRight.transform.rotation = input.rightHandRotation;
}
}
#endregion
#region Public Overrides SimulationBehaviour
/// <inheritdoc />
public override void Render()
{
base.Render();
if (Object.HasInputAuthority)
{
// Extrapolate for local user
_networkTransformRigAvatar.InterpolationTarget.position = _actualAvatarPosition;
_networkTransformRigAvatar.InterpolationTarget.rotation = _actualAvatarRotation;
_networkTransformRigAvatar.InterpolationTarget.localScale = Avatar.transform.localScale;
_networkTransformRigCamera.InterpolationTarget.position = Avatar.CameraComponent.transform.position;
_networkTransformRigCamera.InterpolationTarget.rotation = Avatar.CameraComponent.transform.rotation;
if (Avatar.FirstControllerTracking != null)
{
_networkTransformRigHandLeft.InterpolationTarget.position = Avatar.FirstControllerTracking.SensorLeftHandPos;
_networkTransformRigHandLeft.InterpolationTarget.rotation = Avatar.FirstControllerTracking.SensorLeftHandRot;
_networkTransformRigHandRight.InterpolationTarget.position = Avatar.FirstControllerTracking.SensorRightHandPos;
_networkTransformRigHandRight.InterpolationTarget.rotation = Avatar.FirstControllerTracking.SensorRightHandRot;
}
}
}
#endregion
#region Internal Methods
/// <summary>
/// Initializes the network rig, that synchronizes the relevant avatar transforms.
/// </summary>
/// <param name="root">The GameObject that synchronizes the root transform</param>
/// <param name="cam">The GameObject that synchronizes the camera transform</param>
/// <param name="handLeft">The GameObject that synchronizes the left hand transform</param>
/// <param name="handRight">The GameObject that synchronizes the right hand transform</param>
internal void SetNetworkRig(GameObject root, GameObject cam, GameObject handLeft, GameObject handRight)
{
_networkTransformRigAvatar = root.GetComponent<NetworkTransform>();
_networkTransformRigCamera = cam.GetComponent<NetworkTransform>();
_networkTransformRigHandLeft = handLeft.GetComponent<NetworkTransform>();
_networkTransformRigHandRight = handRight.GetComponent<NetworkTransform>();
UxrAvatar avatar = GetComponentInParent<UxrAvatar>();
_networkTransformRigAvatar.InterpolationTarget = avatar.transform;
_networkTransformRigCamera.InterpolationTarget = avatar.CameraComponent.transform;
_networkTransformRigHandLeft.InterpolationTarget = avatar.GetHand(UxrHandSide.Left).Wrist;
_networkTransformRigHandRight.InterpolationTarget = avatar.GetHand(UxrHandSide.Right).Wrist;
_networkTransformRigAvatar.InterpolateErrorCorrection = false;
_networkTransformRigCamera.InterpolateErrorCorrection = false;
_networkTransformRigHandLeft.InterpolateErrorCorrection = false;
_networkTransformRigHandRight.InterpolateErrorCorrection = false;
}
#endregion
#region Unity
/// <summary>
/// Subscribes to events.
/// </summary>
private void OnEnable()
{
UxrAvatar.GlobalAvatarMoved += Avatar_GlobalAvatarMoved;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
private void OnDisable()
{
UxrAvatar.GlobalAvatarMoved -= Avatar_GlobalAvatarMoved;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when a component in UltimateXR had a state change.
/// </summary>
/// <param name="component">Component</param>
/// <param name="eventArgs">Event parameters</param>
private void UxrManager_ComponentStateChanged(IUxrStateSync component, UxrSyncEventArgs eventArgs)
{
if (!Object.HasInputAuthority)
{
return;
}
if (eventArgs.Options.HasFlag(UxrStateSyncOptions.Network))
{
byte[] serializedEvent = eventArgs.SerializeEventBinary(component);
if (serializedEvent != null)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Sending {serializedEvent.Length} bytes from {component.Component.name} ({component.UniqueId}) {eventArgs}");
}
RPC_ComponentStateChanged(serializedEvent);
}
}
}
/// <summary>
/// Called when an avatar moved.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Avatar_GlobalAvatarMoved(object sender, UxrAvatarMoveEventArgs e)
{
if (Object && Object.HasInputAuthority && ReferenceEquals(sender, UxrAvatar.LocalAvatar))
{
_actualAvatarPosition = e.NewPosition;
_actualAvatarRotation = e.NewRotation;
}
}
/// <summary>
/// RPC from client to server to request the current global state upon joining.
/// </summary>
/// <param name="avatarState">The initial state of the avatar that joined</param>
/// <param name="info">Filled by Photon with RPC information</param>
[Rpc(RpcSources.InputAuthority, RpcTargets.StateAuthority, HostMode = RpcHostMode.SourceIsServer)]
private void RPC_NewAvatarJoined(byte[] avatarState, RpcInfo info = default)
{
if (info.Source != PlayerRef.None)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Received request for global state from {info.Source}. Loading avatar state from {avatarState.Length} bytes.");
}
// First load the avatar state
UxrManager.Instance.LoadStateChanges(avatarState);
// Now export the scenario state, except for the new avatar, and send it back
byte[] serializedState = UxrManager.Instance.SaveStateChanges(null, new List<GameObject> { gameObject }, UxrStateSaveLevel.ChangesSinceBeginning, UxrGlobalSettings.Instance.NetFormatInitialState);
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Sending global state in {serializedState.Length} bytes to {info.Source}. Broadcasting {avatarState.Length} bytes to sync new avatar.");
}
// Send global state to new user.
RPC_LoadGlobalState(serializedState);
// Broadcast initial state of new avatar.
RPC_LoadAvatarState(avatarState);
}
else
{
// When using RpcHostMode.SourceIsServer, Source is None.
// Start the host as initialized since it doesn't require a request for the current state.
s_initialStateLoaded = true;
}
}
/// <summary>
/// RPC from server to client that joined to sync to the current state.
/// </summary>
/// <param name="serializedStateData">The serialized state data</param>
[Rpc(RpcSources.StateAuthority, RpcTargets.InputAuthority)]
private void RPC_LoadGlobalState(byte[] serializedStateData)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Receiving {serializedStateData.Length} bytes of global state data.");
}
UxrManager.Instance.LoadStateChanges(serializedStateData);
s_initialStateLoaded = true;
}
/// <summary>
/// RPC from server to all clients to sync the state of a new avatar that joined.
/// </summary>
/// <param name="serializedStateData">The serialized state data</param>
[Rpc(RpcSources.StateAuthority, RpcTargets.All)]
private void RPC_LoadAvatarState(byte[] serializedStateData)
{
if (Object.HasInputAuthority)
{
// Don't execute on the source of the event, we don't want to load our own avatar data.
return;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Receiving {serializedStateData.Length} bytes of avatar state data.");
}
UxrManager.Instance.LoadStateChanges(serializedStateData);
}
/// <summary>
/// RPC to propagate state change events to all other clients.
/// </summary>
/// <param name="serializedEventData">The serialized state change data</param>
[Rpc(RpcSources.InputAuthority, RpcTargets.All)]
private void RPC_ComponentStateChanged(byte[] serializedEventData)
{
if (Object.HasInputAuthority)
{
// Don't execute on the source of the event
return;
}
if (s_initialStateLoaded == false)
{
// Ignore sync events until the initial state is sent, to make sure the syncs are only processed after the initial state.
return;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Receiving {serializedEventData.Length} bytes of event data. Base64: {Convert.ToBase64String(serializedEventData)}");
}
UxrStateSyncResult result = UxrManager.Instance.ExecuteStateSyncEvent(serializedEventData);
if (!result.IsError)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Processed {serializedEventData.Length} bytes of data: {result}");
}
}
}
#endregion
#region Private Types & Data
private static bool s_initialStateLoaded;
private Vector3 _actualAvatarPosition;
private Quaternion _actualAvatarRotation;
private Vector3 _actualAvatarCameraPosition;
private Quaternion _actualAvatarCameraRotation;
private Vector3 _actualAvatarLeftHandPosition;
private Quaternion _actualAvatarLeftHandRotation;
private Vector3 _actualAvatarRightHandPosition;
private Quaternion _actualAvatarRightHandRotation;
private string _avatarName;
#endregion
}
#else
public class UxrPhotonFusionAvatar : MonoBehaviour
{
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7448805a74c81d845a2182d9108e915d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,39 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrPhotonFusionNetwork.RigInput.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
using Fusion;
using UnityEngine;
namespace UltimateXR.Networking.Integrations.Net.PhotonFusion
{
public partial class UxrPhotonFusionNetwork
{
#region Public Types & Data
/// <summary>
/// Stores all the input information that describes an avatar to be used in client/server mode.
/// </summary>
public struct RigInput : INetworkInput
{
#region Public Types & Data
public Vector3 avatarPosition;
public Quaternion avatarRotation;
public Vector3 avatarScale;
public Vector3 cameraPosition;
public Quaternion cameraRotation;
public Vector3 leftHandPosition;
public Quaternion leftHandRotation;
public Vector3 rightHandPosition;
public Quaternion rightHandRotation;
#endregion
}
#endregion
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1b07ac5288184fb0bc2103c93680de63
timeCreated: 1691755462

View File

@@ -0,0 +1,661 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrPhotonFusionNetwork.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UnityEngine;
#if ULTIMATEXR_USE_PHOTONFUSION_SDK && UNITY_EDITOR
using UnityEditor;
#endif
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using UltimateXR.Core.Settings;
using UltimateXR.Core.Threading.TaskControllers;
using UltimateXR.Extensions.System.Collections;
using UltimateXR.Extensions.Unity;
using UltimateXR.Manipulation;
using UnityEngine.SceneManagement;
using Fusion;
using Fusion.Sockets;
using Behaviour = UnityEngine.Behaviour;
#endif
namespace UltimateXR.Networking.Integrations.Net.PhotonFusion
{
/// <summary>
/// Implementation of networking support using Photon Fusion.
/// </summary>
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
public partial class UxrPhotonFusionNetwork : UxrNetworkImplementation, INetworkRunnerCallbacks
#else
public class UxrPhotonFusionNetwork : UxrNetworkImplementation
#endif
{
#region Inspector Properties/Serialized Fields
[Tooltip("Show a UI during play mode with connection options to quickly prototype networking functionality")] [SerializeField] private bool _usePrototypingUI = true;
#endregion
#region Public Overrides UxrNetworkImplementation
/// <inheritdoc />
public override string SdkName => UxrConstants.SdkPhotonFusion;
/// <inheritdoc />
public override bool IsServer
{
get
{
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
return _networkRunner != null && _networkRunner.IsRunning && _networkRunner.IsServer;
#else
return false;
#endif
}
}
/// <inheritdoc />
public override bool IsClient
{
get
{
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
return _networkRunner != null && _networkRunner.IsRunning && _networkRunner.IsClient;
#else
return false;
#endif
}
}
/// <inheritdoc />
public override UxrNetworkCapabilities Capabilities => UxrNetworkCapabilities.NetworkTransform | UxrNetworkCapabilities.NetworkRigidbody;
/// <inheritdoc />
public override string NetworkRigidbodyWarning => "Photon Fusion's NetworkRigidbody components are meant to be used in Client/Server mode. If you plan to use Photon Fusion in Shared mode, do not set up NetworkRigidbody components here. Don't worry! UltimateXR will still synchronize grabbable physics-driven rigidbodies using RPC calls to try to keep the same position/velocity on all users.";
/// <inheritdoc />
public override void SetupGlobal(UxrNetworkManager networkManager, out List<GameObject> newGameObjects, out List<Component> newComponents)
{
newGameObjects = new List<GameObject>();
newComponents = new List<Component>();
#if ULTIMATEXR_USE_PHOTONFUSION_SDK && UNITY_EDITOR
Component newComponent = networkManager.GetComponent<NetworkRunner>();
if (newComponent == null)
{
newComponent = Undo.AddComponent<NetworkRunner>(networkManager.gameObject);
Undo.RegisterFullObjectHierarchyUndo(networkManager.gameObject, "Setup Photon Component");
}
newComponents.Add(newComponent);
#endif
}
/// <inheritdoc />
public override void SetupAvatar(UxrAvatar avatar, out List<GameObject> newGameObjects, out List<Component> newComponents)
{
newGameObjects = new List<GameObject>();
newComponents = new List<Component>();
if (avatar == null)
{
}
#if ULTIMATEXR_USE_PHOTONFUSION_SDK && UNITY_EDITOR
UxrPhotonFusionAvatar fusionAvatar = avatar.GetOrAddComponent<UxrPhotonFusionAvatar>();
newComponents.Add(fusionAvatar);
GameObject networkRig = new GameObject("PhotonNetworkRig");
Undo.RegisterCreatedObjectUndo(networkRig, "Create avatar Photon network rig");
Undo.SetTransformParent(networkRig.transform, avatar.transform, "Parent network rig");
networkRig.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
GameObject networkCamera = new GameObject("NetworkCamera");
Undo.RegisterCreatedObjectUndo(networkCamera, "Create avatar network camera");
Undo.SetTransformParent(networkCamera.transform, networkRig.transform, "Parent network camera");
networkCamera.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
GameObject networkHandLeft = new GameObject("NetworkHandLeft");
Undo.RegisterCreatedObjectUndo(networkHandLeft, "Create avatar network hand left");
Undo.SetTransformParent(networkHandLeft.transform, networkRig.transform, "Parent network hand left");
networkHandLeft.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
GameObject networkHandRight = new GameObject("NetworkHandRight");
Undo.RegisterCreatedObjectUndo(networkHandRight, "Create avatar network hand right");
Undo.SetTransformParent(networkHandRight.transform, networkRig.transform, "Parent network hand right");
networkHandRight.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
NetworkObject avatarNetworkObject = avatar.gameObject.GetOrAddComponent<NetworkObject>();
if (avatarNetworkObject)
{
avatarNetworkObject.AssignSerializedProperty("DestroyWhenStateAuthorityLeaves", p => p.boolValue = true);
}
IEnumerable<Behaviour> rigComponents = SetupNetworkTransform(networkRig, true, UxrNetworkTransformFlags.ChildAll);
IEnumerable<Behaviour> cameraComponents = SetupNetworkTransform(networkCamera, true, UxrNetworkTransformFlags.ChildPositionAndRotation);
IEnumerable<Behaviour> leftHandComponents = SetupNetworkTransform(networkHandLeft, true, UxrNetworkTransformFlags.ChildPositionAndRotation);
IEnumerable<Behaviour> rightHandComponents = SetupNetworkTransform(networkHandRight, true, UxrNetworkTransformFlags.ChildPositionAndRotation);
newComponents.AddRange(new[] { avatarNetworkObject }.Concat(rigComponents).Concat(cameraComponents).Concat(leftHandComponents).Concat(rightHandComponents));
newGameObjects.AddRange(new[] { networkHandLeft, networkHandRight, networkCamera, networkRig });
fusionAvatar.SetNetworkRig(networkRig, networkCamera, networkHandLeft, networkHandRight);
Undo.RegisterFullObjectHierarchyUndo(avatar.gameObject, "Setup Photon Avatar");
#endif
}
/// <inheritdoc />
public override void SetupPostProcess(IEnumerable<UxrAvatar> avatarPrefabs)
{
}
/// <inheritdoc />
public override IEnumerable<Behaviour> AddNetworkTransform(GameObject gameObject, bool worldSpace, UxrNetworkTransformFlags networkTransformFlags)
{
#if ULTIMATEXR_USE_PHOTONFUSION_SDK && UNITY_EDITOR
if (networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ChildTransform) == false)
{
NetworkObject networkObject = gameObject.GetOrAddComponent<NetworkObject>();
yield return networkObject;
}
NetworkTransform networkTransform = gameObject.GetOrAddComponent<NetworkTransform>();
networkTransform.InterpolationSpace = worldSpace ? Spaces.World : Spaces.Local;
yield return networkTransform;
#else
yield break;
#endif
}
/// <inheritdoc />
public override IEnumerable<Behaviour> AddNetworkRigidbody(GameObject gameObject, bool worldSpace, UxrNetworkRigidbodyFlags networkRigidbodyFlagsFlags)
{
#if ULTIMATEXR_USE_PHOTONFUSION_SDK && UNITY_EDITOR
NetworkObject networkObject = gameObject.GetOrAddComponent<NetworkObject>();
NetworkRigidbody networkRigidbody = gameObject.GetOrAddComponent<NetworkRigidbody>();
networkRigidbody.InterpolationSpace = worldSpace ? Spaces.World : Spaces.Local;
yield return networkObject;
yield return networkRigidbody;
#else
yield break;
#endif
}
/// <inheritdoc />
public override void EnableNetworkTransform(GameObject gameObject, bool enable)
{
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
NetworkTransform[] networkTransforms = gameObject.GetComponentsInChildren<NetworkTransform>();
networkTransforms.ForEach(nt => nt.SetEnabled(enable));
#endif
}
/// <inheritdoc />
public override void EnableNetworkRigidbody(GameObject gameObject, bool enable)
{
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
NetworkRigidbody[] networkRigidbodies = gameObject.GetComponentsInChildren<NetworkRigidbody>();
networkRigidbodies.ForEach(nrb => nrb.SetEnabled(enable));
#endif
}
/// <inheritdoc />
public override bool HasAuthority(GameObject gameObject)
{
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
if (_networkRunner == null)
{
return false;
}
NetworkObject networkObject = gameObject.GetComponent<NetworkObject>();
if (networkObject == null)
{
return false;
}
return networkObject.HasStateAuthority;
#else
return false;
#endif
}
/// <inheritdoc />
public override void RequestAuthority(GameObject gameObject)
{
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
if (_networkRunner && _networkRunner.GameMode == GameMode.Shared)
{
NetworkObject networkObject = gameObject.GetComponent<NetworkObject>();
if (networkObject)
{
networkObject.RequestStateAuthority();
}
}
#endif
}
/// <inheritdoc />
public override void CheckReassignGrabAuthority(GameObject gameObject)
{
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
UxrGrabbableObject grabbableObject = gameObject.GetComponent<UxrGrabbableObject>();
NetworkObject networkObject = gameObject.GetComponent<NetworkObject>();
if (networkObject != null && grabbableObject != null)
{
UxrAvatar avatarAuthority = UxrAvatar.EnabledComponents.FirstOrDefault(a => a.GetComponent<NetworkObject>() != null && a.GetComponent<NetworkObject>().StateAuthority == networkObject.StateAuthority);
if (avatarAuthority == null || !UxrGrabManager.Instance.IsBeingGrabbedBy(grabbableObject, avatarAuthority))
{
// No avatar has authority or the avatar that grabbed it doesn't have it anymore. Change authority to first one.
UxrAvatar firstAvatar = UxrGrabManager.Instance.GetGrabbingHands(grabbableObject).First().Avatar;
if (firstAvatar == UxrAvatar.LocalAvatar)
{
UxrNetworkManager.Instance.RequestAuthority(gameObject);
}
}
}
#endif
}
/// <inheritdoc />
public override bool HasNetworkTransformSyncComponents(GameObject gameObject)
{
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
return gameObject.GetComponent<NetworkTransform>() != null || gameObject.GetComponent<NetworkRigidbody>() != null;
#else
return false;
#endif
}
#endregion
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
#region INetworkRunnerCallbacks
/// <inheritdoc />
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionNetwork)}.{nameof(OnPlayerJoined)} PlayerId = {player.PlayerId}");
}
if (!_usePrototypingUI)
{
return;
}
if (_gameMode == GameMode.Single || _gameMode == GameMode.Server || _gameMode == GameMode.Host || (_gameMode == GameMode.AutoHostOrClient && _networkRunner.IsServer))
{
SpawnPlayer(runner, player);
}
if (_gameMode == GameMode.Shared && player == _networkRunner.LocalPlayer)
{
SpawnPlayer(runner, player);
}
}
/// <inheritdoc />
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionNetwork)}.{nameof(OnPlayerLeft)} PlayerId = {player.PlayerId}");
}
if (!_usePrototypingUI)
{
return;
}
if (_gameMode == GameMode.Single || _gameMode == GameMode.Server || _gameMode == GameMode.Host)
{
TryDespawnPlayer(runner, player);
}
if (_gameMode == GameMode.Shared && player == _networkRunner.LocalPlayer)
{
// Avatar has "destroy when state authority leaves" enabled.
}
}
/// <inheritdoc />
public void OnInput(NetworkRunner runner, NetworkInput input)
{
if (UxrAvatar.LocalAvatar == null)
{
return;
}
if (!_usePrototypingUI)
{
return;
}
RigInput rigInput = new RigInput();
rigInput.avatarPosition = UxrAvatar.LocalAvatar.transform.position;
rigInput.avatarRotation = UxrAvatar.LocalAvatar.transform.rotation;
rigInput.avatarScale = UxrAvatar.LocalAvatar.transform.localScale;
rigInput.cameraPosition = UxrAvatar.LocalAvatar.CameraComponent.transform.position;
rigInput.cameraRotation = UxrAvatar.LocalAvatar.CameraComponent.transform.rotation;
rigInput.leftHandPosition = UxrAvatar.LocalAvatar.GetHandBone(UxrHandSide.Left).transform.position;
rigInput.leftHandRotation = UxrAvatar.LocalAvatar.GetHandBone(UxrHandSide.Left).transform.rotation;
rigInput.rightHandPosition = UxrAvatar.LocalAvatar.GetHandBone(UxrHandSide.Right).transform.position;
rigInput.rightHandRotation = UxrAvatar.LocalAvatar.GetHandBone(UxrHandSide.Right).transform.rotation;
input.Set(rigInput);
}
/// <inheritdoc />
public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input)
{
}
/// <inheritdoc />
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionNetwork)}.{nameof(OnShutdown)} Reason: {shutdownReason}");
}
_spawnedAvatars.Clear();
}
/// <inheritdoc />
public void OnConnectedToServer(NetworkRunner runner)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionNetwork)}.{nameof(OnConnectedToServer)}");
}
}
/// <inheritdoc />
public void OnDisconnectedFromServer(NetworkRunner runner)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionNetwork)}.{nameof(OnDisconnectedFromServer)}");
}
}
/// <inheritdoc />
public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionNetwork)}.{nameof(OnConnectRequest)} from {request.RemoteAddress}");
}
}
/// <inheritdoc />
public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionNetwork)}.{nameof(OnConnectFailed)} Reason: {reason}");
}
}
/// <inheritdoc />
public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message)
{
}
/// <inheritdoc />
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
{
}
/// <inheritdoc />
public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data)
{
}
/// <inheritdoc />
public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken)
{
}
/// <inheritdoc />
public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ArraySegment<byte> data)
{
}
/// <inheritdoc />
public void OnSceneLoadDone(NetworkRunner runner)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionNetwork)}.{nameof(OnSceneLoadDone)}");
}
}
/// <inheritdoc />
public void OnSceneLoadStart(NetworkRunner runner)
{
}
#endregion
#region Unity
/// <summary>
/// Gets the network runner.
/// </summary>
protected override void Awake()
{
base.Awake();
if (!enabled)
{
return;
}
_networkRunner = gameObject.GetComponent<NetworkRunner>();
if (_networkRunner == null)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.NetworkingModule} Can't get network runner. Is Photon selected in the {nameof(UxrNetworkManager)}?");
}
}
}
/// <summary>
/// Shows the connection UI if its enabled.
/// </summary>
private void OnGUI()
{
if (!_usePrototypingUI)
{
return;
}
if (_networkRunner != null && _networkRunner.IsRunning)
{
return;
}
int labelHeight = 25;
int buttonWidth = 200;
int buttonHeight = 40;
int posY = 0;
GUI.Box(new Rect(0, 0, Screen.width, Screen.height), string.Empty);
GUI.Box(new Rect(0, posY, buttonWidth, buttonHeight), "UltimateXR Photon Fusion");
posY += buttonHeight;
if (_networkRunner != null && _networkRunner.IsStarting)
{
GUI.Box(new Rect(0, posY, buttonWidth, labelHeight), "Starting network");
return;
}
GUI.Box(new Rect(0, posY, buttonWidth, labelHeight), "Select Room Name:");
posY += labelHeight;
_roomName = GUI.TextField(new Rect(0, posY, buttonWidth, labelHeight), _roomName);
posY += buttonHeight;
if (GUI.Button(new Rect(0, posY, buttonWidth, buttonHeight), "No Multiplayer"))
{
new UxrTaskController(ct => StartPrototypeSession(GameMode.Single), true);
}
posY += buttonHeight;
if (GUI.Button(new Rect(0, posY, buttonWidth, buttonHeight), "Start Host"))
{
new UxrTaskController(ct => StartPrototypeSession(GameMode.Host), true);
}
posY += buttonHeight;
if (GUI.Button(new Rect(0, posY, buttonWidth, buttonHeight), "Start Client"))
{
new UxrTaskController(ct => StartPrototypeSession(GameMode.Client), true);
}
posY += buttonHeight;
if (GUI.Button(new Rect(0, posY, buttonWidth, buttonHeight), "Auto Host/Client"))
{
new UxrTaskController(ct => StartPrototypeSession(GameMode.AutoHostOrClient), true);
}
posY += buttonHeight;
if (GUI.Button(new Rect(0, posY, buttonWidth, buttonHeight), "Start Shared"))
{
new UxrTaskController(ct => StartPrototypeSession(GameMode.Shared), true);
}
}
#endregion
#region Private Methods
/// <summary>
/// Starts a multi-user session for prototyping.
/// </summary>
/// <param name="mode">The game mode</param>
private async Task StartPrototypeSession(GameMode mode)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionNetwork)}.{nameof(StartPrototypeSession)} in mode {mode}");
}
_gameMode = mode;
_networkRunner.ProvideInput = true;
INetworkSceneManager networkSceneManager = null;
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
Type type = assembly.GetType(NetworkSceneManagerDefault);
if (type != null)
{
networkSceneManager = gameObject.AddComponent(type) as INetworkSceneManager;
break;
}
}
await _networkRunner.StartGame(new StartGameArgs
{
GameMode = mode,
SessionName = _roomName,
Scene = SceneManager.GetActiveScene().buildIndex,
SceneManager = networkSceneManager
});
}
/// <summary>
/// Spawns a player's avatar.
/// </summary>
/// <param name="runner">The network runner</param>
/// <param name="player">The player to spawn the avatar for</param>
private void SpawnPlayer(NetworkRunner runner, PlayerRef player)
{
UxrAvatar firstAvatarPrefab = UxrNetworkManager.Instance.RegisteredAvatarPrefabs.FirstOrDefault();
if (firstAvatarPrefab == null)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.NetworkingModule} Can't spawn avatar prefab. Register avatars in {nameof(UxrNetworkManager)} first.");
}
}
else
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionNetwork)}.{nameof(SpawnPlayer)} Spawning player for PlayerId = {player.PlayerId} in {_gameMode} mode");
}
NetworkObject playerObject = runner.Spawn(firstAvatarPrefab.gameObject, Vector3.zero, Quaternion.identity, player);
_spawnedAvatars.Add(player, playerObject);
}
}
/// <summary>
/// Tries to despawn a player.
/// </summary>
/// <param name="runner">Network runner</param>
/// <param name="player">The player to despawn</param>
private void TryDespawnPlayer(NetworkRunner runner, PlayerRef player)
{
if (_spawnedAvatars.TryGetValue(player, out NetworkObject networkObject))
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonFusionNetwork)}.{nameof(TryDespawnPlayer)} Despawning player for PlayerId = {player.PlayerId} in {_gameMode} mode");
}
runner.Despawn(networkObject);
_spawnedAvatars.Remove(player);
}
}
#endregion
#region Private Data
private const string NetworkSceneManagerDefault = "Fusion.NetworkSceneManagerDefault";
private GameMode _gameMode = GameMode.Single;
private NetworkRunner _networkRunner;
private string _roomName = "TestRoom";
private readonly Dictionary<PlayerRef, NetworkObject> _spawnedAvatars = new Dictionary<PlayerRef, NetworkObject>();
#endregion
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 27c4fd979be9b29478efbe5c1fbf252a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 54c7ff38565edab4eb178a859829182c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrClientNetworkTransform.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
#if ULTIMATEXR_USE_UNITY_NETCODE
using Unity.Netcode.Components;
#else
using UnityEngine;
#endif
namespace UltimateXR.Networking.Integrations.Net.UnityNetCode
{
#if ULTIMATEXR_USE_UNITY_NETCODE
public class UxrClientNetworkTransform : NetworkTransform
{
/// <summary>
/// Behave like a NetworkTransform but authority is the owner, not the server.
/// </summary>
protected override bool OnIsServerAuthoritative()
{
return false;
}
}
#else
public class UxrClientNetworkTransform : MonoBehaviour
{
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2cdb9d15b9979084aa96d70e62a54c0a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,351 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrUnityNetCodeAvatar.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
#if ULTIMATEXR_USE_UNITY_NETCODE
using System;
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Settings;
using UltimateXR.Core.StateSave;
using UltimateXR.Core.StateSync;
using UltimateXR.Extensions.System;
using UltimateXR.Extensions.System.Collections;
using UltimateXR.Core.Instantiation;
using Unity.Netcode;
#endif
namespace UltimateXR.Networking.Integrations.Net.UnityNetCode
{
#if ULTIMATEXR_USE_UNITY_NETCODE
public class UxrUnityNetCodeAvatar : NetworkBehaviour, IUxrNetworkAvatar
{
#region Inspector Properties/Serialized Fields
[Tooltip("List of objects that will be disabled when the avatar is in local mode, to avoid intersections with the camera for example")] [SerializeField] private List<GameObject> _localDisabledGameObjects;
#endregion
#region Implicit IUxrNetworkAvatar
/// <inheritdoc />
public IList<GameObject> LocalDisabledGameObjects => _localDisabledGameObjects;
/// <inheritdoc />
public bool IsLocal { get; private set; }
/// <inheritdoc />
public UxrAvatar Avatar { get; private set; }
/// <inheritdoc />
public string AvatarName
{
get => _avatarName;
set
{
_avatarName = value;
if (Avatar != null)
{
Avatar.name = value;
}
}
}
/// <inheritdoc />
public event Action AvatarSpawned;
/// <inheritdoc />
public event Action AvatarDespawned;
/// <inheritdoc />
public void InitializeNetworkAvatar(UxrAvatar avatar, bool isLocal, string uniqueId, string avatarName)
{
IsLocal = isLocal;
AvatarName = avatarName;
avatar.AvatarMode = isLocal ? UxrAvatarMode.Local : UxrAvatarMode.UpdateExternally;
if (isLocal)
{
LocalDisabledGameObjects.ForEach(o => o.SetActive(false));
}
avatar.CombineUniqueId(uniqueId.GetGuid(), true);
}
#endregion
#region Public Methods
/// <summary>
/// Request authority of the local avatar over an object.
/// </summary>
/// <param name="networkObject">The object to get authority over</param>
public void RequestAuthority(NetworkObject networkObject)
{
RequestAuthorityServerRpc(networkObject);
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when a component in UltimateXR had a state change.
/// </summary>
/// <param name="component">Component</param>
/// <param name="eventArgs">Event parameters</param>
private void UxrManager_ComponentStateChanged(IUxrStateSync component, UxrSyncEventArgs eventArgs)
{
if (!IsOwner)
{
return;
}
if (eventArgs.Options.HasFlag(UxrStateSyncOptions.Network))
{
byte[] serializedEvent = eventArgs.SerializeEventBinary(component);
if (serializedEvent != null)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Sending {serializedEvent.Length} bytes from {component.Component.name} ({component.UniqueId}) {eventArgs}");
}
ComponentStateChangedServerRpc(serializedEvent);
}
}
}
#endregion
#region Event Trigger Methods
/// <inheritdoc />
public override void OnNetworkSpawn()
{
Avatar = GetComponent<UxrAvatar>();
InitializeNetworkAvatar(Avatar, IsOwner, OwnerClientId.ToString(), $"Player {OwnerClientId} ({(IsOwner ? "Local" : "External")})");
if (IsOwner)
{
UxrManager.ComponentStateChanged += UxrManager_ComponentStateChanged;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrUnityNetCodeAvatar)}.{nameof(OnNetworkSpawn)}: Is Local? {IsLocal}, Name: {AvatarName}. OwnerClientId: {OwnerClientId}, UniqueId: {Avatar.UniqueId}.");
}
AvatarSpawned?.Invoke();
if (UxrInstanceManager.HasInstance)
{
UxrInstanceManager.Instance.NotifyNetworkSpawn(Avatar.gameObject);
}
if (IsOwner)
{
if (!IsServer)
{
byte[] localAvatarState = UxrManager.Instance.SaveStateChanges(new List<GameObject> { Avatar.gameObject }, null, UxrStateSaveLevel.ChangesSinceBeginning, UxrGlobalSettings.Instance.NetFormatInitialState);
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} Requesting global state and sending local avatar state in {localAvatarState.Length} bytes.");
}
// Send the initial avatar state to the server and request the current scene state.
// Call after AvatarSpawned() in case any event handler changes the avatar state.
NewAvatarJoinedServerRpc(localAvatarState);
}
else
{
// Server creates the session and doesn't need to send the initial state.
s_initialStateLoaded = true;
}
}
}
/// <inheritdoc />
public override void OnNetworkDespawn()
{
if (Avatar && IsOwner)
{
UxrManager.ComponentStateChanged -= UxrManager_ComponentStateChanged;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.NetworkingModule} {nameof(UxrUnityNetCodeAvatar)}.{nameof(OnNetworkDespawn)}: Is Local? {IsLocal}, Name: {AvatarName}");
}
AvatarDespawned?.Invoke();
}
#endregion
#region Private Methods
/// <summary>
/// Server RPC to request the current global state upon joining.
/// </summary>
/// <param name="avatarState">The initial state of the avatar that joined</param>
/// <param name="serverRpcParams">Filled by NetCode with info</param>
[ServerRpc]
private void NewAvatarJoinedServerRpc(byte[] avatarState, ServerRpcParams serverRpcParams = default)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Received request for global state from client {serverRpcParams.Receive.SenderClientId}.");
}
// First load the avatar state
UxrManager.Instance.LoadStateChanges(avatarState);
// Now export the scenario state, except for the new avatar, and send it back
byte[] serializedState = UxrManager.Instance.SaveStateChanges(null, new List<GameObject> { gameObject }, UxrStateSaveLevel.ChangesSinceBeginning, UxrGlobalSettings.Instance.NetFormatInitialState);
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Sending global state in {serializedState.Length} bytes to client {serverRpcParams.Receive.SenderClientId}. Broadcasting {avatarState.Length} bytes to sync new avatar.");
}
// Send global state to new user.
ClientRpcParams clientRpcParams = new ClientRpcParams
{
Send = new ClientRpcSendParams
{
TargetClientIds = new[] { serverRpcParams.Receive.SenderClientId }
}
};
LoadGlobalStateClientRpc(serializedState, clientRpcParams);
// Broadcast initial state of new avatar.
LoadAvatarStateClientRpc(avatarState);
}
/// <summary>
/// Server RPC call to propagate state change events to all other clients.
/// </summary>
/// <param name="serializedEventData">The serialized state change data</param>
[ServerRpc]
private void ComponentStateChangedServerRpc(byte[] serializedEventData)
{
ComponentStateChangedClientRpc(serializedEventData);
}
/// <summary>
/// Server RPC requesting authority over an object.
/// </summary>
/// <param name="networkObjectReference">Object to get authority over</param>
/// <param name="serverRpcParams">Filled by NetCode with info</param>
[ServerRpc]
private void RequestAuthorityServerRpc(NetworkObjectReference networkObjectReference, ServerRpcParams serverRpcParams = default)
{
if (networkObjectReference.TryGet(out NetworkObject networkObject))
{
NetworkManager networkManager = UxrNetworkManager.Instance.GetComponent<NetworkManager>();
if (networkManager != null)
{
networkObject.ChangeOwnership(serverRpcParams.Receive.SenderClientId);
}
}
else
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.NetworkingModule} {nameof(UxrUnityNetCodeAvatar)}.{nameof(RequestAuthorityServerRpc)}() Cannot find target network object.");
}
}
}
/// <summary>
/// Targeted client RPC to client that joined to sync to the current state.
/// </summary>
/// <param name="serializedStateData">The serialized state data</param>
/// <param name="clientRpcParams">Target of the RPC</param>
[ClientRpc]
private void LoadGlobalStateClientRpc(byte[] serializedStateData, ClientRpcParams clientRpcParams = default)
{
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Receiving {serializedStateData.Length} bytes of global state data.");
}
UxrManager.Instance.LoadStateChanges(serializedStateData);
s_initialStateLoaded = true;
}
/// <summary>
/// Client RPC to sync the state of a new avatar that joined.
/// </summary>
/// <param name="serializedStateData">The serialized state data</param>
[ClientRpc]
private void LoadAvatarStateClientRpc(byte[] serializedStateData)
{
if (IsOwner)
{
// Don't execute on the source of the event, we don't want to load our own avatar data.
return;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Receiving {serializedStateData.Length} bytes of avatar state data.");
}
UxrManager.Instance.LoadStateChanges(serializedStateData);
}
/// <summary>
/// Client RPC call to execute a state change event. It will execute on all clients except the one that generated it,
/// which can be identified because it's the one with ownership.
/// </summary>
/// <param name="serializedEventData">The serialized state change data</param>
[ClientRpc]
private void ComponentStateChangedClientRpc(byte[] serializedEventData)
{
if (IsOwner)
{
// Don't execute on the source of the event.
return;
}
if (s_initialStateLoaded == false)
{
// Ignore sync events until the initial state is sent, to make sure the syncs are only processed after the initial state.
return;
}
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.NetworkingModule} Receiving {serializedEventData.Length} bytes of data. Base64: {Convert.ToBase64String(serializedEventData)}");
}
UxrStateSyncResult result = UxrManager.Instance.ExecuteStateSyncEvent(serializedEventData);
}
#endregion
#region Private Types & Data
private static bool s_initialStateLoaded;
private string _avatarName;
#endregion
}
#else
public class UxrPhotonFusionAvatar : MonoBehaviour
{
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 331b06803190dac428d6de4f688107e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,449 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrUnityNetCodeNetwork.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UnityEngine;
#if ULTIMATEXR_USE_UNITY_NETCODE && UNITY_EDITOR
using UnityEditor;
#endif
#if ULTIMATEXR_USE_UNITY_NETCODE
using System.Linq;
using UltimateXR.Extensions.System.Collections;
using UltimateXR.Extensions.Unity;
using UltimateXR.Manipulation;
using Unity.Netcode;
using Unity.Netcode.Transports.UTP;
using NetworkObject = Unity.Netcode.NetworkObject;
using NetworkRigidbody = Unity.Netcode.Components.NetworkRigidbody;
using NetworkTransform = Unity.Netcode.Components.NetworkTransform;
#endif
#pragma warning disable 414 // Disable warnings due to unused values
namespace UltimateXR.Networking.Integrations.Net.UnityNetCode
{
/// <summary>
/// Implementation of networking support using Unity NetCode.
/// </summary>
public class UxrUnityNetCodeNetwork : UxrNetworkImplementation
{
#region Inspector Properties/Serialized Fields
[Tooltip("Show a UI during play mode with connection options to quickly prototype networking functionality")] [SerializeField] private bool _usePrototypingUI = true;
#endregion
#region Public Overrides UxrNetworkImplementation
/// <inheritdoc />
public override string SdkName => UxrConstants.SdkUnityNetCode;
/// <inheritdoc />
public override bool IsServer
{
get
{
#if ULTIMATEXR_USE_UNITY_NETCODE
return NetworkManager.Singleton != null && NetworkManager.Singleton.IsServer;
#else
return false;
#endif
}
}
/// <inheritdoc />
public override bool IsClient
{
get
{
#if ULTIMATEXR_USE_UNITY_NETCODE
return NetworkManager.Singleton != null && NetworkManager.Singleton.IsClient;
#else
return false;
#endif
}
}
/// <inheritdoc />
public override UxrNetworkCapabilities Capabilities => UxrNetworkCapabilities.NetworkTransform; // The following is momentarily disabled until we get a workaround for not allowing re-parenting during startup | UxrNetworkCapabilities.NetworkRigidbody;
/// <inheritdoc />
public override string NetworkRigidbodyWarning => $"{UxrConstants.SdkUnityNetCode} does not allow re-parenting NetworkIdentity GameObjects during startup. Until there is a workaround, NetworkRigidbody support here will be disabled. Don't worry! UltimateXR will still synchronize grabbable physics-driven rigidbodies using RPC calls to try to keep the same position/velocity on all users.";
/// <inheritdoc />
public override void SetupGlobal(UxrNetworkManager networkManager, out List<GameObject> newGameObjects, out List<Component> newComponents)
{
newGameObjects = new List<GameObject>();
newComponents = new List<Component>();
#if ULTIMATEXR_USE_UNITY_NETCODE && UNITY_EDITOR
GameObject networkManagerGo = new GameObject("NetCodeNetworkManager");
Undo.RegisterCreatedObjectUndo(networkManagerGo, "Create NetCode Network Manager");
networkManagerGo.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
networkManagerGo.transform.SetSiblingIndex(networkManager.transform.GetSiblingIndex() + 1);
NetworkManager netCodeNetworkManager = networkManagerGo.GetOrAddComponent<NetworkManager>();
UnityTransport unityTransport = networkManagerGo.GetOrAddComponent<UnityTransport>();
netCodeNetworkManager.NetworkConfig.NetworkTransport = unityTransport;
Undo.RegisterFullObjectHierarchyUndo(networkManager.gameObject, "Setup NetCode NetworkManager");
newComponents.Add(unityTransport);
newComponents.Add(netCodeNetworkManager);
newGameObjects.Add(networkManagerGo);
#endif
}
/// <inheritdoc />
public override void SetupAvatar(UxrAvatar avatar, out List<GameObject> newGameObjects, out List<Component> newComponents)
{
newGameObjects = new List<GameObject>();
newComponents = new List<Component>();
#if ULTIMATEXR_USE_UNITY_NETCODE && UNITY_EDITOR
if (avatar == null)
{
return;
}
UxrUnityNetCodeAvatar netCodeAvatar = avatar.GetOrAddComponent<UxrUnityNetCodeAvatar>();
newComponents.Add(netCodeAvatar);
IEnumerable<Behaviour> avatarComponents = SetupClientNetworkTransform(avatar.gameObject, true, UxrNetworkTransformFlags.All);
IEnumerable<Behaviour> cameraComponents = SetupClientNetworkTransform(avatar.CameraComponent.gameObject, true, UxrNetworkTransformFlags.ChildPositionAndRotation);
IEnumerable<Behaviour> leftHandComponents = SetupClientNetworkTransform(avatar.GetHand(UxrHandSide.Left).Wrist.gameObject, true, UxrNetworkTransformFlags.ChildPositionAndRotation);
IEnumerable<Behaviour> rightHandComponents = SetupClientNetworkTransform(avatar.GetHand(UxrHandSide.Right).Wrist.gameObject, true, UxrNetworkTransformFlags.ChildPositionAndRotation);
newComponents.AddRange(avatarComponents.ToList().Concat(cameraComponents).Concat(leftHandComponents).Concat(rightHandComponents));
Undo.RegisterFullObjectHierarchyUndo(avatar.gameObject, "Setup NetCode Avatar");
#endif
}
/// <inheritdoc />
public override void SetupPostProcess(IEnumerable<UxrAvatar> avatarPrefabs)
{
#if ULTIMATEXR_USE_UNITY_NETCODE && UNITY_EDITOR
NetworkManager netCodeNetworkManager = FindObjectOfType<NetworkManager>();
if (netCodeNetworkManager != null && netCodeNetworkManager.NetworkConfig.PlayerPrefab == null && avatarPrefabs.Any())
{
netCodeNetworkManager.NetworkConfig.PlayerPrefab = avatarPrefabs.First().gameObject;
Undo.RegisterCompleteObjectUndo(netCodeNetworkManager, "Setup NetCode Avatar");
}
#endif
}
/// <inheritdoc />
public override IEnumerable<Behaviour> AddNetworkTransform(GameObject gameObject, bool worldSpace, UxrNetworkTransformFlags networkTransformFlags)
{
#if ULTIMATEXR_USE_UNITY_NETCODE && UNITY_EDITOR
if (networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ChildTransform) == false)
{
NetworkObject networkObject = gameObject.GetOrAddComponent<NetworkObject>();
yield return networkObject;
}
NetworkTransform networkTransform = gameObject.GetOrAddComponent<NetworkTransform>();
networkTransform.InLocalSpace = !worldSpace;
networkTransform.SyncPositionX = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.PositionX);
networkTransform.SyncPositionY = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.PositionY);
networkTransform.SyncPositionZ = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.PositionZ);
networkTransform.SyncRotAngleX = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.RotationX);
networkTransform.SyncRotAngleY = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.RotationY);
networkTransform.SyncRotAngleZ = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.RotationZ);
networkTransform.SyncScaleX = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ScaleX);
networkTransform.SyncScaleY = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ScaleY);
networkTransform.SyncScaleZ = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ScaleZ);
yield return networkTransform;
#else
yield break;
#endif
}
/// <inheritdoc />
public override IEnumerable<Behaviour> AddNetworkRigidbody(GameObject gameObject, bool worldSpace, UxrNetworkRigidbodyFlags networkRigidbodyFlags)
{
#if ULTIMATEXR_USE_UNITY_NETCODE && UNITY_EDITOR
// Building list forces evaluation of AddNetworkTransform IEnumerable and creates the components
List<Behaviour> networkTransformComponents = new List<Behaviour>(AddNetworkTransform(gameObject, worldSpace, UxrNetworkTransformFlags.All));
NetworkRigidbody networkRigidbody = gameObject.GetOrAddComponent<NetworkRigidbody>();
yield return networkRigidbody;
// Return transform components after, so that when removing the components the NetworkRigidbody is removed before the identity. Otherwise Mirror will complain.
foreach (Behaviour newBehaviour in networkTransformComponents)
{
yield return newBehaviour;
}
#else
yield break;
#endif
}
/// <inheritdoc />
public override void EnableNetworkTransform(GameObject gameObject, bool enable)
{
#if ULTIMATEXR_USE_UNITY_NETCODE
NetworkTransform[] networkTransforms = gameObject.GetComponentsInChildren<NetworkTransform>();
networkTransforms.ForEach(nt => nt.SetEnabled(enable));
#endif
}
/// <inheritdoc />
public override void EnableNetworkRigidbody(GameObject gameObject, bool enable)
{
#if ULTIMATEXR_USE_UNITY_NETCODE
EnableNetworkTransform(gameObject, enabled);
NetworkRigidbody[] networkRigidbodies = gameObject.GetComponentsInChildren<NetworkRigidbody>();
networkRigidbodies.ForEach(nrb => nrb.SetEnabled(enable));
#endif
}
/// <inheritdoc />
public override bool HasAuthority(GameObject gameObject)
{
#if ULTIMATEXR_USE_UNITY_NETCODE
NetworkObject networkObject = gameObject.GetComponent<NetworkObject>();
if (networkObject == null)
{
return false;
}
return networkObject.IsOwner;
#else
return false;
#endif
}
/// <inheritdoc />
public override void RequestAuthority(GameObject gameObject)
{
#if ULTIMATEXR_USE_UNITY_NETCODE
if (gameObject == null)
{
return;
}
UxrUnityNetCodeAvatar netCodeAvatar = UxrAvatar.LocalAvatar.GetComponentInChildren<UxrUnityNetCodeAvatar>();
NetworkObject networkObject = gameObject.GetComponent<NetworkObject>();
if (netCodeAvatar != null && networkObject != null)
{
netCodeAvatar.RequestAuthority(networkObject);
}
#endif
}
/// <inheritdoc />
public override void CheckReassignGrabAuthority(GameObject gameObject)
{
#if ULTIMATEXR_USE_UNITY_NETCODE
UxrGrabbableObject grabbableObject = gameObject.GetComponent<UxrGrabbableObject>();
NetworkObject networkObject = gameObject.GetComponent<NetworkObject>();
if (networkObject != null && grabbableObject != null)
{
UxrAvatar avatarAuthority = UxrAvatar.EnabledComponents.FirstOrDefault(a => a.GetComponent<NetworkObject>() != null && a.GetComponent<NetworkObject>().OwnerClientId == networkObject.OwnerClientId);
if (avatarAuthority == null || !UxrGrabManager.Instance.IsBeingGrabbedBy(grabbableObject, avatarAuthority))
{
// No avatar has authority or the avatar that grabbed it doesn't have it anymore. Change authority to first one.
UxrAvatar firstAvatar = UxrGrabManager.Instance.GetGrabbingHands(grabbableObject).First().Avatar;
if (firstAvatar == UxrAvatar.LocalAvatar)
{
UxrNetworkManager.Instance.RequestAuthority(gameObject);
}
}
}
#endif
}
/// <inheritdoc />
public override bool HasNetworkTransformSyncComponents(GameObject gameObject)
{
#if ULTIMATEXR_USE_UNITY_NETCODE
return gameObject.GetComponent<NetworkTransform>() != null || gameObject.GetComponent<NetworkRigidbody>() != null;
#else
return false;
#endif
}
#endregion
#if ULTIMATEXR_USE_UNITY_NETCODE
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Start()
{
base.Start();
if (NetworkManager.Singleton != null && NetworkManager.Singleton.NetworkConfig.NetworkTransport is UnityTransport unityTransport)
{
_networkAddress = unityTransport.ConnectionData.Address;
_networkPort = unityTransport.ConnectionData.Port.ToString();
}
}
/// <summary>
/// Shows the connection UI if its enabled.
/// </summary>
private void OnGUI()
{
if (!_usePrototypingUI)
{
return;
}
if (NetworkManager.Singleton == null)
{
return;
}
PosY = 0;
GUI.Box(new Rect(0, 0, Screen.width, Screen.height), string.Empty);
GUI.Box(new Rect(0, PosY, ButtonWidth, ButtonHeight), "UltimateXR Unity NetCode");
PosY += ButtonHeight;
if (NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsClient || NetworkManager.Singleton.IsServer)
{
if (NetworkManager.Singleton.ShutdownInProgress)
{
return;
}
if (NetworkManager.Singleton.IsHost)
{
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Stop Host"))
{
NetworkManager.Singleton.Shutdown();
}
}
else if (NetworkManager.Singleton.IsServer)
{
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Stop Server"))
{
NetworkManager.Singleton.Shutdown();
}
}
else if (NetworkManager.Singleton.IsClient)
{
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Disconnect Client"))
{
NetworkManager.Singleton.Shutdown();
}
}
return;
}
GUI.Box(new Rect(0, PosY, ButtonWidth, LabelHeight), "Network Address:");
GUI.Box(new Rect(ButtonWidth + 10, PosY, ButtonWidth / 2, LabelHeight), "Port:");
PosY += LabelHeight;
_networkAddress = GUI.TextField(new Rect(0, PosY, ButtonWidth, LabelHeight), _networkAddress);
_networkPort = GUI.TextField(new Rect(ButtonWidth + 10, PosY, ButtonWidth / 2, LabelHeight), _networkPort);
PosY += ButtonHeight;
if (NetworkManager.Singleton != null && NetworkManager.Singleton.NetworkConfig.NetworkTransport is UnityTransport unityTransport)
{
ushort.TryParse(_networkPort, out ushort port);
unityTransport.SetConnectionData(string.IsNullOrEmpty(_networkAddress) ? DefaultNetworkAddress : _networkAddress, port);
}
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Start Host"))
{
NetworkManager.Singleton.StartHost();
}
PosY += ButtonHeight;
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Start Server"))
{
NetworkManager.Singleton.StartServer();
}
PosY += ButtonHeight;
if (GUI.Button(new Rect(0, PosY, ButtonWidth, ButtonHeight), "Start Client"))
{
NetworkManager.Singleton.StartClient();
}
}
#endregion
#region Private Methods
/// <summary>
/// Helper method to set up UxrClientNetworkTransform components for a given object.
/// </summary>
/// <param name="go">The GameObject to set up</param>
/// <param name="worldSpace">Whether to use world-space coordinates or local-space coordinates</param>
/// <param name="flags">Option flags</param>
/// <returns>List of components that were added: an UxrClientNetworkTransform and NetworkObject</returns>
private IEnumerable<Behaviour> SetupClientNetworkTransform(GameObject go, bool worldSpace, UxrNetworkTransformFlags networkTransformFlags)
{
if (go != null)
{
if (networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ChildTransform) == false)
{
NetworkObject networkObject = go.GetOrAddComponent<NetworkObject>();
yield return networkObject;
}
UxrClientNetworkTransform clientNetworkTransform = go.GetOrAddComponent<UxrClientNetworkTransform>();
clientNetworkTransform.InLocalSpace = !worldSpace;
clientNetworkTransform.SyncPositionX = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.PositionX);
clientNetworkTransform.SyncPositionY = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.PositionY);
clientNetworkTransform.SyncPositionZ = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.PositionZ);
clientNetworkTransform.SyncRotAngleX = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.RotationX);
clientNetworkTransform.SyncRotAngleY = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.RotationY);
clientNetworkTransform.SyncRotAngleZ = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.RotationZ);
clientNetworkTransform.SyncScaleX = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ScaleX);
clientNetworkTransform.SyncScaleY = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ScaleY);
clientNetworkTransform.SyncScaleZ = networkTransformFlags.HasFlag(UxrNetworkTransformFlags.ScaleZ);
yield return clientNetworkTransform;
}
}
#endregion
#region Private Data
private const string DefaultNetworkAddress = "127.0.0.1";
private const ushort DefaultNetworkPort = 7777;
private const int LabelHeight = 25;
private const int ButtonWidth = 200;
private const int ButtonHeight = 40;
private int PosY { get; set; }
private string _networkAddress = DefaultNetworkAddress;
private string _networkPort = DefaultNetworkPort.ToString();
#endregion
#endif
}
}
#pragma warning restore 414

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2538d188e76399b42afad64b10e0d936
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 75590be471323744dbd5f4e0ca0ce121
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: afa911fc405ade641809d6f8374d8c50
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,119 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrDissonanceNetwork.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UnityEngine;
#if ULTIMATEXR_USE_DISSONANCE_SDK && UNITY_EDITOR
using UnityEditor;
#endif
#if ULTIMATEXR_USE_DISSONANCE_SDK
using UltimateXR.Extensions.Unity;
using Dissonance;
#endif
namespace UltimateXR.Networking.Integrations.Voice.Dissonance
{
/// <summary>
/// Implementation of networking voice support using Dissonance.
/// </summary>
public class UxrDissonanceNetwork : UxrNetworkVoiceImplementation
{
#region Public Overrides UxrNetworkVoiceImplementation
/// <inheritdoc />
public override string SdkName => UxrConstants.SdkDissonance;
/// <inheritdoc />
public override IEnumerable<string> CompatibleNetworkSDKs
{
get
{
yield return UxrConstants.SdkFishNet;
yield return UxrConstants.SdkMirror;
yield return UxrConstants.SdkPhotonFusion;
yield return UxrConstants.SdkUnityNetCode;
}
}
/// <inheritdoc />
public override void SetupGlobal(string networkingSdk, UxrNetworkManager networkManager, out List<GameObject> newGameObjects, out List<Component> newComponents)
{
newGameObjects = new List<GameObject>();
newComponents = new List<Component>();
#if ULTIMATEXR_USE_DISSONANCE_SDK && UNITY_EDITOR
if (string.IsNullOrEmpty(networkingSdk))
{
return;
}
string setupPrefabGuid = null;
GameObject setupInstance = null;
if (string.Equals(networkingSdk, UxrConstants.SdkFishNet))
{
Debug.LogWarning($"{UxrConstants.NetworkingModule} FishNet Dissonance integration package doesn't come with a prefab and components should be added manually. We're working on a pull request to add integration seamlessly.");
}
else if (string.Equals(networkingSdk, UxrConstants.SdkMirror))
{
setupPrefabGuid = "1264c01c7f8182e47ac9f784af03d895";
}
else if (string.Equals(networkingSdk, UxrConstants.SdkPhotonFusion))
{
setupPrefabGuid = "803e2767acc738a4498f245ae19bb598";
}
else if (string.Equals(networkingSdk, UxrConstants.SdkUnityNetCode))
{
setupPrefabGuid = "2c50758a6d3b8114a8ce30a2fd9e4380";
}
if (setupPrefabGuid != null)
{
setupInstance = PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(setupPrefabGuid))) as GameObject;
if (setupInstance == null)
{
Debug.LogError($"{UxrConstants.NetworkingModule} Could not find the {UxrConstants.SdkDissonance} setup prefab for {networkingSdk}. Check for the {networkingSdk} integration here: https://placeholder-software.co.uk/dissonance/docs/Basics/Getting-Started.html");
}
else
{
Undo.RegisterCreatedObjectUndo(setupInstance, "Create Dissonance GameObject");
}
}
if (setupInstance != null)
{
VoiceBroadcastTrigger broadcastTrigger = Undo.AddComponent<VoiceBroadcastTrigger>(setupInstance);
VoiceReceiptTrigger receiptTrigger = Undo.AddComponent<VoiceReceiptTrigger>(setupInstance);
broadcastTrigger.ChannelType = CommTriggerTarget.Room;
broadcastTrigger.RoomName = "Global";
receiptTrigger.RoomName = "Global";
Undo.RegisterFullObjectHierarchyUndo(setupInstance, "Setup Dissonance GameObject");
newGameObjects.Add(setupInstance);
newComponents.Add(broadcastTrigger);
newComponents.Add(receiptTrigger);
}
#endif
}
/// <inheritdoc />
public override void SetupAvatar(string networkingSdk, UxrAvatar avatar, out List<GameObject> newGameObjects, out List<Component> newComponents)
{
newGameObjects = new List<GameObject>();
newComponents = new List<Component>();
// No setup required
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 23dab6befd4969f428546efcc8d22e14
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4a63d5e9d329bea40bfc993ee33ef280
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,124 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrPhotonVoiceNetwork.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.Unity;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if ULTIMATEXR_USE_PHOTONFUSION_SDK
using Fusion;
#endif
#if ULTIMATEXR_USE_PHOTONVOICE_SDK
using Photon.Voice.Fusion;
using Photon.Voice.Unity;
#endif
namespace UltimateXR.Networking.Integrations.Voice.PhotonVoice
{
/// <summary>
/// Implementation of networking voice support using Photon Fusion.
/// </summary>
public class UxrPhotonVoiceNetwork : UxrNetworkVoiceImplementation
{
#region Public Overrides UxrNetworkVoiceImplementation
/// <inheritdoc />
public override string SdkName => UxrConstants.SdkPhotonVoice;
/// <inheritdoc />
public override IEnumerable<string> CompatibleNetworkSDKs
{
get
{
yield return UxrConstants.SdkPhotonFusion;
}
}
/// <inheritdoc />
public override void SetupGlobal(string networkingSdk, UxrNetworkManager networkManager, out List<GameObject> newGameObjects, out List<Component> newComponents)
{
newGameObjects = new List<GameObject>();
newComponents = new List<Component>();
#if ULTIMATEXR_USE_PHOTONFUSION_SDK && ULTIMATEXR_USE_PHOTONVOICE_SDK && UNITY_EDITOR
Component runner = networkManager.CreatedGlobalComponents.FirstOrDefault(g => g.GetComponent<NetworkRunner>() != null);
if (runner)
{
GameObject recorderObject = new GameObject("Recorder");
Undo.RegisterCreatedObjectUndo(recorderObject, "Create Photon Voice Support GameObject");
Undo.SetTransformParent(recorderObject.transform, runner.transform, "Parent Photon Voice GameObject");
recorderObject.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
FusionVoiceClient voiceClientComponent = Undo.AddComponent<FusionVoiceClient>(runner.gameObject);
Recorder recorderComponent = Undo.AddComponent<Recorder>(recorderObject);
voiceClientComponent.UseFusionAppSettings = true;
voiceClientComponent.UseFusionAuthValues = true;
voiceClientComponent.PrimaryRecorder = recorderComponent;
Undo.RegisterFullObjectHierarchyUndo(runner.gameObject, "Setup Photon GameObject");
newGameObjects.Add(recorderObject);
newComponents.Add(recorderComponent);
newComponents.Add(voiceClientComponent);
}
else
{
Debug.LogError($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonVoiceNetwork)}.{nameof(SetupGlobal)} Cannot find {nameof(NetworkRunner)} to set up.");
}
#endif
}
/// <inheritdoc />
public override void SetupAvatar(string networkingSdk, UxrAvatar avatar, out List<GameObject> newGameObjects, out List<Component> newComponents)
{
newGameObjects = new List<GameObject>();
newComponents = new List<Component>();
#if ULTIMATEXR_USE_PHOTONFUSION_SDK && ULTIMATEXR_USE_PHOTONVOICE_SDK && UNITY_EDITOR
Camera cameraComponent = avatar.CameraComponent;
if (cameraComponent != null)
{
GameObject photonVoice = new GameObject("PhotonVoice");
Undo.RegisterCreatedObjectUndo(photonVoice, "Create Photon Voice Support GameObject");
Undo.SetTransformParent(photonVoice.transform, cameraComponent.transform, "Parent Photon Voice GameObject");
photonVoice.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
Component voiceNetworkObjectComponent = avatar.GetOrAddComponent<VoiceNetworkObject>();
Component speakerComponent = photonVoice.GetOrAddComponent<Speaker>();
Component audioSourceComponent = photonVoice.GetOrAddComponent<AudioSource>();
Undo.RegisterCompleteObjectUndo(avatar.gameObject, "Setup Photon Voice");
Undo.RegisterFullObjectHierarchyUndo(cameraComponent.gameObject, "Setup Photon Voice");
newGameObjects.Add(photonVoice);
newComponents.Add(voiceNetworkObjectComponent);
newComponents.Add(speakerComponent);
newComponents.Add(audioSourceComponent);
}
else if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.NetworkingModule} {nameof(UxrPhotonVoiceNetwork)}.{nameof(SetupAvatar)} Cannot find {nameof(Camera)} on avatar to set up voice components.");
}
#endif
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ec413275618078540911aab8c69d90c1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: