Added Unity project files

This commit is contained in:
2026-06-07 16:58:24 +01:00
parent 3cc05d260b
commit 23bbcab156
3942 changed files with 453676 additions and 0 deletions

View File

@ -0,0 +1,5 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("VRC.ClientSim.Editor")]
[assembly: InternalsVisibleTo("VRC.ClientSim.Editor.Tests")]
[assembly: InternalsVisibleTo("VRC.ClientSim.Tests")]

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bfb84e0933b14ae380634bcd70b18a70
timeCreated: 1641884637

View File

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

View File

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

View File

@ -0,0 +1,19 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8474ec07ef88d52458e79ca0f5240d96, type: 3}
m_Name: Cam_InternalUI
m_EditorClassIdentifier:
cameraName: Cam_InternalUI
renderLayer:
serializedVersion: 2
m_Bits: 524288
useOcclusionCulling: 0

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d6ef66aee2eba604da699a98a28bba17
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
using UnityEngine;
namespace VRC.SDK3.ClientSim
{
public class ClientSimStackedCamera : ScriptableObject
{
[SerializeField] private string cameraName = "Generic Stacked Camera";
[SerializeField] private LayerMask renderLayer;
[SerializeField] private bool useOcclusionCulling = true;
public string CameraName => cameraName;
public LayerMask RenderLayer => renderLayer;
public bool UseOcclusionCulling => useOcclusionCulling;
}
}

View File

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

View File

@ -0,0 +1,120 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.XR;
namespace VRC.SDK3.ClientSim
{
public class ClientSimStackedVRCameraSystem : MonoBehaviour
{
[SerializeField]
private ClientSimStackedCamera[] cameraStack;
private bool _isCameraStackingEnabled;
private Camera _mainSceneCamera;
private bool _isInitialized;
private bool _isReady;
private List<Camera> _cameras;
private ClientSimMenu _clientSimMenu;
private IClientSimEventDispatcher _eventDispatcher;
public IReadOnlyList<Camera> Cameras => _cameras;
// all layers handled by this system - note that this does not change whether the system is active or not,
// meaning it is valid to call this at any time, which `OnCameraSettingsChanged` may need to do
public LayerMask RequestedStackLayers => cameraStack.Aggregate(0, (mask, cam) => mask | cam.RenderLayer);
public void Initialize(Camera playerCamera, ClientSimMenu menu, IClientSimEventDispatcher eventDispatcher)
{
_mainSceneCamera = playerCamera;
_clientSimMenu = menu;
_eventDispatcher = eventDispatcher;
}
public void Ready()
{
_isReady = true;
}
void Update()
{
if(!_isReady) return;
if (!_isInitialized) { InitializeStackedSystem(); }
}
void OnDisable()
{
if (_mainSceneCamera != null)
{
if (_isCameraStackingEnabled)
DestroyCameraStack();
}
}
private void InitializeStackedSystem()
{
_cameras = new List<Camera>();
if (_mainSceneCamera != null)
{
CreateCameraStack();
_isInitialized = true;
_eventDispatcher.SendEvent(new ClientSimStackedCameraReadyEvent());
}
}
private void CreateCameraStack()
{
for (int i = 0; i < cameraStack.Length; i++)
{
AddCamera(i);
}
_isCameraStackingEnabled = true;
}
private void DestroyCameraStack()
{
for (int i = 0; i < _cameras.Count; i++)
{
DestroyCamera(i);
}
_isCameraStackingEnabled = false;
}
private void AddCamera(int index)
{
var cameraObj = new GameObject();
cameraObj.transform.parent = _mainSceneCamera.transform;
Camera cam = cameraObj.AddComponent<Camera>();
XRDevice.DisableAutoXRCameraTracking(cam, true);
cam.CopyFrom(_mainSceneCamera); // Start by copying all the settings from the main camera
#if VRC_VR_STEAM // We only want this on SteamVR.
cameraObj.AddComponent<SteamVRCantedProjectionCullingFix>();
#endif
_cameras.Add(cam);
cameraObj.tag = "Untagged";
cameraObj.name = $"StackedCamera : {cameraStack[index].CameraName}";
cam.clearFlags = CameraClearFlags.Depth;
cam.depth = 100 - index;
cam.cullingMask = cameraStack[index].RenderLayer;
cam.useOcclusionCulling = cameraStack[index].UseOcclusionCulling;
//Remove this cameras layers from the base camera
_mainSceneCamera.cullingMask = _mainSceneCamera.cullingMask ^ cameraStack[index].RenderLayer;
// Set the ClientSim UI canvas to use this camera
_clientSimMenu.SetCanvasCamera(cam);
}
private void DestroyCamera(int index)
{
Camera cam = _cameras[index];
_cameras.RemoveAt(index);
//Restore Layers from this camera to the main camera
_mainSceneCamera.cullingMask = _mainSceneCamera.cullingMask | cameraStack[index].RenderLayer;
Destroy(cam.gameObject);
}
}
}

View File

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

View File

@ -0,0 +1,12 @@
using UnityEngine;
namespace VRC.SDK3.ClientSim
{
public abstract class ClientSimBehaviour : MonoBehaviour
{
protected virtual void Awake()
{
this.PreventComponentFromSaving();
}
}
}

View File

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

View File

