Added Unity project files
This commit is contained in:
@ -0,0 +1,495 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Components;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon;
|
||||
using VRC.Utility;
|
||||
using Object = UnityEngine.Object;
|
||||
using VRCStation = VRC.SDK3.Components.VRCStation;
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
using VRC.SDK3.ClientSim.Persistence;
|
||||
using VRC.SDKBase.Network;
|
||||
#endif
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// ClientSimPlayer is the container class for all the player related systems.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When the user clicks on the player in the inspector, this class also allows you to edit settings,
|
||||
/// such as locomotion and audio values.
|
||||
/// </remarks>
|
||||
// TODO split into local and remote versions
|
||||
[AddComponentMenu("")]
|
||||
[SelectionBase]
|
||||
public class ClientSimPlayer : ClientSimBehaviour, IClientSimPlayerApiProvider
|
||||
{
|
||||
[SerializeField]
|
||||
private ClientSimPlayerController playerController;
|
||||
[SerializeField]
|
||||
private ClientSimPlayerStationManager stationManager;
|
||||
[SerializeField]
|
||||
private ClientSimPlayerRaycaster playerRaycaster;
|
||||
[SerializeField]
|
||||
private ClientSimTrackingProviderBase playerTrackingData;
|
||||
[SerializeField]
|
||||
private ClientSimPlayerAvatarManager playerAvatar;
|
||||
[SerializeField]
|
||||
private ClientSimReticle reticle;
|
||||
|
||||
private ClientSimCombatSystemHelper _combatSystemHelper;
|
||||
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private IClientSimPlayerManager _playerManager;
|
||||
private ClientSimInteractManager _interactManager;
|
||||
private IClientSimSceneManager _sceneManager;
|
||||
private IClientSimProxyObjectProvider _proxyProvider;
|
||||
private IClientSimUdonEventSender _udonEventSender;
|
||||
private ClientSimSettings _settings;
|
||||
|
||||
public VRCPlayerApi Player { get; private set; }
|
||||
public bool IsUserVR { get; private set; }
|
||||
public bool isInstanceOwner;
|
||||
public bool isSuspended;
|
||||
public bool isVRCPlus;
|
||||
|
||||
// Public to allow users to edit values in editor.
|
||||
public ClientSimPlayerLocomotionData locomotionData = new();
|
||||
public ClientSimPlayerPickupData pickupData = new();
|
||||
public ClientSimPlayerAudioData audioData = new();
|
||||
public ClientSimPlayerTagsData tagData = new();
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
public GameObject[] PlayerPersistenceObjects = Array.Empty<GameObject>();
|
||||
public GameObject[] PlayerPersistenceRootObjects = Array.Empty<GameObject>();
|
||||
public ClientSimPlayerDataStorage PlayerDataPrefab;
|
||||
internal ClientSimPlayerDataStorage PlayerDataObject;
|
||||
internal ClientSimPlayerObjectStorage PlayerObjectData;
|
||||
private ClientSimPlayerRestoredStatus playerRestoredStatus = new();
|
||||
#endif
|
||||
|
||||
public void SetPlayer(VRCPlayerApi player)
|
||||
{
|
||||
Player = player;
|
||||
}
|
||||
|
||||
public void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimInput input,
|
||||
ClientSimSettings settings,
|
||||
IClientSimHighlightManager highlightManager,
|
||||
IClientSimTooltipManager tooltipManager,
|
||||
IClientSimInteractiveLayerProvider interactiveLayerProvider,
|
||||
IClientSimMousePositionProvider mousePositionProvider,
|
||||
IClientSimSceneManager sceneManager,
|
||||
IClientSimProxyObjectProvider proxyProvider,
|
||||
IClientSimUdonEventSender udonEventSender,
|
||||
IClientSimBlacklistManager blacklistManager,
|
||||
IClientSimUdonManager udonManager,
|
||||
IClientSimSyncedObjectManager syncedObjectManager,
|
||||
IClientSimPlayerManager playerManager,
|
||||
IClientSimPlayerHeightManager heightManager)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_settings = settings;
|
||||
_sceneManager = sceneManager;
|
||||
_proxyProvider = proxyProvider;
|
||||
_playerManager = playerManager;
|
||||
_udonEventSender = udonEventSender;
|
||||
|
||||
// TODO take settings and spawn desktop vs vr tracking data
|
||||
playerTrackingData.Initialize(eventDispatcher, input, settings, heightManager);
|
||||
IsUserVR = playerTrackingData.IsVR();
|
||||
|
||||
_interactManager = new ClientSimInteractManager(playerTrackingData, pickupData);
|
||||
|
||||
playerRaycaster.Initialize(
|
||||
eventDispatcher,
|
||||
input,
|
||||
this,
|
||||
pickupData,
|
||||
highlightManager,
|
||||
tooltipManager,
|
||||
interactiveLayerProvider,
|
||||
playerTrackingData,
|
||||
mousePositionProvider,
|
||||
_interactManager,
|
||||
playerTrackingData,
|
||||
stationManager);
|
||||
|
||||
stationManager.Initialize(eventDispatcher, this);
|
||||
|
||||
playerController.Initialize(
|
||||
eventDispatcher,
|
||||
input,
|
||||
this,
|
||||
locomotionData,
|
||||
sceneManager,
|
||||
proxyProvider,
|
||||
playerTrackingData,
|
||||
stationManager);
|
||||
|
||||
playerAvatar.Initialize(eventDispatcher);
|
||||
|
||||
if (settings.spawnPlayer)
|
||||
{
|
||||
if (!IsUserVR)
|
||||
{
|
||||
reticle.Initialize(_eventDispatcher, _settings, mousePositionProvider);
|
||||
}
|
||||
// TODO initialize VR raycast visualizers
|
||||
}
|
||||
}
|
||||
|
||||
public void SetEventDispatcher(IClientSimEventDispatcher eventDispatcher)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
}
|
||||
|
||||
public void InitializeCombat()
|
||||
{
|
||||
if (_combatSystemHelper != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_combatSystemHelper = gameObject.AddComponent<ClientSimCombatSystemHelper>();
|
||||
_combatSystemHelper.Initialize(Player, _eventDispatcher, _proxyProvider, playerController);
|
||||
}
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
|
||||
internal void SetupPlayerPersistence(IClientSimEventDispatcher eventDispatcher, IClientSimUdonEventSender udonEventSender, IClientSimBlacklistManager blacklistManager, IClientSimUdonManager udonManager, IClientSimSyncedObjectManager syncedObjectManager,IClientSimPlayerManager playerManager)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_udonEventSender = udonEventSender;
|
||||
|
||||
eventDispatcher.Subscribe<ClientSimOnPlayerDataDecodedEvent>(OnPlayerDataDecoded);
|
||||
eventDispatcher.Subscribe<ClientSimOnPlayerObjectsDecodedEvent>(OnPlayerObjectsDecoded);
|
||||
|
||||
GameObject dataObject = Instantiate(PlayerDataPrefab.gameObject);
|
||||
int playerId = playerManager.GetPlayerID(Player);
|
||||
|
||||
PlayerDataObject = dataObject.GetComponent<ClientSimPlayerDataStorage>();
|
||||
PlayerDataObject.Init(Player, udonEventSender, eventDispatcher);
|
||||
PlayerDataObject.gameObject.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector;
|
||||
|
||||
PlayerObjectData = dataObject.GetComponent<ClientSimPlayerObjectStorage>();
|
||||
PlayerObjectData.Init(Player, udonEventSender, eventDispatcher);
|
||||
|
||||
blacklistManager.AddObjectAndChildrenToBlackList(PlayerDataObject.gameObject);
|
||||
|
||||
// spawning PlayerObjects
|
||||
VRCPlayerObject[] playerObjects = ClientSimNetworkingUtilities.GetPlayerObjectList();
|
||||
|
||||
VRCSceneDescriptor sdk3Descriptor = (VRCSceneDescriptor)VRC_SceneDescriptor.Instance;
|
||||
|
||||
List<GameObject> playerObjectInstances = new List<GameObject>();
|
||||
|
||||
int baseId = (playerId * ClientSimNetworkingUtilities.MaxID) +
|
||||
ClientSimNetworkingUtilities.FirstPlayerPersistenceID;
|
||||
|
||||
for (int i = 0; i < playerObjects.Length; i++)
|
||||
{
|
||||
VRCPlayerObject playerObject = playerObjects[i];
|
||||
GameObject playerObjectGameObject = playerObject.gameObject;
|
||||
Transform playerObjectTransform = playerObjectGameObject.transform;
|
||||
playerObjectGameObject.SetActive(false);
|
||||
|
||||
GameObject instance = Object.Instantiate(playerObjectGameObject, playerObjectTransform.parent, true);
|
||||
instance.transform.localScale = playerObjectTransform.localScale;
|
||||
instance.transform.localPosition = playerObjectTransform.localPosition;
|
||||
instance.transform.localRotation = playerObjectTransform.localRotation;
|
||||
|
||||
playerObjectInstances.Add(instance);
|
||||
|
||||
instance.transform.name = playerObjectTransform.name + " [" + playerId + "]";
|
||||
|
||||
INetworkID[] networkIds = instance.GetComponentsInChildren<INetworkID>(true);
|
||||
|
||||
foreach (INetworkID networkId in networkIds)
|
||||
{
|
||||
Component component = networkId as Component;
|
||||
string path = component.transform.Path(instance.transform);
|
||||
|
||||
GameObject ppOriginal = playerObjectTransform.Find(path).gameObject;
|
||||
|
||||
int indexNetworkObject = sdk3Descriptor.NetworkIDCollection.FindIndex((x) => x.gameObject == ppOriginal);
|
||||
|
||||
if (indexNetworkObject == -1)
|
||||
{
|
||||
this.LogError($"Failed to locate player persistence view ID for {playerObjectTransform.name}/{path}");
|
||||
continue;
|
||||
}
|
||||
|
||||
NetworkIDPair networkIdPair = sdk3Descriptor.NetworkIDCollection[indexNetworkObject];
|
||||
|
||||
int viewId = ClientSimNetworkingUtilities.FlattenPlayerViewId(networkIdPair.ID) + baseId;
|
||||
|
||||
ConfigureObject(component.gameObject, viewId, playerId, networkId, null, udonManager, syncedObjectManager);
|
||||
|
||||
viewId++;
|
||||
|
||||
if (viewId >= (playerId * ClientSimNetworkingUtilities.MaxID) +ClientSimNetworkingUtilities.MaxPlayerPersistenceID)
|
||||
{
|
||||
this.LogError("Ran out of player persistence view IDs.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (VRCStation station in instance.GetComponentsInChildren<VRCStation>()){
|
||||
ClientSimStationHelper.InitializeStations(station);
|
||||
}
|
||||
|
||||
instance.SetActive(true);
|
||||
}
|
||||
|
||||
IEnumerable<GameObject> withChildren = playerObjectInstances.SelectMany(obj => obj.GetComponentsInChildren<Transform>(true)).Select(t => t.gameObject);
|
||||
PlayerPersistenceObjects = withChildren.ToArray();
|
||||
PlayerPersistenceRootObjects = playerObjectInstances.ToArray();
|
||||
}
|
||||
|
||||
private static void ConfigureObject(
|
||||
GameObject obj,
|
||||
int viewId,
|
||||
int playerId,
|
||||
INetworkID networkId,
|
||||
string objectName = null,
|
||||
IClientSimUdonManager udonManager = null,
|
||||
IClientSimSyncedObjectManager syncedObjectManager = null)
|
||||
{
|
||||
VRCEnablePersistence enablePersistence = obj.GetComponentInParent<VRCEnablePersistence>(true);
|
||||
bool SavePersistence = enablePersistence != null;
|
||||
|
||||
if (!obj.TryGetComponent(out ClientSimNetworkingView MainView))
|
||||
{
|
||||
MainView = obj.AddComponent<ClientSimNetworkingView>();
|
||||
}
|
||||
|
||||
MainView.SetNetworkId(viewId);
|
||||
MainView.SetPlayerId(playerId);
|
||||
MainView.SetPersist(SavePersistence);
|
||||
|
||||
InitilizeNetworkHolder(obj, MainView);
|
||||
|
||||
switch (networkId)
|
||||
{
|
||||
case VRCObjectPool vrcop:
|
||||
{
|
||||
if (vrcop)
|
||||
{
|
||||
vrcop.NetworkConfigure();
|
||||
syncedObjectManager.InitializeObjectPool(vrcop);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VRCObjectSync vrcos:
|
||||
{
|
||||
if (vrcos)
|
||||
{
|
||||
vrcos.NetworkConfigure();
|
||||
// the player initialization happens before the sdk has set the synced object callbacks so we need to do it here
|
||||
if(VRCObjectSync.OnAwake == null)
|
||||
syncedObjectManager.InitializeObjectSync(vrcos);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VRC.SDK3.Network.VRCNetworkBehaviour vrcnb3:
|
||||
{
|
||||
if (vrcnb3)
|
||||
{
|
||||
vrcnb3.NetworkConfigure();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UdonBehaviour udon:
|
||||
{
|
||||
if (udon)
|
||||
{
|
||||
|
||||
if (UdonManager.Instance.HasLoaded)
|
||||
{
|
||||
udon.IsNetworkingSupported = true;
|
||||
UdonManager.Instance.RegisterUdonBehaviour(udon);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case VRCPickup vrcPickup:
|
||||
{
|
||||
if (vrcPickup)
|
||||
{
|
||||
ClientSimPickupHelper.InitializePickup(vrcPickup);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IClientSimSyncable[] syncables = obj.GetComponentsInChildren<IClientSimSyncable>(true);
|
||||
foreach (IClientSimSyncable syncable in syncables)
|
||||
{
|
||||
syncable.SetOwner(playerId);
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitilizeNetworkHolder(GameObject obj, ClientSimNetworkingView mainView)
|
||||
{
|
||||
if (!obj.TryGetComponent<ClientSimNetworkIdHolder>(out var networkIdHolder))
|
||||
{
|
||||
networkIdHolder = obj.AddComponent<ClientSimNetworkIdHolder>();
|
||||
networkIdHolder.SetNetworkView(mainView);
|
||||
networkIdHolder.SetNetworkComponents();
|
||||
|
||||
mainView.AddNetworkedObject(networkIdHolder);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePlayerPersistenceObjects()
|
||||
{
|
||||
for (int i = 0; i < PlayerPersistenceObjects.Length; i++)
|
||||
{
|
||||
Object.Destroy(PlayerPersistenceObjects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckPlayerRestored()
|
||||
{
|
||||
if (playerRestoredStatus.HasDecodedPlayerData &&
|
||||
playerRestoredStatus.HasDecodedPlayerObjects &&
|
||||
!playerRestoredStatus.PlayerRestored)
|
||||
{
|
||||
playerRestoredStatus.PlayerRestored = true;
|
||||
_udonEventSender.RunEvent(UdonManager.UDON_EVENT_ONPLAYERRESTORED, ("player", Player));
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPlayerRestoredEvent
|
||||
{
|
||||
player = Player
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerDataDecoded(ClientSimOnPlayerDataDecodedEvent payload)
|
||||
{
|
||||
if (payload.player.playerId != Player.playerId) return;
|
||||
|
||||
playerRestoredStatus.HasDecodedPlayerData = true;
|
||||
CheckPlayerRestored();
|
||||
}
|
||||
|
||||
private void OnPlayerObjectsDecoded(ClientSimOnPlayerObjectsDecodedEvent payload)
|
||||
{
|
||||
if (payload.player.playerId != Player.playerId) return;
|
||||
|
||||
playerRestoredStatus.HasDecodedPlayerObjects = true;
|
||||
CheckPlayerRestored();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_eventDispatcher?.Unsubscribe<ClientSimOnPlayerDataDecodedEvent>(OnPlayerDataDecoded);
|
||||
_eventDispatcher?.Unsubscribe<ClientSimOnPlayerObjectsDecodedEvent>(OnPlayerObjectsDecoded);
|
||||
|
||||
RemovePlayerPersistenceObjects();
|
||||
}
|
||||
|
||||
public void EnablePlayerObjects()
|
||||
{
|
||||
for (int i = 0; i < PlayerPersistenceRootObjects.Length; i++)
|
||||
{
|
||||
PlayerPersistenceRootObjects[i].SetActive(true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (!Player.isLocal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Camera playerCamera = playerTrackingData.GetCamera();
|
||||
if (playerCamera != null)
|
||||
{
|
||||
_sceneManager.SetupCamera(playerCamera);
|
||||
}
|
||||
}
|
||||
|
||||
public void EnablePlayer(Transform spawnPoint, float spawnRadius)
|
||||
{
|
||||
Vector3 position = ClientSimPlayerSpawner.GetRandomPositionAroundSpawn(spawnPoint.position, spawnRadius);
|
||||
playerController.Teleport(position, spawnPoint.rotation, false);
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public ClientSimPlayerController GetPlayerController()
|
||||
{
|
||||
return playerController;
|
||||
}
|
||||
|
||||
public IClientSimPlayerCameraProvider GetCameraProvider()
|
||||
{
|
||||
return playerTrackingData;
|
||||
}
|
||||
|
||||
public IClientSimTrackingProvider GetTrackingProvider()
|
||||
{
|
||||
return playerTrackingData;
|
||||
}
|
||||
|
||||
public IClientSimPlayerStationManager GetStationHandler()
|
||||
{
|
||||
return stationManager;
|
||||
}
|
||||
|
||||
public IClientSimPlayerAvatarDataProvider GetAvatarDataProvider()
|
||||
{
|
||||
return playerAvatar;
|
||||
}
|
||||
|
||||
public ClientSimCombatSystemHelper GetCombatHelper()
|
||||
{
|
||||
return _combatSystemHelper;
|
||||
}
|
||||
|
||||
public Vector3 GetPosition()
|
||||
{
|
||||
if (playerController != null)
|
||||
{
|
||||
return playerController.GetPosition();
|
||||
}
|
||||
return transform.position;
|
||||
}
|
||||
|
||||
public Quaternion GetRotation()
|
||||
{
|
||||
if (playerController != null)
|
||||
{
|
||||
return playerController.GetRotation();
|
||||
}
|
||||
return transform.rotation;
|
||||
}
|
||||
|
||||
public void SimulateVRCPlusGift()
|
||||
{
|
||||
_eventDispatcher?.SendEvent(new ClientSimOnVRCPlusMassGift
|
||||
{
|
||||
gifter = Player,
|
||||
numGifts = 10,
|
||||
});
|
||||
}
|
||||
|
||||
private class ClientSimPlayerRestoredStatus
|
||||
{
|
||||
public bool HasDecodedPlayerData = false;
|
||||
public bool HasDecodedPlayerObjects = false;
|
||||
public bool PlayerRestored = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0d02ab56371a09488276de93c16f8de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,76 @@
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
// Listens to Events:
|
||||
// - ClientSimOnTrackingScaleUpdateEvent
|
||||
// TODO split into local and remote versions
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimPlayerAvatarManager : ClientSimBehaviour, IClientSimPlayerAvatarDataProvider
|
||||
{
|
||||
[SerializeField]
|
||||
private Animator avatarAnimator;
|
||||
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
|
||||
// TODO initialize with option for Generic or Humanoid
|
||||
// TODO better initialization options for Local vs Remote
|
||||
public void Initialize(IClientSimEventDispatcher eventDispatcher)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
|
||||
_eventDispatcher.Subscribe<ClientSimOnTrackingScaleUpdateEvent>(OnTrackingScaleUpdate);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_eventDispatcher?.Unsubscribe<ClientSimOnTrackingScaleUpdateEvent>(OnTrackingScaleUpdate);
|
||||
}
|
||||
|
||||
#region ClientSim Events
|
||||
|
||||
private void OnTrackingScaleUpdate(ClientSimOnTrackingScaleUpdateEvent trackingEvent)
|
||||
{
|
||||
transform.localScale = trackingEvent.trackingScale * Vector3.one;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IClientSimPlayerAvatarDataProvider
|
||||
|
||||
public Transform GetBoneTransform(HumanBodyBones bone)
|
||||
{
|
||||
if (avatarAnimator == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return avatarAnimator.GetBoneTransform(bone);
|
||||
}
|
||||
|
||||
public Quaternion GetBoneRotation(HumanBodyBones bone)
|
||||
{
|
||||
if (avatarAnimator == null)
|
||||
{
|
||||
return Quaternion.identity;
|
||||
}
|
||||
|
||||
Transform boneTransform = GetBoneTransform(bone);
|
||||
return boneTransform ? boneTransform.rotation : Quaternion.identity;
|
||||
}
|
||||
|
||||
public Vector3 GetBonePosition(HumanBodyBones bone)
|
||||
{
|
||||
if (avatarAnimator == null)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
Transform boneTransform = GetBoneTransform(bone);
|
||||
return boneTransform ? boneTransform.position : Vector3.zero;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac1020b347eeb4e45afabd4d605aa3ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,481 @@
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon.Common;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
// Sends Events:
|
||||
// - ClientSimOnPlayerTeleportedEvent
|
||||
// - ClientSimOnPlayerRespawnEvent
|
||||
// - ClientSimOnPlayerMovedEvent
|
||||
// Listens to Events:
|
||||
// - ClientSimMenuStateChangedEvent
|
||||
// - ClientSimMenuRespawnClickedEvent
|
||||
// - ClientSimMouseReleasedEvent
|
||||
// - ClientSimPlayerDeathStatusChangedEvent
|
||||
// - ClientSimOnTrackingScaleUpdateEvent
|
||||
// Listens to Input Events:
|
||||
// - Jump
|
||||
// - Run
|
||||
[AddComponentMenu("")]
|
||||
[DefaultExecutionOrder(-3000)] // Update before player raycasting
|
||||
public class ClientSimPlayerController : ClientSimBehaviour, IDisposable
|
||||
{
|
||||
private const float CROUCH_SPEED_MULTIPLIER = 0.35f;
|
||||
private const float PRONE_SPEED_MULTIPLIER = 0.15f;
|
||||
|
||||
private const float STICK_TO_GROUND_FORCE = 2f;
|
||||
private const float RATE_OF_AIR_ACCELERATION = 5f;
|
||||
|
||||
private IClientSimPlayerStationManager _stationManager;
|
||||
private IClientSimPlayerLocomotionData _playerLocomotionData;
|
||||
private IClientSimPlayerApiProvider _playerApi;
|
||||
private IClientSimSceneManager _sceneManager;
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private IClientSimInput _input;
|
||||
private IClientSimTrackingProvider _trackingProvider;
|
||||
|
||||
private CharacterController _characterController;
|
||||
private Transform _cameraProxyObject;
|
||||
|
||||
private bool _isDead;
|
||||
private bool _isWalking = true; // Player defaults to walking
|
||||
private bool _jump;
|
||||
|
||||
private Vector2 _prevInput;
|
||||
// Check if the directionality changed to apply "stutter stepping" for legacy locomotion.
|
||||
private bool _directionChanged;
|
||||
|
||||
private bool _velSet;
|
||||
// TODO fix handling of SetVelocity as retaining the velocity causes strange bugs due to ignoring collisions
|
||||
// that normally would stop the player.
|
||||
private Vector3 _playerRetainedVelocity;
|
||||
|
||||
private bool _menuIsOpen;
|
||||
private bool _mouseReleased;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
_characterController = GetComponent<CharacterController>();
|
||||
}
|
||||
|
||||
public void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimInput input,
|
||||
IClientSimPlayerApiProvider playerApiProvider,
|
||||
IClientSimPlayerLocomotionData locomotionData,
|
||||
IClientSimSceneManager sceneManager,
|
||||
IClientSimProxyObjectProvider proxyProvider,
|
||||
IClientSimTrackingProvider trackingProvider,
|
||||
IClientSimPlayerStationManager stationManager)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_input = input;
|
||||
_playerApi = playerApiProvider;
|
||||
_playerLocomotionData = locomotionData;
|
||||
_sceneManager = sceneManager;
|
||||
_trackingProvider = trackingProvider;
|
||||
_stationManager = stationManager;
|
||||
|
||||
_cameraProxyObject = proxyProvider.CameraProxy().transform;
|
||||
|
||||
Subscribe();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
NotifyPlayerMoved();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void Subscribe()
|
||||
{
|
||||
// Input will be null with incorrect Unity input project settings.
|
||||
_input?.SubscribeJump(JumpInput);
|
||||
_input?.SubscribeRun(RunInput);
|
||||
|
||||
_eventDispatcher.Subscribe<ClientSimMenuStateChangedEvent>(SetMenuOpen);
|
||||
_eventDispatcher.Subscribe<ClientSimMenuRespawnClickedEvent>(MenuRespawnEvent);
|
||||
_eventDispatcher.Subscribe<ClientSimMouseReleasedEvent>(MouseReleasedEvent);
|
||||
_eventDispatcher.Subscribe<ClientSimPlayerDeathStatusChangedEvent>(CombatStatusEvent);
|
||||
_eventDispatcher.Subscribe<ClientSimOnTrackingScaleUpdateEvent>(OnTrackingScaleUpdated);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_input?.UnsubscribeJump(JumpInput);
|
||||
_input?.UnsubscribeRun(RunInput);
|
||||
|
||||
_eventDispatcher.Unsubscribe<ClientSimMenuStateChangedEvent>(SetMenuOpen);
|
||||
_eventDispatcher.Unsubscribe<ClientSimMenuRespawnClickedEvent>(MenuRespawnEvent);
|
||||
_eventDispatcher.Unsubscribe<ClientSimMouseReleasedEvent>(MouseReleasedEvent);
|
||||
_eventDispatcher.Unsubscribe<ClientSimPlayerDeathStatusChangedEvent>(CombatStatusEvent);
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnTrackingScaleUpdateEvent>(OnTrackingScaleUpdated);
|
||||
}
|
||||
|
||||
#region Input Events
|
||||
|
||||
private void JumpInput(bool value, HandType hand)
|
||||
{
|
||||
// Only handle on down, and not on release.
|
||||
if (!value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_jump && _characterController.isGrounded && _playerLocomotionData.GetJump() > 0)
|
||||
{
|
||||
_jump = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void RunInput(bool value)
|
||||
{
|
||||
_isWalking = !value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ClientSim Events
|
||||
|
||||
private void SetMenuOpen(ClientSimMenuStateChangedEvent stateChangedEvent)
|
||||
{
|
||||
_menuIsOpen = stateChangedEvent.isMenuOpen;
|
||||
}
|
||||
|
||||
private void MenuRespawnEvent(ClientSimMenuRespawnClickedEvent stateChangedEvent)
|
||||
{
|
||||
Respawn();
|
||||
}
|
||||
|
||||
private void MouseReleasedEvent(ClientSimMouseReleasedEvent mouseReleasedEvent)
|
||||
{
|
||||
_mouseReleased = mouseReleasedEvent.isReleased;
|
||||
}
|
||||
|
||||
private void CombatStatusEvent(ClientSimPlayerDeathStatusChangedEvent combatStatusEvent)
|
||||
{
|
||||
_isDead = combatStatusEvent.isDead;
|
||||
}
|
||||
|
||||
private void OnTrackingScaleUpdated(ClientSimOnTrackingScaleUpdateEvent scaleUpdatedEvent)
|
||||
{
|
||||
NotifyPlayerMoved();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public Vector3 GetPosition()
|
||||
{
|
||||
return transform.position;
|
||||
}
|
||||
|
||||
public Quaternion GetRotation()
|
||||
{
|
||||
return transform.rotation;
|
||||
}
|
||||
|
||||
public Vector3 GetVelocity()
|
||||
{
|
||||
return _characterController.velocity;
|
||||
}
|
||||
|
||||
public void SetVelocity(Vector3 velocity)
|
||||
{
|
||||
_playerRetainedVelocity = velocity;
|
||||
_velSet = true;
|
||||
_jump = false;
|
||||
}
|
||||
|
||||
public bool IsGrounded()
|
||||
{
|
||||
return _characterController.isGrounded;
|
||||
}
|
||||
|
||||
public void Respawn()
|
||||
{
|
||||
Transform spawnPoint = _sceneManager.GetSpawnPoint(false);
|
||||
Vector3 position = ClientSimPlayerSpawner.GetRandomPositionAroundSpawn(spawnPoint.position, _sceneManager.GetSpawnRadius());
|
||||
Teleport(position, spawnPoint.rotation, false);
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPlayerRespawnEvent { player = _playerApi.Player });
|
||||
}
|
||||
|
||||
public void Respawn(int index)
|
||||
{
|
||||
|
||||
Transform spawnPoint = _sceneManager.GetSpawnPoint(index);
|
||||
if (spawnPoint == null)
|
||||
{
|
||||
this.LogError($"Spawn {index} not found. Spawning at spawn 0");
|
||||
spawnPoint = _sceneManager.GetSpawnPoint(0);
|
||||
}
|
||||
Vector3 position = ClientSimPlayerSpawner.GetRandomPositionAroundSpawn(spawnPoint.position, _sceneManager.GetSpawnRadius());
|
||||
Teleport(position, spawnPoint.rotation, false);
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPlayerRespawnEvent { player = _playerApi.Player });
|
||||
}
|
||||
|
||||
public void Teleport(Transform point, bool fromPlaySpace)
|
||||
{
|
||||
Teleport(point.position, Quaternion.Euler(0, point.rotation.eulerAngles.y, 0), fromPlaySpace);
|
||||
}
|
||||
|
||||
public void Teleport(Vector3 position, Quaternion floorRotation, bool fromPlaySpace)
|
||||
{
|
||||
floorRotation = Quaternion.Euler(0, floorRotation.eulerAngles.y, 0);
|
||||
if (fromPlaySpace)
|
||||
{
|
||||
VRCPlayerApi.TrackingData playspaceData = _trackingProvider.GetTrackingData(VRCPlayerApi.TrackingDataType.Origin);
|
||||
Vector3 playspacePos = playspaceData.position - transform.position;
|
||||
Quaternion playspaceRot = playspaceData.rotation * Quaternion.Inverse(transform.rotation);
|
||||
floorRotation = floorRotation * Quaternion.Inverse(playspaceRot);
|
||||
position += floorRotation * -playspacePos;
|
||||
}
|
||||
|
||||
this.Log($"Moving player to {position.ToString("F3")} " +
|
||||
$"and rotation {floorRotation.eulerAngles.ToString("F3")} " +
|
||||
$"(fromPlaySpace={fromPlaySpace})");
|
||||
|
||||
transform.rotation = floorRotation;
|
||||
transform.position = position;
|
||||
|
||||
NotifyPlayerMoved();
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPlayerTeleportedEvent { player = _playerApi.Player });
|
||||
|
||||
Physics.SyncTransforms();
|
||||
}
|
||||
|
||||
#region Stations
|
||||
|
||||
public void EnterStation(IClientSimStation station)
|
||||
{
|
||||
if (!station.IsMobile())
|
||||
{
|
||||
_characterController.enabled = false;
|
||||
Transform spawn = station.EnterLocation();
|
||||
Teleport(spawn, false);
|
||||
}
|
||||
// VRChatBug: Note that in the else case, the player is teleported to a location that is twice the distance
|
||||
// to the station, but since this appears to be a bug, it will not be implemented.
|
||||
}
|
||||
|
||||
public void ExitStation(IClientSimStation station, bool skipTeleport)
|
||||
{
|
||||
_characterController.enabled = true;
|
||||
|
||||
if (!skipTeleport)
|
||||
{
|
||||
Transform spawn = station.ExitLocation();
|
||||
Teleport(spawn, false);
|
||||
}
|
||||
|
||||
_jump = false;
|
||||
}
|
||||
|
||||
public void SitPosition(Transform seat)
|
||||
{
|
||||
transform.SetPositionAndRotation(seat.position, seat.rotation);
|
||||
NotifyPlayerMoved();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Handle below respawn height.
|
||||
if (transform.position.y < _sceneManager.GetRespawnHeight())
|
||||
{
|
||||
Respawn();
|
||||
}
|
||||
|
||||
GetInput();
|
||||
|
||||
NotifyPlayerMoved();
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
Physics.SyncTransforms();
|
||||
Vector2 speed = GetSpeed();
|
||||
Vector2 input = _prevInput;
|
||||
|
||||
if (!_stationManager.CanPlayerMove(input.magnitude))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_menuIsOpen || _isDead)
|
||||
{
|
||||
input = Vector2.zero;
|
||||
_jump = false;
|
||||
}
|
||||
|
||||
// Immobile does not affect Jump
|
||||
if (_playerLocomotionData.GetImmobilized())
|
||||
{
|
||||
input = Vector2.zero;
|
||||
}
|
||||
|
||||
// Always move along the camera forward as it is the direction that it being aimed at
|
||||
Vector3 desiredMove = input.y * speed.x * transform.forward + input.x * speed.y * transform.right;
|
||||
desiredMove.y = 0;
|
||||
|
||||
float gravityContribution = _playerLocomotionData.GetGravityStrength() * Time.fixedDeltaTime * Physics.gravity.y;
|
||||
|
||||
if (!_velSet)
|
||||
{
|
||||
if (_characterController.isGrounded)
|
||||
{
|
||||
_playerRetainedVelocity = Vector3.zero;
|
||||
_playerRetainedVelocity.y = -STICK_TO_GROUND_FORCE;
|
||||
if (_jump)
|
||||
{
|
||||
if (!_playerLocomotionData.GetUseLegacyLocomotion())
|
||||
{
|
||||
_playerRetainedVelocity = desiredMove;
|
||||
}
|
||||
_playerRetainedVelocity.y = _playerLocomotionData.GetJump();
|
||||
desiredMove = Vector3.zero;
|
||||
_jump = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Slowly add velocity from movement inputs
|
||||
if (!_playerLocomotionData.GetUseLegacyLocomotion())
|
||||
{
|
||||
Vector3 localVelocity = transform.InverseTransformVector(_characterController.velocity);
|
||||
localVelocity.x = Mathf.Clamp(localVelocity.x, -speed.y, speed.y);
|
||||
localVelocity.z = Mathf.Clamp(localVelocity.z, -speed.x, speed.x);
|
||||
|
||||
Vector3 maxAc = new Vector3(speed.y - localVelocity.x, 0, speed.x - localVelocity.z);
|
||||
Vector3 minAc = new Vector3(-speed.y - localVelocity.x, 0, -speed.x - localVelocity.z);
|
||||
|
||||
Vector3 inputAcceleration = Time.fixedDeltaTime * RATE_OF_AIR_ACCELERATION * new Vector3(input.x * speed.y, 0, input.y * speed.x);
|
||||
inputAcceleration.x = Mathf.Clamp(inputAcceleration.x, minAc.x, maxAc.x);
|
||||
inputAcceleration.z = Mathf.Clamp(inputAcceleration.z, minAc.z, maxAc.z);
|
||||
|
||||
inputAcceleration = transform.TransformVector(inputAcceleration);
|
||||
_playerRetainedVelocity += inputAcceleration;
|
||||
desiredMove = Vector3.zero;
|
||||
}
|
||||
// Legacy stutter stepping
|
||||
else if (_directionChanged)
|
||||
{
|
||||
_playerRetainedVelocity = Vector3.zero;
|
||||
}
|
||||
_playerRetainedVelocity.y += gravityContribution;
|
||||
}
|
||||
}
|
||||
else // Dumb behavior that hopefully needs to be removed
|
||||
{
|
||||
_characterController.Move(new Vector3(desiredMove.x * 0.05f, desiredMove.y * 0.05f + gravityContribution, desiredMove.z * 0.05f) * Time.fixedDeltaTime);
|
||||
desiredMove = Vector3.zero;
|
||||
}
|
||||
|
||||
desiredMove += _playerRetainedVelocity;
|
||||
|
||||
_characterController.Move(desiredMove * Time.fixedDeltaTime);
|
||||
|
||||
_velSet = false;
|
||||
|
||||
NotifyPlayerMoved();
|
||||
}
|
||||
|
||||
#region Input
|
||||
|
||||
private void GetInput()
|
||||
{
|
||||
GetMovementInput();
|
||||
if (_menuIsOpen && Mathf.Max(_prevInput.x, _prevInput.y) > 0)
|
||||
{
|
||||
_input.SendToggleMenuEvent(true, HandType.RIGHT);
|
||||
}
|
||||
// Only allow these input actions while the menu is closed
|
||||
if (!_menuIsOpen)
|
||||
{
|
||||
RotateView();
|
||||
}
|
||||
}
|
||||
|
||||
private void GetMovementInput()
|
||||
{
|
||||
Vector2 input = _input.GetMovementAxes();
|
||||
if (input.sqrMagnitude > 1)
|
||||
{
|
||||
input.Normalize();
|
||||
}
|
||||
|
||||
_directionChanged = (input.sqrMagnitude < 1e-3 ^ _prevInput.sqrMagnitude < 1e-3);
|
||||
_prevInput = input;
|
||||
}
|
||||
|
||||
// TODO Move rotation of the player controller to be done in the tracking provider and have the player controller
|
||||
// copy the head rotation. This would allow more generic handling of mouse released, VR snap turning, and locked in station.
|
||||
private void RotateView()
|
||||
{
|
||||
// Allow player controller to look left and right when not in a locked station and for desktop users
|
||||
// when the mouse is not released..
|
||||
if (!_mouseReleased && !_stationManager.IsLockedInStation())
|
||||
{
|
||||
float yRot = _input.GetLookHorizontal();
|
||||
transform.rotation *= Quaternion.Euler(0f, yRot, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Used in tests to help look a specific direction
|
||||
public void LookTowardsPoint(Vector3 point)
|
||||
{
|
||||
if (_stationManager.IsLockedInStation())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
point.y = transform.position.y;
|
||||
transform.LookAt(point);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Notify all systems that are dependent on the player's position that the player has moved to a new location.
|
||||
private void NotifyPlayerMoved()
|
||||
{
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPlayerMovedEvent { player = _playerApi.Player });
|
||||
|
||||
if (_cameraProxyObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var cameraTrackingData = _trackingProvider.GetTrackingData(VRCPlayerApi.TrackingDataType.Head);
|
||||
_cameraProxyObject.SetPositionAndRotation(cameraTrackingData.position, cameraTrackingData.rotation);
|
||||
_cameraProxyObject.localScale = _trackingProvider.GetTrackingScale() * Vector3.one;
|
||||
}
|
||||
|
||||
private Vector2 GetSpeed()
|
||||
{
|
||||
// TODO check current bindings to see if non keyboard and only use runspeed.
|
||||
Vector2 speed = new Vector2(
|
||||
_isWalking? _playerLocomotionData.GetWalkSpeed() : _playerLocomotionData.GetRunSpeed(),
|
||||
_playerLocomotionData.GetStrafeSpeed());
|
||||
|
||||
switch (_trackingProvider.GetPlayerStance())
|
||||
{
|
||||
case ClientSimPlayerStanceEnum.CROUCHING:
|
||||
speed *= CROUCH_SPEED_MULTIPLIER;
|
||||
break;
|
||||
case ClientSimPlayerStanceEnum.PRONE:
|
||||
speed *= PRONE_SPEED_MULTIPLIER;
|
||||
break;
|
||||
}
|
||||
|
||||
return speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 978f5eb2f40732b4a90c6107b7610ecf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,553 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon.Common;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// This system is responsible for handling pickups.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sends Events:
|
||||
/// - ClientSimOnPickupEvent
|
||||
/// - ClientSimOnPickupDropEvent
|
||||
/// - ClientSimOnPickupUseDownEvent
|
||||
/// - ClientSimOnPickupUseUpEvent
|
||||
/// Listens to Input Events:
|
||||
/// - Grab
|
||||
/// - Use
|
||||
/// - Drop
|
||||
/// </remarks>
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimPlayerHand : ClientSimBehaviour, IDisposable
|
||||
{
|
||||
// The duration after picking up an object to start sending pickup UseDown and UseUp events.
|
||||
private const float INITIAL_PICKUP_DURATION = 0.5f;
|
||||
// The distance at which a pickup will force snap to the hand.
|
||||
private const float MAX_PICKUP_DISTANCE = 0.25f;
|
||||
// How many units will the object rotate during manipulation.
|
||||
private const float DESKTOP_ROTATION_MULTIPLIER = 2f;
|
||||
// How many units will an object be moved forward or backwards during manipulation.
|
||||
private const float DESKTOP_MANIPULATE_MULTIPLIER = 0.01f;
|
||||
// How far can the object move away from the hand during manipulation.
|
||||
private const float DESKTOP_MANIPULATION_MAX_DISTANCE = 0.64f;
|
||||
|
||||
private static readonly Quaternion _gripOffsetRotation = Quaternion.Euler(0, 35, 0);
|
||||
private static readonly Quaternion _gunOffsetRotation = Quaternion.Euler(0, 305, 0);
|
||||
private static readonly Quaternion _desktopManipulationRotation = Quaternion.Euler(180, 35, 90);
|
||||
|
||||
[SerializeField]
|
||||
private HandType handType;
|
||||
[SerializeField]
|
||||
private Transform handTransform;
|
||||
[SerializeField]
|
||||
private ClientSimPlayerHand otherHand;
|
||||
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private IClientSimInput _input;
|
||||
private IClientSimTrackingProvider _trackingProvider;
|
||||
private IClientSimPlayerApiProvider _player;
|
||||
private IClientSimPlayerPickupData _pickupData;
|
||||
private VRC_Pickup.PickupHand _pickupHandType;
|
||||
|
||||
// The object this hand hovering to know if it should pickup an object.
|
||||
private IClientSimPickupable _hoverPickupable;
|
||||
// The object currently held by this hand.
|
||||
private IClientSimPickupable _heldPickupable;
|
||||
private Rigidbody _heldPickupRigidbody;
|
||||
private Transform _heldPickupTransform;
|
||||
private GameObject _heldPickupGameObject;
|
||||
private FixedJoint _heldPickupJoint;
|
||||
|
||||
// Used for determining pickup throw
|
||||
private Vector3 _previousHandPosition;
|
||||
private Vector3 _previousHandRotation;
|
||||
|
||||
// Check if the use input is down (true) or up (false)
|
||||
private bool _useInputHeldDown;
|
||||
// Has this pickup fired the UseDown event, to know if we need to fire the UseUp event.
|
||||
private bool _isUseDown;
|
||||
|
||||
private bool _initialGrab;
|
||||
private float _grabActionStartTime;
|
||||
private float _dropActionStartTime;
|
||||
|
||||
public void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimInput input,
|
||||
IClientSimTrackingProvider trackingProvider,
|
||||
IClientSimPlayerApiProvider player,
|
||||
IClientSimPlayerPickupData pickupData)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_input = input;
|
||||
_trackingProvider = trackingProvider;
|
||||
_player = player;
|
||||
_pickupData = pickupData;
|
||||
|
||||
// Too many hand enums...
|
||||
_pickupHandType = (handType == HandType.LEFT ? VRC_Pickup.PickupHand.Left : VRC_Pickup.PickupHand.Right);
|
||||
|
||||
enabled = false; // Only enabled while holding something to reduce Update checks.
|
||||
|
||||
// Subscribe to input events
|
||||
// Input will be null with incorrect Unity input project settings.
|
||||
_input?.SubscribeGrab(GrabInput);
|
||||
_input?.SubscribeUse(UseInput);
|
||||
_input?.SubscribeDrop(DropInput);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Unsubscribe
|
||||
_input?.UnsubscribeGrab(GrabInput);
|
||||
_input?.UnsubscribeUse(UseInput);
|
||||
_input?.UnsubscribeDrop(DropInput);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsHolding())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_initialGrab && _useInputHeldDown)
|
||||
{
|
||||
HandleUseInput();
|
||||
}
|
||||
|
||||
UpdateManipulation();
|
||||
UpdatePosition();
|
||||
|
||||
_previousHandPosition = handTransform.position;
|
||||
_previousHandRotation = handTransform.rotation.eulerAngles;
|
||||
}
|
||||
|
||||
|
||||
#region ClientSim Input
|
||||
|
||||
private void GrabInput(bool value, HandType hand)
|
||||
{
|
||||
if (hand != handType)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (value)
|
||||
{
|
||||
// Try to grab hover object
|
||||
if (!IsHolding() && _hoverPickupable != null)
|
||||
{
|
||||
Pickup(_hoverPickupable);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If releasing grab input and holding a pickup that is not auto hold, drop the pickup.
|
||||
if (IsHolding() && !ShouldAutoHoldPickupable(_heldPickupable))
|
||||
{
|
||||
ForceDrop(_heldPickupable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UseInput(bool value, HandType hand)
|
||||
{
|
||||
if (hand != handType)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Save input state to know when we can fire the first UseDown event after being picked up. See Update.
|
||||
_useInputHeldDown = value;
|
||||
|
||||
if (!IsHolding())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HandleUseInput();
|
||||
}
|
||||
|
||||
private void DropInput(bool value, HandType hand)
|
||||
{
|
||||
if (hand != handType)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not try to drop anything if not holding a pickup.
|
||||
if (!IsHolding())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (value)
|
||||
{
|
||||
// Button was just pressed. Start a timer to know how much "throw" charge should be added.
|
||||
_dropActionStartTime = Time.time;
|
||||
}
|
||||
else
|
||||
{
|
||||
Drop(_heldPickupable, Time.time - _dropActionStartTime);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void SetHoverPickupable(IClientSimPickupable pickupable)
|
||||
{
|
||||
_hoverPickupable = pickupable;
|
||||
}
|
||||
|
||||
public bool IsHolding()
|
||||
{
|
||||
return _heldPickupable != null;
|
||||
}
|
||||
|
||||
private bool ShouldAutoHoldPickupable(IClientSimPickupable pickupable)
|
||||
{
|
||||
// Some VR controllers do not support auto hold.
|
||||
return pickupable.AutoHold() && _trackingProvider.SupportsPickupAutoHold();
|
||||
}
|
||||
|
||||
private void Pickup(IClientSimPickupable pickupable)
|
||||
{
|
||||
if (IsHolding())
|
||||
{
|
||||
LogErrorMessage("Cannot pickup a pickup while holding another.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pickupable.IsHeld())
|
||||
{
|
||||
// Allow yourself to grab a pickup from your other hand.
|
||||
if (otherHand != null && otherHand._heldPickupable == pickupable)
|
||||
{
|
||||
otherHand.ForceDrop(pickupable);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogErrorMessage("Cannot pickup a pickup someone else is holding.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
handTransform.localPosition = Vector3.zero;
|
||||
handTransform.localRotation = Quaternion.identity;
|
||||
|
||||
_heldPickupable = pickupable;
|
||||
_heldPickupTransform = pickupable.GetTransform();
|
||||
_heldPickupGameObject = pickupable.GetGameObject();
|
||||
_heldPickupRigidbody = pickupable.GetRigidbody();
|
||||
|
||||
LogMessage($"Picking up object {_heldPickupGameObject.name}");
|
||||
|
||||
VRC_Pickup pickup = pickupable.GetPickup();
|
||||
_pickupData.SetPickupInHand(_pickupHandType, pickup);
|
||||
pickupable.Pickup(_player.Player, _pickupHandType, ForceDrop);
|
||||
|
||||
// Set the grab time to know if the player has held long enough to send Use events
|
||||
_grabActionStartTime = Time.time;
|
||||
_initialGrab = true;
|
||||
// Set self enabled to allow for pickup manipulation
|
||||
enabled = true;
|
||||
|
||||
|
||||
VRC_Pickup.PickupOrientation pickupOrientation = pickupable.GetOrientation();
|
||||
Transform pickupExactGrip = pickupable.GetGripLocation();
|
||||
Transform pickupExactGun = pickupable.GetGunLocation();
|
||||
|
||||
// Calculate offset
|
||||
Transform pickupHoldPoint = null;
|
||||
Quaternion offsetRotation = Quaternion.identity;
|
||||
if (pickupOrientation == VRC_Pickup.PickupOrientation.Grip && pickupExactGrip != null)
|
||||
{
|
||||
pickupHoldPoint = pickupExactGrip;
|
||||
offsetRotation = _gripOffsetRotation;
|
||||
}
|
||||
else if (pickupOrientation == VRC_Pickup.PickupOrientation.Gun && pickupExactGun != null)
|
||||
{
|
||||
pickupHoldPoint = pickupExactGun;
|
||||
offsetRotation = _gunOffsetRotation;
|
||||
}
|
||||
|
||||
|
||||
Vector3 positionOffset;
|
||||
Quaternion rotationOffset;
|
||||
|
||||
// Grab as if no pickup point
|
||||
if (pickupHoldPoint == null)
|
||||
{
|
||||
rotationOffset = Quaternion.Inverse(handTransform.rotation) * _heldPickupTransform.rotation;
|
||||
positionOffset = handTransform.InverseTransformDirection(_heldPickupTransform.position - handTransform.position);
|
||||
|
||||
if (positionOffset.magnitude > MAX_PICKUP_DISTANCE && pickupOrientation == VRC_Pickup.PickupOrientation.Any)
|
||||
{
|
||||
positionOffset = positionOffset.normalized * MAX_PICKUP_DISTANCE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rotationOffset = offsetRotation * Quaternion.Inverse(Quaternion.Inverse(_heldPickupTransform.rotation) * pickupHoldPoint.rotation);
|
||||
positionOffset = rotationOffset * _heldPickupTransform.InverseTransformDirection(_heldPickupTransform.position - pickupHoldPoint.position);
|
||||
}
|
||||
|
||||
|
||||
Vector3 position = handTransform.position + handTransform.TransformDirection(positionOffset);
|
||||
Quaternion rotation = handTransform.rotation * rotationOffset;
|
||||
|
||||
// Move hand and pickup to the same location
|
||||
handTransform.position = _heldPickupTransform.position = position;
|
||||
handTransform.rotation = _heldPickupTransform.rotation = rotation;
|
||||
|
||||
// Link with hand rigidbody
|
||||
_heldPickupJoint = handTransform.gameObject.AddComponent<FixedJoint>();
|
||||
_heldPickupJoint.connectedBody = _heldPickupRigidbody;
|
||||
|
||||
// Set the owner of this object to the player picking it up.
|
||||
Networking.SetOwner(_player.Player, _heldPickupGameObject);
|
||||
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPickupEvent
|
||||
{
|
||||
player = _player.Player,
|
||||
handType = handType,
|
||||
pickup = pickupable,
|
||||
});
|
||||
|
||||
// Notify pickup handlers of object pickup.
|
||||
foreach (var pickupHandler in _heldPickupGameObject.GetComponents<IClientSimPickupHandler>())
|
||||
{
|
||||
pickupHandler.OnPickup();
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceDrop()
|
||||
{
|
||||
if (_heldPickupable != null)
|
||||
{
|
||||
ForceDrop(_heldPickupable);
|
||||
}
|
||||
}
|
||||
|
||||
private void ForceDrop(IClientSimPickupable pickupable)
|
||||
{
|
||||
Drop(pickupable, 0);
|
||||
}
|
||||
|
||||
private void Drop(IClientSimPickupable pickupable, float throwHoldDuration)
|
||||
{
|
||||
if (_heldPickupable != pickupable || !pickupable.IsHeld() || pickupable.GetHoldingPlayer() != _player.Player)
|
||||
{
|
||||
LogErrorMessage("Cannot drop a pickup that you aren't holding.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that UseUp is called before the drop event finishes.
|
||||
OnPickupUseUp();
|
||||
|
||||
// Check to return early and ensure no errors if OnPickupUseUp calls Drop.
|
||||
if (_heldPickupable != pickupable || !pickupable.IsHeld() || pickupable.GetHoldingPlayer() != _player.Player)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LogMessage($"Dropping object {_heldPickupGameObject.name}");
|
||||
|
||||
// Unlink from arm rigidbody
|
||||
if (_heldPickupJoint != null)
|
||||
{
|
||||
Destroy(_heldPickupJoint);
|
||||
}
|
||||
|
||||
// When exiting playmode while holding an object, Drop will be called and Time.deltaTime will be 0.
|
||||
// This check prevents setting the velocity to NaN due to divide by zero.
|
||||
if (Time.deltaTime > 0)
|
||||
{
|
||||
_heldPickupRigidbody.velocity = (handTransform.position - _previousHandPosition) * (0.5f / Time.deltaTime);
|
||||
_heldPickupRigidbody.angularVelocity = (handTransform.rotation.eulerAngles - _previousHandRotation);
|
||||
}
|
||||
|
||||
|
||||
// Calculate throw velocity
|
||||
// TODO Verify how VR handles throwing pickups
|
||||
if (!_heldPickupRigidbody.isKinematic)
|
||||
{
|
||||
float holdDuration = Mathf.Clamp(throwHoldDuration, 0, 3);
|
||||
if (holdDuration > 0.2f)
|
||||
{
|
||||
float power = holdDuration * 500 * pickupable.GetThrowVelocityBoostScale();
|
||||
Vector3 throwForce = power * transform.TransformDirection(_gripOffsetRotation * Vector3.forward);
|
||||
_heldPickupRigidbody.AddForce(throwForce);
|
||||
LogMessage($"Adding throw force: {throwForce}");
|
||||
}
|
||||
}
|
||||
|
||||
pickupable.Drop(_player.Player);
|
||||
_pickupData.SetPickupInHand(_pickupHandType, null);
|
||||
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPickupDropEvent
|
||||
{
|
||||
player = _player.Player,
|
||||
handType = handType,
|
||||
pickup = pickupable,
|
||||
});
|
||||
|
||||
// Notify pickup handlers that the object has been dropped.
|
||||
foreach (var pickupHandler in _heldPickupGameObject.GetComponents<IClientSimPickupHandler>())
|
||||
{
|
||||
pickupHandler.OnDrop();
|
||||
}
|
||||
|
||||
_heldPickupable = null;
|
||||
_heldPickupTransform = null;
|
||||
_heldPickupGameObject = null;
|
||||
_heldPickupRigidbody = null;
|
||||
|
||||
// Prevent throwing an exception when exiting playmode due to this object being destroyed.
|
||||
if (this != null)
|
||||
{
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
handTransform.localPosition = Vector3.zero;
|
||||
handTransform.localRotation = Quaternion.identity;
|
||||
}
|
||||
|
||||
private bool HeldLongEnoughForUseEvents()
|
||||
{
|
||||
float grabDuration = Time.time - _grabActionStartTime;
|
||||
return grabDuration >= INITIAL_PICKUP_DURATION;
|
||||
}
|
||||
|
||||
private void HandleUseInput()
|
||||
{
|
||||
// Grab time has not been long enough to send use events
|
||||
if (!HeldLongEnoughForUseEvents())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only auto hold pickups can be used.
|
||||
if (!_heldPickupable.AutoHold())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_useInputHeldDown)
|
||||
{
|
||||
OnPickupUseDown();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnPickupUseUp();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPickupUseDown()
|
||||
{
|
||||
LogMessage($"Pickup Use Down {_heldPickupGameObject.name}");
|
||||
_initialGrab = false;
|
||||
_isUseDown = true;
|
||||
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPickupUseDownEvent
|
||||
{
|
||||
player = _player.Player,
|
||||
handType = handType,
|
||||
pickup = _heldPickupable,
|
||||
});
|
||||
|
||||
// Notify pickup handlers that the object has Use Down.
|
||||
foreach (var pickupHandler in _heldPickupGameObject.GetComponents<IClientSimPickupHandler>())
|
||||
{
|
||||
pickupHandler.OnPickupUseDown();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPickupUseUp()
|
||||
{
|
||||
// Prevent calling UseUp if UseDown was never called.
|
||||
if (!_isUseDown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LogMessage($"Pickup Use Up {_heldPickupGameObject.name}");
|
||||
_isUseDown = false;
|
||||
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPickupUseUpEvent
|
||||
{
|
||||
player = _player.Player,
|
||||
handType = handType,
|
||||
pickup = _heldPickupable,
|
||||
});
|
||||
|
||||
// Notify pickup handlers that the object has Use Up.
|
||||
foreach (var pickupHandler in _heldPickupGameObject.GetComponents<IClientSimPickupHandler>())
|
||||
{
|
||||
pickupHandler.OnPickupUseUp();
|
||||
}
|
||||
}
|
||||
|
||||
// Apply desktop hand rotations
|
||||
private void UpdateManipulation()
|
||||
{
|
||||
if (handType != HandType.RIGHT || !_heldPickupable.AllowManipulation())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the input for rotating the pickup.
|
||||
Vector3 angles = new Vector3(
|
||||
_input.GetPickupRotateUpDown(),
|
||||
_input.GetPickupRotateLeftRight(),
|
||||
_input.GetPickupRotateCwCcw());
|
||||
|
||||
// Only apply rotation if some input has been detected.
|
||||
if (angles.sqrMagnitude > 0)
|
||||
{
|
||||
// Rotate the input angles to match rotation based on desktop view.
|
||||
angles = transform.rotation * _desktopManipulationRotation * angles;
|
||||
|
||||
// Apply rotation to hand.
|
||||
handTransform.Rotate(angles, DESKTOP_ROTATION_MULTIPLIER,Space.World);
|
||||
}
|
||||
|
||||
// Move pickup forward and back.
|
||||
float manipulateForwardBack = _input.GetPickupManipulateDistance();
|
||||
if (!Mathf.Approximately(manipulateForwardBack, 0))
|
||||
{
|
||||
Vector3 forward = _gripOffsetRotation * Vector3.forward;
|
||||
Vector3 offset = forward * Mathf.Sign(manipulateForwardBack) * DESKTOP_MANIPULATE_MULTIPLIER;
|
||||
Vector3 handLocal = handTransform.localPosition + offset;
|
||||
handLocal = Vector3.ClampMagnitude(handLocal, DESKTOP_MANIPULATION_MAX_DISTANCE);
|
||||
handTransform.localPosition = handLocal;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePosition(bool force = false)
|
||||
{
|
||||
if ((_heldPickupRigidbody != null && _heldPickupRigidbody.isKinematic) || force)
|
||||
{
|
||||
_heldPickupTransform.SetPositionAndRotation(handTransform.position, handTransform.rotation);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogMessage(string message)
|
||||
{
|
||||
this.Log($"[{handType}] {message}");
|
||||
}
|
||||
|
||||
private void LogErrorMessage(string message)
|
||||
{
|
||||
this.LogError($"[{handType}] {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5604cf6b2917b144a0c023cac36e1b7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,276 @@
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon.Common;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// This system is responsible for handling finding objects that can be interacted with or picked up.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sends Events:
|
||||
/// - ClientSimRaycastHitResultsEvent
|
||||
/// - ClientSimInteractEvent
|
||||
/// Listens to Events:
|
||||
/// - ClientSimOnPlayerMovedEvent
|
||||
/// - ClientSimPlayerDeathStatusChangedEvent
|
||||
/// Listens to Input Events:
|
||||
/// - Use
|
||||
/// </remarks>
|
||||
[AddComponentMenu("")]
|
||||
// Unity Event System Updates at -1000. Send raycast events before then to ensure UI interactions happen same frame.
|
||||
[DefaultExecutionOrder(-2000)]
|
||||
public class ClientSimPlayerRaycaster : ClientSimBehaviour, IDisposable
|
||||
{
|
||||
[SerializeField]
|
||||
private ClientSimPlayerHand leftHand;
|
||||
[SerializeField]
|
||||
private ClientSimPlayerHand rightHand;
|
||||
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private IClientSimInput _input;
|
||||
private IClientSimPlayerApiProvider _playerApiProvider;
|
||||
private IClientSimHighlightManager _highlightManager;
|
||||
private IClientSimTooltipManager _tooltipManager;
|
||||
private IClientSimInteractiveLayerProvider _interactiveLayerProvider;
|
||||
private IClientSimInteractManager _interactManager;
|
||||
private IClientSimTrackingProvider _trackingProvider;
|
||||
private IClientSimPlayerStationManager _stationManager;
|
||||
|
||||
private ClientSimRaycaster _leftHandRaycaster;
|
||||
private ClientSimRaycaster _rightHandRaycaster;
|
||||
|
||||
private ClientSimRaycastResults _hoverLeft;
|
||||
private ClientSimRaycastResults _hoverRight;
|
||||
|
||||
public void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimInput input,
|
||||
IClientSimPlayerApiProvider playerApiProvider,
|
||||
IClientSimPlayerPickupData pickupData,
|
||||
IClientSimHighlightManager highlightManager,
|
||||
IClientSimTooltipManager tooltipManager,
|
||||
IClientSimInteractiveLayerProvider interactiveLayerProvider,
|
||||
IClientSimPlayerCameraProvider cameraProvider,
|
||||
IClientSimMousePositionProvider mousePositionProvider,
|
||||
IClientSimInteractManager interactManager,
|
||||
IClientSimTrackingProvider trackingProvider,
|
||||
IClientSimPlayerStationManager stationManager)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_input = input;
|
||||
_playerApiProvider = playerApiProvider;
|
||||
_highlightManager = highlightManager;
|
||||
_tooltipManager = tooltipManager;
|
||||
_interactiveLayerProvider = interactiveLayerProvider;
|
||||
_interactManager = interactManager;
|
||||
_trackingProvider = trackingProvider;
|
||||
_stationManager = stationManager;
|
||||
|
||||
leftHand.Initialize(_eventDispatcher, _input, trackingProvider, _playerApiProvider, pickupData);
|
||||
rightHand.Initialize(_eventDispatcher, _input, trackingProvider, _playerApiProvider, pickupData);
|
||||
|
||||
// Input will be null with incorrect Unity input project settings.
|
||||
_input?.SubscribeUse(UseInput);
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerMovedEvent>(OnPlayerMoved);
|
||||
_eventDispatcher.Subscribe<ClientSimPlayerDeathStatusChangedEvent>(CombatStatusEvent);
|
||||
|
||||
// Create raycasters
|
||||
if (_trackingProvider.IsVR())
|
||||
{
|
||||
_leftHandRaycaster = new ClientSimRaycaster(
|
||||
new ClientSimTransformRayProvider(_trackingProvider.GetHandRaycastTransform(HandType.LEFT)),
|
||||
_interactiveLayerProvider,
|
||||
_interactManager);
|
||||
|
||||
_rightHandRaycaster = new ClientSimRaycaster(
|
||||
new ClientSimTransformRayProvider(_trackingProvider.GetHandRaycastTransform(HandType.RIGHT)),
|
||||
_interactiveLayerProvider,
|
||||
_interactManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Left hand is always null for desktop users.
|
||||
_leftHandRaycaster = null;
|
||||
|
||||
// Right hand is from the player's camera.
|
||||
_rightHandRaycaster = new ClientSimRaycaster(
|
||||
new ClientSimCameraRayProvider(cameraProvider, mousePositionProvider),
|
||||
_interactiveLayerProvider,
|
||||
_interactManager);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_input?.UnsubscribeUse(UseInput);
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnPlayerMovedEvent>(OnPlayerMoved);
|
||||
_eventDispatcher.Unsubscribe<ClientSimPlayerDeathStatusChangedEvent>(CombatStatusEvent);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
UpdateHandPositions();
|
||||
|
||||
SetHoverLeft(_leftHandRaycaster?.CheckForInteracts());
|
||||
SetHoverRight(_rightHandRaycaster.CheckForInteracts());
|
||||
}
|
||||
|
||||
private void SetHoverLeft(ClientSimRaycastResults raycastResults)
|
||||
{
|
||||
SetHover(ref _hoverLeft, HandType.LEFT, leftHand, raycastResults);
|
||||
}
|
||||
|
||||
private void SetHoverRight(ClientSimRaycastResults raycastResults)
|
||||
{
|
||||
SetHover(ref _hoverRight, HandType.RIGHT, rightHand, raycastResults);
|
||||
}
|
||||
|
||||
private void SetHover(
|
||||
ref ClientSimRaycastResults handHover,
|
||||
HandType handType,
|
||||
ClientSimPlayerHand playerHand,
|
||||
ClientSimRaycastResults raycastResults)
|
||||
{
|
||||
// TODO optimize this to check if previous was the same as new and not disable/re-enable.
|
||||
if (handHover != null && handHover.interactable != null)
|
||||
{
|
||||
_highlightManager.DisableObjectHighlight(handHover.hitObject);
|
||||
_tooltipManager.DisableTooltip(handHover.interactable);
|
||||
}
|
||||
|
||||
raycastResults = FilterRaycastResults(raycastResults);
|
||||
|
||||
// If the player is not holding something, or the hit object is a UIShape, set the hover.
|
||||
// This allows players to interact with UI while still holding objects,
|
||||
// but holding objects will block pickups and interacts.
|
||||
if (!playerHand.IsHolding() || (raycastResults != null && raycastResults.uiShape != null))
|
||||
{
|
||||
handHover = raycastResults;
|
||||
}
|
||||
else
|
||||
{
|
||||
handHover = null;
|
||||
}
|
||||
|
||||
// Highlight the object if it has an interactable
|
||||
if (handHover != null && handHover.interactable != null)
|
||||
{
|
||||
_highlightManager.EnableObjectHighlight(handHover.hitObject);
|
||||
_tooltipManager.DisplayTooltip(handHover.interactable);
|
||||
}
|
||||
|
||||
// If the hovered object has a pickupable that can be interacted with, set that as this hand's hovered pickup.
|
||||
IClientSimPickupable pickupable = handHover?.GetPickupable();
|
||||
if (pickupable != null && !_interactManager.CanInteract(pickupable, handHover.distance))
|
||||
{
|
||||
pickupable = null;
|
||||
}
|
||||
playerHand.SetHoverPickupable(pickupable);
|
||||
|
||||
_eventDispatcher.SendEvent(new ClientSimRaycastHitResultsEvent
|
||||
{
|
||||
handType = handType,
|
||||
raycastResults = handHover
|
||||
});
|
||||
}
|
||||
|
||||
private ClientSimRaycastResults FilterRaycastResults(ClientSimRaycastResults results)
|
||||
{
|
||||
if (results == null || results.hitObject == null)
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
// If the user is in a station with "CanUseStationFromStation" set to false, disable interacts
|
||||
// for any object that contains a station script.
|
||||
if (_stationManager.InStation()
|
||||
&& !_stationManager.GetCurrentStation().CanUseStationFromStation()
|
||||
&& results.hitObject.GetComponent<IClientSimStation>() != null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private void TryInteract(HandType handType, ClientSimRaycastResults hover)
|
||||
{
|
||||
// Nothing to interact with.
|
||||
if (hover == null || hover.interactable == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Interact with the object and get the components that were interacted with.
|
||||
var interacts = _interactManager.Interact(hover.hitObject, hover.distance);
|
||||
|
||||
// Notify ClientSim of interacted objects.
|
||||
_eventDispatcher.SendEvent(new ClientSimInteractEvent
|
||||
{
|
||||
handType = handType,
|
||||
interactObject = hover.hitObject,
|
||||
interactDistance = hover.distance,
|
||||
interacts = interacts,
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateHandPositions()
|
||||
{
|
||||
// TODO do not update hands if player controller is stuck in a collider.
|
||||
// Note that these hands are only for pickups and that raycast logic uses the tracking provider hands.
|
||||
|
||||
var leftHandData = _trackingProvider.GetTrackingData(VRCPlayerApi.TrackingDataType.LeftHand);
|
||||
leftHand.transform.SetPositionAndRotation(leftHandData.position, leftHandData.rotation);
|
||||
leftHand.UpdatePosition();
|
||||
|
||||
var rightHandData = _trackingProvider.GetTrackingData(VRCPlayerApi.TrackingDataType.RightHand);
|
||||
rightHand.transform.SetPositionAndRotation(rightHandData.position, rightHandData.rotation);
|
||||
rightHand.UpdatePosition();
|
||||
}
|
||||
|
||||
#region ClientSim Input
|
||||
|
||||
private void UseInput(bool value, HandType hand)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (hand == HandType.LEFT)
|
||||
{
|
||||
TryInteract(HandType.LEFT, _hoverLeft);
|
||||
}
|
||||
if (hand == HandType.RIGHT)
|
||||
{
|
||||
TryInteract(HandType.RIGHT, _hoverRight);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ClientSim Events
|
||||
|
||||
private void OnPlayerMoved(ClientSimOnPlayerMovedEvent moveEvent)
|
||||
{
|
||||
UpdateHandPositions();
|
||||
}
|
||||
|
||||
private void CombatStatusEvent(ClientSimPlayerDeathStatusChangedEvent combatStatusEvent)
|
||||
{
|
||||
leftHand.ForceDrop();
|
||||
rightHand.ForceDrop();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07cd0208a6e39194699dcdb10e3689fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,212 @@
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// This system is responsible for the player entering and exiting stations as well as repositioning the player to the station at the end of frame.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sends Events:
|
||||
/// - ClientSimOnPlayerEnteredStationEvent
|
||||
/// - ClientSimOnPlayerExitedStationEvent
|
||||
/// Listens to Events:
|
||||
/// - ClientSimOnPlayerTeleportedEvent
|
||||
/// </remarks>
|
||||
[AddComponentMenu("")]
|
||||
// High execution order to ensure the player is positioned properly at the end of the frame.
|
||||
[DefaultExecutionOrder(30000)]
|
||||
public class ClientSimPlayerStationManager : ClientSimBehaviour, IClientSimPlayerStationManager, IDisposable
|
||||
{
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private IClientSimPlayerApiProvider _playerApiProvider;
|
||||
private ClientSimPlayerController _playerController;
|
||||
private IClientSimStation _currentStation;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
_playerController = GetComponent<ClientSimPlayerController>();
|
||||
}
|
||||
|
||||
public void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimPlayerApiProvider playerApiProvider)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_playerApiProvider = playerApiProvider;
|
||||
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerTeleportedEvent>(OnPlayerTeleported);
|
||||
|
||||
// Only enable this object while sitting in a station to prevent unneeded update checks.
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnPlayerTeleportedEvent>(OnPlayerTeleported);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
UpdateStationPosition();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
// VRChatBug: VRChat seems to not handle the rotation in late update causing player's rotation to jitter
|
||||
// while in a station that is updated in late update. This is not recreated here.
|
||||
UpdateStationPosition();
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
UpdateStationPosition();
|
||||
}
|
||||
|
||||
public bool InStation()
|
||||
{
|
||||
return _currentStation != null;
|
||||
}
|
||||
|
||||
public IClientSimStation GetCurrentStation()
|
||||
{
|
||||
return _currentStation;
|
||||
}
|
||||
|
||||
public bool IsLockedInStation()
|
||||
{
|
||||
return InStation() && _currentStation.IsLockedInStation();
|
||||
}
|
||||
|
||||
public bool CanPlayerMove(float moveValue)
|
||||
{
|
||||
return !InStation() || CanPlayerMoveWhileSeated(moveValue);
|
||||
}
|
||||
|
||||
public void EnterStation(IClientSimStation station)
|
||||
{
|
||||
GameObject stationObj = station.GetStationGameObject();
|
||||
if (_currentStation != null)
|
||||
{
|
||||
// VRChatBug: VRCStation.CanUseStationFromStation does not care about actually trying to enter stations.
|
||||
// This will only block the interact on the object. Calling enter station while sitting in a station
|
||||
// with this property set to false will still allow you to exit the current station and enter the new one.
|
||||
ExitStation(_currentStation, true);
|
||||
}
|
||||
|
||||
VRCPlayerApi player = _playerApiProvider.Player;
|
||||
|
||||
this.Log($"Entering Station {Tools.GetGameObjectPath(stationObj)}");
|
||||
|
||||
// Immobilize the player while sitting in the station.
|
||||
// VRChatBug: Note that "mobile" stations require that seated be set to false and have mobility set to mobile.
|
||||
if (!station.IsMobile())
|
||||
{
|
||||
player.Immobilize(true);
|
||||
}
|
||||
|
||||
station.EnterStation(player);
|
||||
|
||||
_playerController.EnterStation(station);
|
||||
|
||||
// Set the station after notifying the player controller to prevent exit on Teleport.
|
||||
_currentStation = station;
|
||||
enabled = true;
|
||||
|
||||
// Notify all station handlers after notifying to ensure that player location is updated first.
|
||||
var vrcStation = station.GetStation();
|
||||
foreach (var stationHandler in stationObj.GetComponents<IClientSimStationHandler>())
|
||||
{
|
||||
stationHandler.OnStationEnter(vrcStation);
|
||||
}
|
||||
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPlayerEnteredStationEvent {player = player, station = station});
|
||||
}
|
||||
|
||||
public void ExitStation(IClientSimStation station, bool forcedExit = false)
|
||||
{
|
||||
// Prevent Exception on exit playmode when this object is destroyed.
|
||||
if (this == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject stationObj = station.GetStationGameObject();
|
||||
if (_currentStation != station)
|
||||
{
|
||||
this.LogError($"Cannot exit station that the player is not in. {Tools.GetGameObjectPath(stationObj)}");
|
||||
return;
|
||||
}
|
||||
|
||||
_currentStation = null;
|
||||
enabled = false;
|
||||
|
||||
this.Log($"Exiting Station {Tools.GetGameObjectPath(stationObj)}");
|
||||
|
||||
VRCPlayerApi player = _playerApiProvider.Player;
|
||||
|
||||
player.Immobilize(false);
|
||||
|
||||
station.ExitStation(player);
|
||||
|
||||
// Notify all station handlers first as player exit is handled after this event.
|
||||
var vrcStation = station.GetStation();
|
||||
foreach (var stationHandler in stationObj.GetComponents<IClientSimStationHandler>())
|
||||
{
|
||||
stationHandler.OnStationExit(vrcStation);
|
||||
}
|
||||
|
||||
_playerController.ExitStation(station, forcedExit);
|
||||
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPlayerExitedStationEvent {player = player, station = station});
|
||||
}
|
||||
|
||||
private void UpdateStationPosition()
|
||||
{
|
||||
if (!InStation() || _currentStation.IsMobile())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_playerController.SitPosition(_currentStation.EnterLocation());
|
||||
}
|
||||
|
||||
// Returns if the player should move, and exit station if the player is in a non mobile station with exit enabled.
|
||||
private bool CanPlayerMoveWhileSeated(float speed)
|
||||
{
|
||||
if (Mathf.Abs(speed) >= 0.1f && !_currentStation.DisableStationExit())
|
||||
{
|
||||
ExitStation(_currentStation);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_currentStation.IsMobile())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#region ClientSim Events
|
||||
|
||||
// Note that respawn is considered a teleport and will automatically be handled by this event.
|
||||
private void OnPlayerTeleported(ClientSimOnPlayerTeleportedEvent teleportEvent)
|
||||
{
|
||||
if (InStation())
|
||||
{
|
||||
ExitStation(_currentStation, true);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 661af135a574ca54298877eed2ca87a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,104 @@
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// This system is responsible for displaying the Reticle in the center of the screen
|
||||
/// and for displaying the mouse UI pointer if there is a UI Shape under the mouse.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Listens to Events:
|
||||
/// - ClientSimMouseReleasedEvent
|
||||
/// - ClientSimRaycastHitResultsEvent
|
||||
/// </remarks>
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimReticle : ClientSimBehaviour, IDisposable
|
||||
{
|
||||
[SerializeField]
|
||||
private Texture2D reticle;
|
||||
[SerializeField]
|
||||
private Texture2D uiShapeHoverIcon;
|
||||
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private ClientSimSettings _settings;
|
||||
private IClientSimMousePositionProvider _mousePositionProvider;
|
||||
|
||||
private bool _mouseReleased = false;
|
||||
private int _lastUiShapeHoveredFrame = -1;
|
||||
|
||||
public void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
ClientSimSettings settings,
|
||||
IClientSimMousePositionProvider mousePositionProvider)
|
||||
{
|
||||
_settings = settings;
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_mousePositionProvider = mousePositionProvider;
|
||||
|
||||
_eventDispatcher.Subscribe<ClientSimMouseReleasedEvent>(MouseReleasedEvent);
|
||||
_eventDispatcher.Subscribe<ClientSimRaycastHitResultsEvent>(OnRaycastHit);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_eventDispatcher?.Unsubscribe<ClientSimMouseReleasedEvent>(MouseReleasedEvent);
|
||||
_eventDispatcher?.Unsubscribe<ClientSimRaycastHitResultsEvent>(OnRaycastHit);
|
||||
}
|
||||
|
||||
#region ClientSim Events
|
||||
|
||||
private void MouseReleasedEvent(ClientSimMouseReleasedEvent mouseReleasedEvent)
|
||||
{
|
||||
_mouseReleased = mouseReleasedEvent.isReleased;
|
||||
}
|
||||
|
||||
private void OnRaycastHit(ClientSimRaycastHitResultsEvent hitEvent)
|
||||
{
|
||||
if (hitEvent.raycastResults?.uiShape != null)
|
||||
{
|
||||
_lastUiShapeHoveredFrame = Time.frameCount;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool ShouldShowReticle()
|
||||
{
|
||||
return _settings.showDesktopReticle && !_mouseReleased;
|
||||
}
|
||||
|
||||
private bool ShouldShowUiShapeHoverIcon()
|
||||
{
|
||||
return _lastUiShapeHoveredFrame == Time.frameCount;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (ShouldShowReticle())
|
||||
{
|
||||
Vector2 center = ClientSimBaseInput.GetScreenCenter();
|
||||
Vector2 size = new Vector2(reticle.width, reticle.height);
|
||||
Rect position = new Rect(center - size * 0.5f, size);
|
||||
GUI.DrawTexture(position, reticle);
|
||||
}
|
||||
|
||||
if (ShouldShowUiShapeHoverIcon())
|
||||
{
|
||||
Vector2 mousePos = _mousePositionProvider.GetMousePosition();
|
||||
// GUI draws with inverted y
|
||||
mousePos.y = Screen.height - mousePos.y;
|
||||
|
||||
Vector2 size = new Vector2(uiShapeHoverIcon.width, uiShapeHoverIcon.height);
|
||||
Rect position = new Rect(mousePos - new Vector2(8, 8), size);
|
||||
GUI.DrawTexture(position, uiShapeHoverIcon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d04f5e2ca86a6fe48854dbfe994387e2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd9b7c69283640f4386cc0285820889d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,9 @@
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimPlayerApiProvider
|
||||
{
|
||||
VRCPlayerApi Player { get; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69178fe96c1e3c448b9a01d50b93eada
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,17 @@
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimPlayerAudioData
|
||||
{
|
||||
void SetAvatarAudioVolumetricRadius(float value);
|
||||
void SetAvatarAudioNearRadius(float value);
|
||||
void SetAvatarAudioFarRadius(float value);
|
||||
void SetAvatarAudioGain(float value);
|
||||
void SetAvatarAudioForceSpatial(bool value);
|
||||
void SetAvatarAudioCustomCurve(bool value);
|
||||
void SetVoiceLowpass(bool value);
|
||||
void SetVoiceVolumetricRadius(float value);
|
||||
void SetVoiceDistanceFar(float value);
|
||||
void SetVoiceDistanceNear(float value);
|
||||
void SetVoiceGain(float value);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 344cfc27cd6427343b0e7b7892893413
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,11 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimPlayerAvatarDataProvider
|
||||
{
|
||||
Transform GetBoneTransform(HumanBodyBones bone);
|
||||
Quaternion GetBoneRotation(HumanBodyBones bone);
|
||||
Vector3 GetBonePosition(HumanBodyBones bone);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9daea73a61febf242b9a4aa963cc0dd7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,10 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimPlayerCameraProvider
|
||||
{
|
||||
Camera GetCamera();
|
||||
Camera GetCameraForObject(GameObject obj);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 374aacd37bf23bb4dbb349f92df17be0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,20 @@
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimPlayerLocomotionData
|
||||
{
|
||||
float GetJump();
|
||||
void SetJump(float jump);
|
||||
float GetRunSpeed();
|
||||
void SetRunSpeed(float runSpeed);
|
||||
float GetWalkSpeed();
|
||||
void SetWalkSpeed(float walkSpeed);
|
||||
float GetStrafeSpeed();
|
||||
void SetStrafeSpeed(float strafeSpeed);
|
||||
float GetGravityStrength();
|
||||
void SetGravityStrength(float gravity);
|
||||
bool GetImmobilized();
|
||||
void SetImmobilized(bool immobilize);
|
||||
void SetUseLegacyLocomotion(bool useLegacyLocomotion);
|
||||
bool GetUseLegacyLocomotion();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdbc920da2e346a44992a6071c69023c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,12 @@
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimPlayerPickupData
|
||||
{
|
||||
void SetPickupsEnabled(bool enabled);
|
||||
bool GetPickupsEnabled();
|
||||
VRC_Pickup GetPickupInHand(VRC_Pickup.PickupHand hand);
|
||||
void SetPickupInHand(VRC_Pickup.PickupHand hand, VRC_Pickup pickup);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 865cb549269f42b8913b28f067401835
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,12 @@
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimPlayerStationManager
|
||||
{
|
||||
bool InStation();
|
||||
IClientSimStation GetCurrentStation();
|
||||
bool IsLockedInStation();
|
||||
bool CanPlayerMove(float moveValue);
|
||||
void EnterStation(IClientSimStation station);
|
||||
void ExitStation(IClientSimStation station, bool forcedExit = false);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1830e2049be64bfc95285fd733b6944b
|
||||
timeCreated: 1641177044
|
||||
@ -0,0 +1,10 @@
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimPlayerTagsData
|
||||
{
|
||||
void ClearPlayerTags();
|
||||
void SetPlayerTag(string tagName, string tagValue);
|
||||
string GetPlayerTag(string tagName);
|
||||
bool HasPlayerTag(string tagName, string tagValue);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a8ee738cc0b39b42b0f6fdae509f4ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon.Common;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimTrackingProvider : IClientSimPlayerCameraProvider
|
||||
{
|
||||
VRCPlayerApi.TrackingData GetTrackingData(VRCPlayerApi.TrackingDataType trackingDataType);
|
||||
Transform GetTrackingTransform(VRCPlayerApi.TrackingDataType trackingDataType);
|
||||
float GetTrackingScale();
|
||||
void SetTrackingScale(float scale);
|
||||
ClientSimPlayerStanceEnum GetPlayerStance();
|
||||
Transform GetHandRaycastTransform(HandType handType);
|
||||
bool IsVR();
|
||||
bool SupportsPickupAutoHold();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 736c7c603fbe7034ebf2e75b8c986a16
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81b23f9b431070c449c87756f670f7de
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
[Serializable]
|
||||
public class ClientSimPlayerAudioData : IClientSimPlayerAudioData
|
||||
{
|
||||
public float voiceVolumetricRadius = 0;
|
||||
public float voiceDistanceNear = 0;
|
||||
public float voiceDistanceFar = 25;
|
||||
public float voiceGain = 15;
|
||||
public bool voiceLowpass = false;
|
||||
|
||||
public float avatarAudioVolumetricRadius = 40;
|
||||
public float avatarAudioNearRadius = 40;
|
||||
public float avatarAudioFarRadius = 40;
|
||||
public float avatarAudioGain = 10;
|
||||
public bool avatarAudioCustomCurve;
|
||||
public bool avatarAudioForceSpatial;
|
||||
|
||||
#region Player Audio
|
||||
|
||||
public void SetVoiceGain(float value) => voiceGain = value;
|
||||
public void SetVoiceDistanceNear(float value) => voiceDistanceNear = value;
|
||||
public void SetVoiceDistanceFar(float value) => voiceDistanceFar = value;
|
||||
public void SetVoiceVolumetricRadius(float value) => voiceVolumetricRadius = value;
|
||||
public void SetVoiceLowpass(bool value) => voiceLowpass = value;
|
||||
|
||||
public float GetVoiceGain() => voiceGain;
|
||||
public float GetVoiceDistanceNear() => voiceDistanceNear;
|
||||
public float GetVoiceDistanceFar() => voiceDistanceFar;
|
||||
public float GetVoiceVolumetricRadius() => voiceVolumetricRadius;
|
||||
public bool GetVoiceLowpass() => voiceLowpass;
|
||||
#endregion
|
||||
|
||||
#region Avatar Audio
|
||||
|
||||
public void SetAvatarAudioVolumetricRadius(float value)
|
||||
{
|
||||
avatarAudioVolumetricRadius = value;
|
||||
}
|
||||
|
||||
public void SetAvatarAudioNearRadius(float value)
|
||||
{
|
||||
avatarAudioNearRadius = value;
|
||||
}
|
||||
|
||||
public void SetAvatarAudioFarRadius(float value)
|
||||
{
|
||||
avatarAudioFarRadius = value;
|
||||
}
|
||||
|
||||
public void SetAvatarAudioGain(float value)
|
||||
{
|
||||
avatarAudioGain = value;
|
||||
}
|
||||
|
||||
public void SetAvatarAudioForceSpatial(bool value)
|
||||
{
|
||||
avatarAudioForceSpatial = value;
|
||||
}
|
||||
|
||||
public void SetAvatarAudioCustomCurve(bool value)
|
||||
{
|
||||
avatarAudioCustomCurve = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b5b48cc61d892d4b92c2736dc813f7c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,89 @@
|
||||
using System;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
[Serializable]
|
||||
public class ClientSimPlayerLocomotionData : IClientSimPlayerLocomotionData
|
||||
{
|
||||
private const float DEFAULT_RUN_SPEED = 4;
|
||||
private const float DEFAULT_WALK_SPEED = 2;
|
||||
|
||||
public float walkSpeed = DEFAULT_WALK_SPEED;
|
||||
public float strafeSpeed = DEFAULT_WALK_SPEED;
|
||||
public float runSpeed = DEFAULT_RUN_SPEED;
|
||||
public float jumpSpeed;
|
||||
public float gravityStrength = 1f;
|
||||
public bool immobilized = false;
|
||||
public bool useLegacyLocomotion = false;
|
||||
|
||||
public float GetJump()
|
||||
{
|
||||
return jumpSpeed;
|
||||
}
|
||||
|
||||
public void SetJump(float value)
|
||||
{
|
||||
jumpSpeed = value;
|
||||
}
|
||||
|
||||
public float GetRunSpeed()
|
||||
{
|
||||
return runSpeed;
|
||||
}
|
||||
|
||||
public void SetRunSpeed(float value)
|
||||
{
|
||||
runSpeed = value;
|
||||
}
|
||||
|
||||
public float GetWalkSpeed()
|
||||
{
|
||||
return walkSpeed;
|
||||
}
|
||||
|
||||
public void SetWalkSpeed(float value)
|
||||
{
|
||||
walkSpeed = value;
|
||||
}
|
||||
|
||||
public float GetStrafeSpeed()
|
||||
{
|
||||
return strafeSpeed;
|
||||
}
|
||||
|
||||
public void SetStrafeSpeed(float value)
|
||||
{
|
||||
strafeSpeed = value;
|
||||
}
|
||||
|
||||
public float GetGravityStrength()
|
||||
{
|
||||
return gravityStrength;
|
||||
}
|
||||
|
||||
public void SetGravityStrength(float value)
|
||||
{
|
||||
gravityStrength = value;
|
||||
}
|
||||
|
||||
public bool GetImmobilized()
|
||||
{
|
||||
return immobilized;
|
||||
}
|
||||
|
||||
public void SetImmobilized(bool value)
|
||||
{
|
||||
immobilized = value;
|
||||
}
|
||||
|
||||
public void SetUseLegacyLocomotion(bool value)
|
||||
{
|
||||
useLegacyLocomotion = value;
|
||||
}
|
||||
|
||||
public bool GetUseLegacyLocomotion()
|
||||
{
|
||||
return useLegacyLocomotion;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ada3387971992d648ae3741d3fae31bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
[Serializable]
|
||||
public class ClientSimPlayerPickupData : IClientSimPlayerPickupData
|
||||
{
|
||||
public bool pickupsEnabled = true;
|
||||
private VRC_Pickup _leftHandPickup;
|
||||
private VRC_Pickup _rightHandPickup;
|
||||
|
||||
public void SetPickupsEnabled(bool enabled)
|
||||
{
|
||||
pickupsEnabled = enabled;
|
||||
}
|
||||
|
||||
public bool GetPickupsEnabled()
|
||||
{
|
||||
return pickupsEnabled;
|
||||
}
|
||||
|
||||
public VRC_Pickup GetPickupInHand(VRC_Pickup.PickupHand hand)
|
||||
{
|
||||
switch (hand)
|
||||
{
|
||||
case VRC_Pickup.PickupHand.Left:
|
||||
return _leftHandPickup;
|
||||
case VRC_Pickup.PickupHand.Right:
|
||||
return _rightHandPickup;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPickupInHand(VRC_Pickup.PickupHand hand, VRC_Pickup pickup)
|
||||
{
|
||||
switch (hand)
|
||||
{
|
||||
case VRC_Pickup.PickupHand.Left:
|
||||
_leftHandPickup = pickup;
|
||||
break;
|
||||
case VRC_Pickup.PickupHand.Right:
|
||||
_rightHandPickup = pickup;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33c07b59a4c05b34fbe53c47ba5000ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
[Serializable]
|
||||
public class ClientSimPlayerTagsData : IClientSimPlayerTagsData
|
||||
{
|
||||
private readonly Dictionary<string, string> _tags = new Dictionary<string, string>();
|
||||
|
||||
public void ClearPlayerTags()
|
||||
{
|
||||
_tags.Clear();
|
||||
}
|
||||
|
||||
public void SetPlayerTag(string tagName, string tagValue)
|
||||
{
|
||||
_tags[tagName] = tagValue;
|
||||
}
|
||||
|
||||
public string GetPlayerTag(string tagName)
|
||||
{
|
||||
if (_tags.TryGetValue(tagName, out string tagValue))
|
||||
{
|
||||
return tagValue;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public bool HasPlayerTag(string tagName, string tagValue)
|
||||
{
|
||||
return GetPlayerTag(tagName) == tagValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b66bd713ab919834096fa0fc3b58869a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc69316ead1241668e91076f6d8027dd
|
||||
timeCreated: 1708638333
|
||||
@ -0,0 +1,406 @@
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim.Persistence
|
||||
{
|
||||
public class ClientSimPlayerDataConverter : JsonConverter<ClientSimPlayerDataTypeUnion>
|
||||
{
|
||||
public override ClientSimPlayerDataTypeUnion ReadJson(JsonReader reader, Type objectType, ClientSimPlayerDataTypeUnion existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
JObject jsonObject = JObject.Load(reader);
|
||||
|
||||
if (jsonObject["type"] == null)
|
||||
{
|
||||
Debug.LogError("Failed to deserialize PlayerData: No type found");
|
||||
return null;
|
||||
}
|
||||
if (jsonObject["value"] == null)
|
||||
{
|
||||
Debug.LogError("Failed to deserialize PlayerData: No value found");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ClientSimPlayerDataType type = GetPlayerDataType(jsonObject["type"].ToString());
|
||||
object value = GetTypedValue(type, jsonObject["value"], reader, existingValue, serializer);
|
||||
return new ClientSimPlayerDataTypeUnion { Type = type, Value = value };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Failed to parse PlayerData {objectType} : {e.Message}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
private ClientSimPlayerDataType GetPlayerDataType(string typeName)
|
||||
{
|
||||
return typeName switch
|
||||
{
|
||||
"Color" => ClientSimPlayerDataType.Color,
|
||||
"Color32" => ClientSimPlayerDataType.Color32,
|
||||
"Quaternion" => ClientSimPlayerDataType.Quaternion,
|
||||
"Vector2" => ClientSimPlayerDataType.Vector2,
|
||||
"Vector3" => ClientSimPlayerDataType.Vector3,
|
||||
"Vector4" => ClientSimPlayerDataType.Vector4,
|
||||
"Bool" => ClientSimPlayerDataType.WrappedBool,
|
||||
"UByte" => ClientSimPlayerDataType.WrappedUByte,
|
||||
"Byte" => ClientSimPlayerDataType.WrappedByte,
|
||||
"Bytes" => ClientSimPlayerDataType.WrappedBytes,
|
||||
"Float" => ClientSimPlayerDataType.WrappedFloat,
|
||||
"Double" => ClientSimPlayerDataType.WrappedDouble,
|
||||
"Long" => ClientSimPlayerDataType.WrappedLong,
|
||||
"ULong" => ClientSimPlayerDataType.WrappedULong,
|
||||
"Int" => ClientSimPlayerDataType.WrappedInt,
|
||||
"UInt" => ClientSimPlayerDataType.WrappedUInt,
|
||||
"Short" => ClientSimPlayerDataType.WrappedShort,
|
||||
"UShort" => ClientSimPlayerDataType.WrappedUShort,
|
||||
"String" => ClientSimPlayerDataType.WrappedString,
|
||||
_ => ClientSimPlayerDataType.Color
|
||||
};
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, ClientSimPlayerDataTypeUnion value, JsonSerializer serializer)
|
||||
{
|
||||
JsonConverter converter = null;
|
||||
if (value.Type == ClientSimPlayerDataType.Color) converter = new ColorConverter();
|
||||
else if (value.Type == ClientSimPlayerDataType.Color32) converter = new Color32Converter();
|
||||
else if (value.Type == ClientSimPlayerDataType.Quaternion) converter = new QuaternionConverter();
|
||||
else if (value.Type == ClientSimPlayerDataType.Vector2) converter = new Vector2Converter();
|
||||
else if (value.Type == ClientSimPlayerDataType.Vector3) converter = new Vector3Converter();
|
||||
else if (value.Type == ClientSimPlayerDataType.Vector4) converter = new Vector4Converter();
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("type");
|
||||
writer.WriteValue(value.Type.ToString().Replace("Wrapped", ""));
|
||||
writer.WritePropertyName("value");
|
||||
if (converter != null)
|
||||
{
|
||||
converter.WriteJson(writer, value.Value, serializer);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValue(value.Value);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static object GetTypedValue(ClientSimPlayerDataType type, object deserializedValue, JsonReader reader, ClientSimPlayerDataTypeUnion existingValue, JsonSerializer serializer)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ClientSimPlayerDataType.Color:
|
||||
ColorUtility.TryParseHtmlString("#" + deserializedValue, out Color loadedColor);
|
||||
return loadedColor;
|
||||
|
||||
case ClientSimPlayerDataType.Color32:
|
||||
ColorUtility.TryParseHtmlString("#" + deserializedValue, out Color loadedColor32);
|
||||
return new Color32(
|
||||
(byte)(loadedColor32.r * 255),
|
||||
(byte)(loadedColor32.g * 255),
|
||||
(byte)(loadedColor32.b * 255),
|
||||
(byte)(loadedColor32.a * 255));
|
||||
|
||||
case ClientSimPlayerDataType.Quaternion:
|
||||
JObject quaternionObj = (JObject)deserializedValue;
|
||||
return new Quaternion(
|
||||
(float)quaternionObj["x"],
|
||||
(float)quaternionObj["y"],
|
||||
(float)quaternionObj["z"],
|
||||
(float)quaternionObj["w"]);
|
||||
|
||||
case ClientSimPlayerDataType.Vector2:
|
||||
JObject vector2Obj = (JObject)deserializedValue;
|
||||
return new Vector2(
|
||||
(float)vector2Obj["x"],
|
||||
(float)vector2Obj["y"]);
|
||||
|
||||
case ClientSimPlayerDataType.Vector3:
|
||||
JObject vector3Obj = (JObject)deserializedValue;
|
||||
return new Vector3(
|
||||
(float)vector3Obj["x"],
|
||||
(float)vector3Obj["y"],
|
||||
(float)vector3Obj["z"]);
|
||||
|
||||
case ClientSimPlayerDataType.Vector4:
|
||||
JObject vector4Obj = (JObject)deserializedValue;
|
||||
return new Vector4(
|
||||
(float)vector4Obj["x"],
|
||||
(float)vector4Obj["y"],
|
||||
(float)vector4Obj["z"],
|
||||
(float)vector4Obj["w"]);
|
||||
|
||||
case ClientSimPlayerDataType.WrappedBytes:
|
||||
JValue byteArrayObj = (JValue)deserializedValue;
|
||||
return Convert.FromBase64String((string)byteArrayObj.Value ?? string.Empty);
|
||||
|
||||
case ClientSimPlayerDataType.WrappedBool: return (bool)(JValue)deserializedValue;
|
||||
case ClientSimPlayerDataType.WrappedUByte: return (byte)(JValue)deserializedValue;
|
||||
case ClientSimPlayerDataType.WrappedByte: return (sbyte)(JValue)deserializedValue;
|
||||
case ClientSimPlayerDataType.WrappedFloat: return (float)(JValue)deserializedValue;
|
||||
case ClientSimPlayerDataType.WrappedDouble: return (double)(JValue)deserializedValue;
|
||||
case ClientSimPlayerDataType.WrappedLong: return (long)(JValue)deserializedValue;
|
||||
case ClientSimPlayerDataType.WrappedULong: return (ulong)(JValue)deserializedValue;
|
||||
case ClientSimPlayerDataType.WrappedInt: return (int)(JValue)deserializedValue;
|
||||
case ClientSimPlayerDataType.WrappedUInt: return (uint)(JValue)deserializedValue;
|
||||
case ClientSimPlayerDataType.WrappedShort: return (short)(JValue)deserializedValue;
|
||||
case ClientSimPlayerDataType.WrappedUShort: return (ushort)(JValue)deserializedValue;
|
||||
case ClientSimPlayerDataType.WrappedString: return (string)(JValue)deserializedValue;
|
||||
default: return deserializedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ColorConverter : JsonConverter<Color>
|
||||
{
|
||||
public override Color ReadJson(JsonReader reader, Type objectType, Color existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
ColorUtility.TryParseHtmlString("#" + reader.Value, out Color loadedColor);
|
||||
return loadedColor;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to parse color {objectType} : {ex.Message}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, Color color, JsonSerializer serializer)
|
||||
{
|
||||
string val = ColorUtility.ToHtmlStringRGBA(color);
|
||||
writer.WriteValue(val);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Color32Converter : JsonConverter<Color32>
|
||||
{
|
||||
public override Color32 ReadJson(JsonReader reader, Type objectType, Color32 existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
ColorUtility.TryParseHtmlString("#" + reader.Value, out Color loadedColor);
|
||||
return loadedColor;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to parse color {objectType} : {ex.Message}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, Color32 color, JsonSerializer serializer)
|
||||
{
|
||||
string val = ColorUtility.ToHtmlStringRGBA(color);
|
||||
writer.WriteValue(val);
|
||||
}
|
||||
}
|
||||
|
||||
internal class QuaternionConverter : JsonConverter<Quaternion>
|
||||
{
|
||||
public override Quaternion ReadJson(JsonReader reader, Type objectType, Quaternion existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.Log($"Quaternion: type={objectType} ({existingValue.GetType()}), val={existingValue}");
|
||||
|
||||
float x = 0f, y = 0f, z = 0f, w = 0f;
|
||||
while (reader.Read())
|
||||
{
|
||||
string propertyName = (string)reader.Value;
|
||||
|
||||
reader.Read();
|
||||
switch (propertyName)
|
||||
{
|
||||
case "x":
|
||||
x = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
case "y":
|
||||
y = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
case "z":
|
||||
z = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
case "w":
|
||||
w = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new Quaternion(x, y, z, w);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to parse quaternion {objectType} : {ex.Message}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, Quaternion value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("x");
|
||||
writer.WriteValue(value.x);
|
||||
writer.WritePropertyName("y");
|
||||
writer.WriteValue(value.y);
|
||||
writer.WritePropertyName("z");
|
||||
writer.WriteValue(value.z);
|
||||
writer.WritePropertyName("w");
|
||||
writer.WriteValue(value.w);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
public class Vector2Converter : JsonConverter<Vector2>
|
||||
{
|
||||
public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
float x = 0f, y = 0f;
|
||||
while (reader.Read())
|
||||
{
|
||||
string propertyName = (string)reader.Value;
|
||||
|
||||
reader.Read();
|
||||
switch (propertyName)
|
||||
{
|
||||
case "x":
|
||||
x = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
case "y":
|
||||
y = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to parse Vector2 {objectType} : {ex.Message}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("x");
|
||||
writer.WriteValue(value.x);
|
||||
writer.WritePropertyName("y");
|
||||
writer.WriteValue(value.y);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
public class Vector3Converter : JsonConverter<Vector3>
|
||||
{
|
||||
public override Vector3 ReadJson(JsonReader reader, Type objectType, Vector3 existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
float x = 0f, y = 0f, z = 0f;
|
||||
while (reader.Read())
|
||||
{
|
||||
string propertyName = (string)reader.Value;
|
||||
|
||||
reader.Read();
|
||||
switch (propertyName)
|
||||
{
|
||||
case "x":
|
||||
x = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
case "y":
|
||||
y = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
case "z":
|
||||
z = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to parse Vector3 {objectType} : {ex.Message}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("x");
|
||||
writer.WriteValue(value.x);
|
||||
writer.WritePropertyName("y");
|
||||
writer.WriteValue(value.y);
|
||||
writer.WritePropertyName("z");
|
||||
writer.WriteValue(value.z);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
public class Vector4Converter : JsonConverter<Vector4>
|
||||
{
|
||||
public override Vector4 ReadJson(JsonReader reader, Type objectType, Vector4 existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
float x = 0f, y = 0f, z = 0f, w = 0f;
|
||||
while (reader.Read())
|
||||
{
|
||||
string propertyName = (string)reader.Value;
|
||||
|
||||
reader.Read();
|
||||
switch (propertyName)
|
||||
{
|
||||
case "x":
|
||||
x = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
case "y":
|
||||
y = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
case "z":
|
||||
z = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
case "w":
|
||||
w = Convert.ToSingle(reader.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector4(x, y, z, w);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to parse Vector4 {objectType} : {ex.Message}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, Vector4 value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("x");
|
||||
writer.WriteValue(value.x);
|
||||
writer.WritePropertyName("y");
|
||||
writer.WriteValue(value.y);
|
||||
writer.WritePropertyName("z");
|
||||
writer.WriteValue(value.z);
|
||||
writer.WritePropertyName("w");
|
||||
writer.WriteValue(value.w);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d47ee12e2ac84367b7e3ced0dc76be28
|
||||
timeCreated: 1708717453
|
||||
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
namespace VRC.SDK3.ClientSim.Persistence
|
||||
{
|
||||
public class ClientSimPlayerDataPair
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public ClientSimPlayerDataTypeUnion Value { get; set; }
|
||||
public DateTime LastUpdated;
|
||||
|
||||
|
||||
public ClientSimPlayerDataPair() {
|
||||
Key = null;
|
||||
Value = null;
|
||||
LastUpdated = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7da7fcefc4434d3e9b5e16edf46de990
|
||||
timeCreated: 1708638443
|
||||
@ -0,0 +1,884 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
using UnityEngine.SceneManagement;
|
||||
using VRC.SDK3.Data;
|
||||
using VRC.SDK3.Persistence;
|
||||
using VRC.Udon;
|
||||
#endif
|
||||
|
||||
namespace VRC.SDK3.ClientSim.Persistence
|
||||
{
|
||||
[AddComponentMenu("")] // hides component in Add Component menu
|
||||
public class ClientSimPlayerDataStorage : ClientSimBehaviour
|
||||
{
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
public static string PlayerDataFolder => Path.Combine("ClientSimStorage", "PlayerData");
|
||||
internal static string PlayerDataFilePath(VRCPlayerApi player)
|
||||
{
|
||||
string root = Path.GetDirectoryName(Application.dataPath);
|
||||
string path = Path.Combine(root, PlayerDataFolder);
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
return path + "/PlayerData_" + $"{player.playerId}" + $"_{SceneManager.GetActiveScene().name}" + ".json";
|
||||
}
|
||||
|
||||
private VRCPlayerApi _player;
|
||||
private IClientSimUdonEventSender _udonEventSender;
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
|
||||
private readonly Dictionary<string, ClientSimPlayerDataPair> leData = new();
|
||||
private readonly Dictionary<string, PlayerData.Info> localInfoChanges = new();
|
||||
private readonly List<PlayerData.Info> queuedPlayerDataUpdates = new();
|
||||
|
||||
private bool doDecode;
|
||||
private bool isDoneDecoding;
|
||||
private bool hasPostedPlayerDataDecoded;
|
||||
private bool hasPostedPlayerRestored;
|
||||
|
||||
public int Size
|
||||
=> System.Text.Encoding.UTF8.GetByteCount(JsonConvert.SerializeObject(leData, Formatting.Indented, new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
}));
|
||||
|
||||
public IEnumerable<string> GetKeys()
|
||||
{
|
||||
return leData.Keys;
|
||||
}
|
||||
|
||||
public bool HasKey(string key) => leData.ContainsKey(key);
|
||||
|
||||
public Type GetType(string key)
|
||||
{
|
||||
if (!leData.TryGetValue(key, out ClientSimPlayerDataPair value))
|
||||
return null;
|
||||
|
||||
switch (value.Value.Type)
|
||||
{
|
||||
default:
|
||||
case ClientSimPlayerDataType.None:
|
||||
return null;
|
||||
case ClientSimPlayerDataType.Vector2:
|
||||
return typeof(Vector2);
|
||||
case ClientSimPlayerDataType.Vector3:
|
||||
return typeof(Vector3);
|
||||
case ClientSimPlayerDataType.Vector4:
|
||||
return typeof(Vector4);
|
||||
case ClientSimPlayerDataType.Quaternion:
|
||||
return typeof(Quaternion);
|
||||
case ClientSimPlayerDataType.Color:
|
||||
return typeof(Color);
|
||||
case ClientSimPlayerDataType.Color32:
|
||||
return typeof(Color32);
|
||||
case ClientSimPlayerDataType.WrappedString:
|
||||
return typeof(string);
|
||||
case ClientSimPlayerDataType.WrappedShort:
|
||||
return typeof(short);
|
||||
case ClientSimPlayerDataType.WrappedUShort:
|
||||
return typeof(ushort);
|
||||
case ClientSimPlayerDataType.WrappedInt:
|
||||
return typeof(int);
|
||||
case ClientSimPlayerDataType.WrappedUInt:
|
||||
return typeof(uint);
|
||||
case ClientSimPlayerDataType.WrappedLong:
|
||||
return typeof(long);
|
||||
case ClientSimPlayerDataType.WrappedULong:
|
||||
return typeof(ulong);
|
||||
case ClientSimPlayerDataType.WrappedFloat:
|
||||
return typeof(float);
|
||||
case ClientSimPlayerDataType.WrappedDouble:
|
||||
return typeof(double);
|
||||
case ClientSimPlayerDataType.WrappedBool:
|
||||
return typeof(bool);
|
||||
case ClientSimPlayerDataType.WrappedByte:
|
||||
return typeof(sbyte);
|
||||
case ClientSimPlayerDataType.WrappedUByte:
|
||||
return typeof(byte);
|
||||
case ClientSimPlayerDataType.WrappedBytes:
|
||||
return typeof(byte[]);
|
||||
}
|
||||
}
|
||||
|
||||
#region Setters
|
||||
|
||||
private PlayerData.State _SetWrapper(string key, Func<ClientSimPlayerDataPair, bool> set, bool isRestore, bool flushChanges, DateTime lastUpdated)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
throw new ArgumentException("Key was invalid");
|
||||
|
||||
if (!isRestore && leData.TryGetValue(key, out ClientSimPlayerDataPair value))
|
||||
{
|
||||
if (set(value))
|
||||
{
|
||||
value.LastUpdated = DateTime.Now;
|
||||
localInfoChanges[key] = new PlayerData.Info(key, PlayerData.State.Changed);
|
||||
if (flushChanges)
|
||||
{
|
||||
FlushLocalInfoChanges();
|
||||
}
|
||||
return PlayerData.State.Changed;
|
||||
}
|
||||
return PlayerData.State.Unchanged;
|
||||
}
|
||||
|
||||
value = new ClientSimPlayerDataPair { Key = key, LastUpdated = isRestore ? lastUpdated : DateTime.Now };
|
||||
leData[key] = value;
|
||||
set(value);
|
||||
|
||||
var state = isRestore ? PlayerData.State.Restored : PlayerData.State.Added;
|
||||
localInfoChanges[key] = new PlayerData.Info(key, state);
|
||||
if (flushChanges)
|
||||
{
|
||||
FlushLocalInfoChanges();
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
public PlayerData.State SetBool(string key, bool value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedBool)
|
||||
{
|
||||
if (pair.Value.AsWrappedBool() == value)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedBool,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetByte(string key, byte value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedUByte)
|
||||
{
|
||||
if (pair.Value.AsWrappedUByte() == value)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedUByte,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetSByte(string key, sbyte value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedByte)
|
||||
{
|
||||
if (pair.Value.AsWrappedSByte() == value)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedByte,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetBytes(string key, byte[] value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedBytes)
|
||||
{
|
||||
if (pair.Value.AsWrappedBytes().SequenceEqual(value))
|
||||
return false;
|
||||
|
||||
pair.Value.Value = value.ToArray();
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedBytes,
|
||||
Value = value.ToArray()
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetString(string key, string value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedString)
|
||||
{
|
||||
if (pair.Value.AsWrappedString() == value)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedString,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetShort(string key, short value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedShort)
|
||||
{
|
||||
if (pair.Value.AsWrappedShort() == value)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedShort,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetUShort(string key, ushort value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedUShort)
|
||||
{
|
||||
if (pair.Value.AsWrappedUShort() == value)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedUShort,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetInt(string key, int value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedInt)
|
||||
{
|
||||
if (pair.Value.AsWrappedInt() == value)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedInt,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetUInt(string key, uint value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedUInt)
|
||||
{
|
||||
if (pair.Value.AsWrappedUInt() == value)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedUInt,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetLong(string key, long value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedLong)
|
||||
{
|
||||
if (pair.Value.AsWrappedLong() == value)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedLong,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetULong(string key, ulong value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedULong)
|
||||
{
|
||||
if (pair.Value.AsWrappedULong() == value)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedULong,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetFloat(string key, float value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedFloat)
|
||||
{
|
||||
if (Math.Abs(pair.Value.AsWrappedFloat() - value) < 0.000001f)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedFloat,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetDouble(string key, double value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedDouble)
|
||||
{
|
||||
if (Math.Abs(pair.Value.AsWrappedDouble() - value) < 0.000001)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.WrappedDouble,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetVector2(string key, Vector2 value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.Vector2)
|
||||
{
|
||||
if (Math.Abs(pair.Value.AsVector2().x - value.x) < 0.000001f
|
||||
&& Math.Abs(pair.Value.AsVector2().y - value.y) < 0.000001f)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.Vector2,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetVector3(string key, Vector3 value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.Vector3)
|
||||
{
|
||||
if (Math.Abs(pair.Value.AsVector3().x - value.x) < 0.000001f
|
||||
&& Math.Abs(pair.Value.AsVector3().y - value.y) < 0.000001f
|
||||
&& Math.Abs(pair.Value.AsVector3().z - value.z) < 0.000001f)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.Vector3,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetVector4(string key, Vector4 value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.Vector4)
|
||||
{
|
||||
if (Math.Abs(pair.Value.AsVector4().x - value.x) < 0.000001f
|
||||
&& Math.Abs(pair.Value.AsVector4().y - value.y) < 0.000001f
|
||||
&& Math.Abs(pair.Value.AsVector4().z - value.z) < 0.000001f
|
||||
&& Math.Abs(pair.Value.AsVector4().w - value.w) < 0.000001f)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.Vector4,
|
||||
Value = value
|
||||
};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetQuaternion(string key, Quaternion value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.Quaternion)
|
||||
{
|
||||
if (Math.Abs(pair.Value.AsQuaternion().x - value.x) < 0.000001f
|
||||
&& Math.Abs(pair.Value.AsQuaternion().y - value.y) < 0.000001f
|
||||
&& Math.Abs(pair.Value.AsQuaternion().z - value.z) < 0.000001f
|
||||
&& Math.Abs(pair.Value.AsQuaternion().w - value.w) < 0.000001f)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.Quaternion,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetColor(string key, Color value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.Color)
|
||||
{
|
||||
var color = pair.Value.AsColor();
|
||||
if (Math.Abs(color.r - value.r) < 0.01f
|
||||
&& Math.Abs(color.g - value.g) < 0.01f
|
||||
&& Math.Abs(color.b - value.b) < 0.01f
|
||||
&& Math.Abs(color.a - value.a) < 0.01f)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.Color,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public PlayerData.State SetColor32(string key, Color32 value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
|
||||
{
|
||||
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
|
||||
|
||||
bool Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
if (pair.Value?.Type == ClientSimPlayerDataType.Color32)
|
||||
{
|
||||
var color32 = pair.Value.AsColor32();
|
||||
if (Math.Abs(color32.r - value.r) < 0.000001f
|
||||
&& Math.Abs(color32.g - value.g) < 0.000001f
|
||||
&& Math.Abs(color32.b - value.b) < 0.000001f
|
||||
&& Math.Abs(color32.a - value.a) < 0.000001f)
|
||||
return false;
|
||||
pair.Value.Value = value;
|
||||
}
|
||||
else
|
||||
pair.Value = new ClientSimPlayerDataTypeUnion()
|
||||
{
|
||||
Type = ClientSimPlayerDataType.Color32,
|
||||
Value = value
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Getters
|
||||
|
||||
private ClientSimPlayerDataPair _GetChecked(string key, ClientSimPlayerDataType expectedType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
throw new ArgumentException("Key was invalid");
|
||||
if (!leData.TryGetValue(key, out ClientSimPlayerDataPair data) || data.Value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.Value.Type != expectedType)
|
||||
{
|
||||
this.LogError($"Data at {key} was not a {expectedType}, it was a {data.Value.Type}");
|
||||
return null;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public bool GetBool(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedBool)?.Value.AsWrappedBool() ?? default;
|
||||
|
||||
public byte GetByte(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedUByte)?.Value.AsWrappedUByte() ?? default;
|
||||
|
||||
public sbyte GetSByte(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedByte)?.Value.AsWrappedSByte() ?? default;
|
||||
|
||||
public byte[] GetBytes(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedBytes)?.Value.AsWrappedBytes().ToArray();
|
||||
|
||||
public string GetString(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedString)?.Value.AsWrappedString();
|
||||
|
||||
public short GetShort(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedShort)?.Value.AsWrappedShort() ?? default;
|
||||
|
||||
public ushort GetUShort(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedUShort)?.Value.AsWrappedUShort() ?? default;
|
||||
|
||||
public int GetInt(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedInt)?.Value.AsWrappedInt() ?? default;
|
||||
|
||||
public uint GetUInt(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedUInt)?.Value.AsWrappedUInt() ?? default;
|
||||
|
||||
public long GetLong(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedLong)?.Value.AsWrappedLong() ?? default;
|
||||
|
||||
public ulong GetULong(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedULong)?.Value.AsWrappedULong() ?? default;
|
||||
|
||||
public float GetFloat(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedFloat)?.Value.AsWrappedFloat() ?? default;
|
||||
|
||||
public double GetDouble(string key)
|
||||
=> _GetChecked(key, ClientSimPlayerDataType.WrappedDouble)?.Value.AsWrappedDouble() ?? default;
|
||||
|
||||
public Vector2 GetVector2(string key)
|
||||
{
|
||||
ClientSimPlayerDataPair data = _GetChecked(key, ClientSimPlayerDataType.Vector2);
|
||||
if (data != null)
|
||||
return new Vector2(data.Value.AsVector2().x, data.Value.AsVector2().y);
|
||||
return default;
|
||||
}
|
||||
|
||||
public Vector3 GetVector3(string key)
|
||||
{
|
||||
ClientSimPlayerDataPair data = _GetChecked(key, ClientSimPlayerDataType.Vector3);
|
||||
if (data != null)
|
||||
return new Vector3(data.Value.AsVector3().x, data.Value.AsVector3().y, data.Value.AsVector3().z);
|
||||
return default;
|
||||
}
|
||||
|
||||
public Vector4 GetVector4(string key)
|
||||
{
|
||||
ClientSimPlayerDataPair data = _GetChecked(key, ClientSimPlayerDataType.Vector4);
|
||||
if (data != null)
|
||||
return new Vector4(data.Value.AsVector4().x, data.Value.AsVector4().y, data.Value.AsVector4().z, data.Value.AsVector4().w);
|
||||
return default;
|
||||
}
|
||||
|
||||
public Quaternion GetQuaternion(string key)
|
||||
{
|
||||
ClientSimPlayerDataPair data = _GetChecked(key, ClientSimPlayerDataType.Quaternion);
|
||||
if (data != null)
|
||||
return new Quaternion(data.Value.AsQuaternion().x, data.Value.AsQuaternion().y, data.Value.AsQuaternion().z, data.Value.AsQuaternion().w);
|
||||
return default;
|
||||
}
|
||||
|
||||
public Color GetColor(string key)
|
||||
{
|
||||
ClientSimPlayerDataPair data = _GetChecked(key, ClientSimPlayerDataType.Color);
|
||||
if (data != null)
|
||||
return new Color(data.Value.AsColor().r, data.Value.AsColor().g, data.Value.AsColor().b, data.Value.AsColor().a);
|
||||
return default;
|
||||
}
|
||||
|
||||
public Color32 GetColor32(string key)
|
||||
{
|
||||
ClientSimPlayerDataPair data = _GetChecked(key, ClientSimPlayerDataType.Color32);
|
||||
if (data != null)
|
||||
return new Color32(data.Value.AsColor32().r, data.Value.AsColor32().g, data.Value.AsColor32().b, data.Value.AsColor32().a);
|
||||
return default;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DataPropagation
|
||||
|
||||
// can't decode json here because this is called before scene manager is ready
|
||||
public void Init(VRCPlayerApi player, IClientSimUdonEventSender udonEventSender, IClientSimEventDispatcher eventDispatcher)
|
||||
{
|
||||
_player = player;
|
||||
_udonEventSender = udonEventSender;
|
||||
_eventDispatcher = eventDispatcher;
|
||||
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerDataClearedEvent>(OnPlayerDataCleared);
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerRestoredEvent>(OnPlayerRestored);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnPlayerDataClearedEvent>(OnPlayerDataCleared);
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnPlayerRestoredEvent>(OnPlayerRestored);
|
||||
}
|
||||
|
||||
private void OnPlayerJoined(ClientSimOnPlayerJoinedEvent payload)
|
||||
{
|
||||
// remote player test data is decoded via ClientSimPlayerDataWindow.OnPlayerJoined
|
||||
if (payload.player.playerId == _player.playerId)
|
||||
doDecode = true;
|
||||
}
|
||||
|
||||
private void OnPlayerDataCleared(ClientSimOnPlayerDataClearedEvent payload)
|
||||
{
|
||||
leData.Clear();
|
||||
}
|
||||
|
||||
private void OnPlayerRestored(ClientSimOnPlayerRestoredEvent payload)
|
||||
{
|
||||
if (payload.player.isLocal)
|
||||
hasPostedPlayerRestored = true;
|
||||
}
|
||||
|
||||
private void Encode()
|
||||
{
|
||||
// PlayerData updates before OnPlayerRestored are ignored
|
||||
if (!hasPostedPlayerRestored)
|
||||
return;
|
||||
|
||||
string json = JsonConvert.SerializeObject(leData, Formatting.Indented, new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
});
|
||||
File.WriteAllText(PlayerDataFilePath(_player), json);
|
||||
}
|
||||
|
||||
internal void Decode(bool isRestore)
|
||||
{
|
||||
string path = PlayerDataFilePath(_player);
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
File.WriteAllText(path, "{}");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(path);
|
||||
var data = JsonConvert.DeserializeObject<Dictionary<string, ClientSimPlayerDataPair>>(json);
|
||||
foreach (KeyValuePair<string, ClientSimPlayerDataPair> kvp in data)
|
||||
{
|
||||
Set(kvp.Value);
|
||||
}
|
||||
|
||||
FlushLocalInfoChanges(); // bulk flush changes
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.LogError($"Error initializing PlayerData: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
isDoneDecoding = true;
|
||||
|
||||
PlayerData.State Set(ClientSimPlayerDataPair pair)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (pair.Value.Type)
|
||||
{
|
||||
case ClientSimPlayerDataType.Color: return SetColor(pair.Key, pair.Value.AsColor(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.Color32: return SetColor32(pair.Key, pair.Value.AsColor32(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.Quaternion: return SetQuaternion(pair.Key, pair.Value.AsQuaternion(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.Vector2: return SetVector2(pair.Key, pair.Value.AsVector2(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.Vector3: return SetVector3(pair.Key, pair.Value.AsVector3(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.Vector4: return SetVector4(pair.Key, pair.Value.AsVector4(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedBool: return SetBool(pair.Key, pair.Value.AsWrappedBool(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedByte: return SetSByte(pair.Key, pair.Value.AsWrappedSByte(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedUByte: return SetByte(pair.Key, pair.Value.AsWrappedByte(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedBytes: return SetBytes(pair.Key, pair.Value.AsWrappedBytes(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedFloat: return SetFloat(pair.Key, pair.Value.AsWrappedFloat(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedDouble: return SetDouble(pair.Key, pair.Value.AsWrappedDouble(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedLong: return SetLong(pair.Key, pair.Value.AsWrappedLong(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedULong: return SetULong(pair.Key, pair.Value.AsWrappedULong(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedInt: return SetInt(pair.Key, pair.Value.AsWrappedInt(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedUInt: return SetUInt(pair.Key, pair.Value.AsWrappedUInt(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedShort: return SetShort(pair.Key, pair.Value.AsWrappedShort(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedUShort: return SetUShort(pair.Key, pair.Value.AsWrappedUShort(), pair.LastUpdated, isRestore, false);
|
||||
case ClientSimPlayerDataType.WrappedString: return SetString(pair.Key, pair.Value.AsWrappedString(), pair.LastUpdated, isRestore, false);
|
||||
|
||||
default:
|
||||
case ClientSimPlayerDataType.None: return PlayerData.State.Unchanged;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.LogError("Error reading PlayerData: " +
|
||||
$"key={pair.Key}, " +
|
||||
$"type={pair.Value.Type} ({pair.Value.Value.GetType()}), " +
|
||||
$"value={pair.Value.Value}, " +
|
||||
$"error={e.Message}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FlushLocalInfoChanges()
|
||||
{
|
||||
var infos = leData
|
||||
.Select(kvp =>
|
||||
localInfoChanges.TryGetValue(kvp.Key, out var value)
|
||||
? value
|
||||
: new PlayerData.Info(kvp.Key, PlayerData.State.Unchanged))
|
||||
.ToArray();
|
||||
|
||||
QueuePlayerDataUpdate(infos);
|
||||
|
||||
localInfoChanges.Clear();
|
||||
}
|
||||
|
||||
internal void QueuePlayerDataUpdate(PlayerData.Info[] infos)
|
||||
{
|
||||
if (infos.Length == 0)
|
||||
return;
|
||||
|
||||
queuedPlayerDataUpdates.AddRange(infos);
|
||||
}
|
||||
|
||||
private void RaisePlayerDataUpdated(PlayerData.Info[] infos)
|
||||
{
|
||||
_udonEventSender.RunEvent(UdonManager.UDON_EVENT_ONPLAYERDATAUPDATED, ("player", _player), ("infos", infos));
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPlayerDataUpdatedEvent
|
||||
{
|
||||
player = _player,
|
||||
playerData = leData
|
||||
});
|
||||
}
|
||||
|
||||
private float lastCheckTime = 0;
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (queuedPlayerDataUpdates.Count > 0)
|
||||
{
|
||||
Encode();
|
||||
RaisePlayerDataUpdated(queuedPlayerDataUpdates.ToArray());
|
||||
queuedPlayerDataUpdates.Clear();
|
||||
}
|
||||
|
||||
if (doDecode)
|
||||
{
|
||||
doDecode = false;
|
||||
Decode(true);
|
||||
}
|
||||
else if (isDoneDecoding && !hasPostedPlayerDataDecoded)
|
||||
{
|
||||
hasPostedPlayerDataDecoded = true;
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPlayerDataDecodedEvent { player = _player });
|
||||
}
|
||||
|
||||
if (Time.realtimeSinceStartup - lastCheckTime > 30f)
|
||||
{
|
||||
lastCheckTime = Time.realtimeSinceStartup;
|
||||
float dataLimit = ClientSimMain.TryGetInstance(out var instance) ? instance.GetPlayerDataUsageLimit() : 0;
|
||||
float sz = Size;
|
||||
|
||||
if (sz >= dataLimit)
|
||||
UdonManager.Instance.RunEvent(UdonManager.UDON_EVENT_ONPLAYERDATASTORAGEEXCEEDED, ("player", _player));
|
||||
else if (sz >= dataLimit * 0.7f)
|
||||
UdonManager.Instance.RunEvent(UdonManager.UDON_EVENT_ONPLAYERDATASTORAGEWARNING, ("player", _player));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ef04bdeba52437cb909dc841044fd57
|
||||
timeCreated: 1708619978
|
||||
@ -0,0 +1,28 @@
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
namespace VRC.SDK3.ClientSim.Persistence
|
||||
{
|
||||
public enum ClientSimPlayerDataType : byte
|
||||
{
|
||||
None = 0,
|
||||
Vector2 = 1,
|
||||
Vector3 = 2,
|
||||
Vector4 = 3,
|
||||
Quaternion = 4,
|
||||
Color = 5,
|
||||
Color32 = 6,
|
||||
WrappedString = 7,
|
||||
WrappedShort = 8,
|
||||
WrappedInt = 9,
|
||||
WrappedFloat = 10,
|
||||
WrappedBool = 11,
|
||||
WrappedByte = 12,
|
||||
WrappedBytes = 13,
|
||||
WrappedUShort = 14,
|
||||
WrappedUByte = 15,
|
||||
WrappedUInt = 16,
|
||||
WrappedULong = 17,
|
||||
WrappedDouble = 18,
|
||||
WrappedLong = 19,
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee1e38bff02f4812896fb863095a29ad
|
||||
timeCreated: 1708638423
|
||||
@ -0,0 +1,39 @@
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim.Persistence
|
||||
{
|
||||
[JsonConverter(typeof(ClientSimPlayerDataConverter))]
|
||||
public class ClientSimPlayerDataTypeUnion
|
||||
{
|
||||
public object Value { get; set; }
|
||||
public ClientSimPlayerDataType Type { get; set; }
|
||||
public ClientSimPlayerDataTypeUnion() {
|
||||
this.Type = ClientSimPlayerDataType.None;
|
||||
this.Value = null;
|
||||
}
|
||||
|
||||
public Vector2 AsVector2() => (Vector2)Value;
|
||||
public Vector3 AsVector3() => (Vector3)Value;
|
||||
public Vector4 AsVector4() => (Vector4)Value;
|
||||
public Quaternion AsQuaternion() => (Quaternion)Value;
|
||||
public Color AsColor() => (Color)Value;
|
||||
public Color32 AsColor32() => (Color32)Value;
|
||||
public string AsWrappedString() { return (string)Value; }
|
||||
public short AsWrappedShort() { return (short)Value; }
|
||||
public ushort AsWrappedUShort() { return (ushort)Value; }
|
||||
public int AsWrappedInt() { return (int)Value; }
|
||||
public uint AsWrappedUInt() { return (uint)Value; }
|
||||
public long AsWrappedLong() { return (long)Value; }
|
||||
public ulong AsWrappedULong() { return ( ulong)Value; }
|
||||
public float AsWrappedFloat() { return ( float)Value; }
|
||||
public double AsWrappedDouble() { return (double)Value; }
|
||||
public bool AsWrappedBool() { return (bool)Value; }
|
||||
public byte AsWrappedByte() { return (byte)Value ; }
|
||||
public sbyte AsWrappedSByte() { return (sbyte)Value; }
|
||||
public byte[] AsWrappedBytes() { return (byte[])Value; }
|
||||
public byte AsWrappedUByte() { return (byte)Value; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53d85c02f89642f49608b67f2788c77a
|
||||
timeCreated: 1708638433
|
||||
@ -0,0 +1,472 @@
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.SDK3.ClientSim.Persistence
|
||||
{
|
||||
public static class ClientSimPlayerDataWrapper
|
||||
{
|
||||
public static void ConfigureSDK()
|
||||
{
|
||||
System.Func<VRCPlayerApi, IEnumerable<string>> _getKeys = GetKeys;
|
||||
|
||||
FieldInfo getKeysInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getKeys", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getKeysInfo?.SetValue(null, _getKeys);
|
||||
|
||||
System.Func<VRCPlayerApi, string, bool> _hasKey = HasKey;
|
||||
|
||||
FieldInfo hasKeyInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_hasKey", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
hasKeyInfo?.SetValue(null, _hasKey);
|
||||
|
||||
System.Func<VRCPlayerApi, string, Type> _getType = GetType;
|
||||
|
||||
FieldInfo getTypeInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getType", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getTypeInfo?.SetValue(null, _getType);
|
||||
|
||||
System.Action<string, bool> _setBool = SetBool;
|
||||
System.Func<VRCPlayerApi, string, bool> _getBool = GetBool;
|
||||
|
||||
FieldInfo setBoolInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setBool", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setBoolInfo?.SetValue(null, _setBool);
|
||||
|
||||
FieldInfo getBoolInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getBool", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getBoolInfo?.SetValue(null, _getBool);
|
||||
|
||||
System.Action<string, byte> _setByte = SetByte;
|
||||
System.Func<VRCPlayerApi, string, byte> _getByte = GetByte;
|
||||
|
||||
FieldInfo setByteInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setByte", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setByteInfo?.SetValue(null, _setByte);
|
||||
|
||||
FieldInfo getByteInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getByte", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getByteInfo?.SetValue(null, _getByte);
|
||||
|
||||
System.Action<string, sbyte> _setUByte = SetSByte;
|
||||
System.Func<VRCPlayerApi, string, sbyte> _getUByte = GetSByte;
|
||||
|
||||
FieldInfo setUByteInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setSByte", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setUByteInfo?.SetValue(null, _setUByte);
|
||||
|
||||
FieldInfo getUByteInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getSByte", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getUByteInfo?.SetValue(null, _getUByte);
|
||||
|
||||
System.Action<string, byte[]> _setBytes = SetBytes;
|
||||
System.Func<VRCPlayerApi, string, byte[]> _getBytes = GetBytes;
|
||||
|
||||
FieldInfo setBytesInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setBytes", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setBytesInfo?.SetValue(null, _setBytes);
|
||||
|
||||
FieldInfo getBytesInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getBytes", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getBytesInfo?.SetValue(null, _getBytes);
|
||||
|
||||
System.Action<string, string> _setString = SetString;
|
||||
System.Func<VRCPlayerApi, string, string> _getString = GetString;
|
||||
|
||||
FieldInfo setStringInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setString", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setStringInfo?.SetValue(null, _setString);
|
||||
|
||||
FieldInfo getStringInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getString", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getStringInfo?.SetValue(null, _getString);
|
||||
|
||||
System.Action<string, short> _setShort = SetShort;
|
||||
System.Func<VRCPlayerApi, string, short> _getShort = GetShort;
|
||||
|
||||
FieldInfo setShortInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setShort", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setShortInfo?.SetValue(null, _setShort);
|
||||
|
||||
FieldInfo getShortInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getShort", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getShortInfo?.SetValue(null, _getShort);
|
||||
|
||||
System.Action<string, ushort> _setUShort = SetUShort;
|
||||
System.Func<VRCPlayerApi, string, ushort> _getUShort = GetUShort;
|
||||
|
||||
FieldInfo setUShortInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setUShort", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setUShortInfo?.SetValue(null, _setUShort);
|
||||
|
||||
FieldInfo getUShortInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getUShort", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getUShortInfo?.SetValue(null, _getUShort);
|
||||
|
||||
System.Action<string, int> _setInt = SetInt;
|
||||
System.Func<VRCPlayerApi, string, int> _getInt = GetInt;
|
||||
|
||||
FieldInfo setIntInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setInt", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setIntInfo?.SetValue(null, _setInt);
|
||||
|
||||
FieldInfo getIntInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getInt", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getIntInfo?.SetValue(null, _getInt);
|
||||
|
||||
System.Action<string, uint> _setUInt = SetUInt;
|
||||
System.Func<VRCPlayerApi, string, uint> _getUInt = GetUInt;
|
||||
|
||||
FieldInfo setUIntInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setUInt", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setUIntInfo?.SetValue(null, _setUInt);
|
||||
|
||||
FieldInfo getUIntInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getUInt", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getUIntInfo?.SetValue(null, _getUInt);
|
||||
|
||||
System.Action<string, long> _setLong = SetLong;
|
||||
System.Func<VRCPlayerApi, string, long> _getLong = GetLong;
|
||||
|
||||
FieldInfo setLongInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setLong", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setLongInfo?.SetValue(null, _setLong);
|
||||
|
||||
FieldInfo getLongInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getLong", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getLongInfo?.SetValue(null, _getLong);
|
||||
|
||||
System.Action<string, ulong> _setULong = SetULong;
|
||||
System.Func<VRCPlayerApi, string, ulong> _getULong = GetULong;
|
||||
|
||||
FieldInfo setULongInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setULong", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setULongInfo?.SetValue(null, _setULong);
|
||||
|
||||
FieldInfo getULongInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getULong", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getULongInfo?.SetValue(null, _getULong);
|
||||
|
||||
System.Action<string, double> _setDouble = SetDouble;
|
||||
System.Func<VRCPlayerApi, string, double> _getDouble = GetDouble;
|
||||
|
||||
FieldInfo setDoubleInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setDouble", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setDoubleInfo?.SetValue(null, _setDouble);
|
||||
|
||||
FieldInfo getDoubleInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getDouble", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getDoubleInfo?.SetValue(null, _getDouble);
|
||||
|
||||
System.Action<string, float> _setFloat = SetFloat;
|
||||
System.Func<VRCPlayerApi, string, float> _getFloat = GetFloat;
|
||||
|
||||
FieldInfo setFloatInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setFloat", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setFloatInfo?.SetValue(null, _setFloat);
|
||||
|
||||
FieldInfo getFloatInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getFloat", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getFloatInfo?.SetValue(null, _getFloat);
|
||||
|
||||
System.Action<string, Vector2> _setVector2 = SetVector2;
|
||||
System.Func<VRCPlayerApi, string, Vector2> _getVector2 = GetVector2;
|
||||
|
||||
FieldInfo setVector2Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setVector2", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setVector2Info?.SetValue(null, _setVector2);
|
||||
|
||||
FieldInfo getVector2Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getVector2", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getVector2Info?.SetValue(null, _getVector2);
|
||||
|
||||
System.Action<string, Vector3> _setVector3 = SetVector3;
|
||||
System.Func<VRCPlayerApi, string, Vector3> _getVector3 = GetVector3;
|
||||
|
||||
FieldInfo setVector3Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setVector3", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setVector3Info?.SetValue(null, _setVector3);
|
||||
|
||||
FieldInfo getVector3Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getVector3", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getVector3Info?.SetValue(null, _getVector3);
|
||||
|
||||
System.Action<string, Vector4> _setVector4 = SetVector4;
|
||||
System.Func<VRCPlayerApi, string, Vector4> _getVector4 = GetVector4;
|
||||
|
||||
FieldInfo setVector4Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setVector4", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setVector4Info?.SetValue(null, _setVector4);
|
||||
|
||||
FieldInfo getVector4Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getVector4", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getVector4Info?.SetValue(null, _getVector4);
|
||||
|
||||
System.Action<string, Quaternion> _setQuaternion = SetQuaternion;
|
||||
System.Func<VRCPlayerApi, string, Quaternion> _getQuaternion = GetQuaternion;
|
||||
|
||||
FieldInfo setQuaternionInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setQuaternion", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setQuaternionInfo?.SetValue(null, _setQuaternion);
|
||||
|
||||
FieldInfo getQuaternionInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getQuaternion", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getQuaternionInfo?.SetValue(null, _getQuaternion);
|
||||
|
||||
System.Action<string, UnityEngine.Color> _setColor = SetColor;
|
||||
System.Func<VRCPlayerApi, string, UnityEngine.Color> _getColor = GetColor;
|
||||
|
||||
FieldInfo setColorInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setColor", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setColorInfo?.SetValue(null, _setColor);
|
||||
|
||||
FieldInfo getColorInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getColor", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getColorInfo?.SetValue(null, _getColor);
|
||||
|
||||
System.Action<string, UnityEngine.Color32> _setColor32 = SetColor32;
|
||||
System.Func<VRCPlayerApi, string, UnityEngine.Color32> _getColor32 = GetColor32;
|
||||
|
||||
FieldInfo setColor32Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setColor32", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
setColor32Info?.SetValue(null, _setColor32);
|
||||
|
||||
FieldInfo getColor32Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getColor32", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
getColor32Info?.SetValue(null, _getColor32);
|
||||
}
|
||||
|
||||
public static int GetUsage(VRCPlayerApi player)
|
||||
=> FindStorage(player, out ClientSimPlayerDataStorage storage) ? storage.Size : 0;
|
||||
|
||||
private static bool FindStorage(VRCPlayerApi playerApi, out ClientSimPlayerDataStorage storage)
|
||||
{
|
||||
ClientSimPlayer player = playerApi.GetClientSimPlayer();
|
||||
if (!player)
|
||||
{
|
||||
UnityEngine.Debug.LogError("Could not locate player with id " + playerApi.playerId + " for PlayerData storage.");
|
||||
storage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
storage = player.PlayerDataObject;
|
||||
if (!storage)
|
||||
{
|
||||
UnityEngine.Debug.LogError("Could not locate PlayerData storage for player " + playerApi.playerId);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetKeys(VRCPlayerApi playerApi)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return Enumerable.Empty<string>();
|
||||
return storage.GetKeys();
|
||||
}
|
||||
|
||||
public static bool HasKey(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return false;
|
||||
return storage.HasKey(key);
|
||||
}
|
||||
|
||||
public static Type GetType(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return null;
|
||||
return storage.GetType(key);
|
||||
}
|
||||
|
||||
public static void SetBool(string key, bool data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetBool(key, data);
|
||||
}
|
||||
|
||||
public static bool GetBool(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetBool(key);
|
||||
}
|
||||
|
||||
public static void SetByte(string key, byte data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetByte(key, data);
|
||||
}
|
||||
|
||||
public static byte GetByte(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetByte(key);
|
||||
}
|
||||
|
||||
public static void SetSByte(string key, sbyte data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetSByte(key, data);
|
||||
}
|
||||
|
||||
public static sbyte GetSByte(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetSByte(key);
|
||||
}
|
||||
|
||||
public static void SetBytes(string key, byte[] data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetBytes(key, data);
|
||||
}
|
||||
|
||||
public static byte[] GetBytes(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetBytes(key);
|
||||
}
|
||||
|
||||
public static void SetString(string key, string data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetString(key, data);
|
||||
}
|
||||
|
||||
public static string GetString(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetString(key);
|
||||
}
|
||||
|
||||
public static void SetShort(string key, short data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetShort(key, data);
|
||||
}
|
||||
|
||||
public static void SetUShort(string key, ushort data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetUShort(key, data);
|
||||
}
|
||||
|
||||
public static short GetShort(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetShort(key);
|
||||
}
|
||||
|
||||
public static ushort GetUShort(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetUShort(key);
|
||||
}
|
||||
|
||||
public static void SetInt(string key, int data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetInt(key, data);
|
||||
}
|
||||
|
||||
public static void SetUInt(string key, uint data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetUInt(key, data);
|
||||
}
|
||||
|
||||
public static int GetInt(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetInt(key);
|
||||
}
|
||||
|
||||
public static uint GetUInt(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetUInt(key);
|
||||
}
|
||||
|
||||
public static void SetLong(string key, long data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetLong(key, data);
|
||||
}
|
||||
|
||||
public static long GetLong(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetLong(key);
|
||||
}
|
||||
|
||||
public static void SetULong(string key, ulong data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetULong(key, data);
|
||||
}
|
||||
|
||||
public static ulong GetULong(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetULong(key);
|
||||
}
|
||||
|
||||
public static void SetFloat(string key, float data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetFloat(key, data);
|
||||
}
|
||||
|
||||
public static float GetFloat(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetFloat(key);
|
||||
}
|
||||
|
||||
public static void SetDouble(string key, double data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetDouble(key, data);
|
||||
}
|
||||
|
||||
public static double GetDouble(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetDouble(key);
|
||||
}
|
||||
|
||||
public static void SetVector2(string key, Vector2 data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetVector2(key, data);
|
||||
}
|
||||
|
||||
public static Vector2 GetVector2(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetVector2(key);
|
||||
}
|
||||
|
||||
public static void SetVector3(string key, Vector3 data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetVector3(key, data);
|
||||
}
|
||||
|
||||
public static Vector3 GetVector3(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetVector3(key);
|
||||
}
|
||||
|
||||
public static void SetVector4(string key, Vector4 data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetVector4(key, data);
|
||||
}
|
||||
|
||||
public static Vector4 GetVector4(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetVector4(key);
|
||||
}
|
||||
|
||||
public static void SetQuaternion(string key, Quaternion data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetQuaternion(key, data);
|
||||
}
|
||||
|
||||
public static Quaternion GetQuaternion(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetQuaternion(key);
|
||||
}
|
||||
|
||||
public static void SetColor(string key, UnityEngine.Color data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetColor(key, data);
|
||||
}
|
||||
|
||||
public static UnityEngine.Color GetColor(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetColor(key);
|
||||
}
|
||||
|
||||
public static void SetColor32(string key, UnityEngine.Color32 data)
|
||||
{
|
||||
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
|
||||
storage.SetColor32(key, data);
|
||||
}
|
||||
|
||||
public static UnityEngine.Color32 GetColor32(VRCPlayerApi playerApi, string key)
|
||||
{
|
||||
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
|
||||
return storage.GetColor32(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf5156727fe54ec1b396e496f91f03d4
|
||||
timeCreated: 1708639223
|
||||
@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using VRC.SDK3.ClientSim.Interfaces;
|
||||
using VRC.SDK3.Data;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon;
|
||||
|
||||
namespace VRC.SDK3.ClientSim.Persistence
|
||||
{
|
||||
[AddComponentMenu("")] // hides component in Add Component menu
|
||||
public class ClientSimPlayerObjectStorage : ClientSimBehaviour
|
||||
{
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
public static string PlayerObjectsFolder => Path.Combine("ClientSimStorage", "PlayerObjects");
|
||||
internal static string ActiveSceneName;
|
||||
internal static string PlayerDataFilePath(VRCPlayerApi player)
|
||||
{
|
||||
string root = Path.GetDirectoryName(Application.dataPath);
|
||||
string path = Path.Combine(root, PlayerObjectsFolder);
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
return path + "/PlayerObject_" + $"{player.playerId}" + $"_{ActiveSceneName}" + ".json";
|
||||
}
|
||||
|
||||
private VRCPlayerApi _player;
|
||||
private IClientSimUdonEventSender _udonEventSender;
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
|
||||
private Dictionary<int,IClientSimNetworkView> _persistentObjects = new Dictionary<int, IClientSimNetworkView>();
|
||||
private DataDictionary _persistentObjectData = new DataDictionary();
|
||||
|
||||
private bool _isInitialized = false;
|
||||
private bool _HasJoined = false;
|
||||
|
||||
private Coroutine _ContinuousUpdate;
|
||||
|
||||
private const float _updateInterval = 1 / 4f;
|
||||
|
||||
private bool hadUpdate = false;
|
||||
|
||||
public int Size =>
|
||||
VRCJson.TrySerializeToJson(_persistentObjectData, JsonExportType.Beautify, out DataToken json)
|
||||
? System.Text.Encoding.UTF8.GetByteCount(json.String)
|
||||
: 0;
|
||||
|
||||
public void Init(VRCPlayerApi player, IClientSimUdonEventSender udonEventSender, IClientSimEventDispatcher eventDispatcher)
|
||||
{
|
||||
_player = player;
|
||||
_udonEventSender = udonEventSender;
|
||||
_eventDispatcher = eventDispatcher;
|
||||
|
||||
ActiveSceneName = SceneManager.GetActiveScene().name;
|
||||
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
|
||||
UdonBehaviour.RequestSerializationHook += RequestSerializationHook;
|
||||
_isInitialized = true;
|
||||
|
||||
_ContinuousUpdate = StartCoroutine(UpdateContinuous());
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if(_eventDispatcher != null)
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
|
||||
|
||||
if(_ContinuousUpdate != null)
|
||||
StopCoroutine(_ContinuousUpdate);
|
||||
}
|
||||
|
||||
public IEnumerator UpdateContinuous()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (_HasJoined && _isInitialized)
|
||||
{
|
||||
Encode();
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(_updateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
public void RequestSerializationHook(UdonBehaviour udonBehaviour)
|
||||
{
|
||||
ClientSimPersistenceEventSending.Instance.QueueRequest(udonBehaviour, this);
|
||||
}
|
||||
|
||||
private void OnPlayerJoined(ClientSimOnPlayerJoinedEvent payload)
|
||||
{
|
||||
if (payload.player.playerId == _player.playerId)
|
||||
{
|
||||
_HasJoined = true;
|
||||
Decode();
|
||||
}
|
||||
}
|
||||
|
||||
public void Encode(GameObject gameObject = null)
|
||||
{
|
||||
if (!_isInitialized || !_HasJoined) return;
|
||||
|
||||
if(_persistentObjectData == null)
|
||||
_persistentObjectData = new DataDictionary();
|
||||
|
||||
foreach (var keyValuePersistentObject in _persistentObjects)
|
||||
{
|
||||
DataToken key = keyValuePersistentObject.Key.ToString();
|
||||
if (!_persistentObjectData.ContainsKey(key))
|
||||
_persistentObjectData.Add(key, new DataList());
|
||||
_persistentObjectData[key] = keyValuePersistentObject.Value.Encode(gameObject);
|
||||
}
|
||||
|
||||
hadUpdate = true;
|
||||
}
|
||||
|
||||
private async UniTask SaveToFile(string data)
|
||||
{
|
||||
await UniTask.SwitchToTaskPool();
|
||||
try{
|
||||
await File.WriteAllTextAsync(PlayerDataFilePath(_player), data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.LogError($"Error saving PlayerObjects: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void Decode()
|
||||
{
|
||||
string path = PlayerDataFilePath(_player);
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
File.WriteAllText(path, "{}");
|
||||
}
|
||||
|
||||
string json = File.ReadAllText(path);
|
||||
if (!VRCJson.TryDeserializeFromJson(json, out DataToken token))
|
||||
{
|
||||
this.LogError($"Error initializing PlayerObjects: {token.Error}");
|
||||
return;
|
||||
}
|
||||
|
||||
_persistentObjectData = token.DataDictionary;
|
||||
ClientSimPlayer player = _player.GetClientSimPlayer();
|
||||
foreach (GameObject persistantObject in player.PlayerPersistenceObjects)
|
||||
{
|
||||
IClientSimNetworkId networkId = persistantObject.GetComponent<IClientSimNetworkId>();
|
||||
if (networkId == null) continue;
|
||||
int id = networkId.GetNetworkId();
|
||||
|
||||
IClientSimNetworkView serializer = persistantObject.GetComponent<IClientSimNetworkView>();
|
||||
|
||||
_persistentObjects.TryAdd(id, serializer);
|
||||
if (_persistentObjectData.TryGetValue(id.ToString(), out var data))
|
||||
{
|
||||
_persistentObjects[id].Decode(data.DataList);
|
||||
}
|
||||
}
|
||||
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPlayerObjectsDecodedEvent { player = _player });
|
||||
}
|
||||
|
||||
private float lastCheckTime = 0;
|
||||
public void LateUpdate()
|
||||
{
|
||||
if (hadUpdate)
|
||||
{
|
||||
hadUpdate = false;
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPlayerObjectUpdateEndedEvent());
|
||||
|
||||
VRCJson.TrySerializeToJson(_persistentObjectData, JsonExportType.Beautify, out DataToken json);
|
||||
SaveToFile(json.String).Forget();
|
||||
}
|
||||
|
||||
if (Time.realtimeSinceStartup - lastCheckTime > 30f)
|
||||
{
|
||||
lastCheckTime = Time.realtimeSinceStartup;
|
||||
float objLimit = ClientSimMain.TryGetInstance(out var instance) ? instance.GetPlayerObjectsUsageLimit() : 0;
|
||||
float sz = Size;
|
||||
|
||||
if (sz >= objLimit)
|
||||
UdonManager.Instance.RunEvent(UdonManager.UDON_EVENT_ONPLAYEROBJECTSTORAGEEXCEEDED, ("player", _player));
|
||||
else if (sz >= objLimit * 0.7f)
|
||||
UdonManager.Instance.RunEvent(UdonManager.UDON_EVENT_ONPLAYERDATASTORAGEWARNING, ("player", _player));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06c904c1b8134763a58e319723ec67ad
|
||||
timeCreated: 1709650412
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4f803301349f4243a1d317f92168fc6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,218 @@
|
||||
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.ClientSim.PlayerTracking;
|
||||
using VRC.Udon.Common;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
// Listens to Events:
|
||||
// - ClientSimMouseReleasedEvent
|
||||
// - ClientSimOnPlayerEnteredStationEvent
|
||||
// - ClientSimOnPlayerExitedStationEvent
|
||||
// Listens to Input Events:
|
||||
// - ToggleCrouch
|
||||
// - ToggleProne
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimDesktopTrackingProvider : ClientSimTrackingProviderBase
|
||||
{
|
||||
[SerializeField]
|
||||
private Transform playerXRotationBase;
|
||||
[SerializeField]
|
||||
private Transform playerYRotationBase;
|
||||
|
||||
private bool _mouseReleased = false;
|
||||
private ClientSimDesktopTrackingRotator _desktopRotator;
|
||||
private IClientSimStation _currentStation;
|
||||
|
||||
public override void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimInput input,
|
||||
ClientSimSettings settings,
|
||||
IClientSimPlayerHeightManager heightManager)
|
||||
{
|
||||
base.Initialize(eventDispatcher, input, settings, heightManager);
|
||||
|
||||
SetTrackingItemPositions();
|
||||
|
||||
_desktopRotator = new ClientSimDesktopTrackingRotator(playerXRotationBase, playerYRotationBase);
|
||||
}
|
||||
|
||||
private void SetTrackingItemPositions()
|
||||
{
|
||||
head.localPosition = new Vector3(0, STANDING_HEIGHT, .1f);
|
||||
head.localRotation = Quaternion.identity;
|
||||
|
||||
rightHand.localPosition = new Vector3(0.15f, -0.13f, 0.4f);
|
||||
rightHand.localRotation = Quaternion.Euler(-35, 0, -90);
|
||||
|
||||
leftHand.localPosition = new Vector3(-0.15f, -0.13f, 0.4f);
|
||||
leftHand.localRotation = Quaternion.Euler(-35, 0, -90);
|
||||
}
|
||||
|
||||
#region IClientSimInputEventSubscribable
|
||||
|
||||
public override void SubscribeInputEvents()
|
||||
{
|
||||
base.SubscribeInputEvents();
|
||||
|
||||
input.SubscribeToggleCrouch(ToggleCrouchInput);
|
||||
input.SubscribeToggleProne(ToggleProneInput);
|
||||
}
|
||||
|
||||
public override void UnsubscribeInputEvents()
|
||||
{
|
||||
base.UnsubscribeInputEvents();
|
||||
|
||||
input.UnsubscribeToggleCrouch(ToggleCrouchInput);
|
||||
input.UnsubscribeToggleProne(ToggleProneInput);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IClientSimInputEventSubscribable
|
||||
|
||||
public override void SubscribeEvents()
|
||||
{
|
||||
base.SubscribeEvents();
|
||||
|
||||
eventDispatcher.Subscribe<ClientSimMouseReleasedEvent>(MouseReleasedEvent);
|
||||
eventDispatcher.Subscribe<ClientSimOnPlayerEnteredStationEvent>(PlayerEnteredStation);
|
||||
eventDispatcher.Subscribe<ClientSimOnPlayerExitedStationEvent>(PlayerExitedStation);
|
||||
}
|
||||
|
||||
public override void UnsubscribeEvents()
|
||||
{
|
||||
base.UnsubscribeEvents();
|
||||
|
||||
eventDispatcher.Unsubscribe<ClientSimMouseReleasedEvent>(MouseReleasedEvent);
|
||||
eventDispatcher.Unsubscribe<ClientSimOnPlayerEnteredStationEvent>(PlayerEnteredStation);
|
||||
eventDispatcher.Unsubscribe<ClientSimOnPlayerExitedStationEvent>(PlayerExitedStation);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ClientSim Events
|
||||
|
||||
private void MouseReleasedEvent(ClientSimMouseReleasedEvent mouseReleasedEvent)
|
||||
{
|
||||
_mouseReleased = mouseReleasedEvent.isReleased;
|
||||
}
|
||||
|
||||
private void PlayerEnteredStation(ClientSimOnPlayerEnteredStationEvent stationEvent)
|
||||
{
|
||||
_currentStation = stationEvent.station;
|
||||
_desktopRotator.SetStation(_currentStation);
|
||||
|
||||
if (_currentStation.IsSeated())
|
||||
{
|
||||
SetStance(ClientSimPlayerStanceEnum.SITTING);
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayerExitedStation(ClientSimOnPlayerExitedStationEvent stationEvent)
|
||||
{
|
||||
_currentStation = null;
|
||||
_desktopRotator.SetStation(null);
|
||||
SetStance(ClientSimPlayerStanceEnum.STANDING);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ClientSim Input
|
||||
|
||||
private void ToggleCrouchInput(bool value)
|
||||
{
|
||||
// Only handle on down, and not on release.
|
||||
if (!value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetPlayerStance() == ClientSimPlayerStanceEnum.CROUCHING)
|
||||
{
|
||||
SetStance(ClientSimPlayerStanceEnum.STANDING);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStance(ClientSimPlayerStanceEnum.CROUCHING);
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleProneInput(bool value)
|
||||
{
|
||||
// Only handle on down, and not on release.
|
||||
if (!value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetPlayerStance() == ClientSimPlayerStanceEnum.PRONE)
|
||||
{
|
||||
SetStance(ClientSimPlayerStanceEnum.STANDING);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStance(ClientSimPlayerStanceEnum.PRONE);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// If mouse is released, do not update rotation.
|
||||
if (!_mouseReleased)
|
||||
{
|
||||
_desktopRotator.HandleRotation(input.GetLookHorizontal(), input.GetLookVertical());
|
||||
}
|
||||
}
|
||||
|
||||
private void SetStance(ClientSimPlayerStanceEnum stance)
|
||||
{
|
||||
// If in a station, ignore all non sitting stances.
|
||||
if (_currentStation != null && _currentStation.IsLockedInStation() && stance != ClientSimPlayerStanceEnum.SITTING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 cameraPosition = head.localPosition;
|
||||
switch (stance)
|
||||
{
|
||||
case ClientSimPlayerStanceEnum.PRONE:
|
||||
cameraPosition.y = PRONE_HEIGHT;
|
||||
break;
|
||||
case ClientSimPlayerStanceEnum.CROUCHING:
|
||||
cameraPosition.y = CROUCHING_HEIGHT;
|
||||
break;
|
||||
case ClientSimPlayerStanceEnum.SITTING:
|
||||
cameraPosition.y = SITTING_HEIGHT;
|
||||
break;
|
||||
case ClientSimPlayerStanceEnum.STANDING:
|
||||
cameraPosition.y = STANDING_HEIGHT;
|
||||
break;
|
||||
}
|
||||
|
||||
head.localPosition = cameraPosition;
|
||||
}
|
||||
|
||||
public override Transform GetHandRaycastTransform(HandType handType)
|
||||
{
|
||||
throw new ClientSimException("Desktop tracking does not support arm based raycasting");
|
||||
}
|
||||
|
||||
public override bool IsVR()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool SupportsPickupAutoHold()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void LookTowardsPoint(Vector3 point)
|
||||
{
|
||||
_desktopRotator.LookAtPoint(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cdb19253bd15ee4296b716139111626
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,83 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim.PlayerTracking
|
||||
{
|
||||
public class ClientSimDesktopTrackingRotator
|
||||
{
|
||||
private const float MINIMUM_X_ANGLE = -80f;
|
||||
private const float MAXIMUM_X_ANGLE = 70f;
|
||||
private const float MINIMUM_Y_ANGLE = -90F;
|
||||
private const float MAXIMUM_Y_ANGLE = 90F;
|
||||
|
||||
private Quaternion _targetBaseYRot;
|
||||
private Quaternion _targetHeadXRot;
|
||||
|
||||
private readonly Transform _headXTransform;
|
||||
private readonly Transform _headYTransform;
|
||||
|
||||
private IClientSimStation _currentStation;
|
||||
|
||||
public ClientSimDesktopTrackingRotator(Transform headXTransform, Transform headYTransform)
|
||||
{
|
||||
_headXTransform = headXTransform;
|
||||
_headYTransform = headYTransform;
|
||||
|
||||
_targetBaseYRot = _headYTransform.localRotation;
|
||||
_targetHeadXRot = _headXTransform.localRotation;
|
||||
}
|
||||
|
||||
public void SetStation(IClientSimStation station)
|
||||
{
|
||||
_currentStation = station;
|
||||
_targetBaseYRot = Quaternion.identity;
|
||||
}
|
||||
|
||||
public void HandleRotation(float xDelta, float yDelta)
|
||||
{
|
||||
_targetHeadXRot *= Quaternion.Euler(-yDelta, 0f, 0f);
|
||||
_targetHeadXRot = ClampRotationAroundAxis(_targetHeadXRot, 0, MINIMUM_X_ANGLE, MAXIMUM_X_ANGLE);
|
||||
|
||||
// Player in a station, allow limited rotation on Y axis.
|
||||
if (_currentStation != null && _currentStation.IsLockedInStation())
|
||||
{
|
||||
_targetBaseYRot *= Quaternion.Euler(0f, xDelta, 0f);
|
||||
_targetBaseYRot = ClampRotationAroundAxis(_targetBaseYRot, 1, MINIMUM_Y_ANGLE, MAXIMUM_Y_ANGLE);
|
||||
}
|
||||
|
||||
_headXTransform.localRotation = _targetHeadXRot;
|
||||
_headYTransform.localRotation = _targetBaseYRot;
|
||||
}
|
||||
|
||||
// Used in tests to force rotate the player to look at an object.
|
||||
public void LookAtPoint(Vector3 point)
|
||||
{
|
||||
// Get the amount the player needs to rotate on Y
|
||||
Vector3 localizedYPoint = _headYTransform.InverseTransformPoint(point);
|
||||
localizedYPoint.y = 0;
|
||||
float yAngle = Vector3.SignedAngle(Vector3.forward, localizedYPoint, Vector3.up);
|
||||
|
||||
// Get the amount the player needs to rotate on X
|
||||
Vector3 localizedXPoint = _headXTransform.InverseTransformPoint(point);
|
||||
localizedXPoint.x = 0;
|
||||
float xAngle = Vector3.SignedAngle(Vector3.forward, localizedXPoint, Vector3.left);
|
||||
|
||||
HandleRotation(yAngle, xAngle);
|
||||
}
|
||||
|
||||
private static Quaternion ClampRotationAroundAxis(Quaternion q, int index, float minAngle, float maxAngle)
|
||||
{
|
||||
q.x /= q.w;
|
||||
q.y /= q.w;
|
||||
q.z /= q.w;
|
||||
q.w = 1.0f;
|
||||
|
||||
float angle = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q[index]);
|
||||
|
||||
angle = Mathf.Clamp(angle, minAngle, maxAngle);
|
||||
|
||||
q[index] = Mathf.Tan(0.5f * Mathf.Deg2Rad * angle);
|
||||
|
||||
return q;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4416d1ef28048a94683820481afedf33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,10 @@
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public enum ClientSimPlayerStanceEnum
|
||||
{
|
||||
STANDING,
|
||||
CROUCHING,
|
||||
PRONE,
|
||||
SITTING
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 062e0aeaac1ea1e4b9c22f6b612b3163
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,270 @@
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon.Common;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// System responsible for providing tracking data to the rest of ClientSim.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Currently only used for DesktopTracking, but can be extended for VR and even fake VR implementations.
|
||||
/// Sends Events:
|
||||
/// - ClientSimOnPlayerHeightUpdateEvent
|
||||
/// - ClientSimOnTrackingScaleUpdateEvent
|
||||
/// Listens to Events:
|
||||
/// - ClientSimOnPlayerHeightUpdateEvent
|
||||
/// </remarks>
|
||||
[DefaultExecutionOrder(-3000)] // Update before player raycasting
|
||||
public abstract class ClientSimTrackingProviderBase : ClientSimBehaviour, IClientSimTrackingProvider, IDisposable
|
||||
{
|
||||
private const float MINIMUM_TRACKING_SCALE = 0.1f;
|
||||
private const float MAXIMUM_TRACKING_SCALE = 50f;
|
||||
|
||||
// TODO calculate this value based on the avatar instead of hard coding it.
|
||||
public const float AVATAR_HEIGHT = 1.9f;
|
||||
|
||||
protected const float STANDING_HEIGHT = 1.75f;
|
||||
protected const float CROUCHING_HEIGHT = 1.0f;
|
||||
protected const float PRONE_HEIGHT = 0.5f;
|
||||
protected const float SITTING_HEIGHT = 1.2f;
|
||||
|
||||
[SerializeField]
|
||||
protected Transform head;
|
||||
[SerializeField]
|
||||
protected Transform leftHand;
|
||||
[SerializeField]
|
||||
protected Transform rightHand;
|
||||
[SerializeField]
|
||||
protected Transform playspace;
|
||||
[SerializeField]
|
||||
protected Transform playerAudioListener;
|
||||
[SerializeField]
|
||||
protected Camera playerCamera;
|
||||
|
||||
protected IClientSimEventDispatcher eventDispatcher;
|
||||
protected IClientSimInput input;
|
||||
protected ClientSimSettings settings;
|
||||
protected IClientSimPlayerHeightManager heightManager;
|
||||
|
||||
private float _trackingScale = 1;
|
||||
|
||||
public static float CalculateTrackingScaleFromPlayerHeight(float playerHeight)
|
||||
{
|
||||
return playerHeight / AVATAR_HEIGHT;
|
||||
}
|
||||
|
||||
public static float CalculatePlayerHeightFromTrackingScale(float trackingScale)
|
||||
{
|
||||
return trackingScale * AVATAR_HEIGHT;
|
||||
}
|
||||
|
||||
public virtual void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimInput input,
|
||||
ClientSimSettings settings,
|
||||
IClientSimPlayerHeightManager heightManager)
|
||||
{
|
||||
this.eventDispatcher = eventDispatcher;
|
||||
this.input = input;
|
||||
this.settings = settings;
|
||||
this.heightManager = heightManager;
|
||||
|
||||
SubscribeEvents();
|
||||
|
||||
// Input will be null with incorrect Unity input project settings.
|
||||
if (input != null)
|
||||
{
|
||||
SubscribeInputEvents();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
// Send event for this to ensure everything that uses the player height is properly updated.
|
||||
eventDispatcher.SendEvent(new ClientSimOnPlayerHeightUpdateEvent
|
||||
{
|
||||
playerHeight = heightManager.GetAvatarEyeHeightAsMeters()
|
||||
});
|
||||
|
||||
|
||||
// Only disable audio listeners and cameras if the player is spawned.
|
||||
if (settings.spawnPlayer)
|
||||
{
|
||||
// Destroy other audio listeners
|
||||
foreach (var listener in FindObjectsByType<AudioListener>(FindObjectsSortMode.None))
|
||||
{
|
||||
if (listener.transform == playerAudioListener)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DestroyImmediate(listener);
|
||||
}
|
||||
|
||||
// Disable all cameras that do not render to a render texture.
|
||||
foreach (var worldCamera in FindObjectsByType<Camera>(FindObjectsSortMode.None))
|
||||
{
|
||||
if (worldCamera == playerCamera)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (worldCamera.targetTexture != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
worldCamera.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UnsubscribeEvents();
|
||||
|
||||
// Input will be null with incorrect Unity input project settings.
|
||||
if (input != null)
|
||||
{
|
||||
UnsubscribeInputEvents();
|
||||
}
|
||||
}
|
||||
|
||||
#region IClientSimTrackingProvider
|
||||
|
||||
public virtual VRCPlayerApi.TrackingData GetTrackingData(VRCPlayerApi.TrackingDataType trackingDataType)
|
||||
{
|
||||
VRCPlayerApi.TrackingData data = new VRCPlayerApi.TrackingData();
|
||||
|
||||
switch (trackingDataType)
|
||||
{
|
||||
case VRCPlayerApi.TrackingDataType.Head:
|
||||
data.position = head.position;
|
||||
data.rotation = head.rotation;
|
||||
break;
|
||||
case VRCPlayerApi.TrackingDataType.LeftHand:
|
||||
data.position = leftHand.position;
|
||||
data.rotation = leftHand.rotation;
|
||||
break;
|
||||
case VRCPlayerApi.TrackingDataType.RightHand:
|
||||
data.position = rightHand.position;
|
||||
data.rotation = rightHand.rotation;
|
||||
break;
|
||||
case VRCPlayerApi.TrackingDataType.Origin:
|
||||
data.position = playspace.position;
|
||||
data.rotation = playspace.rotation;
|
||||
break;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public virtual Transform GetTrackingTransform(VRCPlayerApi.TrackingDataType trackingDataType)
|
||||
{
|
||||
switch (trackingDataType)
|
||||
{
|
||||
case VRCPlayerApi.TrackingDataType.Head:
|
||||
return head;
|
||||
case VRCPlayerApi.TrackingDataType.LeftHand:
|
||||
return leftHand;
|
||||
case VRCPlayerApi.TrackingDataType.RightHand:
|
||||
return rightHand;
|
||||
case VRCPlayerApi.TrackingDataType.Origin:
|
||||
return playspace;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public float GetTrackingScale()
|
||||
{
|
||||
return _trackingScale;
|
||||
}
|
||||
|
||||
public void SetTrackingScale(float scale)
|
||||
{
|
||||
scale = Mathf.Clamp(scale, MINIMUM_TRACKING_SCALE, MAXIMUM_TRACKING_SCALE);
|
||||
|
||||
_trackingScale = scale;
|
||||
playspace.localScale = scale * Vector3.one;
|
||||
|
||||
// Audio listener must always be scale 1 to ensure ONSP sounds correctly.
|
||||
playerAudioListener.localScale = 1.0f / scale * Vector3.one;
|
||||
|
||||
eventDispatcher.SendEvent(new ClientSimOnTrackingScaleUpdateEvent { trackingScale = scale });
|
||||
}
|
||||
|
||||
public ClientSimPlayerStanceEnum GetPlayerStance()
|
||||
{
|
||||
// Check heights starting from shortest
|
||||
float headHeight = head.localPosition.y;
|
||||
if (headHeight <= PRONE_HEIGHT)
|
||||
{
|
||||
return ClientSimPlayerStanceEnum.PRONE;
|
||||
}
|
||||
if (headHeight <= CROUCHING_HEIGHT)
|
||||
{
|
||||
return ClientSimPlayerStanceEnum.CROUCHING;
|
||||
}
|
||||
|
||||
return ClientSimPlayerStanceEnum.STANDING;
|
||||
}
|
||||
|
||||
public abstract Transform GetHandRaycastTransform(HandType handType);
|
||||
|
||||
public abstract bool IsVR();
|
||||
|
||||
public abstract bool SupportsPickupAutoHold();
|
||||
|
||||
public abstract void LookTowardsPoint(Vector3 point);
|
||||
|
||||
#endregion
|
||||
|
||||
#region IClientSimPlayerCameraProvider
|
||||
|
||||
public Camera GetCamera()
|
||||
{
|
||||
return playerCamera;
|
||||
}
|
||||
|
||||
public Camera GetCameraForObject(GameObject obj)
|
||||
{
|
||||
// TODO: Make this interact with camera stacking
|
||||
return playerCamera;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ClientSim Events
|
||||
|
||||
private void OnPlayerHeightUpdate(ClientSimOnPlayerHeightUpdateEvent heightEvent)
|
||||
{
|
||||
// Convert player height to tracking scale and set the new scale.
|
||||
SetTrackingScale(CalculateTrackingScaleFromPlayerHeight(heightEvent.playerHeight));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public virtual void SubscribeInputEvents() { }
|
||||
|
||||
public virtual void UnsubscribeInputEvents() { }
|
||||
|
||||
public virtual void SubscribeEvents()
|
||||
{
|
||||
eventDispatcher.Subscribe<ClientSimOnPlayerHeightUpdateEvent>(OnPlayerHeightUpdate);
|
||||
}
|
||||
|
||||
public virtual void UnsubscribeEvents()
|
||||
{
|
||||
eventDispatcher.Unsubscribe<ClientSimOnPlayerHeightUpdateEvent>(OnPlayerHeightUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d6fc3f30fc48c247855805690cd90cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user