Add ultimate xr
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="IUxrNetworkAvatar.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Avatar;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for network avatar components. Network avatar components are responsible for setting the avatar in the
|
||||
/// correct mode (local/external) and sending/receiving global component state changes.
|
||||
/// </summary>
|
||||
public interface IUxrNetworkAvatar
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Event called right after the avatar was spawned.
|
||||
/// </summary>
|
||||
event Action AvatarSpawned;
|
||||
|
||||
/// <summary>
|
||||
/// Event called right after the avatar was despawned.
|
||||
/// </summary>
|
||||
event Action AvatarDespawned;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this avatar is the avatar controller by the user (true) or a remote avatar (false).
|
||||
/// </summary>
|
||||
bool IsLocal { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar component.
|
||||
/// </summary>
|
||||
UxrAvatar Avatar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of objects that will be disabled when the avatar is in local mode. This allows to avoid rendering the
|
||||
/// head or elements that could intersect with the camera.
|
||||
/// </summary>
|
||||
IList<GameObject> LocalDisabledGameObjects { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the avatar name.
|
||||
/// </summary>
|
||||
string AvatarName { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an avatar. Should be called by the implementation right after the avatar was spawned.
|
||||
/// </summary>
|
||||
/// <param name="avatar">Avatar component</param>
|
||||
/// <param name="isLocal">Whether the avatar is local</param>
|
||||
/// <param name="uniqueId">A unique Id to identify the avatar, usually the user unique network ID</param>
|
||||
/// <param name="avatarName">The name of the avatar, to assign it to the avatar GameObject and a label if there is a label</param>
|
||||
void InitializeNetworkAvatar(UxrAvatar avatar, bool isLocal, string uniqueId, string avatarName);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91938a039f394b78a0bfb2ac69c9462b
|
||||
timeCreated: 1691531713
|
||||
@@ -0,0 +1,133 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="IUxrNetworkImplementation.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Avatar;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for classes that implement network functionality.
|
||||
/// </summary>
|
||||
public interface IUxrNetworkImplementation : IUxrNetworkSdk
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether there is a networking session currently active and the local user is the server.
|
||||
/// </summary>
|
||||
bool IsServer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether there is a networking session currently active and the local user is a client.
|
||||
/// </summary>
|
||||
bool IsClient { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SDK capabilities.
|
||||
/// </summary>
|
||||
UxrNetworkCapabilities Capabilities { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a warning string if the NetworkRigidbody support requires extra attention. This is used by the
|
||||
/// UxrNetworkManagerEditor to show a warning text box in case this implementation is selected.
|
||||
/// </summary>
|
||||
string NetworkRigidbodyWarning { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Adds global support for the SDK if necessary, by adding required GameObjects and/or components to the
|
||||
/// <see cref="UxrNetworkManager" /> or the scene where it is located.
|
||||
/// </summary>
|
||||
/// <param name="networkManager">The network manager</param>
|
||||
/// <param name="newGameObjects">Returns a list of GameObjects that were created, if any</param>
|
||||
/// <param name="newComponents">Returns a list of components that were created, if any</param>
|
||||
void SetupGlobal(UxrNetworkManager networkManager, out List<GameObject> newGameObjects, out List<Component> newComponents);
|
||||
|
||||
/// <summary>
|
||||
/// Adds network synchronization functionality to an <see cref="UxrAvatar" />. Each avatar passed as argument is an
|
||||
/// instance of a prefab in the <see cref="UxrNetworkManager" /> avatar list.
|
||||
/// All changes will be applied to the prefab after the setup returns and the avatar instance will be destroyed. To get
|
||||
/// references to the actual prefabs to, for example, fill a list of spawnable prefabs, use
|
||||
/// <see cref="SetupPostProcess" />.
|
||||
/// </summary>
|
||||
/// <param name="avatar">The avatar to add functionality to</param>
|
||||
/// <param name="newGameObjects">Returns a list of GameObjects that were created, if any</param>
|
||||
/// <param name="newComponents">Returns a list of components that were created, if any</param>
|
||||
void SetupAvatar(UxrAvatar avatar, out List<GameObject> newGameObjects, out List<Component> newComponents);
|
||||
|
||||
/// <summary>
|
||||
/// Called after the setup finished. At this point the modifications on the avatar prefabs have been applied.
|
||||
/// This can be used to fill a list of spawnable prefabs which is a common requirement in networking SDKs.
|
||||
/// </summary>
|
||||
void SetupPostProcess(IEnumerable<UxrAvatar> avatarPrefabs);
|
||||
|
||||
/// <summary>
|
||||
/// Adds network synchronization functionality to a <see cref="Transform" />.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The GameObject to add functionality to</param>
|
||||
/// <param name="worldSpace">Whether to synchronize world space coordinates (true) or local space (false)</param>
|
||||
/// <param name="networkTransformFlags">Which elements to synchronize</param>
|
||||
/// <returns>List of components that were added</returns>
|
||||
IEnumerable<Behaviour> AddNetworkTransform(GameObject gameObject, bool worldSpace, UxrNetworkTransformFlags networkTransformFlags);
|
||||
|
||||
/// <summary>
|
||||
/// Adds network synchronization functionality to a <see cref="Rigidbody" />.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The GameObject with the rigidbody to add functionality to</param>
|
||||
/// <param name="worldSpace">Whether to synchronize world space coordinates (true) or local space (false)</param>
|
||||
/// <param name="networkRigidbodyFlags">Options</param>
|
||||
/// <returns>List of components that were added</returns>
|
||||
IEnumerable<Behaviour> AddNetworkRigidbody(GameObject gameObject, bool worldSpace, UxrNetworkRigidbodyFlags networkRigidbodyFlags);
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables a network transform component.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">GameObject where the network transform is located</param>
|
||||
/// <param name="enable">Whether to enable or disable the component</param>
|
||||
void EnableNetworkTransform(GameObject gameObject, bool enable);
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables a network rigidbody component.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">GameObject where the network rigidbody is located</param>
|
||||
/// <param name="enable">Whether to enable or disable the component</param>
|
||||
void EnableNetworkRigidbody(GameObject gameObject, bool enable);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the current client has the authority over a network GameObject.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The GameObject to check the authority of</param>
|
||||
bool HasAuthority(GameObject gameObject);
|
||||
|
||||
/// <summary>
|
||||
/// Requests authority of the local user over a network GameObject.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The GameObject to request authority over</param>
|
||||
void RequestAuthority(GameObject gameObject);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an object that is being grabbed is missing a client authority, and assigns a new avatar as authority.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The GameObject with a <see cref="UxrGrabbableOBject" /> component</param>
|
||||
void CheckReassignGrabAuthority(GameObject gameObject);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether an object has networking components (NetworkTransform/NetworkRigidbody) to sync its transform.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">GameObject to check</param>
|
||||
/// <returns>
|
||||
/// Boolean telling whether the object has any NetworkTransform/NetworkRigidbody components using the
|
||||
/// implementation.
|
||||
/// </returns>
|
||||
bool HasNetworkTransformSyncComponents(GameObject gameObject);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 164159fb76970a34eba3ae3dc27956ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="IUxrNetworkSdk.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for classes that implement network functionality using an SDK.
|
||||
/// </summary>
|
||||
public interface IUxrNetworkSdk
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SDK implemented.
|
||||
/// </summary>
|
||||
string SdkName { get; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ed285b62fd157c429d99ee9550a53da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="IUxrNetworkVoiceImplementation.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Avatar;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for classes that implement network voice transmission functionality.
|
||||
/// </summary>
|
||||
public interface IUxrNetworkVoiceImplementation : IUxrNetworkSdk
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the compatible networking SDKs that this voice implementation can work with.
|
||||
/// </summary>
|
||||
IEnumerable<string> CompatibleNetworkSDKs { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Adds global support for the SDK if necessary, by adding required GameObjects and/or components to the
|
||||
/// <see cref="UxrNetworkManager" /> or the scene where it is located.
|
||||
/// </summary>
|
||||
/// <param name="networkingSdk">
|
||||
/// The networking SDK that is used. Since a voice implementation might support more than one
|
||||
/// networking SDK, this parameter tells which networking SDK is currently selected
|
||||
/// </param>
|
||||
/// <param name="networkManager">The network manager</param>
|
||||
/// <param name="newGameObjects">Returns a list of GameObjects that were created, if any</param>
|
||||
/// <param name="newComponents">Returns a list of components that were created, if any</param>
|
||||
void SetupGlobal(string networkingSdk, UxrNetworkManager networkManager, out List<GameObject> newGameObjects, out List<Component> newComponents);
|
||||
|
||||
/// <summary>
|
||||
/// Adds network voice functionality to an <see cref="UxrAvatar" />.
|
||||
/// </summary>
|
||||
/// <param name="networkingSdk">
|
||||
/// The networking SDK that is used. Since a voice implementation might support more than one
|
||||
/// networking SDK, this parameter tells which networking SDK is currently selected
|
||||
/// </param>
|
||||
/// <param name="avatar">The avatar to add voice functionality to</param>
|
||||
/// <param name="newGameObjects">Returns a list of GameObjects that were created, if any</param>
|
||||
/// <param name="newComponents">Returns a list of components that were created, if any</param>
|
||||
void SetupAvatar(string networkingSdk, UxrAvatar avatar, out List<GameObject> newGameObjects, out List<Component> newComponents);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0eefa7179ad574458773d592312027e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 736cd6300b11b9f49816b2ec38fc0b6f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ab76a24169150d4eb81eab0f4d6409a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75d39a48e6630cf46aafb8a01a2a1b39
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec5835b639bf45b4b95e91375e169613
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"reference": "GUID:7c88a4a7926ee5145ad2dfa06f454c67"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05341feb6ff954c46826779cf33502be
|
||||
AssemblyDefinitionReferenceImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fe2525dafa0a5246ad16c39abb7544a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 230855043fe1c234c97a316567668d55
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81f68be0652d69e4b82f09943363c604
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cd6d50a617b3d648b98f9d69964c52a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93acbfc66a5bc4e4aa5167ba1b96dc6b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65af5fca2bde7184d8ac076e99570fd0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee77d08b9af636c40bddc2a88b75faf8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7448805a74c81d845a2182d9108e915d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b07ac5288184fb0bc2103c93680de63
|
||||
timeCreated: 1691755462
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27c4fd979be9b29478efbe5c1fbf252a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54c7ff38565edab4eb178a859829182c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cdb9d15b9979084aa96d70e62a54c0a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 331b06803190dac428d6de4f688107e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2538d188e76399b42afad64b10e0d936
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75590be471323744dbd5f4e0ca0ce121
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afa911fc405ade641809d6f8374d8c50
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23dab6befd4969f428546efcc8d22e14
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a63d5e9d329bea40bfc993ee33ef280
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec413275618078540911aab8c69d90c1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrNetworkCapabilities.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates the different capabilities of a Networking SDK.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum UxrNetworkCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// The SDK has support for components that add network synchronization of <see cref="Transform" /> components.
|
||||
/// </summary>
|
||||
NetworkTransform = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The SDK has support for components that add network synchronization of <see cref="Rigidbody" /> components.
|
||||
/// </summary>
|
||||
NetworkRigidbody = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// The SDK has support for voice transmission.
|
||||
/// </summary>
|
||||
Voice = 1 << 16
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df0f21c79fef1ad43ba671fa48715a66
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,32 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrNetworkComponentReferences.Origin.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
public partial class UxrNetworkComponentReferences
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates where the components come from.
|
||||
/// </summary>
|
||||
public enum Origin
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The source of the components is a <see cref="UxrNetworkImplementation" />.
|
||||
/// </summary>
|
||||
Network,
|
||||
|
||||
/// <summary>
|
||||
/// The source of the components is a <see cref="UxrNetworkVoiceImplementation" />.
|
||||
/// </summary>
|
||||
NetworkVoice,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bacbae206eaa479b9977d49c88e57aba
|
||||
timeCreated: 1690896605
|
||||
@@ -0,0 +1,231 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrNetworkComponentReferences.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Components;
|
||||
using UltimateXR.Core.Settings;
|
||||
using UltimateXR.Extensions.System.Collections;
|
||||
using UltimateXR.Extensions.Unity;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that stores added network components from different SDKs so that they can be tracked more easily.
|
||||
/// They are mainly used by the UxrNetworkManagerEditor to delete added components.
|
||||
/// </summary>
|
||||
public partial class UxrNetworkComponentReferences : UxrComponent
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private Origin _origin;
|
||||
[SerializeField] private List<GameObject> _addedGameObjects;
|
||||
[SerializeField] private List<Component> _addedComponents;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the origin of the components.
|
||||
/// </summary>
|
||||
public Origin ComponentOrigin => _origin;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the networking GameObjects that were registered by this component so that they can be deleted later
|
||||
/// using <see cref="DestroyWithAddedComponents" />.
|
||||
/// </summary>
|
||||
public List<GameObject> AddedGameObjects
|
||||
{
|
||||
get => _addedGameObjects;
|
||||
set => _addedGameObjects = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the networking components that were registered by this component so that they can be deleted later
|
||||
/// using <see cref="DestroyWithAddedComponents" />.
|
||||
/// </summary>
|
||||
public List<Component> AddedComponents
|
||||
{
|
||||
get => _addedComponents;
|
||||
set => _addedComponents = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="UxrNetworkComponentReferences" /> on a given GameObject that will keep track of all added
|
||||
/// network components.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">Object to process</param>
|
||||
/// <param name="addedGameObjects">Network GameObjects that were added</param>
|
||||
/// <param name="addedComponents">Network components that were added</param>
|
||||
/// <param name="origin">The origin of the components</param>
|
||||
/// <returns>The created <see cref="UxrNetworkComponentReferences" /></returns>
|
||||
public static UxrNetworkComponentReferences RegisterNetworkComponents(GameObject gameObject, IEnumerable<GameObject> addedGameObjects, IEnumerable<Component> addedComponents, Origin origin)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
UxrNetworkComponentReferences networkComponents = Undo.AddComponent<UxrNetworkComponentReferences>(gameObject);
|
||||
SerializedObject serializedObject = new SerializedObject(networkComponents);
|
||||
|
||||
serializedObject.FindProperty(UxrConstants.Editor.PropertyObjectHideFlags).enumValueFlag = (int)ComponentHideFlags;
|
||||
serializedObject.FindProperty(nameof(_origin)).enumValueIndex = (int)origin;
|
||||
|
||||
SerializedProperty propAddedGameObjects = serializedObject.FindProperty(nameof(_addedGameObjects));
|
||||
SerializedProperty propAddedComponents = serializedObject.FindProperty(nameof(_addedComponents));
|
||||
propAddedGameObjects.arraySize = addedGameObjects.Count();
|
||||
propAddedComponents.arraySize = addedComponents.Count();
|
||||
|
||||
int i = 0;
|
||||
|
||||
foreach (GameObject go in addedGameObjects)
|
||||
{
|
||||
propAddedGameObjects.GetArrayElementAtIndex(i++).objectReferenceValue = go;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
|
||||
foreach (Component component in addedComponents)
|
||||
{
|
||||
propAddedComponents.GetArrayElementAtIndex(i++).objectReferenceValue = component;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
return networkComponents;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys all added network components in a GameObject and any of its children.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">GameObject to process</param>
|
||||
public static void DestroyNetworkComponentsRecursively(GameObject gameObject, Origin origin)
|
||||
{
|
||||
gameObject.GetComponentsInChildren<UxrNetworkComponentReferences>(true).Where(c => c._origin == origin).ForEach(c => c.DestroyWithAddedComponents());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys this component together with all the networking components that were added to this same GameObject.
|
||||
/// </summary>
|
||||
public void DestroyWithAddedComponents()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
if (_addedComponents != null)
|
||||
{
|
||||
foreach (Component c in _addedComponents.Where(c => c != null))
|
||||
{
|
||||
Destroy(c);
|
||||
}
|
||||
|
||||
_addedComponents.Clear();
|
||||
}
|
||||
|
||||
if (_addedGameObjects != null)
|
||||
{
|
||||
foreach (GameObject g in _addedGameObjects.Where(g => g != null))
|
||||
{
|
||||
Destroy(g);
|
||||
}
|
||||
|
||||
_addedGameObjects.Clear();
|
||||
}
|
||||
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
for (int i = 0; i < _addedComponents.Count; ++i)
|
||||
{
|
||||
if (_addedComponents[i] != null)
|
||||
{
|
||||
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
|
||||
{
|
||||
Debug.Log($"{UxrConstants.NetworkingModule} Destroying component {_addedComponents[i].GetType().Name} in {_addedComponents[i].GetPathUnderScene()}");
|
||||
}
|
||||
|
||||
Undo.DestroyObjectImmediate(_addedComponents[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
|
||||
{
|
||||
Debug.Log($"{UxrConstants.NetworkingModule} component {i} is null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _addedGameObjects.Count; ++i)
|
||||
{
|
||||
if (_addedGameObjects[i] != null)
|
||||
{
|
||||
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
|
||||
{
|
||||
Debug.Log($"{UxrConstants.NetworkingModule} Destroying GameObject {_addedGameObjects[i].GetPathUnderScene()}");
|
||||
}
|
||||
|
||||
Undo.DestroyObjectImmediate(_addedGameObjects[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
|
||||
{
|
||||
Debug.Log($"{UxrConstants.NetworkingModule} GameObject {i} is null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_addedGameObjects.Clear();
|
||||
_addedComponents.Clear();
|
||||
Undo.DestroyObjectImmediate(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the visibility.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
hideFlags = ComponentHideFlags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a workaround to make hideFlags work on prefabs. Maybe related:
|
||||
/// https://forum.unity.com/threads/is-it-impossible-to-save-component-hideflags-in-a-prefab.976974/
|
||||
/// </summary>
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
hideFlags = ComponentHideFlags;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the required hide flags for this component.
|
||||
/// </summary>
|
||||
private static HideFlags ComponentHideFlags => HideFlags.HideInInspector | HideFlags.HideInHierarchy | HideFlags.NotEditable;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf8199c5ecbb72e47a45ee11da635cfd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,98 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrNetworkImplementation.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Core.Components;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class required to add support for a network SDK.
|
||||
/// </summary>
|
||||
public abstract class UxrNetworkImplementation : UxrComponent, IUxrNetworkImplementation
|
||||
{
|
||||
#region Implicit IUxrNetworkImplementation
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool IsServer { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool IsClient { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract UxrNetworkCapabilities Capabilities { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string NetworkRigidbodyWarning => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void SetupGlobal(UxrNetworkManager networkManager, out List<GameObject> newGameObjects, out List<Component> newComponents);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void SetupAvatar(UxrAvatar avatar, out List<GameObject> newGameObjects, out List<Component> newComponents);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void SetupPostProcess(IEnumerable<UxrAvatar> avatarPrefabs);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IEnumerable<Behaviour> AddNetworkTransform(GameObject gameObject, bool worldSpace, UxrNetworkTransformFlags networkTransformFlags);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IEnumerable<Behaviour> AddNetworkRigidbody(GameObject gameObject, bool worldSpace, UxrNetworkRigidbodyFlags networkRigidbodyFlags);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void EnableNetworkTransform(GameObject gameObject, bool enable);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void EnableNetworkRigidbody(GameObject gameObject, bool enable);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool HasAuthority(GameObject gameObject);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void RequestAuthority(GameObject gameObject);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void CheckReassignGrabAuthority(GameObject gameObject);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool HasNetworkTransformSyncComponents(GameObject gameObject);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implicit IUxrNetworkSdk
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string SdkName { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to set up NetworkTransform 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, usually a NetworkTransform and NetworkObject or similar</returns>
|
||||
protected IEnumerable<Behaviour> SetupNetworkTransform(GameObject go, bool worldSpace, UxrNetworkTransformFlags flags)
|
||||
{
|
||||
if (go != null)
|
||||
{
|
||||
IEnumerable<Behaviour> newComponents = ((IUxrNetworkImplementation)this).AddNetworkTransform(go, worldSpace, flags);
|
||||
|
||||
foreach (Behaviour newBehaviour in newComponents)
|
||||
{
|
||||
yield return newBehaviour;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 454c7b233e0646c4390a75a3fa766750
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrNetworkManager.AvatarSetup.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using UltimateXR.Avatar;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
public partial class UxrNetworkManager
|
||||
{
|
||||
#region Private Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Stores an avatar setup entry. It contains the avatar prefab that was enabled for networking and the components that
|
||||
/// were added from an arbitrary SDK to add support.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
private class AvatarSetup
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private UxrAvatar _avatarPrefab;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the avatar component in the prefab.
|
||||
/// </summary>
|
||||
public UxrAvatar AvatarPrefab => _avatarPrefab;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a735904a76b40bd80c131a637c1640e
|
||||
timeCreated: 1689787855
|
||||
@@ -0,0 +1,38 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrNetworkManager.GrabbablePhysicsSetup.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Manipulation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
public partial class UxrNetworkManager
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Stores information of NetworkTransform components that were added to synchronize <see cref="UxrGrabbableObject" />
|
||||
/// with rigidbodies.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class GrabbablePhysicsSetup
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private string _sdkUsed;
|
||||
[SerializeField] private List<string> _processedScenePaths = new List<string>();
|
||||
[SerializeField] private bool _processedPathPrefabs;
|
||||
[SerializeField] private string _processedRootPath;
|
||||
[SerializeField] private List<UxrGrabbableObject> _processedPrefabs = new List<UxrGrabbableObject>();
|
||||
[SerializeField] private List<string> _debugInfoLines = new List<string>();
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fcb88dad5224458bfab6f69224088de
|
||||
timeCreated: 1690104296
|
||||
@@ -0,0 +1,327 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrNetworkManager.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Components.Singleton;
|
||||
using UltimateXR.Core.Settings;
|
||||
using UltimateXR.Extensions.Unity;
|
||||
using UltimateXR.Manipulation;
|
||||
using UnityEngine;
|
||||
|
||||
#pragma warning disable 414 // Unused values
|
||||
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Network Manager. This singleton will take care of all communication between the different users to keep them in
|
||||
/// sync.
|
||||
/// </summary>
|
||||
public partial class UxrNetworkManager : UxrSingleton<UxrNetworkManager>
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private UxrNetworkImplementation _networkImplementation;
|
||||
[SerializeField] private UxrNetworkVoiceImplementation _networkVoiceImplementation;
|
||||
[SerializeField] private bool _useSameSdkVoice = true;
|
||||
[SerializeField] private List<GameObject> _createdGlobalGameObjects = new List<GameObject>();
|
||||
[SerializeField] private List<Component> _createdGlobalComponents = new List<Component>();
|
||||
[SerializeField] private List<GameObject> _createdGlobalVoiceGameObjects = new List<GameObject>();
|
||||
[SerializeField] private List<Component> _createdGlobalVoiceComponents = new List<Component>();
|
||||
[SerializeField] private List<string> _createdGlobalGameObjectPaths = new List<string>();
|
||||
[SerializeField] private List<string> _createdGlobalComponentPaths = new List<string>();
|
||||
[SerializeField] private List<string> _createdGlobalVoiceGameObjectPaths = new List<string>();
|
||||
[SerializeField] private List<string> _createdGlobalVoiceComponentPaths = new List<string>();
|
||||
[SerializeField] private List<AvatarSetup> _registeredAvatars = new List<AvatarSetup>();
|
||||
[SerializeField] private bool _grabbablePhysicsAddProjectScenes;
|
||||
[SerializeField] private bool _grabbablePhysicsAddPathPrefabs;
|
||||
[SerializeField] private string _grabbablePhysicsPathRoot;
|
||||
[SerializeField] private bool _grabbablePhysicsOnlyLog;
|
||||
[SerializeField] private GrabbablePhysicsSetup _grabbablePhysicsSetupInfo = new GrabbablePhysicsSetup();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Event called right after the authority of the local user over a GameObject was requested.
|
||||
/// </summary>
|
||||
public event Action<GameObject> LocalAuthorityRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Event called right after a NetworkTransform component was enabled or disabled.
|
||||
/// </summary>
|
||||
public event Action<GameObject, bool> NetworkTransformEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Event called right after a NetworkRigidbody component was enabled or disabled.
|
||||
/// </summary>
|
||||
public event Action<GameObject, bool> NetworkRigidbodyEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether there is a network session active.
|
||||
/// </summary>
|
||||
public static bool IsSessionActive => IsServer || IsClient;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the current user is owner of the session. This can be either because there is no multiplayer session
|
||||
/// active or because the local user is the server.
|
||||
/// </summary>
|
||||
public static bool NoSessionOrSessionOwner => Instance == null || Instance._networkImplementation == null || Instance._networkImplementation.IsServer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether there is a network session active and the local user is the host (client and server at the same time).
|
||||
/// </summary>
|
||||
public static bool IsHost => IsServer && IsClient;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether there is a network session active and the local user is the server.
|
||||
/// </summary>
|
||||
public static bool IsServer => HasInstance && Instance._networkImplementation != null && Instance._networkImplementation.IsServer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether there is a network session active and the local user is the server.
|
||||
/// </summary>
|
||||
public static bool IsClient => HasInstance && Instance._networkImplementation != null && Instance._networkImplementation.IsClient;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the network implementation.
|
||||
/// </summary>
|
||||
public UxrNetworkImplementation NetworkImplementation => _networkImplementation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the network voice implementation.
|
||||
/// </summary>
|
||||
public UxrNetworkVoiceImplementation NetworkVoiceImplementation => _networkVoiceImplementation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the global GameObjects created to add support for the given network SDK.
|
||||
/// </summary>
|
||||
public IEnumerable<GameObject> CreatedGlobalGameObjects => _createdGlobalGameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the global Components created to add support for the given network SDK.
|
||||
/// </summary>
|
||||
public IEnumerable<Component> CreatedGlobalComponents => _createdGlobalComponents;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the global GameObjects created to add support for the given network voice SDK.
|
||||
/// </summary>
|
||||
public IEnumerable<GameObject> CreatedGlobalVoiceGameObjects => _createdGlobalVoiceGameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the global Components created to add support for the given network voice SDK.
|
||||
/// </summary>
|
||||
public IEnumerable<Component> CreatedGlobalVoiceComponents => _createdGlobalVoiceComponents;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the registered avatar prefabs.
|
||||
/// </summary>
|
||||
public IEnumerable<UxrAvatar> RegisteredAvatarPrefabs
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var avatar in _registeredAvatars)
|
||||
{
|
||||
yield return avatar.AvatarPrefab;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Requests authority of the local avatar over the given target object. The target object should have a valid network
|
||||
/// component using any given SDK, such as NetworkObject.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">GameObject with networking component</param>
|
||||
public void RequestAuthority(GameObject gameObject)
|
||||
{
|
||||
NetworkImplementation.RequestAuthority(gameObject);
|
||||
OnAuthorityRequested(gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables the NetworkTransform on the given object and all its children.
|
||||
/// </summary>
|
||||
/// <param name="target">Target</param>
|
||||
/// <param name="enabled">Enabled state</param>
|
||||
public void SetNetworkTransformEnabled(GameObject target, bool enabled)
|
||||
{
|
||||
NetworkImplementation.EnableNetworkTransform(gameObject, enabled);
|
||||
OnNetworkTransformEnabled(gameObject, enabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables the NetworkRigidbody on the given object and all its children.
|
||||
/// </summary>
|
||||
/// <param name="target">Target</param>
|
||||
/// <param name="enabled">Enabled state</param>
|
||||
public void SetNetworkRigidbodyEnabled(GameObject target, bool enabled)
|
||||
{
|
||||
NetworkImplementation.EnableNetworkRigidbody(target, enabled);
|
||||
OnNetworkRigidbodyEnabled(target, enabled);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to events.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
if (NetworkImplementation)
|
||||
{
|
||||
UxrGrabManager.Instance.ObjectGrabbed += GrabManager_ObjectGrabbed;
|
||||
UxrGrabManager.Instance.ObjectReleased += GrabManager_ObjectReleased;
|
||||
UxrGrabManager.Instance.ObjectPlaced += GrabManager_ObjectPlaced;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from events.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
if (NetworkImplementation)
|
||||
{
|
||||
UxrGrabManager.Instance.ObjectGrabbed -= GrabManager_ObjectGrabbed;
|
||||
UxrGrabManager.Instance.ObjectReleased -= GrabManager_ObjectReleased;
|
||||
UxrGrabManager.Instance.ObjectPlaced -= GrabManager_ObjectPlaced;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called when an object was grabbed. Checks if the grabbed object's authority or a NetworkRigidbody enabled
|
||||
/// state need to change. A free object that was grabbed will now belong to the grabbing avatar and have its
|
||||
/// NetworkRigidbody disabled because it is not driven by physics anymore, but by the avatar's tracked hand.
|
||||
/// </summary>
|
||||
/// <param name="sender">Event sender</param>
|
||||
/// <param name="e">Event parameters</param>
|
||||
private void GrabManager_ObjectGrabbed(object sender, UxrManipulationEventArgs e)
|
||||
{
|
||||
if (e.IsGrabbedStateChanged && e.GrabbableObject.RigidBodySource != null && e.GrabbableObject.RigidBodyDynamicOnRelease && e.GrabbableObject.CanUseRigidBody)
|
||||
{
|
||||
// From not grabbed to grabbed. Switch authority to local avatar if it's the one that grabbed it.
|
||||
|
||||
if (e.Grabber != null && e.Grabber.Avatar == UxrAvatar.LocalAvatar && !NetworkImplementation.HasAuthority(e.GrabbableObject.gameObject))
|
||||
{
|
||||
RequestAuthority(e.GrabbableObject.gameObject);
|
||||
}
|
||||
|
||||
// Disable network rigidbody because avatar will drive the grabbed object's transform.
|
||||
SetNetworkRigidbodyEnabled(e.GrabbableObject.gameObject, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an object was released. Checks if the avatar that had the authority needs to give it to another avatar
|
||||
/// that keeps the grab or if the object was thrown and the NetworkRigidbody needs to be enabled.
|
||||
/// </summary>
|
||||
/// <param name="sender">Event sender</param>
|
||||
/// <param name="e">Event parameters</param>
|
||||
private void GrabManager_ObjectReleased(object sender, UxrManipulationEventArgs e)
|
||||
{
|
||||
if (e.GrabbableObject.RigidBodySource != null && e.GrabbableObject.RigidBodyDynamicOnRelease && e.GrabbableObject.CanUseRigidBody)
|
||||
{
|
||||
if (e.IsGrabbedStateChanged)
|
||||
{
|
||||
// From grabbed to released. Enable network rigidbody.
|
||||
SetNetworkRigidbodyEnabled(e.GrabbableObject.gameObject, true);
|
||||
}
|
||||
else if (e.Grabber != null && !UxrGrabManager.Instance.IsBeingGrabbedBy(e.GrabbableObject, e.Grabber.Avatar))
|
||||
{
|
||||
// An avatar released its last grip, check if we need to reassign the network authority
|
||||
NetworkImplementation.CheckReassignGrabAuthority(e.GrabbableObject.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an object was placed to disable the NetworkRigidbody. Checks if the place call comes from a user Place
|
||||
/// instead of a manipulation event, because manipulation events come from a grab where the rigidbody was already
|
||||
/// disabled at the time of the grab.
|
||||
/// </summary>
|
||||
/// <param name="sender">Event sender</param>
|
||||
/// <param name="e">Event parameters</param>
|
||||
private void GrabManager_ObjectPlaced(object sender, UxrManipulationEventArgs e)
|
||||
{
|
||||
if (e.GrabbableObject.RigidBodySource != null && e.GrabbableObject.RigidBodyDynamicOnRelease && e.GrabbableObject.CanUseRigidBody)
|
||||
{
|
||||
if (e.Grabber == null)
|
||||
{
|
||||
// Source is a "manual" place
|
||||
SetNetworkRigidbodyEnabled(e.GrabbableObject.gameObject, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Trigger Methods
|
||||
|
||||
/// <summary>
|
||||
/// Event trigger for <see cref="NetworkRigidbodyEnabled" />.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The GameObject with the NetworkRigidbody</param>
|
||||
/// <param name="enabled">Whether the component was enabled or disabled</param>
|
||||
private void OnNetworkRigidbodyEnabled(GameObject gameObject, bool enabled)
|
||||
{
|
||||
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
|
||||
{
|
||||
Debug.Log($"{UxrConstants.NetworkingModule} NetworkRigidbody {(enabled ? "enabled" : "disabled")} on GameObject {gameObject.GetPathUnderScene()}.");
|
||||
}
|
||||
|
||||
NetworkRigidbodyEnabled?.Invoke(gameObject, enabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event trigger for <see cref="NetworkTransformEnabled" />.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The GameObject with the NetworkRigidbody</param>
|
||||
/// <param name="enabled">Whether the component was enabled or disabled</param>
|
||||
private void OnNetworkTransformEnabled(GameObject gameObject, bool enabled)
|
||||
{
|
||||
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
|
||||
{
|
||||
Debug.Log($"{UxrConstants.NetworkingModule} NetworkTransform {(enabled ? "enabled" : "disabled")} on GameObject {gameObject.GetPathUnderScene()}.");
|
||||
}
|
||||
|
||||
NetworkTransformEnabled?.Invoke(gameObject, enabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event trigger for <see cref="LocalAuthorityRequested" />.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The GameObject to get the authority for</param>
|
||||
private void OnAuthorityRequested(GameObject gameObject)
|
||||
{
|
||||
if (UxrGlobalSettings.Instance.LogLevelNetworking >= UxrLogLevel.Relevant)
|
||||
{
|
||||
Debug.Log($"{UxrConstants.NetworkingModule} Authority requested of local avatar over GameObject {gameObject.GetPathUnderScene()}.");
|
||||
}
|
||||
|
||||
LocalAuthorityRequested?.Invoke(gameObject);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore 414
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74f1ec7cdfc6494489e3e3440fa7e4b7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrNetworkRigidbodyFlags.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the different options for rigidbody network synchronization.
|
||||
/// </summary>
|
||||
public enum UxrNetworkRigidbodyFlags
|
||||
{
|
||||
None = 0,
|
||||
All = None // Leave for future implementations
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e88866ca230c53d469c62d77fa9d0cb9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrNetworkTransformFlags.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the different elements that can be synchronized by a network transform component.
|
||||
/// </summary>
|
||||
public enum UxrNetworkTransformFlags
|
||||
{
|
||||
None = 0,
|
||||
|
||||
// Flags for separate transform components:
|
||||
|
||||
PositionX = 1 << 0,
|
||||
PositionY = 1 << 1,
|
||||
PositionZ = 1 << 2,
|
||||
RotationX = 1 << 3,
|
||||
RotationY = 1 << 4,
|
||||
RotationZ = 1 << 5,
|
||||
ScaleX = 1 << 6,
|
||||
ScaleY = 1 << 7,
|
||||
ScaleZ = 1 << 8,
|
||||
|
||||
/// <summary>
|
||||
/// When set, it tells that the transform is a child of another transform that is also tracked. This usually means that
|
||||
/// it doesn't require a NetworkObject, just a NetworkTransform.
|
||||
/// </summary>
|
||||
ChildTransform = 1 << 31,
|
||||
|
||||
// Composite flags for root transforms:
|
||||
|
||||
Position = PositionX | PositionY | PositionZ,
|
||||
Rotation = RotationX | RotationY | RotationZ,
|
||||
Scale = ScaleX | ScaleY | ScaleZ,
|
||||
PositionAndRotation = Position | Rotation,
|
||||
All = Position | Rotation | Scale,
|
||||
|
||||
// Composite flags for transforms that have another network transform above:
|
||||
|
||||
ChildRotation = ChildTransform | RotationX | RotationY | RotationZ,
|
||||
ChildScale = ChildTransform | ScaleX | ScaleY | ScaleZ,
|
||||
ChildPositionAndRotation = ChildTransform | Position | Rotation,
|
||||
ChildAll = ChildTransform | Position | Rotation | Scale
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b55ab3ac07d503247bab52706e706458
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrNetworkVoiceImplementation.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Core.Components;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class required to add support for a network voice communication SDK.
|
||||
/// </summary>
|
||||
public abstract class UxrNetworkVoiceImplementation : UxrComponent, IUxrNetworkVoiceImplementation
|
||||
{
|
||||
#region Implicit IUxrNetworkSdk
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string SdkName { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implicit IUxrNetworkVoiceImplementation
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IEnumerable<string> CompatibleNetworkSDKs { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void SetupGlobal(string networkingSdk, UxrNetworkManager networkManager, out List<GameObject> newGameObjects, out List<Component> newComponents);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void SetupAvatar(string networkingSdk, UxrAvatar avatar, out List<GameObject> newGameObjects, out List<Component> newComponents);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8f3ca6da69343f40a5656486c6670de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user