@ -0,0 +1,155 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using VRC.Core;
namespace VRC.SDK3.ClientSim
{
public static class ClientSimRuntimeLoader
{
private const string EDITOR_ONLY_TAG = "EditorOnly";
// Used in tests to prevent the runtime initialized methods from executing.
private static bool _isInTestMode = false;
private static ClientSimSettings _testSettingsOverride;
private static ClientSimEventDispatcher _testEventDispatcherOverride;
#region ClientSim Initialization
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void OnBeforeSceneLoad()
{
StartClientSim(GetSettings(), GetEventDispatcher());
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
private static void OnAfterSceneLoad()
{
// Delete all editor only objects before creating ClientSim.
DestroyEditorOnly(GetSettings());
#if VRC_ENABLE_PLAYER_PERSISTENCE
if (ClientSimMain.TryGetInstance(out var instance))
instance.EnablePlayerObjects();
#endif
}
#endregion
#region Test Methods
[PublicAPI]
public static void BeginUnityTesting(
ClientSimSettings testSettingsOverride,
ClientSimEventDispatcher testEventDispatcherOverride = null)
{
_isInTestMode = true;
_testSettingsOverride = testSettingsOverride;
_testEventDispatcherOverride = testEventDispatcherOverride;
if (_testSettingsOverride == null)
{
_testSettingsOverride = new ClientSimSettings();
}
}
[PublicAPI]
public static void EndUnityTesting()
{
_isInTestMode = false;
_testSettingsOverride = null;
}
[PublicAPI]
public static bool IsInUnityTest()
{
return _isInTestMode;
}
#endregion
private static ClientSimSettings GetSettings()
{
return IsInUnityTest() ? _testSettingsOverride : ClientSimSettings.Instance;
}
private static ClientSimEventDispatcher GetEventDispatcher()
{
return IsInUnityTest() ? _testEventDispatcherOverride : null;
}
private static bool IsClientSimEnabled(ClientSimSettings settings)
{
return
settings.enableClientSim &&
Application.isPlaying;
}
// Start client sim with the given settings.
// Optional event dispatcher can be passed in to listen to startup events. Mainly used in tests.
public static void StartClientSim(
ClientSimSettings settings,
IClientSimEventDispatcher eventDispatcher = null)
{
if (!IsClientSimEnabled(settings))
{
return;
}
// Delete all editor only objects before creating ClientSim.
DestroyEditorOnly(settings);
ClientSimMain.CreateInstance(settings, eventDispatcher);
// TODO: Below is disabled for now because the rest of the ClientSim initialization code doesn't work if it's called with a delay.
// Currently, not loading RemoteConfig will not cause any issues, but it may in the future, so this is left in as a reminder.
// Create ClientSim Instance later
/*void CreateClientSimInstance() => ClientSimMain.CreateInstance(settings, eventDispatcher);
// If the Remote Config is not initialized, attempt init before starting ClientSim
// Start ClientSim after attempt, regardless of success or failure
if (!ConfigManager.RemoteConfig.IsInitialized())
{
API.SetOnlineMode(true);
ConfigManager.RemoteConfig.Init(CreateClientSimInstance, CreateClientSimInstance);
}
// Otherwise, start ClientSim immediately
else
{
CreateClientSimInstance();
}*/
}
private static void DestroyEditorOnly(ClientSimSettings settings)
{
if (!settings.enableClientSim || !settings.deleteEditorOnly)
{
return;
}
List<GameObject> rootObjects = new List<GameObject>();
Scene scene = SceneManager.GetActiveScene();
scene.GetRootGameObjects(rootObjects);
Queue<GameObject> queue = new Queue<GameObject>(rootObjects);
while (queue.Count > 0)
{
GameObject obj = queue.Dequeue();
if (obj.CompareTag(EDITOR_ONLY_TAG))
{
obj.Log($"Deleting editor only object: {Tools.GetGameObjectPath(obj)}");
Object.DestroyImmediate(obj);
}
else
{
for (int child = 0; child < obj.transform.childCount; ++child)
{
queue.Enqueue(obj.transform.GetChild(child).gameObject);
}
}
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 47cdc21b10bf4c78890cfe48793dbd14
timeCreated: 1638822453

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 08f8a2a94e3342269e856642c4028c74
timeCreated: 1638922063

View File

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
namespace VRC.SDK3.ClientSim
{
/// <summary>
/// The Event Dispatcher is how ClientSim implements the Observer Pattern.
/// Systems can subscribe to specific event types, and other systems can then send the event.
/// </summary>
public class ClientSimEventDispatcher : IClientSimEventDispatcher, IDisposable
{
private readonly Dictionary<Type, Delegate> _eventSubscribers;
public ClientSimEventDispatcher()
{
_eventSubscribers = new Dictionary<Type, Delegate>();
}
public void Subscribe<T>(Action<T> eventHandler) where T : IClientSimEvent
{
Type t = typeof(T);
if (_eventSubscribers.TryGetValue(t, out Delegate eventDelegate))
{
_eventSubscribers[t] = Delegate.Combine(eventDelegate, eventHandler);
}
else
{
_eventSubscribers.Add(t, eventHandler);
}
}
public void Unsubscribe<T>(Action<T> eventHandler) where T : IClientSimEvent
{
Type t = typeof(T);
if (_eventSubscribers.TryGetValue(t, out Delegate eventDelegate))
{
Delegate remainingDelegate = Delegate.Remove(eventDelegate, eventHandler);
if (remainingDelegate == null)
{
_eventSubscribers.Remove(t);
}
else
{
_eventSubscribers[t] = remainingDelegate;
}
}
}
/// <summary>
/// Sends the event to subscribed receivers
/// </summary>
/// <param name="clientSimEvent"></param>
/// <typeparam name="T"></typeparam>
public void SendEvent<T>(T clientSimEvent) where T : IClientSimEvent
{
// TODO log warning if trying to send events while another is still being processed.
if (_eventSubscribers.TryGetValue(typeof(T), out Delegate eventDelegate)
&& eventDelegate is Action<T> action)
{
action.Invoke(clientSimEvent);
}
}
public void Dispose()
{
_eventSubscribers.Clear();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 039d8e7360df43b2a1b0b5f387761dce
timeCreated: 1638918329

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d1d43bb9f467463cb77ef60d29a6178a
timeCreated: 1640236379

View File

@ -0,0 +1,10 @@
using VRC.SDKBase;
namespace VRC.SDK3.ClientSim
{
public class ClientSimPlayerDeathStatusChangedEvent : IClientSimEvent
{
public VRCPlayerApi player;
public bool isDead;
}
}

View File

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

View File

@ -0,0 +1,9 @@
using VRC.Udon.Common;
namespace VRC.SDK3.ClientSim
{
public class ClientSimCurrentHandEvent : IClientSimEvent
{
public HandType currentUsedHand;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 23f6deb432924c9f898f7f83f000045b
timeCreated: 1641101850

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
using UnityEngine;
using VRC.Udon.Common;
namespace VRC.SDK3.ClientSim
{
public class ClientSimInteractEvent : IClientSimEvent
{
public HandType handType;
public GameObject interactObject;
public float interactDistance;
public List<IClientSimInteractable> interacts;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4d4f50777a7b4f5ba8f64f4fe7cb04c4
timeCreated: 1640814847

View File

@ -0,0 +1,11 @@
namespace VRC.SDK3.ClientSim
{
public class ClientSimMenuStateChangedEvent : IClientSimEvent
{
public bool isMenuOpen;
}
public class ClientSimMenuRespawnClickedEvent : IClientSimEvent { }
public class ClientSimStackedCameraReadyEvent : IClientSimEvent { }
}

View File

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

View File

@ -0,0 +1,7 @@
namespace VRC.SDK3.ClientSim
{
public class ClientSimMouseReleasedEvent : IClientSimEvent
{
public bool isReleased;
}
}

View File

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

View File

@ -0,0 +1,113 @@
using System.Collections.Generic;
using VRC.SDKBase;
#if VRC_ENABLE_PLAYER_PERSISTENCE
using VRC.SDK3.ClientSim.Interfaces;
using VRC.SDK3.ClientSim.Persistence;
#endif
namespace VRC.SDK3.ClientSim
{
public class ClientSimOnPlayerJoinedEvent : IClientSimEvent
{
public VRCPlayerApi player;
}
public class ClientSimOnPlayerLeftEvent : IClientSimEvent
{
public VRCPlayerApi player;
}
public class ClientSimOnPlayerRespawnEvent : IClientSimEvent
{
public VRCPlayerApi player;
}
public class ClientSimOnPlayerTeleportedEvent : IClientSimEvent
{
public VRCPlayerApi player;
}
public class ClientSimOnPlayerMovedEvent : IClientSimEvent
{
public VRCPlayerApi player;
}
public class ClientSimOnPlayerEnteredStationEvent : IClientSimEvent
{
public VRCPlayerApi player;
public IClientSimStation station;
}
public class ClientSimOnPlayerExitedStationEvent : IClientSimEvent
{
public VRCPlayerApi player;
public IClientSimStation station;
}
public class ClientSimOnPlayerHeightUpdateEvent : IClientSimEvent
{
public float playerHeight;
public bool exceedsManualScalingMaximum;
public bool exceedsManualScalingMinimum;
}
public class ClientSimOnTrackingScaleUpdateEvent : IClientSimEvent
{
public float trackingScale;
}
public class ClientSimOnNewMasterEvent : IClientSimEvent
{
public VRCPlayerApi oldMasterPlayer;
public VRCPlayerApi newMasterPlayer;
}
#if VRC_ENABLE_PLAYER_PERSISTENCE
public class ClientSimOnPlayerDataDecodedEvent : IClientSimEvent
{
public VRCPlayerApi player;
}
public class ClientSimOnPlayerObjectsDecodedEvent : IClientSimEvent
{
public VRCPlayerApi player;
}
public class ClientSimOnPlayerRestoredEvent : IClientSimEvent
{
public VRCPlayerApi player;
}
public class ClientSimOnPlayerDataUpdatedEvent : IClientSimEvent
{
public VRCPlayerApi player;
public Dictionary<string, ClientSimPlayerDataPair> playerData;
}
public class ClientSimOnPlayerDataClearedEvent : IClientSimEvent
{
public VRCPlayerApi player;
}
public class ClientSimOnPlayerObjectUpdatedEvent : IClientSimEvent
{
public IClientSimNetworkSerializer Data;
}
public class ClientSimOnPlayerObjectUpdateEndedEvent : IClientSimEvent
{
}
#endif
public class ClientSimOnToggleManualScalingEvent : IClientSimEvent
{
public bool manualScalingAllowed;
}
public class ClientSimOnVRCPlusMassGift : IClientSimEvent
{
public VRCPlayerApi gifter;
public int numGifts;
}
}

View File

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

View File

@ -0,0 +1,33 @@
using VRC.SDKBase;
using VRC.Udon.Common;
namespace VRC.SDK3.ClientSim
{
public class ClientSimOnPickupEvent : IClientSimEvent
{
public VRCPlayerApi player;
public HandType handType;
public IClientSimPickupable pickup;
}
public class ClientSimOnPickupDropEvent : IClientSimEvent
{
public VRCPlayerApi player;
public HandType handType;
public IClientSimPickupable pickup;
}
public class ClientSimOnPickupUseDownEvent : IClientSimEvent
{
public VRCPlayerApi player;
public HandType handType;
public IClientSimPickupable pickup;
}
public class ClientSimOnPickupUseUpEvent : IClientSimEvent
{
public VRCPlayerApi player;
public HandType handType;
public IClientSimPickupable pickup;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: daf6e253eca84774b6643ef83c0594e7
timeCreated: 1640732560

View File

@ -0,0 +1,9 @@
using VRC.SDK3.Platform;
namespace VRC.SDK3.ClientSim
{
public class ClientSimScreenUpdateEvent : IClientSimEvent
{
public ScreenUpdateData data;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 228a1330ea1c47babdd3a40d81194d1d
timeCreated: 1698364360

View File

@ -0,0 +1,10 @@
using VRC.Udon.Common;
namespace VRC.SDK3.ClientSim
{
public class ClientSimRaycastHitResultsEvent : IClientSimEvent
{
public HandType handType;
public ClientSimRaycastResults raycastResults;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 609324b47ca7465991701242bb8a9e0d
timeCreated: 1640274005

View File

@ -0,0 +1,7 @@
namespace VRC.SDK3.ClientSim
{
public class ClientSimReadyEvent : IClientSimEvent
{
}
}

View File

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

View File

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

View File

@ -0,0 +1,4 @@
namespace VRC.SDK3.ClientSim
{
public interface IClientSimEvent { }
}

View File

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

View File

@ -0,0 +1,11 @@
using System;
namespace VRC.SDK3.ClientSim
{
public interface IClientSimEventDispatcher
{
void Subscribe<T>(Action<T> eventHandler) where T : IClientSimEvent;
void Unsubscribe<T>(Action<T> eventHandler) where T : IClientSimEvent;
void SendEvent<T>(T clientSimEvent) where T : IClientSimEvent;
}
}

View File

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

View File

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

View File

@ -0,0 +1,293 @@
using System;
using System.Collections;
using System.IO;
using UnityEngine;
using VRC.SDKBase;
namespace VRC.SDK3.ClientSim
{
// Sends Events:
// - ClientSimPlayerDeathStatusChangedEvent
[AddComponentMenu("")]
public class ClientSimCombatSystemHelper : ClientSimBehaviour, IVRC_Destructible
{
private static readonly string _visualDamagePrefabPath =
Path.Combine("Assets", "VRChat Examples", "Prefabs", "VRCPlayerVisualDamage.prefab");
private VRCPlayerApi _player;
private IClientSimEventDispatcher _eventDispatcher;
private IClientSimProxyObjectProvider _proxyProvider;
private ClientSimPlayerController _playerController;
// Make values public so users can see and modify these values at runtime.
public bool respawnOnDeath;
public bool resetHealthOnRespawn = true;
public float respawnTime = 5f;
public float maxPlayerHealth = 100;
public float currentHealth = 100;
public Transform respawnPoint;
private GameObject _visualDamagePrefab;
private GameObject _visualDamageObj;
private VRC_VisualDamage _visualDamage;
private bool _dead = false;
private static ClientSimCombatSystemHelper GetCombatHelper(VRCPlayerApi player)
{
return player.GetClientSimPlayer().GetCombatHelper();
}
public static void CombatSetup(VRCPlayerApi player)
{
player.GetClientSimPlayer().InitializeCombat();
}
public static void CombatSetMaxHitpoints(VRCPlayerApi player, float maxHealth)
{
ClientSimCombatSystemHelper combatHelper = GetCombatHelper(player);
if (combatHelper == null)
{
return;
}
combatHelper.maxPlayerHealth = maxHealth;
}
public static float CombatGetCurrentHitpoints(VRCPlayerApi player)
{
ClientSimCombatSystemHelper combatHelper = GetCombatHelper(player);
if (combatHelper == null)
{
// If a player doesn't have combat setup, their hitpoints are -1.
return -1;
}
return combatHelper.currentHealth;
}
public static void CombatSetRespawn(VRCPlayerApi player, bool respawnOnDeath, float respawnTime, Transform spawnPoint)
{
ClientSimCombatSystemHelper combatHelper = GetCombatHelper(player);
if (combatHelper == null)
{
return;
}
combatHelper.respawnOnDeath = respawnOnDeath;
combatHelper.respawnTime = respawnTime;
combatHelper.respawnPoint = spawnPoint;
}
public static void CombatSetDamageGraphic(VRCPlayerApi player, GameObject visualDamage)
{
ClientSimCombatSystemHelper combatHelper = GetCombatHelper(player);
if (combatHelper == null)
{
return;
}
combatHelper._visualDamagePrefab = visualDamage;
}
public static IVRC_Destructible CombatGetDestructible(VRCPlayerApi player)
{
return GetCombatHelper(player);
}
public static void CombatSetCurrentHitpoints(VRCPlayerApi player, float health)
{
ClientSimCombatSystemHelper combatHelper = GetCombatHelper(player);
if (combatHelper == null)
{
return;
}
if (player.isLocal)
{
float delta = health - combatHelper.currentHealth;
if (delta <= 0)
{
combatHelper.ApplyDamage(-delta);
}
else
{
combatHelper.ApplyHealing(delta);
}
}
combatHelper.currentHealth = health;
}
public void Initialize(
VRCPlayerApi player,
IClientSimEventDispatcher eventDispatcher,
IClientSimProxyObjectProvider proxyProvider,
ClientSimPlayerController playerController)
{
_player = player;
// Values below will be null for remote players.
_eventDispatcher = eventDispatcher;
_playerController = playerController;
_proxyProvider = proxyProvider;
// TODO add ragdoll to player avatar
}
private void Start()
{
currentHealth = GetMaxHealth();
CreateVisualDamage();
}
private void CreateVisualDamage()
{
if (!_player.isLocal)
{
return;
}
if (_visualDamageObj != null)
{
Destroy(_visualDamageObj);
}
GameObject damage = _visualDamagePrefab;
#if UNITY_EDITOR
// If damage prefab is null, try loading it from the sample prefabs
if (damage == null)
{
damage = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(_visualDamagePrefabPath);
}
#endif
if (damage != null)
{
_visualDamageObj = Instantiate(damage, _proxyProvider.CameraProxy().transform);
_visualDamageObj.transform.localScale = new Vector3(40, 40, 40);
_visualDamageObj.transform.localPosition = new Vector3(0, 0, 0.5f);
_visualDamage = _visualDamageObj.GetComponent<VRC_VisualDamage>();
if (_visualDamage != null)
{
// VRChatBug: Visual Damage script is blacklisted in SDK3 and will be destroyed after spawning.
DestroyImmediate(_visualDamage);
}
}
}
public float GetMaxHealth()
{
return maxPlayerHealth;
}
public float GetCurrentHealth()
{
return currentHealth;
}
private void ApplyVisualDamage()
{
if (_visualDamage != null)
{
try
{
_visualDamage.SetDamagePercent(1 - (currentHealth / maxPlayerHealth));
}
catch (Exception e)
{
this.LogWarning($"Error applying damage: {e}");
}
}
}
public void ApplyDamage(float damage)
{
if (!_player.isLocal)
{
return;
}
if (currentHealth <= 0)
{
return;
}
this.Log($"ApplyDamage: {damage} currentHealth: {currentHealth}");
currentHealth = Mathf.Clamp(currentHealth - damage, 0, maxPlayerHealth);
ApplyVisualDamage();
if (currentHealth <= 0 && !_dead)
{
_dead = true;
this.Log("Player Died");
_player.EnablePickups(false);
_eventDispatcher?.SendEvent(new ClientSimPlayerDeathStatusChangedEvent
{
player = _player,
isDead = true,
});
StartCoroutine(PlayerDied());
}
}
private IEnumerator PlayerDied()
{
yield return new WaitForSeconds(respawnTime);
RevivePlayer();
}
private void RevivePlayer()
{
if (!_player.isLocal)
{
return;
}
_dead = false;
this.Log("Player Revived");
_player.EnablePickups(true);
if (respawnPoint != null && respawnOnDeath)
{
_playerController.Teleport(respawnPoint, false);
}
if (resetHealthOnRespawn)
{
ApplyHealing(maxPlayerHealth);
}
_eventDispatcher?.SendEvent(new ClientSimPlayerDeathStatusChangedEvent
{
player = _player,
isDead = false,
});
}
public void ApplyHealing(float healing)
{
if (!_player.isLocal)
{
return;
}
currentHealth = Mathf.Clamp(currentHealth + healing, 0, maxPlayerHealth);
ApplyVisualDamage();
this.Log($"ApplyHealing: {healing} currentHealth: {currentHealth}");
}
public object[] GetState()
{
throw new System.NotImplementedException();
}
public void SetState(object[] state)
{
throw new System.NotImplementedException();
}
}
}

View File

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

View File

@ -0,0 +1,77 @@
using UnityEngine;
using VRC.SDK3.Components;
namespace VRC.SDK3.ClientSim
{
[AddComponentMenu("")]
public class ClientSimObjectPoolHelper : ClientSimBehaviour, IClientSimSyncable
{
private int _ownerID = 1;
private VRCObjectPool _objectPool;
private IClientSimSyncedObjectManager _syncedObjectManager;
public static void OnSpawn(VRCObjectPool objectPool, int index)
{
ClientSimObjectPoolHelper poolHelper = objectPool.GetComponent<ClientSimObjectPoolHelper>();
if (!poolHelper)
{
throw new ClientSimException("Object Pool has not been initialized yet before trying to spawn an object.");
}
poolHelper.OnObjectSpawned(index);
}
public static void OnReturn(VRCObjectPool objectPool, int index)
{
ClientSimObjectPoolHelper poolHelper = objectPool.GetComponent<ClientSimObjectPoolHelper>();
if (!poolHelper)
{
throw new ClientSimException("Object Pool has not been initialized yet before trying to return an object.");
}
poolHelper.OnObjectReturned(index);
}
public void Initialize(VRCObjectPool objectPool, IClientSimSyncedObjectManager syncedObjectManager)
{
_objectPool = objectPool;
_syncedObjectManager = syncedObjectManager;
syncedObjectManager.AddSyncedObject(this);
}
private void Start()
{
// Catch Helper not initialized.
if (_objectPool == null)
{
this.LogWarning($"Destroying uninitialized Helper. Object: {Tools.GetGameObjectPath(gameObject)}");
DestroyImmediate(this);
}
}
private void OnDestroy()
{
// Nullable needed for uninitialized case.
_syncedObjectManager?.RemoveSyncedObject(this);
}
private void OnObjectSpawned(int index) { }
private void OnObjectReturned(int index) { }
#region IClientSimSyncable
public int GetOwner()
{
return _ownerID;
}
public void SetOwner(int ownerID)
{
_ownerID = ownerID;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,68 @@
using UnityEngine;
using VRC.SDK3.Components;
namespace VRC.SDK3.ClientSim
{
[AddComponentMenu("")]
public class ClientSimObjectSyncHelper : ClientSimPositionSyncedHelperBase
{
private VRCObjectSync _sync;
public static void TeleportTo(VRCObjectSync obj, Vector3 position, Quaternion rotation)
{
obj.GetComponent<ClientSimObjectSyncHelper>().TeleportTo(position, rotation);
}
public static void RespawnObject(VRCObjectSync sync)
{
sync.GetComponent<ClientSimObjectSyncHelper>().Respawn();
}
public static void SetIsKinematic(VRCObjectSync sync, bool value)
{
sync.GetComponent<ClientSimObjectSyncHelper>().SetIsKinematic(value);
}
public static void SetUseGravity(VRCObjectSync sync, bool value)
{
sync.GetComponent<ClientSimObjectSyncHelper>().SetUseGravity(value);
}
public static bool GetIsKinematic(VRCObjectSync sync)
{
return sync.GetComponent<ClientSimObjectSyncHelper>().GetIsKinematic();
}
public static bool GetUseGravity(VRCObjectSync sync)
{
return sync.GetComponent<ClientSimObjectSyncHelper>().GetUseGravity();
}
public static void FlagDiscontinuityHook(VRCObjectSync sync)
{
sync.GetComponent<ClientSimObjectSyncHelper>().FlagDiscontinuity();
}
protected override void Awake()
{
base.Awake();
SyncPosition = true;
}
public void Initialize(VRCObjectSync sync, IClientSimSyncedObjectManager syncedObjectManager)
{
base.Initialize(syncedObjectManager);
_sync = sync;
}
private void Start()
{
// Catch Helper not initialized.
if (_sync == null)
{
this.LogWarning($"Destroying uninitialized Helper. Object: {Tools.GetGameObjectPath(gameObject)}");
DestroyImmediate(this);
}
}
}
}

View File

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

View File

@ -0,0 +1,172 @@
using System;
using UnityEngine;
using VRC.SDKBase;
namespace VRC.SDK3.ClientSim
{
[AddComponentMenu("")]
public class ClientSimPickupHelper : ClientSimBehaviour, IClientSimPickupable
{
private Rigidbody _rigidbody;
private VRC_Pickup _pickup;
private VRCPlayerApi _heldPlayer;
private VRC_Pickup.PickupHand _heldHand;
private Action<IClientSimPickupable> _forceDropHandler;
public static void InitializePickup(VRC_Pickup pickup)
{
ClientSimPickupHelper previousHelper = pickup.gameObject.GetComponent<ClientSimPickupHelper>();
if (previousHelper != null)
{
DestroyImmediate(previousHelper);
pickup.LogWarning($"Destroying old pickup helper on object: {Tools.GetGameObjectPath(pickup.gameObject)}");
}
ClientSimPickupHelper helper = pickup.gameObject.AddComponent<ClientSimPickupHelper>();
helper.SetPickup(pickup);
}
public static void ForceDrop(VRC_Pickup pickup)
{
ClientSimPickupHelper helper = pickup.GetComponent<ClientSimPickupHelper>();
if (helper)
{
helper._forceDropHandler?.Invoke(helper);
}
}
public static VRCPlayerApi GetCurrentPlayer(VRC_Pickup pickup)
{
ClientSimPickupHelper helper = pickup.GetComponent<ClientSimPickupHelper>();
if (!helper)
{
return null;
}
return helper.GetHoldingPlayer();
}
public static VRC_Pickup.PickupHand GetPickupHand(VRC_Pickup pickup)
{
ClientSimPickupHelper helper = pickup.GetComponent<ClientSimPickupHelper>();
if (helper)
{
return helper._heldHand;
}
return VRC_Pickup.PickupHand.None;
}
public static void PickupDestroy(VRC_Pickup pickup)
{
ForceDrop(pickup);
}
public static void PlayHapticForPickup(VRC_Pickup obj, float duration, float amplitude, float frequency)
{
VRCPlayerApi player = obj.currentPlayer;
VRC_Pickup.PickupHand hand = obj.currentHand;
if (Utilities.IsValid(player) && hand != VRC_Pickup.PickupHand.None)
{
player.PlayHapticEventInHand(obj.currentHand, duration, amplitude, frequency);
}
}
private void SetPickup(VRC_Pickup pickup)
{
_pickup = pickup;
_rigidbody = GetComponent<Rigidbody>();
}
#region IClientSimInteractible
public float GetProximity()
{
return _pickup.proximity;
}
public bool CanInteract()
{
return _pickup.pickupable;
}
public string GetInteractText()
{
if (!string.IsNullOrEmpty(_pickup.InteractionText))
{
return _pickup.InteractionText;
}
return AutoHold() ? "Equip" : "Hold to Grab";
}
public Vector3 GetInteractTextPlacement()
{
// VRChatBug: Tooltips always ignore the tooltipPlacement transform and instead place the tooltip at the top
// of the first collider on the object.
return ClientSimTooltip.GetToolTipPosition(gameObject);
}
public void Interact() { }
#endregion
#region IClientSimPickupable
public void Pickup(VRCPlayerApi player, VRC_Pickup.PickupHand heldHand, Action<IClientSimPickupable> forceDropHandler)
{
if (IsHeld())
{
return;
}
_heldPlayer = player;
_heldHand = heldHand;
_forceDropHandler = forceDropHandler;
}
public void Drop(VRCPlayerApi player)
{
if (GetHoldingPlayer() != player)
{
return;
}
_heldPlayer = null;
_heldHand = VRC_Pickup.PickupHand.None;
_forceDropHandler = null;
}
public bool IsHeld() => Utilities.IsValid(_heldPlayer);
public VRCPlayerApi GetHoldingPlayer() => IsHeld() ? _heldPlayer : null;
public bool AutoHold() => _pickup.AutoHold is VRC_Pickup.AutoHoldMode.Yes or VRC_Pickup.AutoHoldMode.Sometimes;
public GameObject GetGameObject() => gameObject;
public Transform GetTransform() => transform;
public Rigidbody GetRigidbody() => _rigidbody;
public VRC_Pickup GetPickup() => _pickup;
public VRC_Pickup.PickupOrientation GetOrientation() => _pickup.orientation;
public Transform GetGunLocation() => _pickup.ExactGun;
public Transform GetGripLocation() => _pickup.ExactGrip;
public float GetThrowVelocityBoostScale() => _pickup.ThrowVelocityBoostScale;
public bool AllowManipulation() => _pickup.allowManipulationWhenEquipped;
#endregion
// TODO display use text after picking up a pickup.
public string PickupText()
{
return _pickup.UseText;
}
}
}

View File

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

View File

@ -0,0 +1,7 @@
namespace VRC.SDK3.ClientSim
{
public class IClientSimPlatformHelper
{
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9b649f5d029e44a281939561cd0adfd9
timeCreated: 1698363870

View File

@ -0,0 +1,151 @@

using UnityEngine;
namespace VRC.SDK3.ClientSim
{
[AddComponentMenu("")]
public abstract class ClientSimPositionSyncedHelperBase :
ClientSimBehaviour,
IClientSimSyncable,
IClientSimPositionSyncable,
IClientSimRespawnHandler
{
private int _ownerID = 1;
private Vector3 _originalPosition;
private Quaternion _originalRotation;
private Rigidbody _rigidbody;
private bool _useGravity;
private bool _isKinematic;
private IClientSimSyncedObjectManager _syncedObjectManager;
public bool SyncPosition { get; protected set; }
protected override void Awake()
{
base.Awake();
_originalPosition = transform.position;
_originalRotation = transform.rotation;
_rigidbody = GetComponent<Rigidbody>();
if (_rigidbody != null)
{
_isKinematic = _rigidbody.isKinematic;
_useGravity = _rigidbody.useGravity;
}
}
public virtual void Initialize(IClientSimSyncedObjectManager syncedObjectManager)
{
_syncedObjectManager = syncedObjectManager;
_syncedObjectManager.AddSyncedObject(this);
}
protected virtual void OnDestroy()
{
// Nullable needed for uninitialized case.
_syncedObjectManager?.RemoveSyncedObject(this);
}
public void TeleportTo(Vector3 position, Quaternion rotation)
{
this.Log($"Teleporting Object {Tools.GetGameObjectPath(gameObject)} to {position} and rotation {rotation.eulerAngles}");
FlagDiscontinuity();
transform.SetPositionAndRotation(position, rotation);
}
public void FlagDiscontinuity()
{
// TODO As of right now, ClientSim doesn't handle any actual sync.
}
#region IClientSimSyncable
public int GetOwner()
{
return _ownerID;
}
public void SetOwner(int ownerID)
{
_ownerID = ownerID;
}
#endregion
#region IClientSimRespawnable
public void Respawn()
{
this.Log($"Respawning Object {Tools.GetGameObjectPath(gameObject)}");
TeleportTo(_originalPosition, _originalRotation);
if (_rigidbody != null)
{
_rigidbody.velocity = Vector3.zero;
}
}
#endregion
#region IClientSimPositionSyncable
public void SetIsKinematic(bool value)
{
_isKinematic = value;
if (_rigidbody)
{
_rigidbody.isKinematic = value;
}
}
public void SetUseGravity(bool value)
{
_useGravity = value;
if (_rigidbody)
{
_rigidbody.useGravity = value;
}
}
public bool GetIsKinematic()
{
return _rigidbody && _rigidbody.isKinematic;
}
public bool GetUseGravity()
{
return _rigidbody && _rigidbody.useGravity;
}
public void UpdatePositionSync()
{
if (_rigidbody != null)
{
// TODO if user is not the owner, set useGravity to false, isKinematic to true.
// This would better simulate ownership, but also make testing awkward.
if (_rigidbody.isKinematic != _isKinematic)
{
_rigidbody.isKinematic = _isKinematic;
this.LogWarning($"Rigidbody.isKinematic was set outside of VRCObjectSync.SetKinematic method! {Tools.GetGameObjectPath(gameObject)}");
}
if (_rigidbody.useGravity != _useGravity)
{
_rigidbody.useGravity = _useGravity;
this.LogWarning($"Rigidbody.useGravity was set outside of VRCObjectSync.SetGravity method! {Tools.GetGameObjectPath(gameObject)}");
}
// VRChatBug: Modifying a collider's "is Trigger" property is also restricted when VRCObjectSync is on
// the same object, but this check will not be added.
}
}
public Transform GetTransform() => transform;
#endregion
}
}

View File

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

View File

@ -0,0 +1,181 @@
using UnityEngine;
using VRC.SDKBase;
namespace VRC.SDK3.ClientSim
{
[AddComponentMenu("")]
[DisallowMultipleComponent]
public class ClientSimSpatialAudioHelper : ONSPAudioSource
{
private const float EPS = 1e-3f;
private VRC_SpatialAudioSource _spatialAudioSource;
private AudioSource _audioSource;
private bool _useAudioSourceCurve;
private ONSPAudioSource _onsp;
private bool _forceUpdate = true;
private bool _updateONSPParams;
public static void InitializeAudio(VRC_SpatialAudioSource obj)
{
#if UNITY_EDITOR
// VRC_SpatialAudioSource executes in editor, meaning it will try to initialize even outside of playmode.
// This code is to prevent adding the ClientSim helper in these cases.
if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode ||
UnityEditor.SceneManagement.EditorSceneManager.IsPreviewSceneObject(obj))
{
return;
}
#endif
ClientSimSpatialAudioHelper spatialAudio = obj.GetComponent<ClientSimSpatialAudioHelper>();
if (spatialAudio != null)
{
DestroyImmediate(spatialAudio);
}
spatialAudio = obj.gameObject.AddComponent<ClientSimSpatialAudioHelper>();
spatialAudio.PreventComponentFromSaving();
spatialAudio.SetSpatializer(obj);
}
private void SetSpatializer(VRC_SpatialAudioSource obj)
{
_spatialAudioSource = obj;
_audioSource = GetComponent<AudioSource>();
_onsp = this;
_forceUpdate = true;
UpdateSettings();
}
private void Start()
{
// Catch Helper not initialized.
if (_spatialAudioSource == null)
{
this.LogWarning($"Destroying uninitialized Helper. Object: {Tools.GetGameObjectPath(gameObject)}");
DestroyImmediate(this);
}
}
private void OnEnable()
{
// ONSP needs to reapply audio settings everytime the object is enabled.
_forceUpdate = true;
_updateONSPParams = true;
}
// Late update to help with testing
private void LateUpdate()
{
UpdateSettings();
}
private void UpdateSettings()
{
if (_spatialAudioSource == null)
{
_spatialAudioSource = GetComponent<VRC_SpatialAudioSource>();
if (_spatialAudioSource == null)
{
Destroy(this);
}
SetSpatializer(_spatialAudioSource);
return;
}
// Check if we need to make changes.
if (
_onsp.EnableSpatialization != _spatialAudioSource.EnableSpatialization ||
_onsp.Gain != _spatialAudioSource.Gain ||
_onsp.Near != _spatialAudioSource.Near ||
_onsp.Far != _spatialAudioSource.Far ||
_useAudioSourceCurve != _spatialAudioSource.UseAudioSourceVolumeCurve
) {
_forceUpdate = true;
_updateONSPParams = true;
}
_onsp.EnableSpatialization = _spatialAudioSource.EnableSpatialization;
_onsp.Gain = _spatialAudioSource.Gain;
_useAudioSourceCurve = _spatialAudioSource.UseAudioSourceVolumeCurve;
_onsp.Near = _spatialAudioSource.Near;
_onsp.Far = _spatialAudioSource.Far;
_onsp.VolumetricRadius = _spatialAudioSource.VolumetricRadius;
// In unity 2022 - updating ONSP params every frame can cause a logspam
// This is a workaround to only update when needed
if (_updateONSPParams)
{
_onsp.SetParameters(ref _audioSource);
_updateONSPParams = false;
}
if (!_onsp.EnableSpatialization)
{
return;
}
if (!_forceUpdate)
{
return;
}
_forceUpdate = false;
if (!_spatialAudioSource.UseAudioSourceVolumeCurve)
{
float near = _onsp.VolumetricRadius + _onsp.Near;
float far = _onsp.VolumetricRadius + Mathf.Max(near, _onsp.Far + EPS);
_audioSource.maxDistance = far;
CreateRolloffCurve(near, far);
CreateSpatialCurve(near, far);
}
}
// Create volume rolloff curve where Volumetric + near is volume 1, then 2^-x fall off to far.
private void CreateRolloffCurve(float near, float far)
{
_audioSource.rolloffMode = AudioRolloffMode.Custom;
AnimationCurve curve = new AnimationCurve();
curve.AddKey(new Keyframe(near, 1));
int max = 8;
for (int loc = 1; loc < max; ++loc)
{
float time = near + Mathf.Pow(2, loc - max) * (far - near);
float value = Mathf.Pow(2.2f, -loc);
curve.AddKey(new Keyframe(time, value));
}
curve.AddKey(new Keyframe(far, 0));
for (int i = 0; i < curve.length; ++i)
{
curve.SmoothTangents(i, 0);
}
_audioSource.SetCustomCurve(AudioSourceCurveType.CustomRolloff, curve);
}
// Create spatial blend curve so that it goes from (Setting) to 3d from min to max
private void CreateSpatialCurve(float near, float far)
{
AnimationCurve spatialCurve = new AnimationCurve();
spatialCurve.AddKey(0, _audioSource.spatialBlend);
spatialCurve.AddKey(_onsp.VolumetricRadius, _audioSource.spatialBlend);
Keyframe nearFrame = new Keyframe(near + EPS, 1);
nearFrame.outTangent = 0;
spatialCurve.AddKey(nearFrame);
Keyframe farFrame = new Keyframe(far, 1);
farFrame.inTangent = 0;
spatialCurve.AddKey(farFrame);
_audioSource.SetCustomCurve(AudioSourceCurveType.SpatialBlend, spatialCurve);
}
}
}

View File

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

View File

@ -0,0 +1,175 @@

using System.IO;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using VRC.Udon.Common;
namespace VRC.SDK3.ClientSim
{
[AddComponentMenu("")]
public class ClientSimStationHelper : ClientSimBehaviour, IClientSimStation
{
private VRCStation _station;
private VRCPlayerApi _usingPlayer;
public Transform EnterLocation() => _station.stationEnterPlayerLocation;
public Transform ExitLocation() => _station.stationExitPlayerLocation;
public bool IsMobile() =>
_station.PlayerMobility == VRCStation.Mobility.Mobile &&
!_station.seated;
public bool IsSeated() => _station.seated;
public bool DisableStationExit() => _station.disableStationExit;
public bool CanUseStationFromStation() => _station.canUseStationFromStation;
public bool IsLockedInStation() => !IsMobile();
public VRCStation GetStation() => _station;
public GameObject GetStationGameObject() => gameObject;
public Transform GetStationTransform() => transform;
public bool IsOccupied() => _usingPlayer != null;
public VRCPlayerApi GetCurrentSittingPlayer() => _usingPlayer;
public static void InitializeStations(VRCStation station)
{
ClientSimStationHelper prevHelper = station.gameObject.GetComponent<ClientSimStationHelper>();
if (prevHelper != null)
{
DestroyImmediate(prevHelper);
station.LogWarning($"Destroying old station helper on object: {Tools.GetGameObjectPath(station.gameObject)}");
}
station.gameObject.AddComponent<ClientSimStationHelper>();
}
public static void UseAttachedStation(VRCPlayerApi player)
{
// UseAttachedStation is a method in the VRCPlayerApi class. This method will take the given player and try to put them in the station component on the GameObject running this Udon program. Since the GameObject is not provided in the parameters, it must be retrieved from the UdonManager by checking the current executing UdonBehaviour.
UdonBehaviour currentUdon = UdonManager.Instance.currentlyExecuting;
if (currentUdon == null)
{
return;
}
VRCStation station = currentUdon.GetComponent<VRCStation>();
if (station == null)
{
return;
}
UseStation(station, player);
}
public static void UseStation(VRCStation station, VRCPlayerApi player)
{
if (!player.isLocal)
{
station.LogWarning($"Trying to force a remote player to enter a station. Force enter a station can only be done for the local player. PlayerId: {player.playerId}, Station: {Tools.GetGameObjectPath(station.gameObject)}");
return;
}
ClientSimStationHelper helper = station.GetComponent<ClientSimStationHelper>();
ClientSimPlayer clientPlayer = player.GetClientSimPlayer();
if (helper != null && clientPlayer != null)
{
clientPlayer.GetStationHandler().EnterStation(helper);
}
}
public static void ExitStation(VRCStation station, VRCPlayerApi player)
{
if (!player.isLocal)
{
station.LogWarning($"Trying to force a remote player to exit a station. Force exit a station can only be done for the local player. PlayerId: {player.playerId}, Station: {Tools.GetGameObjectPath(station.gameObject)}");
return;
}
ClientSimStationHelper helper = station.GetComponent<ClientSimStationHelper>();
ClientSimPlayer clientPlayer = player.GetClientSimPlayer();
if (helper != null && clientPlayer != null)
{
clientPlayer.GetStationHandler().ExitStation(helper);
}
}
protected override void Awake()
{
base.Awake();
_station = GetComponent<VRCStation>();
CheckForMissingComponents();
if (_station.stationEnterPlayerLocation == null)
{
_station.stationEnterPlayerLocation = transform;
}
if (_station.stationExitPlayerLocation == null)
{
_station.stationExitPlayerLocation = transform;
}
}
private void OnDestroy()
{
if (_usingPlayer != null)
{
ExitStation(_station, _usingPlayer);
}
}
private void CheckForMissingComponents()
{
Collider stationCollider = GetComponent<Collider>();
if (stationCollider == null)
{
gameObject.AddComponent<BoxCollider>().isTrigger = true;
}
#if UNITY_EDITOR
UdonBehaviour udon = GetComponent<UdonBehaviour>();
if (udon == null)
{
udon = gameObject.AddComponent<UdonBehaviour>();
udon.interactText = "Sit";
// TODO properly load udon chair program asset.
string sitProgramPath = Path.Combine("Assets", "VRChat Examples", "Prefabs", "VRCChair", "StationGraph.asset");
AbstractUdonProgramSource program = UnityEditor.AssetDatabase.LoadAssetAtPath<AbstractUdonProgramSource>(sitProgramPath);
if (program != null)
{
udon.AssignProgramAndVariables(program.SerializedProgramAsset, new UdonVariableTable());
}
}
#endif
}
public void EnterStation(VRCPlayerApi player)
{
if (_usingPlayer != null || !player.isLocal)
{
return;
}
_usingPlayer = player;
}
public void ExitStation(VRCPlayerApi player)
{
if (_usingPlayer != player)
{
return;
}
_usingPlayer = null;
}
}
}

View File

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

View File

@ -0,0 +1,163 @@
using System.Reflection;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
namespace VRC.SDK3.ClientSim
{
[AddComponentMenu("")]
public class ClientSimUdonHelper :
ClientSimPositionSyncedHelperBase,
IClientSimInteractable,
IClientSimPickupHandler,
IClientSimStationHandler,
IClientSimSyncableHandler
{
private IClientSimUdonManager _udonManager;
private UdonBehaviour _udonBehaviour;
private static readonly FieldInfo _isReady =
typeof(UdonBehaviour).GetField("_isReady", (BindingFlags.Instance | BindingFlags.NonPublic));
public void Initialize(
UdonBehaviour udonBehaviour,
IClientSimUdonManager udonManager,
IClientSimSyncedObjectManager syncedObjectManager,
bool isReady)
{
_udonBehaviour = udonBehaviour;
#pragma warning disable 618
SyncPosition = _udonBehaviour.SynchronizePosition;
#pragma warning restore 618
SetIsReady(isReady);
_udonManager = udonManager;
_udonManager.AddUdonBehaviour(_udonBehaviour);
// Ensure that SyncPosition is set before calling this.
base.Initialize(syncedObjectManager);
}
private void Start()
{
// Catch Helper not initialized.
if (_udonBehaviour == null)
{
this.LogWarning($"Destroying uninitialized Helper. Object: {Tools.GetGameObjectPath(gameObject)}");
DestroyImmediate(this);
}
}
protected override void OnDestroy()
{
base.OnDestroy();
// Nullable needed for uninitialized case.
_udonManager?.RemoveUdonBehaviour(_udonBehaviour);
}
private void SetIsReady(bool isReady)
{
_isReady.SetValue(_udonBehaviour, isReady);
}
public void OnReady()
{
SetIsReady(true);
}
public UdonBehaviour GetUdonBehaviour()
{
return _udonBehaviour;
}
#region IClientSimSyncableHandler
public void OnOwnershipTransferred(int ownerID)
{
_udonBehaviour.RunEvent("_onOwnershipTransferred", ("Player", VRCPlayerApi.GetPlayerById(ownerID)));
}
#endregion
#region IClientSimInteractable
public float GetProximity()
{
return _udonBehaviour.proximity;
}
public bool CanInteract()
{
return _udonBehaviour.IsInteractive;
}
public string GetInteractText()
{
return _udonBehaviour.interactText;
}
public Vector3 GetInteractTextPlacement()
{
// VRChatBug: Tooltips always ignore the tooltipPlacement transform and instead place the tooltip at the top
// of the first collider on the object.
//check if this object has already been destroyed, we can't just do a null check because that still throws a destroyed object error in unity
if (!Utilities.IsValid(this))
{
return Vector3.zero;
}
return ClientSimTooltip.GetToolTipPosition(gameObject);
}
public void Interact()
{
_udonBehaviour.Interact();
}
#endregion
#region IClientSimPickupable
public void OnPickup()
{
_udonBehaviour.OnPickup();
}
public void OnDrop()
{
_udonBehaviour.OnDrop();
}
public void OnPickupUseDown()
{
_udonBehaviour.OnPickupUseDown();
}
public void OnPickupUseUp()
{
_udonBehaviour.OnPickupUseUp();
}
#endregion
#region IClientSimStationHandler
public void OnStationEnter(VRCStation station)
{
VRC.SDK3.Components.VRCStation sdk3Station = station as VRC.SDK3.Components.VRCStation;
_udonBehaviour.RunEvent(sdk3Station.OnLocalPlayerEnterStation, ("Player", Networking.LocalPlayer));
}
public void OnStationExit(VRCStation station)
{
VRC.SDK3.Components.VRCStation sdk3Station = station as VRC.SDK3.Components.VRCStation;
_udonBehaviour.RunEvent(sdk3Station.OnLocalPlayerExitStation, ("Player", Networking.LocalPlayer));
}
#endregion
}
}

View File

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

View File

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

View File

@ -0,0 +1,13 @@
using UnityEngine;
namespace VRC.SDK3.ClientSim
{
public interface IClientSimInteractable
{
float GetProximity();
bool CanInteract();
string GetInteractText();
Vector3 GetInteractTextPlacement();
void Interact();
}
}

View File

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

View File

@ -0,0 +1,10 @@
namespace VRC.SDK3.ClientSim
{
public interface IClientSimPickupHandler
{
void OnPickup();
void OnDrop();
void OnPickupUseDown();
void OnPickupUseUp();
}
}

View File

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

View File

@ -0,0 +1,24 @@
using System;
using UnityEngine;
using VRC.SDKBase;
namespace VRC.SDK3.ClientSim
{
public interface IClientSimPickupable : IClientSimInteractable
{
bool IsHeld();
VRCPlayerApi GetHoldingPlayer();
bool AutoHold();
GameObject GetGameObject();
Transform GetTransform();
Rigidbody GetRigidbody();
VRC_Pickup GetPickup();
VRC_Pickup.PickupOrientation GetOrientation();
Transform GetGunLocation();
Transform GetGripLocation();
float GetThrowVelocityBoostScale();
bool AllowManipulation();
void Pickup(VRCPlayerApi player, VRC_Pickup.PickupHand heldHand, Action<IClientSimPickupable> forceDrop);
void Drop(VRCPlayerApi player);
}
}

View File

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

View File

@ -0,0 +1,9 @@
using VRC.SDK3.Platform;
namespace VRC.SDK3.ClientSim
{
public interface IClientSimPlatformManager
{
void OnScreenUpdate(ScreenUpdateData data);
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0eab22d28a864961b5fdde0d9feae259
timeCreated: 1698363855

View File

@ -0,0 +1,19 @@

using UnityEngine;
namespace VRC.SDK3.ClientSim
{
/// <summary>
/// Represents an object that syncs its position
/// </summary>
public interface IClientSimPositionSyncable : IClientSimSyncable, IClientSimRespawnHandler
{
bool SyncPosition { get; }
void SetIsKinematic(bool value);
void SetUseGravity(bool value);
bool GetIsKinematic();
bool GetUseGravity();
void UpdatePositionSync();
Transform GetTransform();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4b6da4177d7e4ca78b196bbf95952162
timeCreated: 1640133052

View File

@ -0,0 +1,7 @@
namespace VRC.SDK3.ClientSim
{
public interface IClientSimRespawnHandler
{
void Respawn();
}
}

View File

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

View File

@ -0,0 +1,25 @@
using UnityEngine;
using VRC.SDKBase;
namespace VRC.SDK3.ClientSim
{
public interface IClientSimStation
{
Transform EnterLocation();
Transform ExitLocation();
bool IsMobile();
bool IsSeated();
bool DisableStationExit();
bool CanUseStationFromStation();
bool IsLockedInStation();
VRCStation GetStation();
GameObject GetStationGameObject();
Transform GetStationTransform();
void EnterStation(VRCPlayerApi player);
void ExitStation(VRCPlayerApi player);
bool IsOccupied();
VRCPlayerApi GetCurrentSittingPlayer();
}
}

View File

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

View File

@ -0,0 +1,10 @@
using VRC.SDKBase;
namespace VRC.SDK3.ClientSim
{
public interface IClientSimStationHandler
{
void OnStationEnter(VRCStation station);
void OnStationExit(VRCStation station);
}
}

View File

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

View File

@ -0,0 +1,8 @@
namespace VRC.SDK3.ClientSim
{
public interface IClientSimSyncable
{
int GetOwner();
void SetOwner(int ownerID);
}
}

View File

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

View File

@ -0,0 +1,7 @@
namespace VRC.SDK3.ClientSim
{
public interface IClientSimSyncableHandler
{
void OnOwnershipTransferred(int ownerID);
}
}

View File

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

View File

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

View File

@ -0,0 +1,379 @@

#if ENABLE_INPUT_SYSTEM
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.InputSystem;
using VRC.SDKBase;
#endif
using VRC.Udon.Common;
namespace VRC.SDK3.ClientSim
{
// TODO refactor Input system to queue events from Unity's input system instead of firing events right away.
// Unity's input events are processed and sent before ALL monobehaviours. In test environment, this will happen
// right after modifying the input device, which will happen at the end of the frame instead. In order to properly
// process input events the same in runtime and tests, the events will need to be queued and processed in mono behaviours.
// This would also allow for other event types to be included that does not use the Input System (eg vr controls).
/// <summary>
/// An implementation of ClientSimInput that receives input events from Unity's Input System using InputActions.
/// Events from the action system are then sent to systems listening to input events.
/// </summary>
public class ClientSimInputActionBased : ClientSimInputBase
{
private readonly ClientSimSettings _settings;
/* Items need to be wrapped in this define to prevent compiler errors on initial import
when the Input System has not yet been imported or enabled.*/
#if ENABLE_INPUT_SYSTEM
private readonly InputAction _movementHorizontal;
private readonly InputAction _movementVertical;
private readonly InputAction _lookHorizontal;
private readonly InputAction _lookVertical;
private readonly InputAction _jumpLeft;
private readonly InputAction _jumpRight;
private readonly InputAction _useLeft;
private readonly InputAction _useRight;
private readonly InputAction _grabLeft;
private readonly InputAction _grabRight;
private readonly InputAction _dropLeft;
private readonly InputAction _dropRight;
private readonly InputAction _toggleMenuLeft;
private readonly InputAction _toggleMenuRight;
#region NonVR Only
private readonly InputAction _run;
private readonly InputAction _toggleCrouch;
private readonly InputAction _toggleProne;
private readonly InputAction _releaseMouse;
private readonly InputAction _pickupRotateUpDown;
private readonly InputAction _pickupRotateLeftRight;
private readonly InputAction _pickupRotateCwCcw;
private readonly InputAction _pickupManipulateForwardBack;
#endregion
private InputActionAsset InputActions;
public ClientSimInputActionBased(InputActionAsset actionAsset, ClientSimSettings settings)
{
InputActions = actionAsset;
_settings = settings;
_lookHorizontal = actionAsset["LookHorizontal"];
_lookVertical = actionAsset["LookVertical"];
_movementHorizontal = actionAsset["MovementHorizontal"];
_movementVertical = actionAsset["MovementVertical"];
_jumpLeft = actionAsset["JumpLeft"];
_jumpRight = actionAsset["JumpRight"];
_useLeft = actionAsset["UseLeft"];
_useRight = actionAsset["UseRight"];
_grabLeft = actionAsset["GrabLeft"];
_grabRight = actionAsset["GrabRight"];
_dropLeft = actionAsset["DropLeft"];
_dropRight = actionAsset["DropRight"];
_toggleMenuLeft = actionAsset["ToggleMenuLeft"];
_toggleMenuRight = actionAsset["ToggleMenuRight"];
// Desktop only input options.
_run = actionAsset["Run"];
_toggleCrouch = actionAsset["ToggleCrouch"];
_toggleProne = actionAsset["ToggleProne"];
_releaseMouse = actionAsset["ReleaseMouse"];
_pickupRotateUpDown = actionAsset["PickupRotateUpDown"];
_pickupRotateLeftRight = actionAsset["PickupRotateLeftRight"];
_pickupRotateCwCcw = actionAsset["PickupRotateCwCcw"];
_pickupManipulateForwardBack = actionAsset["PickupManipulateForwardBack"];
_jumpLeft.performed += HandleJumpLeft;
_jumpLeft.canceled += HandleJumpLeft;
_jumpRight.performed += HandleJumpRight;
_jumpRight.canceled += HandleJumpRight;
_useLeft.performed += HandleUseLeft;
_useLeft.canceled += HandleUseLeft;
_useRight.performed += HandleUseRight;
_useRight.canceled += HandleUseRight;
_grabLeft.performed += HandleGrabLeft;
_grabLeft.canceled += HandleGrabLeft;
_grabRight.performed += HandleGrabRight;
_grabRight.canceled += HandleGrabRight;
_dropLeft.performed += HandleDropLeft;
_dropLeft.canceled += HandleDropLeft;
_dropRight.performed += HandleDropRight;
_dropRight.canceled += HandleDropRight;
_toggleMenuLeft.performed += HandleToggleMenuLeft;
_toggleMenuLeft.canceled += HandleToggleMenuLeft;
_toggleMenuRight.performed += HandleToggleMenuRight;
_toggleMenuRight.canceled += HandleToggleMenuRight;
_run.performed += HandleRun;
_run.canceled += HandleRun;
_toggleCrouch.performed += HandleToggleCrouch;
_toggleCrouch.canceled += HandleToggleCrouch;
_toggleProne.performed += HandleToggleProne;
_toggleProne.canceled += HandleToggleProne;
_releaseMouse.performed += HandleReleaseMouse;
_releaseMouse.canceled += HandleReleaseMouse;
foreach (InputAction action in actionAsset)
{
action.performed += SetInputDevice;
}
}
private VRCInputMethod _lastInputMethod = VRCInputMethod.Count;
public VRCInputMethod LastInputMethod
{
get => _lastInputMethod;
set
{
// Only send events for changes
if(_lastInputMethod == value) return;
_lastInputMethod = value;
SendInputMethodChangedEvent(_lastInputMethod);
}
}
private const string MOUSE_LOOK_PATTERN = @"Player/Look.*Mouse/delta";
bool isTouchActive = false;
private void SetInputDevice(InputAction.CallbackContext ctx)
{
if(Regex.IsMatch(ctx.action.ToString(), MOUSE_LOOK_PATTERN)) return; // This is to avoid changing the tooltip icon when looking with a mouse
isTouchActive = (ctx.control.device.description.empty || Touchscreen.current != null && ctx.control.device.GetType().Equals(Touchscreen.current.GetType())); // On screen controls
if (!isTouchActive)
{
if (Keyboard.current != null && ctx.control.device.GetType().Equals(Keyboard.current.GetType()))
{
#if VRC_MOBILE && UNITY_ANDROID
//This prevents the android back button from switching to keyboard and disabling touch UI
if (ctx.control.device.description.interfaceName == "Android")
return;
#endif
LastInputMethod = VRCInputMethod.Keyboard;
return;
}
if (Mouse.current != null && ctx.control.device.GetType().Equals(Mouse.current.GetType()))
{
LastInputMethod = VRCInputMethod.Mouse;
return;
}
if (Gamepad.current != null && ctx.control.device.GetType().Equals(Gamepad.current.GetType()))
{
LastInputMethod = VRCInputMethod.Controller;
return;
}
}
else
{
LastInputMethod = VRCInputMethod.Touch;
}
}
public override void Dispose()
{
base.Dispose();
// Go through and unsubscribe from all input actions
_jumpLeft.performed -= HandleJumpLeft;
_jumpLeft.canceled -= HandleJumpLeft;
_jumpRight.performed -= HandleJumpRight;
_jumpRight.canceled -= HandleJumpRight;
_useLeft.performed -= HandleUseLeft;
_useLeft.canceled -= HandleUseLeft;
_useRight.performed -= HandleUseRight;
_useRight.canceled -= HandleUseRight;
_grabLeft.performed -= HandleGrabLeft;
_grabLeft.canceled -= HandleGrabLeft;
_grabRight.performed -= HandleGrabRight;
_grabRight.canceled -= HandleGrabRight;
_dropLeft.performed -= HandleDropLeft;
_dropLeft.canceled -= HandleDropLeft;
_dropRight.performed -= HandleDropRight;
_dropRight.canceled -= HandleDropRight;
_toggleMenuLeft.performed -= HandleToggleMenuLeft;
_toggleMenuLeft.canceled -= HandleToggleMenuLeft;
_toggleMenuRight.performed -= HandleToggleMenuRight;
_toggleMenuRight.canceled -= HandleToggleMenuRight;
_run.performed -= HandleRun;
_run.canceled -= HandleRun;
_toggleCrouch.performed -= HandleToggleCrouch;
_toggleCrouch.canceled -= HandleToggleCrouch;
_toggleProne.performed -= HandleToggleProne;
_toggleProne.canceled -= HandleToggleProne;
_releaseMouse.performed -= HandleReleaseMouse;
_releaseMouse.canceled -= HandleReleaseMouse;
}
private void HandleJumpLeft(InputAction.CallbackContext context)
{
SendJumpEvent(context.ReadValueAsButton(), HandType.LEFT);
}
private void HandleJumpRight(InputAction.CallbackContext context)
{
SendJumpEvent(context.ReadValueAsButton(), HandType.RIGHT);
}
private void HandleUseLeft(InputAction.CallbackContext context)
{
SendUseEvent(context.ReadValueAsButton(), HandType.LEFT);
}
private void HandleUseRight(InputAction.CallbackContext context)
{
SendUseEvent(context.ReadValueAsButton(), HandType.RIGHT);
}
private void HandleGrabLeft(InputAction.CallbackContext context)
{
SendGrabEvent(context.ReadValueAsButton(), HandType.LEFT);
}
private void HandleGrabRight(InputAction.CallbackContext context)
{
SendGrabEvent(context.ReadValueAsButton(), HandType.RIGHT);
}
private void HandleDropLeft(InputAction.CallbackContext context)
{
SendDropEvent(context.ReadValueAsButton(), HandType.LEFT);
}
private void HandleDropRight(InputAction.CallbackContext context)
{
SendDropEvent(context.ReadValueAsButton(), HandType.RIGHT);
}
private void HandleToggleMenuLeft(InputAction.CallbackContext context)
{
SendToggleMenuEvent(context.ReadValueAsButton(), HandType.LEFT);
}
private void HandleToggleMenuRight(InputAction.CallbackContext context)
{
SendToggleMenuEvent(context.ReadValueAsButton(), HandType.RIGHT);
}
private void HandleRun(InputAction.CallbackContext context)
{
SendRunEvent(context.ReadValueAsButton());
}
private void HandleToggleCrouch(InputAction.CallbackContext context)
{
SendToggleCrouchEvent(context.ReadValueAsButton());
}
private void HandleToggleProne(InputAction.CallbackContext context)
{
SendToggleProneEvent(context.ReadValueAsButton());
}
private void HandleReleaseMouse(InputAction.CallbackContext context)
{
SendReleaseMouseEvent(context.ReadValueAsButton());
}
#endif
public override float GetMovementHorizontal()
{
#if ENABLE_INPUT_SYSTEM
return _movementHorizontal.ReadValue<float>();
#else
return 0;
#endif
}
public override float GetMovementVertical()
{
#if ENABLE_INPUT_SYSTEM
return _movementVertical.ReadValue<float>();
#else
return 0;
#endif
}
public override float GetLookHorizontal()
{
#if ENABLE_INPUT_SYSTEM
if (_pickupRotateLeftRight.ReadValue<float>() != 0)
{
return 0;
}
return _lookHorizontal.ReadValue<float>();
#else
return 0;
#endif
}
public override float GetLookVertical()
{
#if ENABLE_INPUT_SYSTEM
if (_pickupRotateUpDown.ReadValue<float>() != 0)
{
return 0;
}
return _lookVertical.ReadValue<float>() * (_settings.invertMouseLook ? -1 : 1);
#else
return 0;
#endif
}
public override float GetPickupRotateUpDown()
{
#if ENABLE_INPUT_SYSTEM
return _pickupRotateUpDown.ReadValue<float>();
#else
return 0;
#endif
}
public override float GetPickupRotateLeftRight()
{
#if ENABLE_INPUT_SYSTEM
return _pickupRotateLeftRight.ReadValue<float>();
#else
return 0;
#endif
}
public override float GetPickupRotateCwCcw()
{
#if ENABLE_INPUT_SYSTEM
return _pickupRotateCwCcw.ReadValue<float>();
#else
return 0;
#endif
}
public override float GetPickupManipulateDistance()
{
#if ENABLE_INPUT_SYSTEM
return _pickupManipulateForwardBack.ReadValue<float>();
#else
return 0;
#endif
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 606ef90f0aab477f88f3df2bd3dece3b
timeCreated: 1640211870

View File

@ -0,0 +1,242 @@
using System;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon.Common;
namespace VRC.SDK3.ClientSim
{
/// <summary>
/// Base method for handling subscribing to and sending input events.
/// Class is abstract to allow for Test versions to mock sending input events.
/// </summary>
public abstract class ClientSimInputBase : IClientSimInput, IDisposable
{
private Action<bool, HandType> _jumpEvent;
private Action<bool, HandType> _useEvent;
private Action<bool, HandType> _grabEvent;
private Action<bool, HandType> _dropEvent;
private Action<bool, HandType> _toggleMenuEvent;
private Action<bool> _runEvent;
private Action<bool> _toggleCrouchEvent;
private Action<bool> _toggleProneEvent;
private Action<bool> _releaseMouseEvent;
private Action<VRCInputMethod> _inputMethodChangedEvent;
public virtual void Dispose()
{
// Clear all subscriptions
_jumpEvent = null;
_useEvent = null;
_grabEvent = null;
_dropEvent = null;
_toggleMenuEvent = null;
_inputMethodChangedEvent = null;
_runEvent = null;
_toggleCrouchEvent = null;
_toggleProneEvent = null;
_releaseMouseEvent = null;
}
#region Event Subscriptions
public void SubscribeJump(Action<bool, HandType> handler)
{
_jumpEvent += handler;
}
public void UnsubscribeJump(Action<bool, HandType> handler)
{
_jumpEvent -= handler;
}
public void SubscribeUse(Action<bool, HandType> handler)
{
_useEvent += handler;
}
public void UnsubscribeUse(Action<bool, HandType> handler)
{
_useEvent -= handler;
}
public void SubscribeGrab(Action<bool, HandType> handler)
{
_grabEvent += handler;
}
public void UnsubscribeGrab(Action<bool, HandType> handler)
{
_grabEvent -= handler;
}
public void SubscribeDrop(Action<bool, HandType> handler)
{
_dropEvent += handler;
}
public void UnsubscribeDrop(Action<bool, HandType> handler)
{
_dropEvent -= handler;
}
public void SubscribeToggleMenu(Action<bool, HandType> handler)
{
_toggleMenuEvent += handler;
}
public void UnsubscribeToggleMenu(Action<bool, HandType> handler)
{
_toggleMenuEvent -= handler;
}
public void SubscribeRun(Action<bool> handler)
{
_runEvent += handler;
}
public void UnsubscribeRun(Action<bool> handler)
{
_runEvent -= handler;
}
public void SubscribeToggleCrouch(Action<bool> handler)
{
_toggleCrouchEvent += handler;
}
public void UnsubscribeToggleCrouch(Action<bool> handler)
{
_toggleCrouchEvent -= handler;
}
public void SubscribeToggleProne(Action<bool> handler)
{
_toggleProneEvent += handler;
}
public void UnsubscribeToggleProne(Action<bool> handler)
{
_toggleProneEvent -= handler;
}
public void SubscribeReleaseMouse(Action<bool> handler)
{
_releaseMouseEvent += handler;
}
public void UnsubscribeReleaseMouse(Action<bool> handler)
{
_releaseMouseEvent -= handler;
}
public void SubscribeInputChangedEvent(Action<VRCInputMethod> handler)
{
_inputMethodChangedEvent += handler;
}
public void UnsubscribeInputChangedEvent(Action<VRCInputMethod> handler)
{
_inputMethodChangedEvent -= handler;
}
#endregion
#region GetAxisData
public Vector2 GetMovementAxes()
{
float x = GetMovementHorizontal();
float y = GetMovementVertical();
return new Vector2(x, y);
}
public Vector2 GetLookAxes()
{
float x = GetLookHorizontal();
float y = GetLookVertical();
return new Vector2(x, y);
}
public abstract float GetMovementHorizontal();
public abstract float GetMovementVertical();
public abstract float GetLookHorizontal();
public abstract float GetLookVertical();
public abstract float GetPickupRotateUpDown();
public abstract float GetPickupRotateLeftRight();
public abstract float GetPickupRotateCwCcw();
public abstract float GetPickupManipulateDistance();
#endregion
public void SendJumpEvent(bool value, HandType handType)
{
_jumpEvent?.Invoke(value, handType);
}
public void SendUseEvent(bool value, HandType handType)
{
_useEvent?.Invoke(value, handType);
}
public void SendGrabEvent(bool value, HandType handType)
{
_grabEvent?.Invoke(value, handType);
}
public void SendDropEvent(bool value, HandType handType)
{
_dropEvent?.Invoke(value, handType);
}
public void SendToggleMenuEvent(bool value, HandType handType)
{
_toggleMenuEvent?.Invoke(value, handType);
}
public void SendRunEvent(bool value)
{
_runEvent?.Invoke(value);
}
public void SendToggleCrouchEvent(bool value)
{
_toggleCrouchEvent?.Invoke(value);
}
public void SendToggleProneEvent(bool value)
{
_toggleProneEvent?.Invoke(value);
}
public void SendReleaseMouseEvent(bool value)
{
_releaseMouseEvent?.Invoke(value);
}
public void SendInputMethodChangedEvent(VRCInputMethod value)
{
_inputMethodChangedEvent?.Invoke(value);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9f663f3afb8c43b9bde934ce5e27b320
timeCreated: 1640210807

View File

@ -0,0 +1,58 @@
using UnityEngine;
using VRC.SDKBase;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
namespace VRC.SDK3.ClientSim
{
/// <summary>
/// Wrapper around ClientSimInputActionBased that supplies the input actions.
/// Classes were split to allow for testing without monobehaviours.
/// </summary>
[AddComponentMenu("")]
public class ClientSimInputManager : ClientSimBehaviour
{
#if ENABLE_INPUT_SYSTEM
private PlayerInput _playerInput;
private ClientSimInputActionBased _input;
protected override void Awake()
{
base.Awake();
_playerInput = GetComponent<PlayerInput>();
}
private void OnDestroy()
{
_input?.Dispose();
}
#endif
public void Initialize(ClientSimSettings settings)
{
#if ENABLE_INPUT_SYSTEM
_input = new ClientSimInputActionBased(_playerInput.actions, settings);
#endif
}
public IClientSimInput GetInput()
{
#if ENABLE_INPUT_SYSTEM
return _input;
#else
return null;
#endif
}
public VRCInputMethod GetLastUsedInputMethod()
{
#if ENABLE_INPUT_SYSTEM
return _input.LastInputMethod;
#else
return VRCInputMethod.Generic;
#endif
}
}
}

View File

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

View File

@ -0,0 +1,938 @@
{
"name": "ClientSimInputMapping",
"maps": [
{
"name": "Player",
"id": "2f50182f-8d0e-45db-86fb-c28a4c2928c3",
"actions": [
{
"name": "LookHorizontal",
"type": "Value",
"id": "fd5ba6df-78c4-4e25-9fd5-7e3d97cff8c1",
"expectedControlType": "Axis",
"processors": "",
"interactions": ""
},
{
"name": "LookVertical",
"type": "Value",
"id": "d9675fd8-9661-4368-984d-f6f7683fe821",
"expectedControlType": "Axis",
"processors": "",
"interactions": ""
},
{
"name": "MovementHorizontal",
"type": "Value",
"id": "c5e34fa0-7b42-436a-aac5-43719b6671e5",
"expectedControlType": "Axis",
"processors": "",
"interactions": ""
},
{
"name": "MovementVertical",
"type": "Value",
"id": "9e5c4572-7e08-413a-8992-d08e65de6588",
"expectedControlType": "Axis",
"processors": "",
"interactions": ""
},
{
"name": "JumpLeft",
"type": "Button",
"id": "4bcd4015-fce1-40e8-bac9-17c60193e8a6",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "JumpRight",
"type": "Button",
"id": "e1d78f1c-246d-42ce-8dad-64769265bc40",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "UseLeft",
"type": "Button",
"id": "1c0d0334-cacd-4c50-81e3-c9efb4436ccb",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "UseRight",
"type": "Button",
"id": "5e78bbbe-04e6-44df-bc85-404f92936b98",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "GrabLeft",
"type": "Button",
"id": "a9b3a7e3-2b29-48d1-8be7-c7a2fba1d70d",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "GrabRight",
"type": "Button",
"id": "e77cb057-0a67-47a5-b73b-cf5f750feae1",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "DropLeft",
"type": "Button",
"id": "5e9b0664-5568-442e-bc7b-000386ea8cb7",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "DropRight",
"type": "Button",
"id": "5ae3c089-7d25-41d2-8245-78c24b806ae9",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "ToggleMenuLeft",
"type": "Button",
"id": "1fe9ba51-431d-42df-b3b8-fb55b37a26d9",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "ToggleMenuRight",
"type": "Button",
"id": "8a4af8b2-f882-4ae3-a6ea-f8d10c88a1f6",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "Run",
"type": "Button",
"id": "e47a69fe-eadc-47da-a81f-909b6c277e00",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "ToggleCrouch",
"type": "Button",
"id": "2064b4f9-486f-476d-800b-52f1208aff6c",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "ToggleProne",
"type": "Button",
"id": "28ffcaeb-1d40-48b8-8ed9-593338b8e9be",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "ReleaseMouse",
"type": "Button",
"id": "20ba4d98-6df4-4d39-aab4-5a0b170f1526",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "PickupRotateUpDown",
"type": "Button",
"id": "5f707c29-abb7-489d-9762-4a19e7a44caf",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "PickupRotateLeftRight",
"type": "Button",
"id": "aaf0f321-7c3d-49d6-991f-1c7ee7cf6425",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "PickupRotateCwCcw",
"type": "Button",
"id": "97928f76-bc32-45a3-94fd-211a439b5a9e",
"expectedControlType": "Button",
"processors": "",
"interactions": ""
},
{
"name": "PickupManipulateForwardBack",
"type": "Value",
"id": "768b624a-5837-4f36-9f89-e4953a7a8951",
"expectedControlType": "Axis",
"processors": "",
"interactions": ""
}
],
"bindings": [
{
"name": "",
"id": "550c6b6a-81c1-4282-b4fd-ac579d21ac6c",
"path": "<Keyboard>/space",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "JumpLeft",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "730090f3-1e28-4ef7-8a82-0ebb910ef667",
"path": "<Keyboard>/c",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "ToggleCrouch",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "b4cfeb73-71f8-45b9-8cdd-8e649dde9d1b",
"path": "<Keyboard>/z",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "ToggleProne",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "e7ed5e53-bd0b-40a1-a286-8b86a4372292",
"path": "<Gamepad>/leftStickPress",
"interactions": "Hold",
"processors": "",
"groups": "Gamepad",
"action": "ToggleProne",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "9d0b9b99-72c2-46cf-8765-fbcc9b4589ef",
"path": "<Keyboard>/tab",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "ReleaseMouse",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "UpDown",
"id": "599162a8-d480-47e8-a889-c48d14357572",
"path": "1DAxis",
"interactions": "",
"processors": "",
"groups": "",
"action": "MovementVertical",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "negative",
"id": "ef0c2b1a-07ce-45fc-8d51-1bd6fdcbb28f",
"path": "<Keyboard>/s",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "MovementVertical",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "positive",
"id": "554ed0de-b631-49a1-b623-878c3ab03682",
"path": "<Keyboard>/w",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "MovementVertical",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "",
"id": "094901d9-a4fa-43ef-8c9f-bfed9d7d8920",
"path": "<Gamepad>/leftStick/y",
"interactions": "",
"processors": "AxisDeadzone",
"groups": "Gamepad",
"action": "MovementVertical",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "LeftRight",
"id": "3c1a54d3-a057-4e61-8b9a-5b1f13341d5a",
"path": "1DAxis",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "MovementHorizontal",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "negative",
"id": "57ae784a-0a44-45cb-b7b8-fcec8b04cad3",
"path": "<Keyboard>/a",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "MovementHorizontal",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "positive",
"id": "a334ecdc-1430-4b6a-afde-3ff6d2154773",
"path": "<Keyboard>/d",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "MovementHorizontal",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "",
"id": "b1ab7f24-f82a-42df-b586-cced45f62b2e",
"path": "<Gamepad>/leftStick/x",
"interactions": "",
"processors": "AxisDeadzone",
"groups": "Gamepad",
"action": "MovementHorizontal",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "adab09ec-3821-417c-82b8-314882199eb0",
"path": "<XRController>{RightHand}/primary2daxis/y",
"interactions": "",
"processors": "",
"groups": "XRController",
"action": "LookVertical",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "a7212801-53a8-4fe8-831b-01974f0c2d9f",
"path": "<Mouse>/delta/y",
"interactions": "",
"processors": "Scale(factor=0.1)",
"groups": "KeyboardMouse",
"action": "LookVertical",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "7d1e21de-8a59-41db-9f28-c95b92b3da8c",
"path": "<Gamepad>/rightStick/y",
"interactions": "",
"processors": "AxisDeadzone,Scale(factor=2)",
"groups": "",
"action": "LookVertical",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "3481d65f-35c3-48f7-b259-6f8d2cac26c8",
"path": "<Mouse>/delta/x",
"interactions": "",
"processors": "Scale(factor=0.1)",
"groups": "KeyboardMouse",
"action": "LookHorizontal",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "4aeeecac-4996-47e7-bd05-af550efb62ac",
"path": "<XRController>{RightHand}/primary2daxis/x",
"interactions": "",
"processors": "Scale(factor=4)",
"groups": "XRController",
"action": "LookHorizontal",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "6533983e-06ef-4adc-abd2-6cd33b462ac8",
"path": "<Gamepad>/rightStick/x",
"interactions": "",
"processors": "AxisDeadzone,Scale(factor=2)",
"groups": "Gamepad",
"action": "LookHorizontal",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "f5444cd9-f41a-4ebb-aadf-c10211534afa",
"path": "<XRController>{RightHand}/grippressed",
"interactions": "",
"processors": "",
"groups": "XRController",
"action": "GrabRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "ba0f411d-3372-4c6d-b9d5-c26e875be59c",
"path": "<Mouse>/leftButton",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "GrabRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "ad175ec7-32cb-49e5-a110-f579d3d3413c",
"path": "<Mouse>/rightButton",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "DropRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "1d68fa18-5dc4-4d90-9365-9c4db81311e2",
"path": "<Gamepad>/buttonNorth",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "DropRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "50eac56e-5677-4524-8d0f-a1fbf16f3900",
"path": "<Gamepad>/rightShoulder",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "DropRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "157bf0cb-a7e7-48d9-88fa-1517693070c5",
"path": "<XRController>{RightHand}/triggerpressed",
"interactions": "",
"processors": "",
"groups": "XRController",
"action": "UseRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "3127ec01-bb2b-4e6d-a7ff-130d739c6b31",
"path": "<Mouse>/leftButton",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "UseRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "5ff2efde-e215-4a78-b8bc-0bff8e57e630",
"path": "<Gamepad>/rightTrigger",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "UseRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "66ff8685-5751-48e9-b8e1-52b8eee82e34",
"path": "<Gamepad>/buttonEast",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "UseRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "9f1da403-960f-41db-b08c-65d7599e899a",
"path": "<Keyboard>/leftShift",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "Run",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "UpDown",
"id": "394c9c63-26cd-4401-a397-184c01a7fb06",
"path": "1DAxis",
"interactions": "",
"processors": "",
"groups": "",
"action": "PickupRotateUpDown",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "negative",
"id": "bf93b7e2-55ce-481e-85c2-0e8b4ae2f3c9",
"path": "<Keyboard>/i",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "PickupRotateUpDown",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "positive",
"id": "9de24fef-2a7c-4bcf-b274-63daff07abfe",
"path": "<Keyboard>/k",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "PickupRotateUpDown",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "ClockwiseCounterClockwise",
"id": "80006719-f2f8-4dc6-9fdf-443d2486f309",
"path": "1DAxis",
"interactions": "",
"processors": "",
"groups": "",
"action": "PickupRotateCwCcw",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "negative",
"id": "f3d77344-6c16-4b26-9dfb-ff765db9be87",
"path": "<Keyboard>/u",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "PickupRotateCwCcw",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "positive",
"id": "aae8cb97-dffd-4fb5-b4d3-8bc92c76da7c",
"path": "<Keyboard>/o",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "PickupRotateCwCcw",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "",
"id": "943c5ae4-0943-427d-95d9-cf4e676b20b0",
"path": "<Mouse>/scroll/y",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "PickupManipulateForwardBack",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "012bc491-7c79-43cd-872a-c9d5c56e6e6f",
"path": "<XRController>{LeftHand}/primary2daxis/y",
"interactions": "",
"processors": "",
"groups": "XRController",
"action": "MovementVertical",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "94d2aec0-a0b9-4934-9b0a-8a27e75d118c",
"path": "<XRController>{LeftHand}/primary2daxis/x",
"interactions": "",
"processors": "",
"groups": "XRController",
"action": "MovementHorizontal",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "11375370-60ed-4eff-968c-85131e5900f9",
"path": "<XRController>{RightHand}/joystickorpadpressed",
"interactions": "",
"processors": "",
"groups": "XRController",
"action": "JumpRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "0650c167-2c34-4480-8deb-3c9c251c9563",
"path": "<Gamepad>/buttonSouth",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "JumpRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "9928474d-83f2-4067-a555-bc7dd2bdf3dd",
"path": "<XRController>{LeftHand}/triggerpressed",
"interactions": "",
"processors": "",
"groups": "XRController",
"action": "UseLeft",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "3462f0fd-c25f-4223-88e1-e448815e9f90",
"path": "<XRController>{LeftHand}/grippressed",
"interactions": "",
"processors": "",
"groups": "XRController",
"action": "GrabLeft",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "6be33530-03b7-47d5-baef-a1873dab0483",
"path": "<XRController>{LeftHand}/primary",
"interactions": "",
"processors": "",
"groups": "XRController",
"action": "ToggleMenuLeft",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "a6937a04-1651-4f9b-b532-f1b1f52f91ea",
"path": "<Keyboard>/escape",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "ToggleMenuLeft",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "LeftRight",
"id": "dc3ca725-f9f3-4b9d-b73d-ccab2de20c89",
"path": "1DAxis",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "PickupRotateLeftRight",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "negative",
"id": "edbe58fa-0e59-446c-9765-589870585e9c",
"path": "<Keyboard>/l",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "PickupRotateLeftRight",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "positive",
"id": "6eab6abd-f646-4064-a093-bef75d51704f",
"path": "<Keyboard>/j",
"interactions": "",
"processors": "",
"groups": "KeyboardMouse",
"action": "PickupRotateLeftRight",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "",
"id": "41374253-d07b-4eb9-b76f-68ff25ad6099",
"path": "<XRController>{RightHand}/primary",
"interactions": "",
"processors": "",
"groups": "XRController",
"action": "ToggleMenuRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "98ed6e07-cbe7-4332-9ab3-7d130fc2be70",
"path": "<Gamepad>/start",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "ToggleMenuRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "813f1ec6-f321-4bd5-b091-a936c2173904",
"path": "<Gamepad>/rightTrigger",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "GrabRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "d2eb4465-b94a-4012-ab0d-5c9fa72c9cb7",
"path": "<Gamepad>/buttonEast",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "GrabRight",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "d39fd262-9f80-4061-bf54-a42d997bb0d6",
"path": "<Gamepad>/leftStickPress",
"interactions": "Press",
"processors": "",
"groups": "Gamepad",
"action": "ToggleCrouch",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "RightStickWithLeftShoulder",
"id": "b2aff2a5-4826-433b-b14b-4c2c8925e56c",
"path": "ButtonWithOneModifier",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "PickupRotateUpDown",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "modifier",
"id": "42575b80-955d-494a-bdff-4e83a6beb850",
"path": "<Gamepad>/leftShoulder",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "PickupRotateUpDown",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "button",
"id": "ada6cac4-3fc3-4dc5-a418-2d6c3f218d24",
"path": "<Gamepad>/rightStick/y",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "PickupRotateUpDown",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "RightStickWithLeftShoulder",
"id": "a69e5173-82f8-4ed7-8d30-af528c6fa262",
"path": "ButtonWithOneModifier",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "PickupRotateLeftRight",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "modifier",
"id": "52c40760-cb07-427d-95c9-cd8539c939e8",
"path": "<Gamepad>/leftShoulder",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "PickupRotateLeftRight",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "button",
"id": "f3ce62de-2286-499f-bfbb-aaad0021a224",
"path": "<Gamepad>/rightStick/x",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "PickupRotateLeftRight",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "LeftStickWithLeftShoulder",
"id": "5466362d-116f-4eb8-9825-010812d07ff7",
"path": "ButtonWithOneModifier",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "PickupRotateCwCcw",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "modifier",
"id": "79ec2bba-44b3-4a1a-a18c-e03370ecce44",
"path": "<Gamepad>/leftShoulder",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "PickupRotateCwCcw",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "button",
"id": "3c906a95-8491-42be-a414-8d69ecdf037b",
"path": "<Gamepad>/leftStick/x",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "PickupRotateCwCcw",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "LeftStickWithLeftShoulder",
"id": "9170df3e-d37e-4c49-8bb3-5a944ff141d5",
"path": "ButtonWithOneModifier",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "PickupManipulateForwardBack",
"isComposite": true,
"isPartOfComposite": false
},
{
"name": "modifier",
"id": "07f0af65-6b32-4bf4-ac49-52784277b1fc",
"path": "<Gamepad>/leftShoulder",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "PickupManipulateForwardBack",
"isComposite": false,
"isPartOfComposite": true
},
{
"name": "button",
"id": "0f1b2753-f8d1-4106-b35b-1551d1c825db",
"path": "<Gamepad>/leftStick/y",
"interactions": "",
"processors": "",
"groups": "Gamepad",
"action": "PickupManipulateForwardBack",
"isComposite": false,
"isPartOfComposite": true
}
]
}
],
"controlSchemes": [
{
"name": "KeyboardMouse",
"bindingGroup": "KeyboardMouse",
"devices": [
{
"devicePath": "<Keyboard>",
"isOptional": false,
"isOR": false
},
{
"devicePath": "<Mouse>",
"isOptional": false,
"isOR": false
}
]
},
{
"name": "Gamepad",
"bindingGroup": "Gamepad",
"devices": [
{
"devicePath": "<Gamepad>",
"isOptional": false,
"isOR": false
}
]
},
{
"name": "XRController",
"bindingGroup": "XRController",
"devices": [
{
"devicePath": "<XRController>{RightHand}",
"isOptional": true,
"isOR": false
},
{
"devicePath": "<XRController>{LeftHand}",
"isOptional": true,
"isOR": false
}
]
}
]
}

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 2a08897387d5ffd4795ccec0e8384a6a
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3}
generateWrapperCode: 0
wrapperCodePath:
wrapperClassName: ClientSimInputMapping
wrapperCodeNamespace: VRC.SDK3.ClientSim

View File

@ -0,0 +1,193 @@

using System;
using System.Collections.Generic;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using VRC.Udon.Common;
namespace VRC.SDK3.ClientSim
{
/// <summary>
/// System responsible for listening to Input Events and sending them to UdonBehaviours.
/// </summary>
/// <remarks>
/// Listens to Events:
/// - ClientSimMenuStateChangedEvent
/// Listens to Input Events:
/// - Jump
/// - Grab
/// - Use
/// - Drop
/// </remarks>
public class ClientSimUdonInput : IDisposable
{
private const float MINIMUM_MOVE_INPUT_EPS = 1e-3f;
private const float MINIMUM_LOOK_INPUT_EPS = 1e-5f;
private readonly IClientSimInput _input;
private readonly IClientSimEventDispatcher _eventDispatcher;
/// <summary>
/// A wrapper for sending events to all UdonBehaviours to create unit tests not dependent on UdonManager.
/// </summary>
private readonly IClientSimUdonInputEventSender _udonInputEventSender;
private Vector2 _prevInput;
private Vector2 _prevLookInput = Vector2.zero;
private Vector2 _prevMoveAxes = Vector2.zero;
private bool _isMenuOpen;
private int _lastMenuUpdateFrame; // TODO update based on processing tick instead of time to allow for better testing.
// All button based events are queued until process time. This is done to ensure that all udon input events
// happen at the same time in the frame and not mixed between when unity's update for the new Input Manager
// sends events. Without this, input events would happen before UdonBehaviour.Update, causing strange out of
// order issues.
private readonly Queue<Action> _queuedEvents = new Queue<Action>();
public ClientSimUdonInput(
IClientSimEventDispatcher eventDispatcher,
IClientSimInput input,
IClientSimUdonInputEventSender udonInputEventSender)
{
_input = input;
_eventDispatcher = eventDispatcher;
_udonInputEventSender = udonInputEventSender;
_eventDispatcher.Subscribe<ClientSimMenuStateChangedEvent>(SetMenuOpen);
// Input will be null with incorrect Unity input project settings.
_input?.SubscribeJump(JumpInput);
_input?.SubscribeUse(UseInput);
_input?.SubscribeGrab(GrabInput);
_input?.SubscribeDrop(DropInput);
_input?.SubscribeInputChangedEvent(SendInputChangedEvent);
}
public void Dispose()
{
_eventDispatcher?.Unsubscribe<ClientSimMenuStateChangedEvent>(SetMenuOpen);
_input?.UnsubscribeJump(JumpInput);
_input?.UnsubscribeUse(UseInput);
_input?.UnsubscribeGrab(GrabInput);
_input?.UnsubscribeDrop(DropInput);
_input?.UnsubscribeInputChangedEvent(SendInputChangedEvent);
}
#region ClientSim Events
private void SetMenuOpen(ClientSimMenuStateChangedEvent stateChangedEvent)
{
_lastMenuUpdateFrame = Time.frameCount;
_isMenuOpen = stateChangedEvent.isMenuOpen;
}
#endregion
#region ClientSim Input
private void JumpInput(bool value, HandType hand)
{
QueueButtonInputEvent(value, hand, UdonManager.UDON_INPUT_JUMP);
}
private void UseInput(bool value, HandType hand)
{
QueueButtonInputEvent(value, hand, UdonManager.UDON_INPUT_USE);
}
private void GrabInput(bool value, HandType hand)
{
QueueButtonInputEvent(value, hand, UdonManager.UDON_INPUT_GRAB);
}
private void DropInput(bool value, HandType hand)
{
QueueButtonInputEvent(value, hand, UdonManager.UDON_INPUT_DROP);
}
private void SendInputChangedEvent(VRCInputMethod inputMethod)
{
_queuedEvents.Enqueue(() =>
{
UdonManager.Instance.RunEvent(UdonManager.UDON_EVENT_ONINPUTMETHODCHANGED, ("inputMethod", inputMethod));
});
}
#endregion
private void SendUdonInputBoolEvent(bool value, HandType hand, string eventName)
{
var args = new UdonInputEventArgs(value, hand);
_udonInputEventSender.RunInputAction(eventName, args);
}
private void SendUdonInputFloatEvent(float value, HandType hand, string eventName)
{
var args = new UdonInputEventArgs(value, hand);
_udonInputEventSender.RunInputAction(eventName, args);
}
private void QueueButtonInputEvent(bool value, HandType hand, string eventName)
{
// Do not queue event if the menu is open.
if (IsMenuOpen())
{
return;
}
_queuedEvents.Enqueue(() =>
{
SendUdonInputBoolEvent(value, hand, eventName);
});
}
public void ProcessInputEvents()
{
// If the menu is open or the menu was updated on this frame, skip sending input events.
if (IsMenuOpen())
{
_queuedEvents.Clear();
return;
}
// Ensure that all udon input events happen at the same time in the frame and not mixed between when unity's
// update for the new Input Manager sends events. Without this, input events would happen before
// UdonBehaviour.Update, causing strange out of order issues.
while (_queuedEvents.Count > 0)
{
Action inputEvent = _queuedEvents.Dequeue();
inputEvent?.Invoke();
}
Vector2 lookAxes = _input.GetLookAxes();
if (Mathf.Abs(lookAxes.x - _prevLookInput.x) > MINIMUM_LOOK_INPUT_EPS)
{
SendUdonInputFloatEvent(lookAxes.x, HandType.RIGHT, UdonManager.UDON_LOOK_HORIZONTAL);
}
if (Mathf.Abs(lookAxes.y - _prevLookInput.y) > MINIMUM_LOOK_INPUT_EPS)
{
SendUdonInputFloatEvent(lookAxes.y, HandType.RIGHT, UdonManager.UDON_LOOK_VERTICAL);
}
_prevLookInput = lookAxes;
Vector2 moveAxes = _input.GetMovementAxes();
if (Mathf.Abs(_prevMoveAxes.x - moveAxes.x) > MINIMUM_MOVE_INPUT_EPS)
{
SendUdonInputFloatEvent(moveAxes.x, HandType.LEFT, UdonManager.UDON_MOVE_HORIZONTAL);
}
if (Mathf.Abs(_prevMoveAxes.y - moveAxes.y) > MINIMUM_MOVE_INPUT_EPS)
{
SendUdonInputFloatEvent(moveAxes.y, HandType.LEFT, UdonManager.UDON_MOVE_VERTICAL);
}
_prevMoveAxes = moveAxes;
}
private bool IsMenuOpen()
{
return _isMenuOpen || _lastMenuUpdateFrame == Time.frameCount;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1e963688a1c048ddac34d738cc8f3072
timeCreated: 1638891929

View File

@ -0,0 +1,33 @@
using UnityEngine;
using VRC.Udon;
namespace VRC.SDK3.ClientSim
{
/// <summary>
/// Wrapper class for UdonInput
/// </summary>
[AddComponentMenu("")]
[DefaultExecutionOrder(1)] // Ensure that input events happen after UdonBehaviour.Update
public class ClientSimUdonInputBehaviour : ClientSimBehaviour
{
private ClientSimUdonInput _udonInput;
public void Initialize(IClientSimEventDispatcher eventDispatcher, IClientSimInput input)
{
_udonInput = new ClientSimUdonInput(
eventDispatcher,
input,
new ClientSimUdonManagerInputEventSender(UdonManager.Instance));
}
private void OnDestroy()
{
_udonInput?.Dispose();
}
public void Update()
{
_udonInput?.ProcessInputEvents();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5fc129f391f3488e947eb13a9208bae4
timeCreated: 1640233289

View File

@ -0,0 +1,23 @@
using VRC.Udon;
using VRC.Udon.Common;
namespace VRC.SDK3.ClientSim
{
/// <summary>
/// Wrapper for UdonManager. Only needed to prevent direct dependency to UdonManger in the UdonInput class.
/// </summary>
public class ClientSimUdonManagerInputEventSender : IClientSimUdonInputEventSender
{
private readonly UdonManager _udonManager;
public ClientSimUdonManagerInputEventSender(UdonManager udonManager)
{
_udonManager = udonManager;
}
public void RunInputAction(string eventName, UdonInputEventArgs args)
{
_udonManager.RunInputAction(eventName, args);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4025ed62957542d2b0609cfa37d16946
timeCreated: 1640269473

View File

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

View File

@ -0,0 +1,56 @@
using System;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon.Common;
namespace VRC.SDK3.ClientSim
{
public interface IClientSimInput
{
float GetMovementHorizontal();
float GetMovementVertical();
Vector2 GetMovementAxes();
float GetLookHorizontal();
float GetLookVertical();
Vector2 GetLookAxes();
float GetPickupRotateUpDown();
float GetPickupRotateLeftRight();
float GetPickupRotateCwCcw();
float GetPickupManipulateDistance();
void SubscribeJump(Action<bool, HandType> handler);
void UnsubscribeJump(Action<bool, HandType> handler);
void SubscribeUse(Action<bool, HandType> handler);
void UnsubscribeUse(Action<bool, HandType> handler);
void SubscribeGrab(Action<bool, HandType> handler);
void UnsubscribeGrab(Action<bool, HandType> handler);
void SubscribeDrop(Action<bool, HandType> handler);
void UnsubscribeDrop(Action<bool, HandType> handler);
void SubscribeToggleMenu(Action<bool, HandType> handler);
void UnsubscribeToggleMenu(Action<bool, HandType> handler);
void SubscribeRun(Action<bool> handler);
void UnsubscribeRun(Action<bool> handler);
void SubscribeToggleCrouch(Action<bool> handler);
void UnsubscribeToggleCrouch(Action<bool> handler);
void SubscribeToggleProne(Action<bool> handler);
void UnsubscribeToggleProne(Action<bool> handler);
void SubscribeReleaseMouse(Action<bool> handler);
void UnsubscribeReleaseMouse(Action<bool> handler);
public void SubscribeInputChangedEvent(Action<VRCInputMethod> handler);
public void UnsubscribeInputChangedEvent(Action<VRCInputMethod> handler);
public void SendToggleMenuEvent(bool value, HandType hand);
}
}

Some files were not shown because too many files have changed in this diff Show More