Added Unity project files
This commit is contained in:
@ -0,0 +1,314 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon.Common;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// The system responsible for managing the mouse position for both ClientSim raycasting
|
||||
/// and for Unity's EventSystem to know where to raycast for UI elements.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sends Events:
|
||||
/// - ClientSimMouseReleasedEvent
|
||||
/// - ClientSimCurrentHandEvent
|
||||
/// Listens to Events:
|
||||
/// - ClientSimMenuStateChangedEvent
|
||||
/// - ClientSimRaycastHitResultsEvent
|
||||
/// Listens to Input Events:
|
||||
/// - Use
|
||||
/// - ReleaseMouse
|
||||
/// </remarks>
|
||||
[AddComponentMenu("")]
|
||||
// Update FrameTick at the end of frame so that Input from playmode and test can be processed in the same order.
|
||||
[DefaultExecutionOrder(10000)]
|
||||
class ClientSimBaseInput : BaseInput, IClientSimMousePositionProvider, IDisposable
|
||||
{
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private IClientSimInput _input;
|
||||
private ClientSimSettings _settings;
|
||||
|
||||
private bool _menuIsOpen;
|
||||
private bool _mouseReleaseKeyIsDown;
|
||||
private bool _prevMouseReleased;
|
||||
|
||||
// Used for interacting with UI
|
||||
private int _frameTick = 0;
|
||||
private bool _rightUseDown = false;
|
||||
private int _rightUseChangeTick = -1;
|
||||
private bool _leftUseDown = false;
|
||||
private int _leftUseChangeTick = -1;
|
||||
|
||||
private HandType _lastHandUsed = HandType.RIGHT;
|
||||
private Camera _playerCamera = null;
|
||||
private bool _uiShapeHit = false;
|
||||
private Vector3 _raycastMousePosition;
|
||||
|
||||
public static Vector2 GetScreenCenter()
|
||||
{
|
||||
return new Vector2(Screen.width, Screen.height) * 0.5f;
|
||||
}
|
||||
|
||||
public Vector2 GetMousePosition()
|
||||
{
|
||||
if (IsMouseFree())
|
||||
{
|
||||
// Due to having multiple inputs enabled or disabled, this method ensures no errors are thrown even
|
||||
// if setup is incorrect.
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
// TODO if gamepad input, emulate mouse position to allow clicking on menus.
|
||||
return UnityEngine.InputSystem.Mouse.current?.position.ReadValue() ?? Vector2.zero;
|
||||
#elif ENABLE_LEGACY_INPUT_MANAGER
|
||||
return base.mousePosition;
|
||||
#else
|
||||
return Vector2.zero;
|
||||
#endif
|
||||
}
|
||||
return GetScreenCenter();
|
||||
}
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
this.PreventComponentFromSaving();
|
||||
}
|
||||
|
||||
public void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimInput input,
|
||||
ClientSimSettings settings)
|
||||
{
|
||||
// Do not lock mouse if the player is never spawned.
|
||||
if (!settings.spawnPlayer)
|
||||
{
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_input = input;
|
||||
_settings = settings;
|
||||
|
||||
_eventDispatcher.Subscribe<ClientSimMenuStateChangedEvent>(SetMenuOpen);
|
||||
_eventDispatcher.Subscribe<ClientSimRaycastHitResultsEvent>(OnRaycastHit);
|
||||
|
||||
// Input will be null with incorrect Unity input project settings.
|
||||
_input?.SubscribeReleaseMouse(InputMouseReleased);
|
||||
_input?.SubscribeUse(InputUse);
|
||||
}
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
// TODO properly pass in the camera provider instead of using this method.
|
||||
_playerCamera = VRC_UiShape.GetEventCamera?.Invoke(this.gameObject);
|
||||
|
||||
foreach (var canvas in FindObjectsByType<Canvas>(FindObjectsSortMode.None))
|
||||
{
|
||||
if ((canvas.renderMode == RenderMode.WorldSpace) && (canvas.worldCamera == null))
|
||||
canvas.worldCamera = _playerCamera;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_eventDispatcher?.Unsubscribe<ClientSimMenuStateChangedEvent>(SetMenuOpen);
|
||||
_eventDispatcher?.Unsubscribe<ClientSimRaycastHitResultsEvent>(OnRaycastHit);
|
||||
|
||||
_input?.UnsubscribeReleaseMouse(InputMouseReleased);
|
||||
_input?.UnsubscribeUse(InputUse);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Update mouse lock every frame to ensure it is always locked when needed.
|
||||
InternalLockUpdate();
|
||||
|
||||
// TODO Move this to input system and support checking when Use input is down or up on the current frame.
|
||||
++_frameTick;
|
||||
}
|
||||
|
||||
#region Overrides
|
||||
|
||||
// Use the screenspace value of the last raycast hit position as the current mouse position.
|
||||
// Using the raycast position decouples Desktop and VR's input source, allowing both to interact with UI without
|
||||
// knowing the source of the raycast (mouse vs controller position)
|
||||
public override Vector2 mousePosition => _raycastMousePosition;
|
||||
|
||||
public override bool GetMouseButton(int button)
|
||||
{
|
||||
return _lastHandUsed == HandType.RIGHT ? _rightUseDown : _leftUseDown;
|
||||
}
|
||||
|
||||
public override bool GetMouseButtonUp(int button)
|
||||
{
|
||||
return
|
||||
_lastHandUsed == HandType.RIGHT
|
||||
? (!_rightUseDown && _rightUseChangeTick == _frameTick)
|
||||
: (!_leftUseDown && _leftUseChangeTick == _frameTick);
|
||||
}
|
||||
|
||||
public override bool GetMouseButtonDown(int button)
|
||||
{
|
||||
return
|
||||
_lastHandUsed == HandType.RIGHT
|
||||
? (_rightUseDown && _rightUseChangeTick == _frameTick)
|
||||
: (_leftUseDown && _leftUseChangeTick == _frameTick);
|
||||
}
|
||||
|
||||
// Override mouse scroll method to prevent errors when input settings are incorrectly setup on first import.
|
||||
public override Vector2 mouseScrollDelta
|
||||
{
|
||||
get
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
return UnityEngine.InputSystem.Mouse.current?.scroll.ReadValue() ?? Vector2.zero;
|
||||
#elif ENABLE_LEGACY_INPUT_MANAGER
|
||||
return base.mouseScrollDelta;
|
||||
#else
|
||||
return Vector2.zero;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Override generic axis method to prevent errors when input settings are incorrectly setup on first import.
|
||||
public override float GetAxisRaw(string axisName)
|
||||
{
|
||||
if (axisName == "Horizontal")
|
||||
{
|
||||
return _input?.GetMovementHorizontal() ?? 0;
|
||||
}
|
||||
if (axisName == "Vertical")
|
||||
{
|
||||
return _input?.GetMovementVertical() ?? 0;
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// Override generic button method to prevent errors when input settings are incorrectly setup on first import.
|
||||
public override bool GetButtonDown(string buttonName)
|
||||
{
|
||||
if (buttonName == "Horizontal")
|
||||
{
|
||||
return Mathf.Abs(_input?.GetMovementHorizontal() ?? 0) > 0.5;
|
||||
}
|
||||
if (buttonName == "Vertical")
|
||||
{
|
||||
return Mathf.Abs(_input?.GetMovementVertical() ?? 0) > 0.5;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ClientSim Events
|
||||
|
||||
private void SetMenuOpen(ClientSimMenuStateChangedEvent stateChangedEvent)
|
||||
{
|
||||
_menuIsOpen = stateChangedEvent.isMenuOpen;
|
||||
CheckMouseRelease();
|
||||
}
|
||||
|
||||
private void OnRaycastHit(ClientSimRaycastHitResultsEvent hitEvent)
|
||||
{
|
||||
if (_lastHandUsed == hitEvent.handType)
|
||||
{
|
||||
var hitResults = hitEvent.raycastResults;
|
||||
_uiShapeHit = hitResults != null && hitResults.uiShape != null;
|
||||
|
||||
_raycastMousePosition = GetScreenCenter();
|
||||
// If there is a player camera and there was a hit point, convert the world point to screen space.
|
||||
// Transforming it now instead of when requested to ensure that player position updates do not affect
|
||||
// interacting with the menu.
|
||||
if (hitResults != null && _playerCamera != null)
|
||||
{
|
||||
_raycastMousePosition = _playerCamera.WorldToScreenPoint(hitResults.hitPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ClientSim Input Events
|
||||
|
||||
private void InputMouseReleased(bool value)
|
||||
{
|
||||
_mouseReleaseKeyIsDown = value;
|
||||
CheckMouseRelease();
|
||||
}
|
||||
|
||||
private void InputUse(bool value, HandType handType)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
if (_lastHandUsed != handType)
|
||||
{
|
||||
_lastHandUsed = handType;
|
||||
_eventDispatcher.SendEvent(new ClientSimCurrentHandEvent { currentUsedHand = _lastHandUsed });
|
||||
}
|
||||
}
|
||||
|
||||
if (handType == HandType.RIGHT)
|
||||
{
|
||||
_rightUseDown = value;
|
||||
_rightUseChangeTick = _frameTick;
|
||||
}
|
||||
else
|
||||
{
|
||||
_leftUseDown = value;
|
||||
_leftUseChangeTick = _frameTick;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public bool HitUIShape()
|
||||
{
|
||||
return _uiShapeHit;
|
||||
}
|
||||
|
||||
private bool IsMouseFree()
|
||||
{
|
||||
return _mouseReleaseKeyIsDown || _menuIsOpen;
|
||||
}
|
||||
|
||||
private void CheckMouseRelease()
|
||||
{
|
||||
bool released = IsMouseFree();
|
||||
if (released != _prevMouseReleased)
|
||||
{
|
||||
_prevMouseReleased = released;
|
||||
_eventDispatcher.SendEvent(new ClientSimMouseReleasedEvent { isReleased = released });
|
||||
}
|
||||
InternalLockUpdate();
|
||||
}
|
||||
|
||||
private void InternalLockUpdate()
|
||||
{
|
||||
// If the menu is open or the tab key is held down, do not lock the mouse and show the cursor.
|
||||
// TODO Check if TrackingProvider is VR and do not lock mouse.
|
||||
if (IsMouseFree() || ClientSimRuntimeLoader.IsInUnityTest())
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
Cursor.visible = true;
|
||||
}
|
||||
// Else hide the cursor and lock the cursor to the center of the screen.
|
||||
else
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
Cursor.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b423402f03c4238b2fe097f146f7d01
|
||||
timeCreated: 1638823270
|
||||
@ -0,0 +1,29 @@
|
||||
|
||||
using UnityEngine;
|
||||
using VRC.Udon;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// System responsible for adding objects to the Udon blacklist.
|
||||
/// </summary>
|
||||
/// <remarks>Really just a wrapper for calling the blacklist method on UdonManager.</remarks>
|
||||
public class ClientSimBlacklistManager : IClientSimBlacklistManager
|
||||
{
|
||||
public void AddObjectAndChildrenToBlackList(GameObject obj)
|
||||
{
|
||||
AddObjectAndChildrenToBlackList(obj, UdonManager.Instance);
|
||||
}
|
||||
|
||||
private void AddObjectAndChildrenToBlackList(GameObject obj, UdonManager udonManager)
|
||||
{
|
||||
udonManager.Blacklist(obj);
|
||||
|
||||
Transform xform = obj.transform;
|
||||
for (int child = 0; child < xform.childCount; ++child)
|
||||
{
|
||||
AddObjectAndChildrenToBlackList(xform.GetChild(child).gameObject, udonManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2929dd4844944c799d13b91350be20d
|
||||
timeCreated: 1638914444
|
||||
@ -0,0 +1,126 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// System responsible for highlighting objects.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimHighlightManager : ClientSimBehaviour, IClientSimHighlightManager
|
||||
{
|
||||
[SerializeField]
|
||||
private Mesh cubeMesh;
|
||||
[SerializeField]
|
||||
private Mesh capsuleMesh;
|
||||
[SerializeField]
|
||||
private Mesh sphereMesh;
|
||||
|
||||
[SerializeField]
|
||||
private GameObject proxyHighlightPrefab;
|
||||
|
||||
// TODO wrap highlightFX to make it easier to test/replace.
|
||||
private HighlightsFX _highlightsFX;
|
||||
|
||||
private readonly Dictionary<GameObject, ClientSimHighlightProxy> _objectRenderProxies =
|
||||
new Dictionary<GameObject, ClientSimHighlightProxy>();
|
||||
|
||||
private readonly Queue<ClientSimHighlightProxy> _proxyMeshQueue = new Queue<ClientSimHighlightProxy>();
|
||||
|
||||
public void Initialize(Camera playerCamera)
|
||||
{
|
||||
_highlightsFX = playerCamera.gameObject.AddComponent<HighlightsFX>();
|
||||
}
|
||||
|
||||
public void EnableObjectHighlight(GameObject obj)
|
||||
{
|
||||
List<Renderer> renderers = GatherRenderers(obj, false);
|
||||
if (renderers.Count == 0)
|
||||
{
|
||||
Renderer rend = GetProxyHighlight(obj);
|
||||
if (rend != null)
|
||||
{
|
||||
renderers.Add(rend);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var rend in renderers)
|
||||
{
|
||||
EnableObjectHighlight(rend, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisableObjectHighlight(GameObject obj)
|
||||
{
|
||||
if (_objectRenderProxies.TryGetValue(obj, out ClientSimHighlightProxy proxy))
|
||||
{
|
||||
_objectRenderProxies.Remove(obj);
|
||||
EnableObjectHighlight(proxy.Renderer, false);
|
||||
|
||||
_proxyMeshQueue.Enqueue(proxy);
|
||||
proxy.DisableProxy();
|
||||
}
|
||||
|
||||
List<Renderer> renderers = GatherRenderers(obj, true);
|
||||
foreach (var rend in renderers)
|
||||
{
|
||||
EnableObjectHighlight(rend, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void EnableObjectHighlight(Renderer rend, bool isEnabled)
|
||||
{
|
||||
_highlightsFX.EnableOutline(rend, isEnabled);
|
||||
}
|
||||
|
||||
private List<Renderer> GatherRenderers(GameObject obj, bool findDisabled)
|
||||
{
|
||||
if(obj == null)
|
||||
{
|
||||
return new List<Renderer>();
|
||||
}
|
||||
List<Renderer> results = new List<Renderer>();
|
||||
foreach (var rend in obj.GetComponentsInChildren<Renderer>(findDisabled))
|
||||
{
|
||||
if (!rend.enabled || rend.isPartOfStaticBatch)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
MeshFilter filter = rend.GetComponent<MeshFilter>();
|
||||
if (filter == null || filter.sharedMesh == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
results.Add(rend);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private Renderer GetProxyHighlight(GameObject obj)
|
||||
{
|
||||
ClientSimHighlightProxy proxy = GetUnusedProxy();
|
||||
|
||||
Collider objCollider = obj.GetComponent<Collider>();
|
||||
_objectRenderProxies.Add(obj, proxy);
|
||||
proxy.EnableProxy(obj.transform, objCollider);
|
||||
|
||||
return proxy.Renderer;
|
||||
}
|
||||
|
||||
private ClientSimHighlightProxy GetUnusedProxy()
|
||||
{
|
||||
if (_proxyMeshQueue.Count == 0)
|
||||
{
|
||||
GameObject tooltipObj = Instantiate(proxyHighlightPrefab, transform);
|
||||
ClientSimHighlightProxy tooltip = tooltipObj.GetComponent<ClientSimHighlightProxy>();
|
||||
tooltip.Initialize(cubeMesh, capsuleMesh, sphereMesh);
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
return _proxyMeshQueue.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1938047de93c66141923068bf4063db5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,125 @@
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
// This class is used for when an object has no usable mesh renderers to highlight. This will copy the collider on
|
||||
// the interact instead of any mesh on or under the interact.
|
||||
// An object will not have usable colliders if:
|
||||
// - There are no mesh renderers on or as a child of the object.
|
||||
// - The mesh renderers do not have a mesh
|
||||
// - The mesh renderers are part of a combined or static batched mesh.
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimHighlightProxy : ClientSimBehaviour
|
||||
{
|
||||
public Renderer Renderer { get; private set; }
|
||||
|
||||
private Mesh _cubeMesh;
|
||||
private Mesh _capsuleMesh;
|
||||
private Mesh _sphereMesh;
|
||||
|
||||
private MeshFilter _filter;
|
||||
private Transform _proxyObject;
|
||||
private Collider _proxyCollider;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
Renderer = GetComponent<Renderer>();
|
||||
_filter = GetComponent<MeshFilter>();
|
||||
}
|
||||
|
||||
public void Initialize(Mesh cubeMesh, Mesh capsuleMesh, Mesh sphereMesh)
|
||||
{
|
||||
_cubeMesh = cubeMesh;
|
||||
_capsuleMesh = capsuleMesh;
|
||||
_sphereMesh = sphereMesh;
|
||||
}
|
||||
|
||||
private void OnWillRenderObject()
|
||||
{
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
private void UpdatePosition()
|
||||
{
|
||||
Vector3 position = _proxyObject.position;
|
||||
Quaternion rotation = _proxyObject.rotation;
|
||||
Vector3 scale = _proxyObject.lossyScale;
|
||||
|
||||
if (_proxyCollider is BoxCollider boxCollider)
|
||||
{
|
||||
position += rotation * Vector3.Scale(boxCollider.center, scale);
|
||||
scale = Vector3.Scale(scale, boxCollider.size);
|
||||
_filter.sharedMesh = _cubeMesh;
|
||||
}
|
||||
else if (_proxyCollider is SphereCollider sphereCollider)
|
||||
{
|
||||
position += rotation * Vector3.Scale(sphereCollider.center, scale);
|
||||
|
||||
// VRChatBug: Unity will always keep spheres round, but VRChat ignores this for the proxy rendering.
|
||||
|
||||
// Sphere colliders are always round. Get the max component as the sphere size.
|
||||
float sphereScale = sphereCollider.radius * 2 * Mathf.Max(scale.x, scale.y, scale.z);
|
||||
scale = sphereScale * Vector3.one;
|
||||
_filter.sharedMesh = _sphereMesh;
|
||||
}
|
||||
else if (_proxyCollider is CapsuleCollider capsuleCollider)
|
||||
{
|
||||
position += rotation * Vector3.Scale(capsuleCollider.center, scale);
|
||||
float height = capsuleCollider.height * 0.5f;
|
||||
float radius = capsuleCollider.radius * 2;
|
||||
float sphereScale = radius;
|
||||
|
||||
switch (capsuleCollider.direction)
|
||||
{
|
||||
case 0: // X
|
||||
rotation *= Quaternion.Euler(0, 0, 90);
|
||||
sphereScale = Mathf.Max(scale.y, scale.z) * radius;
|
||||
height *= scale.x;
|
||||
break;
|
||||
case 1: // Y
|
||||
// No need to rotate since mesh is already along y axis.
|
||||
sphereScale = Mathf.Max(scale.x, scale.z) * radius;
|
||||
height *= scale.y;
|
||||
break;
|
||||
case 2: // Z
|
||||
rotation *= Quaternion.Euler(90, 0, 0);
|
||||
sphereScale = Mathf.Max(scale.y, scale.x) * radius;
|
||||
height *= scale.z;
|
||||
break;
|
||||
}
|
||||
|
||||
height = Mathf.Max(sphereScale * 0.5f, height);
|
||||
scale = new Vector3(sphereScale, height, sphereScale);
|
||||
_filter.sharedMesh = _capsuleMesh;
|
||||
}
|
||||
else if (_proxyCollider is MeshCollider meshCollider)
|
||||
{
|
||||
_filter.sharedMesh = meshCollider.sharedMesh;
|
||||
}
|
||||
|
||||
transform.SetPositionAndRotation(position, rotation);
|
||||
transform.localScale = scale;
|
||||
}
|
||||
|
||||
public void EnableProxy(Transform obj, Collider proxyCollider)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
_filter.sharedMesh = null;
|
||||
_proxyObject = obj;
|
||||
_proxyCollider = proxyCollider;
|
||||
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
public void DisableProxy()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
_filter.sharedMesh = null;
|
||||
_proxyObject = null;
|
||||
_proxyCollider = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cdc8e1faef31d1d479cb70d50716fc35
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,122 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// System responsible for filtering out UI elements that cannot be interacted with.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimInputModule : StandaloneInputModule
|
||||
{
|
||||
private IClientSimInteractiveLayerProvider _interactiveLayerProvider;
|
||||
private ClientSimBaseInput _baseInput;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
this.PreventComponentFromSaving();
|
||||
}
|
||||
|
||||
private void DisableOtherEventSystems()
|
||||
{
|
||||
// Go through and Disable all other event systems in the scene.
|
||||
EventSystem thisEventSystem = GetComponent<EventSystem>();
|
||||
EventSystem[] systems = FindObjectsByType<EventSystem>(FindObjectsSortMode.None);
|
||||
foreach (EventSystem system in systems)
|
||||
{
|
||||
if (system != thisEventSystem)
|
||||
{
|
||||
system.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(IClientSimInteractiveLayerProvider interactiveLayerProvider)
|
||||
{
|
||||
_interactiveLayerProvider = interactiveLayerProvider;
|
||||
}
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
DisableOtherEventSystems();
|
||||
|
||||
// TODO check settings and disable self if player is not spawned to allow normal ui raycasting.
|
||||
m_InputOverride = _baseInput = GetComponent<ClientSimBaseInput>();
|
||||
|
||||
base.Start();
|
||||
}
|
||||
|
||||
|
||||
// Force processing of mouse events even if the cursors is locked.
|
||||
public override void Process()
|
||||
{
|
||||
CursorLockMode currentLockState = Cursor.lockState;
|
||||
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
|
||||
base.Process();
|
||||
|
||||
Cursor.lockState = currentLockState;
|
||||
}
|
||||
|
||||
// Prevent clicking on menus that are not currently interactable.
|
||||
protected override MouseState GetMousePointerEventData(int id)
|
||||
{
|
||||
var pointerEventData = base.GetMousePointerEventData(id);
|
||||
var leftEventData = pointerEventData.GetButtonState(PointerEventData.InputButton.Left).eventData;
|
||||
var pointerRaycast = leftEventData.buttonData.pointerCurrentRaycast;
|
||||
|
||||
// Check if this raycast result is valid. If not, reset the data.
|
||||
if (!ShouldUseRaycastResult(pointerRaycast))
|
||||
{
|
||||
leftEventData.buttonData.pointerCurrentRaycast = new RaycastResult();
|
||||
}
|
||||
|
||||
return pointerEventData;
|
||||
}
|
||||
|
||||
private bool ShouldUseRaycastResult(RaycastResult result)
|
||||
{
|
||||
GameObject hitObj = result.gameObject;
|
||||
if (hitObj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If raycaster did not hit a UI shape, this means some other collider is in front.
|
||||
if (!_baseInput.HitUIShape())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is no UI shape on this object or in its parents, ignore it.
|
||||
VRC_UiShape shape = hitObj.GetComponentInParent<VRC_UiShape>();
|
||||
if (shape == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GameObject shapeObj = shape.gameObject;
|
||||
// Ignore UI elements not on a currently Interactive layer
|
||||
if (((1 << shapeObj.layer) & _interactiveLayerProvider.GetInteractiveLayers()) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3 position = result.worldPosition;
|
||||
// If ray is within any collider on the UIShape, it is valid.
|
||||
foreach (var shapeCollider in shapeObj.GetComponents<Collider>())
|
||||
{
|
||||
// If the closest point is itself, then it is in the collider.
|
||||
if (Vector3.Distance(shapeCollider.ClosestPoint(position), position) < 0.01f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 848153f45093e01469743624ef9f9391
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// System that provides what layers the player can interact with at a given time.
|
||||
/// Interactive layers will change depending on if the Menu is open or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Listens to Events:
|
||||
/// - ClientSimMenuStateChangedEvent
|
||||
/// </remarks>
|
||||
public class ClientSimInteractiveLayerProvider : IClientSimInteractiveLayerProvider, IDisposable
|
||||
{
|
||||
private const int UI_LAYER = 5;
|
||||
private const int PLAYER_LOCAL_LAYER = 10;
|
||||
private const int UI_MENU_LAYER = 12;
|
||||
private const int MIRROR_REFLECTION_LAYER = 18;
|
||||
private const int INTERNAL_UI_LAYER = 19;
|
||||
private const int FIRST_USER_LAYER = 22;
|
||||
|
||||
private readonly int _interactiveLayersDefault;
|
||||
private readonly int _interactiveLayersUI;
|
||||
|
||||
private readonly IClientSimEventDispatcher _eventDispatcher;
|
||||
|
||||
private bool _menuIsOpen;
|
||||
|
||||
public ClientSimInteractiveLayerProvider(IClientSimEventDispatcher eventDispatcher)
|
||||
{
|
||||
// Only the UI and UIMenu layers are interactable when the UI is open.
|
||||
_interactiveLayersUI = (1 << UI_LAYER) | (1 << UI_MENU_LAYER) | (1 << INTERNAL_UI_LAYER);
|
||||
// When the menu is not open, all layers but UI, UIMenu, PlayerLocal, and MirrorReflection layers are interactable.
|
||||
_interactiveLayersDefault = ~(1 << UI_LAYER) & ~(1 << UI_MENU_LAYER) & ~(1 << PLAYER_LOCAL_LAYER) & ~(1 << MIRROR_REFLECTION_LAYER);
|
||||
// If Interaction Passthrough is set, these User Layers will also be ignored.
|
||||
_interactiveLayersDefault &= ~(VRC_SceneDescriptor.Instance.interactThruLayers << FIRST_USER_LAYER);
|
||||
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_eventDispatcher.Subscribe<ClientSimMenuStateChangedEvent>(SetMenuOpen);
|
||||
}
|
||||
|
||||
~ClientSimInteractiveLayerProvider()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_eventDispatcher.Unsubscribe<ClientSimMenuStateChangedEvent>(SetMenuOpen);
|
||||
}
|
||||
|
||||
public LayerMask GetInteractiveLayers()
|
||||
{
|
||||
return _menuIsOpen ? _interactiveLayersUI : _interactiveLayersDefault;
|
||||
}
|
||||
|
||||
private void SetMenuOpen(ClientSimMenuStateChangedEvent stateChangedEvent)
|
||||
{
|
||||
_menuIsOpen = stateChangedEvent.isMenuOpen;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 341c903cb5594e569aa8b82a45974fcf
|
||||
timeCreated: 1639013197
|
||||
@ -0,0 +1,975 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using VRC.Core;
|
||||
using VRC.Economy;
|
||||
using VRC.SDK3.Components;
|
||||
using VRC.SDK3.Network;
|
||||
using VRC.SDK3.Platform;
|
||||
using VRC.SDK3.Rendering;
|
||||
using VRC.SDK3.Video.Components.AVPro;
|
||||
using VRC.SDKBase;
|
||||
using VRC.SDKBase.Network;
|
||||
using VRC.SDKBase.Platform;
|
||||
using VRC.Udon;
|
||||
using VRC.Udon.Security;
|
||||
using VRCNetworkBehaviour = VRC.SDK3.Network.VRCNetworkBehaviour;
|
||||
using VRCStation = VRC.SDKBase.VRCStation;
|
||||
using VRC.SDK3.UdonNetworkCalling;
|
||||
using VRC.Utility;
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
using VRC.SDK3.ClientSim.Persistence;
|
||||
#endif
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// The main system for ClientSim. This handles the spawning of the ClientSim system and initialization.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sends Events:
|
||||
/// - ClientSimReadyEvent
|
||||
/// </remarks>
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimMain : ClientSimBehaviour
|
||||
{
|
||||
private static readonly string _systemPrefabPath = Path.Combine("ClientSim", "Prefabs", "ClientSimSystem");
|
||||
|
||||
private static ClientSimMain _instance;
|
||||
|
||||
[SerializeField]
|
||||
private ClientSimMenu menu;
|
||||
[SerializeField]
|
||||
private ClientSimBaseInput baseInput;
|
||||
[SerializeField]
|
||||
private ClientSimInputModule inputModule;
|
||||
[SerializeField]
|
||||
private ClientSimSyncedObjectManager syncedObjectManager;
|
||||
[SerializeField]
|
||||
private ClientSimInputManager inputManager;
|
||||
[SerializeField]
|
||||
private ClientSimUdonInputBehaviour udonInput;
|
||||
[SerializeField]
|
||||
private ClientSimHighlightManager highlightManager;
|
||||
[SerializeField]
|
||||
private ClientSimTooltipManager tooltipManager;
|
||||
[SerializeField]
|
||||
private ClientSimPlayerSpawner playerSpawner;
|
||||
[SerializeField]
|
||||
private ClientSimStackedVRCameraSystem stackedCameraSystem;
|
||||
[SerializeField]
|
||||
private ClientSimStoreManager storeManager;
|
||||
|
||||
[SerializeField]
|
||||
private GameObject proxyObjectPrefab;
|
||||
|
||||
private ClientSimProxyObjects _proxyObject;
|
||||
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private ClientSimPlayerManager _playerManager;
|
||||
private ClientSimUdonManager _udonManager;
|
||||
private ClientSimSceneManager _sceneManager;
|
||||
private ClientSimBlacklistManager _blacklistManager;
|
||||
private ClientSimInteractiveLayerProvider _interactiveLayerProvider;
|
||||
private IClientSimSessionState _sessionState;
|
||||
private IClientSimUdonEventSender _udonEventSender;
|
||||
|
||||
// public so that height can be adjusted via the settings window
|
||||
public ClientSimPlayerHeightManager HeightManager { get; private set; }
|
||||
|
||||
private ClientSimSettings _settings;
|
||||
private ClientSimPlayer _player;
|
||||
|
||||
private bool _isReady;
|
||||
|
||||
public static void CreateInstance(
|
||||
ClientSimSettings settings,
|
||||
IClientSimEventDispatcher eventDispatcher = null)
|
||||
{
|
||||
if (HasInstance())
|
||||
{
|
||||
throw new ClientSimException("Cannot create an instance of ClientSim while one already exists.");
|
||||
}
|
||||
|
||||
// Configure Delegates for Allowlisted URL Validation
|
||||
VRCUrl.DomainExplicitAllowlistDelegate = () => UrlAllowlistConfig.DomainExplicitAllowlist;
|
||||
VRCUrl.DomainWildcardAllowlistDelegate = () => UrlAllowlistConfig.DomainWildcardAllowlist;
|
||||
|
||||
GameObject systemPrefab = Resources.Load<GameObject>(_systemPrefabPath);
|
||||
|
||||
if (systemPrefab == null)
|
||||
{
|
||||
throw new ClientSimException("Failed to start Client Sim! Main system prefab was not found.");
|
||||
}
|
||||
|
||||
GameObject systemInstance = Instantiate(systemPrefab);
|
||||
systemInstance.name = $"__{systemPrefab.name}";
|
||||
|
||||
ClientSimMain main = systemInstance.GetComponent<ClientSimMain>();
|
||||
if (main == null)
|
||||
{
|
||||
throw new ClientSimException("Failed to start Client Sim! Main system component not found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
main.Initialize(settings, eventDispatcher);
|
||||
}
|
||||
catch (ClientSimException e)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// Tests expect certain exceptions, don't exit play mode
|
||||
if (ClientSimRuntimeLoader.IsInUnityTest())
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Play mode Stopped because: {e.Message}");
|
||||
UnityEditor.EditorApplication.isPlaying = false;
|
||||
}
|
||||
#else
|
||||
Debug.LogError($"Tried to initialize ClientSim outside of the Unity Editor: {e.Message}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasInstance()
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
|
||||
internal static bool TryGetInstance(out ClientSimMain instance)
|
||||
{
|
||||
if (_instance != null)
|
||||
{
|
||||
instance = _instance;
|
||||
return true;
|
||||
}
|
||||
|
||||
instance = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void SpawnRemotePlayer(string name = null)
|
||||
{
|
||||
if (HasInstance())
|
||||
{
|
||||
_instance.SpawnRemotePlayerAndInitialize(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemovePlayer(VRCPlayerApi player)
|
||||
{
|
||||
if (HasInstance())
|
||||
{
|
||||
_instance._playerManager.RemovePlayer(player);
|
||||
}
|
||||
}
|
||||
|
||||
// Used in tests
|
||||
public static void RemoveInstance()
|
||||
{
|
||||
if (HasInstance())
|
||||
{
|
||||
DestroyImmediate(_instance.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
if (_instance)
|
||||
{
|
||||
DestroyImmediate(this);
|
||||
throw new ClientSimException("Multiple instances of ClientSim running!");
|
||||
}
|
||||
_instance = this;
|
||||
|
||||
this.Log("Starting ClientSim");
|
||||
|
||||
DontDestroyOnLoad(this);
|
||||
|
||||
SpawnProxyObjects();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_proxyObject)
|
||||
{
|
||||
_proxyObject.DestroyProxy();
|
||||
}
|
||||
|
||||
_playerManager?.Dispose();
|
||||
|
||||
if (_instance != this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveSDKLinks();
|
||||
_instance = null;
|
||||
}
|
||||
|
||||
private void Initialize(
|
||||
ClientSimSettings settings,
|
||||
IClientSimEventDispatcher eventDispatcher)
|
||||
{
|
||||
_sceneManager = new ClientSimSceneManager();
|
||||
if (!_sceneManager.HasSceneDescriptor())
|
||||
{
|
||||
_instance = null;
|
||||
Destroy(gameObject);
|
||||
Debug.LogWarning("Cannot start ClientSim if there is no scene descriptor!");
|
||||
return;
|
||||
}
|
||||
|
||||
ClientSimNetworkingUtilities.ConfigureNetworkOnScene(VRC_SceneDescriptor.Instance);
|
||||
|
||||
_settings = settings;
|
||||
|
||||
// Event Dispatcher is provided during tests to listen and send events before everything has been initialized.
|
||||
_eventDispatcher = eventDispatcher ?? new ClientSimEventDispatcher();
|
||||
|
||||
_blacklistManager = new ClientSimBlacklistManager();
|
||||
_blacklistManager.AddObjectAndChildrenToBlackList(gameObject);
|
||||
|
||||
UdonManager.Instance.LightReservedLayerMask = LayerHelper.Mask_ReservedRenderLayers.value;
|
||||
|
||||
Texture2DDefaultTextureHolder.BlacklistDefaultTextures(UdonManager.Instance);
|
||||
|
||||
_interactiveLayerProvider = new ClientSimInteractiveLayerProvider(_eventDispatcher);
|
||||
_sessionState = new ClientSimSessionState();
|
||||
|
||||
inputManager.Initialize(_settings);
|
||||
IClientSimInput input = inputManager.GetInput();
|
||||
|
||||
_udonEventSender = new ClientSimUdonManagerEventSender(UdonManager.Instance);
|
||||
HeightManager = new ClientSimPlayerHeightManager(_eventDispatcher, _udonEventSender);
|
||||
|
||||
udonInput.Initialize(_eventDispatcher, input);
|
||||
menu.Initialize(_eventDispatcher, input, _settings, _sessionState, HeightManager);
|
||||
baseInput.Initialize(_eventDispatcher, input, _settings);
|
||||
inputModule.Initialize(_interactiveLayerProvider);
|
||||
|
||||
_playerManager = new ClientSimPlayerManager(_eventDispatcher, HeightManager);
|
||||
// ObjectManager must be initialized before UdonManager to ensure object ownership for leaving players
|
||||
// is handled first.
|
||||
syncedObjectManager.Initialize(_eventDispatcher, _sceneManager, _playerManager);
|
||||
_udonManager = new ClientSimUdonManager(_eventDispatcher, syncedObjectManager, _udonEventSender);
|
||||
playerSpawner.Initialize(_sceneManager, _playerManager, _blacklistManager, _eventDispatcher, null);
|
||||
|
||||
// Option to allow for spawning remote players first to prevent the local player from being master.
|
||||
// TODO replace this with networking test system to save previous player count in the instance.
|
||||
if (!_settings.localPlayerIsMaster)
|
||||
{
|
||||
SpawnRemotePlayerAndInitialize();
|
||||
}
|
||||
|
||||
// Spawn player controller to ensure that Local Player specific methods initialize properly.
|
||||
// This included getting the Player's camera.
|
||||
SpawnLocalPlayer(_settings.customLocalPlayerName);
|
||||
|
||||
_player.Initialize(
|
||||
_eventDispatcher,
|
||||
input,
|
||||
_settings,
|
||||
highlightManager,
|
||||
tooltipManager,
|
||||
_interactiveLayerProvider,
|
||||
baseInput,
|
||||
_sceneManager,
|
||||
_proxyObject,
|
||||
_udonEventSender,
|
||||
_blacklistManager,
|
||||
_udonManager,
|
||||
syncedObjectManager,
|
||||
_playerManager,
|
||||
HeightManager);
|
||||
|
||||
Camera playerCamera = _player.GetCameraProvider().GetCamera();
|
||||
tooltipManager.Initialize(_settings, _player.GetTrackingProvider());
|
||||
highlightManager.Initialize(playerCamera);
|
||||
stackedCameraSystem.Initialize(playerCamera, menu, _eventDispatcher);
|
||||
|
||||
storeManager = new ClientSimStoreManager(_eventDispatcher, new ClientSimUdonManagerEventSender(UdonManager.Instance), _playerManager);
|
||||
|
||||
// Initialize SDK links after everything has been created and initialized.
|
||||
SetupSDKLinks();
|
||||
}
|
||||
|
||||
#region Platform Management
|
||||
|
||||
private Vector2 CurrentResolution;
|
||||
private VRCOrientation CurrentOrientation;
|
||||
private const int ScreenChangePollRate = 100;
|
||||
|
||||
private async UniTaskVoid CheckForScreenChange()
|
||||
{
|
||||
while (_isReady)
|
||||
{
|
||||
if ((int)CurrentResolution.x != UnityEngine.Device.Screen.width || (int)CurrentResolution.y != UnityEngine.Device.Screen.height)
|
||||
{
|
||||
CurrentResolution = new Vector2(UnityEngine.Device.Screen.width, UnityEngine.Device.Screen.height);
|
||||
_udonEventSender.RunEvent("_onVRCCameraSettingsChanged", ("camera", VRCCameraSettings.ScreenCamera));
|
||||
}
|
||||
|
||||
var currentOrientation = DetermineOrientation();
|
||||
if (currentOrientation != CurrentOrientation)
|
||||
{
|
||||
CurrentOrientation = DetermineOrientation();
|
||||
// send ScreenUpdate event
|
||||
var screenUpdateEvent = new ClientSimScreenUpdateEvent()
|
||||
{
|
||||
data = new ScreenUpdateData()
|
||||
{
|
||||
type = ScreenUpdateType.OrientationChanged,
|
||||
orientation = CurrentOrientation,
|
||||
resolution = CurrentResolution
|
||||
}
|
||||
};
|
||||
_eventDispatcher.SendEvent(screenUpdateEvent);
|
||||
}
|
||||
|
||||
await UniTask.Delay(ScreenChangePollRate);
|
||||
}
|
||||
}
|
||||
|
||||
private VRCOrientation DetermineOrientation()
|
||||
{
|
||||
return Screen.width < Screen.height ? VRCOrientation.Portrait : VRCOrientation.Landscape;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (!_sceneManager.HasSceneDescriptor())
|
||||
{
|
||||
throw new ClientSimException("Cannot start ClientSim if there is no world descriptor!");
|
||||
}
|
||||
|
||||
if (_settings.setTargetFrameRate)
|
||||
{
|
||||
Application.targetFrameRate = Mathf.Max(_settings.targetFrameRate, 1);
|
||||
Time.fixedDeltaTime = 1f / Application.targetFrameRate;
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.targetFrameRate = -1;
|
||||
}
|
||||
|
||||
StartCoroutine(InitializeClientSim());
|
||||
}
|
||||
|
||||
private IEnumerator InitializeClientSim()
|
||||
{
|
||||
// VRChatBug: VRChat does not initialize SDK components immediately. This delay is to provide Unity
|
||||
// components time to work before Udon starts to simulate how it is in client.
|
||||
float startDelay = Mathf.Max(_settings.initializationDelay, 0);
|
||||
yield return new WaitForSeconds(startDelay);
|
||||
|
||||
_isReady = true;
|
||||
|
||||
// Enable the player if set to spawn in the settings.
|
||||
if (_settings.spawnPlayer)
|
||||
{
|
||||
// Player can be invalid if we weren't able to spawn it
|
||||
if (_player)
|
||||
{
|
||||
_player.isInstanceOwner = _settings.isInstanceOwner;
|
||||
_player.isVRCPlus = _settings.isVRCPlus;
|
||||
_sceneManager.ResetSpawnOrder(); // Avoids any spawn offsets from pre-initialization of players
|
||||
_player.EnablePlayer(_sceneManager.GetSpawnPoint(false), _sceneManager.GetSpawnRadius());
|
||||
}
|
||||
}
|
||||
|
||||
// Notify UdonManager that ClientSim is ready. This will then notify all registered UdonBehaviours that
|
||||
// they can begin running. Udon will initialize in the next frame in the next Update call.
|
||||
yield return _udonManager.OnClientSimReady();
|
||||
|
||||
// Notify PlayerManager to send OnPlayerJoined events.
|
||||
// This must happen after Udon has been initialized to ensure UdonBehaviours are active to receive the event.
|
||||
// This must happen before ClientSimReadyEvent as some listeners depend on this.
|
||||
_playerManager.OnClientSimReady();
|
||||
|
||||
// Send event indicating ClientSim is initialized and ready.
|
||||
_eventDispatcher.SendEvent(new ClientSimReadyEvent());
|
||||
|
||||
stackedCameraSystem.Ready();
|
||||
|
||||
CheckForScreenChange().Forget();
|
||||
|
||||
this.Log("ClientSim Initialized");
|
||||
}
|
||||
|
||||
private void SpawnProxyObjects()
|
||||
{
|
||||
if (!proxyObjectPrefab)
|
||||
{
|
||||
throw new ClientSimException("Failed to start Client Sim! Proxy object prefab was not found.");
|
||||
}
|
||||
|
||||
GameObject proxyInstance = Instantiate(proxyObjectPrefab);
|
||||
proxyInstance.name = "__" + proxyObjectPrefab.name;
|
||||
DontDestroyOnLoad(proxyInstance);
|
||||
|
||||
_proxyObject = proxyInstance.GetComponent<ClientSimProxyObjects>();
|
||||
if (!_proxyObject)
|
||||
{
|
||||
throw new ClientSimException("Failed to start Client Sim! Proxy object script was not found.");
|
||||
}
|
||||
|
||||
// Do not add this object to the blacklist since proxy objects are not blacklisted in udon.
|
||||
}
|
||||
|
||||
private void SpawnLocalPlayer(string playerName = "")
|
||||
{
|
||||
_player = playerSpawner.SpawnPlayer(playerName, true);
|
||||
}
|
||||
|
||||
private void SpawnRemotePlayerAndInitialize(string playerName = "")
|
||||
{
|
||||
playerSpawner.SpawnPlayer(playerName, false);
|
||||
}
|
||||
|
||||
private bool IsObjectReady(GameObject obj)
|
||||
{
|
||||
return _isReady;
|
||||
}
|
||||
|
||||
public bool IsNetworkReady()
|
||||
{
|
||||
return _isReady;
|
||||
}
|
||||
|
||||
private string GetUniqueStringForObject(GameObject obj)
|
||||
{
|
||||
return obj.GetInstanceID().ToString();
|
||||
}
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
private GameObject[] GetPlayerPersistence(VRCPlayerApi playerApi)
|
||||
{
|
||||
ClientSimPlayer player = playerApi.GetClientSimPlayer();
|
||||
if (player == null || player.PlayerPersistenceRootObjects == null)
|
||||
return System.Array.Empty<GameObject>();
|
||||
|
||||
return player.PlayerPersistenceRootObjects;
|
||||
}
|
||||
|
||||
private Component FindComponentInPlayerObjects(VRCPlayerApi target, Component referenceComponent)
|
||||
{
|
||||
if (referenceComponent == null)
|
||||
throw new ArgumentNullException(nameof(referenceComponent));
|
||||
|
||||
VRCPlayerObject sourceObject = referenceComponent.GetComponentInParent<VRC.SDK3.Components.VRCPlayerObject>(true);
|
||||
if (sourceObject == null)
|
||||
throw new ArgumentException($"{nameof(referenceComponent)} must be on a player object.");
|
||||
|
||||
GameObject networkGameObject = ((Component)referenceComponent.gameObject.GetComponentInParent<INetworkID>(true)).gameObject;
|
||||
VRCSceneDescriptor sdk3Descriptor = (VRCSceneDescriptor)VRC_SceneDescriptor.Instance;
|
||||
int idx = sdk3Descriptor.NetworkIDCollection.FindIndex((x) => x.gameObject == networkGameObject);
|
||||
if (idx < 0)
|
||||
throw new ArgumentException($"{nameof(referenceComponent)} must be on a player object in the NetworkIDCollection.");
|
||||
|
||||
VRC.SDKBase.Network.NetworkIDPair networkIdPair = sdk3Descriptor.NetworkIDCollection[idx];
|
||||
// See ClientSimPlayer.SetupPlayerPersistence for how the ID is calculated
|
||||
int desiredID = ClientSimNetworkingUtilities.FirstPlayerPersistenceID
|
||||
+ networkIdPair.ID;
|
||||
|
||||
GameObject[] objs = GetPlayerPersistence(target);
|
||||
if (objs == null)
|
||||
{
|
||||
Debug.LogError("Player persistence is missing for " + target.displayName);
|
||||
return null;
|
||||
}
|
||||
|
||||
var possibleMatches = VRC.Tools.FindComponentInPossibleClones(sourceObject.gameObject, referenceComponent, objs);
|
||||
|
||||
Debug.Log($"Found {possibleMatches.Count()} possible matches for {referenceComponent.name} in player objects for {target.displayName}");;
|
||||
|
||||
foreach (Component possibleMatch in possibleMatches)
|
||||
{
|
||||
if (!possibleMatch) continue;
|
||||
ClientSimNetworkIdHolder networkIdHolder = possibleMatch.GetComponentInParent<ClientSimNetworkIdHolder>(true);
|
||||
if (!networkIdHolder)
|
||||
continue;
|
||||
|
||||
int thisID = networkIdHolder.GetNetworkView()?.GetNetworkId() ?? 0;
|
||||
if (thisID <= 0)
|
||||
{
|
||||
Debug.LogError($"Network ID is invalid for {possibleMatch.name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
int matchID = ClientSimNetworkingUtilities.FlattenPlayerViewId(thisID);
|
||||
if (matchID == desiredID) return possibleMatch;
|
||||
Debug.LogError($"Network ID {matchID} for {possibleMatch.name} does not match desired ID {desiredID}");
|
||||
}
|
||||
|
||||
Debug.LogError($"Failed to find component {referenceComponent.GetType().Name} in player objects for {target.displayName}");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void EnablePlayerObjects()
|
||||
{
|
||||
_player.EnablePlayerObjects();
|
||||
}
|
||||
|
||||
public int GetPlayerDataUsageLimit() => 1024 * 100;
|
||||
|
||||
public int GetPlayerObjectsUsageLimit() => 1024 * 100;
|
||||
|
||||
public int GetPlayerDataUsage(VRCPlayerApi player)
|
||||
{
|
||||
Debug.LogWarning("Player Data usage is not accurate in ClientSim.");
|
||||
return ClientSimPlayerDataWrapper.GetUsage(player);
|
||||
}
|
||||
|
||||
public int GetPlayerObjectsUsage(VRCPlayerApi player)
|
||||
{
|
||||
Debug.LogWarning("Player Object usage is not accurate in ClientSim.");
|
||||
ClientSimPlayer simPlayer = player.GetClientSimPlayer();
|
||||
return simPlayer.PlayerObjectData == null ? 0 : simPlayer.PlayerObjectData.Size;
|
||||
}
|
||||
|
||||
public void RequestStorageUsageUpdate()
|
||||
{
|
||||
UdonManager.Instance.RunEvent(UdonManager.UDON_EVENT_ONPERSISTENCEUSAGEUPDATED);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
private static void CallNetworkConfigure(VRCNetworkBehaviour behaviour)
|
||||
{
|
||||
behaviour.NetworkConfigure();
|
||||
}
|
||||
|
||||
private static bool IsSuffering() => false; // we did it chat, no more suffering
|
||||
|
||||
private static void SetShadowDistance(float low, float medium, float high, float mobile) => QualitySettings.shadowDistance = high;
|
||||
private static void SetShadowDistanceAll(float distance) => QualitySettings.shadowDistance = distance;
|
||||
private static void ResetShadowDistance() => QualitySettings.shadowDistance = 150; // TODO: base on project quality settings?
|
||||
private void OnCameraSettingsChanged(Camera camera)
|
||||
{
|
||||
camera.farClipPlane = Mathf.Max(camera.farClipPlane, camera.nearClipPlane + 0.1f);
|
||||
|
||||
// don't allow turning off MirrorReflection or stacked layers (InternalUI)
|
||||
camera.cullingMask &= ~(1 << LayerMask.NameToLayer("MirrorReflection"));
|
||||
camera.cullingMask &= ~stackedCameraSystem.RequestedStackLayers;
|
||||
}
|
||||
|
||||
private static void OpenAvatarListing(string id) => Debug.Log($"opening avatar listing for {id}");
|
||||
|
||||
#region VRChat SDK Links
|
||||
|
||||
// If adding to this list, be sure to also remove the link in the RemoveSDKLinks method
|
||||
private void SetupSDKLinks()
|
||||
{
|
||||
UdonBehaviour.OnInit += _udonManager.InitUdon;
|
||||
|
||||
Networking._IsMaster += _playerManager.IsLocalPlayerMaster;
|
||||
Networking._LocalPlayer += _playerManager.LocalPlayer;
|
||||
Networking._GetOwner += _playerManager.GetOwner;
|
||||
Networking._IsOwner += _playerManager.IsOwner;
|
||||
Networking._SetOwner += ClientSimPlayerManager.SetOwner;
|
||||
Networking._GetMaster += _playerManager.GetMaster;
|
||||
Networking._GetInstanceOwner += _playerManager.GetInstanceOwner;
|
||||
|
||||
Networking._IsInstanceOwner += _playerManager.IsInstanceOwner;
|
||||
Networking._IsObjectReady += IsObjectReady;
|
||||
Networking._IsNetworkSettled += IsNetworkReady;
|
||||
Networking._IsSuffering += IsSuffering;
|
||||
|
||||
Networking._GetUniqueName += GetUniqueStringForObject;
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
ClientSimPlayerDataWrapper.ConfigureSDK();
|
||||
Networking._GetPlayerPersistence += GetPlayerPersistence;
|
||||
|
||||
Networking._GetPlayerDataStorageUsage += GetPlayerDataUsage;
|
||||
Networking._GetPlayerObjectStorageUsage += GetPlayerObjectsUsage;
|
||||
Networking._GetPlayerDataStorageLimit += GetPlayerDataUsageLimit;
|
||||
Networking._GetPlayerObjectStorageLimit += GetPlayerObjectsUsageLimit;
|
||||
|
||||
Networking._FindComponentInPlayerObjects += FindComponentInPlayerObjects;
|
||||
#endif
|
||||
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE || VRC_ENABLE_INSTANCE_PERSISTENCE
|
||||
Networking._RequestStorageUsageUpdate += RequestStorageUsageUpdate;
|
||||
#endif
|
||||
|
||||
VRCNetworkBehaviour.OnNetworkBehaviourAwake += CallNetworkConfigure;
|
||||
|
||||
VRCStation.Initialize += ClientSimStationHelper.InitializeStations;
|
||||
VRCStation.useStationDelegate += ClientSimStationHelper.UseStation;
|
||||
VRCStation.exitStationDelegate += ClientSimStationHelper.ExitStation;
|
||||
VRCPlayerApi._UseAttachedStation += ClientSimStationHelper.UseAttachedStation;
|
||||
|
||||
if (_player != null)
|
||||
{
|
||||
VRC_UiShape.GetEventCamera += _player.GetCameraProvider().GetCameraForObject;
|
||||
VRCCameraSettings.ScreenCameraRef = _player.GetCameraProvider().GetCamera();
|
||||
VRCCameraSettings.ScreenCamera.GetCameraMode = () => VRCCameraMode.Screen;
|
||||
}
|
||||
|
||||
VRC_Pickup.OnAwake += ClientSimPickupHelper.InitializePickup;
|
||||
VRC_Pickup.ForceDrop += ClientSimPickupHelper.ForceDrop;
|
||||
VRC_Pickup._GetCurrentPlayer += ClientSimPickupHelper.GetCurrentPlayer;
|
||||
VRC_Pickup._GetPickupHand += ClientSimPickupHelper.GetPickupHand;
|
||||
VRC_Pickup.OnDestroyed += ClientSimPickupHelper.PickupDestroy;
|
||||
VRC_Pickup.HapticEvent += ClientSimPickupHelper.PlayHapticForPickup;
|
||||
|
||||
Components.VRCObjectPool.OnInit += syncedObjectManager.InitializeObjectPool;
|
||||
Components.VRCObjectPool.OnReturn += ClientSimObjectPoolHelper.OnReturn;
|
||||
Components.VRCObjectPool.OnSpawn += ClientSimObjectPoolHelper.OnSpawn;
|
||||
|
||||
Components.VRCObjectSync.OnAwake += syncedObjectManager.InitializeObjectSync;
|
||||
Components.VRCObjectSync.FlagDiscontinuityHook += ClientSimObjectSyncHelper.FlagDiscontinuityHook;
|
||||
Components.VRCObjectSync.RespawnHandler += ClientSimObjectSyncHelper.RespawnObject;
|
||||
Components.VRCObjectSync.TeleportHandler += ClientSimObjectSyncHelper.TeleportTo;
|
||||
Components.VRCObjectSync.SetGravityHook += ClientSimObjectSyncHelper.SetUseGravity;
|
||||
Components.VRCObjectSync.SetKinematicHook += ClientSimObjectSyncHelper.SetIsKinematic;
|
||||
|
||||
VRCPlayerApi._GetPlayerId += _playerManager.GetPlayerID;
|
||||
VRCPlayerApi._GetPlayerById += _playerManager.GetPlayerByID;
|
||||
VRCPlayerApi._isMasterDelegate += _playerManager.IsMaster;
|
||||
VRCPlayerApi._isSuspendedDelegate += _playerManager.IsSuspended;
|
||||
VRCPlayerApi._isVRCPlusDelegate += _playerManager.IsVRCPlus;
|
||||
VRCPlayerApi._TakeOwnership += ClientSimPlayerManager.SetOwner;
|
||||
VRCPlayerApi._IsOwner += _playerManager.IsOwner;
|
||||
VRCPlayerApi._isInstanceOwnerDelegate += _playerManager.IsInstanceOwner;
|
||||
|
||||
VRCPlayerApi._EnablePickups += ClientSimPlayerManager.EnablePickups;
|
||||
VRCPlayerApi._Immobilize += ClientSimPlayerManager.Immobilize;
|
||||
VRCPlayerApi._TeleportTo += ClientSimPlayerManager.TeleportTo;
|
||||
VRCPlayerApi._TeleportToOrientation += ClientSimPlayerManager.TeleportToOrientation;
|
||||
VRCPlayerApi._TeleportToOrientationLerp += ClientSimPlayerManager.TeleportToOrientationLerp;
|
||||
VRCPlayerApi._PlayHapticEventInHand += ClientSimPlayerManager.PlayHapticEventInHand;
|
||||
VRCPlayerApi._GetPlayerByGameObject += ClientSimPlayerManager.GetPlayerByGameObject;
|
||||
VRCPlayerApi._GetPickupInHand += ClientSimPlayerManager.GetPickupInHand;
|
||||
VRCPlayerApi._GetTrackingData += ClientSimPlayerManager.GetTrackingData;
|
||||
VRCPlayerApi._GetBonePosition += ClientSimPlayerManager.GetBonePosition;
|
||||
VRCPlayerApi._GetBoneRotation += ClientSimPlayerManager.GetBoneRotation;
|
||||
|
||||
VRCPlayerApi._ClearPlayerTags += ClientSimPlayerManager.ClearPlayerTags;
|
||||
VRCPlayerApi._SetPlayerTag += ClientSimPlayerManager.SetPlayerTag;
|
||||
VRCPlayerApi._GetPlayerTag += ClientSimPlayerManager.GetPlayerTag;
|
||||
VRCPlayerApi._GetPlayersWithTag += ClientSimPlayerManager.GetPlayersWithTag;
|
||||
VRCPlayerApi._SetSilencedToTagged += ClientSimPlayerManager.SetSilencedToTagged;
|
||||
VRCPlayerApi._SetSilencedToUntagged += ClientSimPlayerManager.SetSilencedToUntagged;
|
||||
VRCPlayerApi._ClearSilence += ClientSimPlayerManager.ClearSilence;
|
||||
|
||||
VRCPlayerApi._IsUserInVR += ClientSimPlayerManager.IsUserInVR;
|
||||
VRCPlayerApi._GetRunSpeed += ClientSimPlayerManager.GetRunSpeed;
|
||||
VRCPlayerApi._SetRunSpeed += ClientSimPlayerManager.SetRunSpeed;
|
||||
VRCPlayerApi._GetWalkSpeed += ClientSimPlayerManager.GetWalkSpeed;
|
||||
VRCPlayerApi._SetWalkSpeed += ClientSimPlayerManager.SetWalkSpeed;
|
||||
VRCPlayerApi._GetJumpImpulse += ClientSimPlayerManager.GetJumpImpulse;
|
||||
VRCPlayerApi._SetJumpImpulse += ClientSimPlayerManager.SetJumpImpulse;
|
||||
VRCPlayerApi._GetStrafeSpeed += ClientSimPlayerManager.GetStrafeSpeed;
|
||||
VRCPlayerApi._SetStrafeSpeed += ClientSimPlayerManager.SetStrafeSpeed;
|
||||
VRCPlayerApi._GetVelocity += ClientSimPlayerManager.GetVelocity;
|
||||
VRCPlayerApi._SetVelocity += ClientSimPlayerManager.SetVelocity;
|
||||
VRCPlayerApi._GetPosition += ClientSimPlayerManager.GetPosition;
|
||||
VRCPlayerApi._GetRotation += ClientSimPlayerManager.GetRotation;
|
||||
VRCPlayerApi._GetGravityStrength += ClientSimPlayerManager.GetGravityStrength;
|
||||
VRCPlayerApi._SetGravityStrength += ClientSimPlayerManager.SetGravityStrength;
|
||||
VRCPlayerApi.IsGrounded += ClientSimPlayerManager.IsGrounded;
|
||||
VRCPlayerApi._UseLegacyLocomotion += ClientSimPlayerManager.UseLegacyLocomotion;
|
||||
VRCPlayerApi._Respawn += ClientSimPlayerManager.Respawn;
|
||||
VRCPlayerApi._RespawnWithIndex += ClientSimPlayerManager.RespawnWithIndex;
|
||||
|
||||
VRCPlayerApi._GetManualAvatarScalingAllowed += ClientSimPlayerManager.GetManualAvatarScalingAllowed;
|
||||
VRCPlayerApi._SetManualAvatarScalingAllowed += ClientSimPlayerManager.SetManualAvatarScalingAllowed;
|
||||
VRCPlayerApi._GetAvatarEyeHeightMinimumAsMeters += ClientSimPlayerManager.GetAvatarEyeHeightMinimumAsMeters;
|
||||
VRCPlayerApi._GetAvatarEyeHeightMaximumAsMeters += ClientSimPlayerManager.GetAvatarEyeHeightMaximumAsMeters;
|
||||
VRCPlayerApi._SetAvatarEyeHeightMinimumByMeters += ClientSimPlayerManager.SetAvatarEyeHeightMinimumByMeters;
|
||||
VRCPlayerApi._SetAvatarEyeHeightMaximumByMeters += ClientSimPlayerManager.SetAvatarEyeHeightMaximumByMeters;
|
||||
VRCPlayerApi._GetAvatarEyeHeightAsMeters += ClientSimPlayerManager.GetAvatarEyeHeightAsMeters;
|
||||
VRCPlayerApi._SetAvatarEyeHeightByMeters += ClientSimPlayerManager.SetAvatarEyeHeightByMeters;
|
||||
VRCPlayerApi._SetAvatarEyeHeightByMultiplier += ClientSimPlayerManager.SetAvatarEyeHeightByMultiplier;
|
||||
|
||||
VRCPlayerApi._CombatSetup += ClientSimCombatSystemHelper.CombatSetup;
|
||||
VRCPlayerApi._CombatSetMaxHitpoints += ClientSimCombatSystemHelper.CombatSetMaxHitpoints;
|
||||
VRCPlayerApi._CombatGetCurrentHitpoints += ClientSimCombatSystemHelper.CombatGetCurrentHitpoints;
|
||||
VRCPlayerApi._CombatSetRespawn += ClientSimCombatSystemHelper.CombatSetRespawn;
|
||||
VRCPlayerApi._CombatSetDamageGraphic += ClientSimCombatSystemHelper.CombatSetDamageGraphic;
|
||||
VRCPlayerApi._CombatGetDestructible += ClientSimCombatSystemHelper.CombatGetDestructible;
|
||||
VRCPlayerApi._CombatSetCurrentHitpoints += ClientSimCombatSystemHelper.CombatSetCurrentHitpoints;
|
||||
|
||||
VRCPlayerApi._SetAvatarAudioVolumetricRadius += ClientSimPlayerManager.SetAvatarAudioVolumetricRadius;
|
||||
VRCPlayerApi._SetAvatarAudioNearRadius += ClientSimPlayerManager.SetAvatarAudioNearRadius;
|
||||
VRCPlayerApi._SetAvatarAudioFarRadius += ClientSimPlayerManager.SetAvatarAudioFarRadius;
|
||||
VRCPlayerApi._SetAvatarAudioGain += ClientSimPlayerManager.SetAvatarAudioGain;
|
||||
VRCPlayerApi._SetAvatarAudioForceSpatial += ClientSimPlayerManager.SetAvatarAudioForceSpatial;
|
||||
VRCPlayerApi._SetAvatarAudioCustomCurve += ClientSimPlayerManager.SetAvatarAudioCustomCurve;
|
||||
|
||||
VRCPlayerApi._SetVoiceGain += ClientSimPlayerManager.SetVoiceGain;
|
||||
VRCPlayerApi._SetVoiceDistanceNear += ClientSimPlayerManager.SetVoiceDistanceNear;
|
||||
VRCPlayerApi._SetVoiceDistanceFar += ClientSimPlayerManager.SetVoiceDistanceFar;
|
||||
VRCPlayerApi._SetVoiceVolumetricRadius += ClientSimPlayerManager.SetVoiceVolumetricRadius;
|
||||
VRCPlayerApi._SetVoiceLowpass += ClientSimPlayerManager.SetVoiceLowpass;
|
||||
|
||||
VRCPlayerApi._GetVoiceGain += ClientSimPlayerManager.GetVoiceGain;
|
||||
VRCPlayerApi._GetVoiceDistanceNear += ClientSimPlayerManager.GetVoiceDistanceNear;
|
||||
VRCPlayerApi._GetVoiceDistanceFar += ClientSimPlayerManager.GetVoiceDistanceFar;
|
||||
VRCPlayerApi._GetVoiceVolumetricRadius += ClientSimPlayerManager.GetVoiceVolumetricRadius;
|
||||
VRCPlayerApi._GetVoiceLowpass += ClientSimPlayerManager.GetVoiceLowpass;
|
||||
|
||||
VRCPlayerApi._GetCurrentLanguage += ClientSimPlayerManager.GetCurrentLanguage;
|
||||
VRCPlayerApi._GetAvailableLanguages += ClientSimPlayerManager.GetAvailableLanguages;
|
||||
|
||||
|
||||
VRC_SpatialAudioSource.Initialize += ClientSimSpatialAudioHelper.InitializeAudio;
|
||||
|
||||
VRCAVProVideoPlayer.Initialize += ClientSimAVProVideoStub.InitializePlayer;
|
||||
|
||||
InputManager._EnableObjectHighlight += highlightManager.EnableObjectHighlight;
|
||||
InputManager._GetLastUsedInputMethod += inputManager.GetLastUsedInputMethod;
|
||||
|
||||
Store._sendProductEvent += ClientSimStoreManager.SendProductEvent;
|
||||
Store._listPurchases += ClientSimStoreManager.ListPurchases;
|
||||
Store._listAvailableProducts += ClientSimStoreManager.ListAvailableProducts;
|
||||
Store._doesPlayerOwnProduct += ClientSimStoreManager.DoesPlayerOwnProduct;
|
||||
Store._doesAnyPlayerOwnProduct += ClientSimStoreManager.DoesAnyPlayerOwnProduct;
|
||||
Store._getPlayersWhoOwnProduct += ClientSimStoreManager.GetPlayersWhoOwnProduct;
|
||||
Store._listProductOwners += ClientSimStoreManager.ListProductOwners;
|
||||
|
||||
VRCQualitySettings.SetShadowDistanceInternal += SetShadowDistance;
|
||||
VRCQualitySettings.SetShadowDistanceInternalAll += SetShadowDistanceAll;
|
||||
VRCQualitySettings.ResetShadowDistanceInternal += ResetShadowDistance;
|
||||
VRCCameraSettings.OnCameraSettingsChanged += OnCameraSettingsChanged;
|
||||
|
||||
NetworkCalling.GetAllQueuedEventsProxy += ClientSimNetworkCalling.GetAllQueuedEventsProxy;
|
||||
NetworkCalling.GetQueuedEventsProxy += ClientSimNetworkCalling.GetQueuedEventsProxy;
|
||||
NetworkCalling.SendCustomNetworkEventProxy += ClientSimNetworkCalling.SendCustomNetworkEventProxy;
|
||||
|
||||
VRC.SDK3.Components.VRCOpenMenu._OpenAvatarListingDelegate = OpenAvatarListing;
|
||||
}
|
||||
|
||||
private void RemoveSDKLinks()
|
||||
{
|
||||
UdonBehaviour.OnInit -= _udonManager.InitUdon;
|
||||
|
||||
Networking._IsMaster -= _playerManager.IsLocalPlayerMaster;
|
||||
Networking._LocalPlayer -= _playerManager.LocalPlayer;
|
||||
Networking._GetOwner -= _playerManager.GetOwner;
|
||||
Networking._IsOwner -= _playerManager.IsOwner;
|
||||
Networking._SetOwner -= ClientSimPlayerManager.SetOwner;
|
||||
|
||||
Networking._IsInstanceOwner -= _playerManager.IsInstanceOwner;
|
||||
Networking._IsObjectReady -= IsObjectReady;
|
||||
Networking._IsNetworkSettled -= IsNetworkReady;
|
||||
Networking._IsSuffering -= IsSuffering;
|
||||
|
||||
Networking._GetUniqueName -= GetUniqueStringForObject;
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
Networking._GetPlayerPersistence -= GetPlayerPersistence;
|
||||
|
||||
Networking._GetPlayerDataStorageUsage -= GetPlayerDataUsage;
|
||||
Networking._GetPlayerObjectStorageUsage -= GetPlayerObjectsUsage;
|
||||
Networking._GetPlayerDataStorageLimit -= GetPlayerDataUsageLimit;
|
||||
Networking._GetPlayerObjectStorageLimit -= GetPlayerObjectsUsageLimit;
|
||||
|
||||
Networking._FindComponentInPlayerObjects -= FindComponentInPlayerObjects;
|
||||
#endif
|
||||
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE || VRC_ENABLE_INSTANCE_PERSISTENCE
|
||||
Networking._RequestStorageUsageUpdate -= RequestStorageUsageUpdate;
|
||||
#endif
|
||||
|
||||
VRCNetworkBehaviour.OnNetworkBehaviourAwake -= CallNetworkConfigure;
|
||||
|
||||
VRCStation.Initialize -= ClientSimStationHelper.InitializeStations;
|
||||
VRCStation.useStationDelegate -= ClientSimStationHelper.UseStation;
|
||||
VRCStation.exitStationDelegate -= ClientSimStationHelper.ExitStation;
|
||||
VRCPlayerApi._UseAttachedStation -= ClientSimStationHelper.UseAttachedStation;
|
||||
|
||||
// Player can be invalid if we weren't able to spawn
|
||||
if (_player)
|
||||
{
|
||||
VRC_UiShape.GetEventCamera -= _player.GetCameraProvider().GetCameraForObject;
|
||||
}
|
||||
|
||||
VRC_Pickup.OnAwake -= ClientSimPickupHelper.InitializePickup;
|
||||
VRC_Pickup.ForceDrop -= ClientSimPickupHelper.ForceDrop;
|
||||
VRC_Pickup._GetCurrentPlayer -= ClientSimPickupHelper.GetCurrentPlayer;
|
||||
VRC_Pickup._GetPickupHand -= ClientSimPickupHelper.GetPickupHand;
|
||||
VRC_Pickup.OnDestroyed -= ClientSimPickupHelper.PickupDestroy;
|
||||
VRC_Pickup.HapticEvent -= ClientSimPickupHelper.PlayHapticForPickup;
|
||||
|
||||
Components.VRCObjectPool.OnInit -= syncedObjectManager.InitializeObjectPool;
|
||||
Components.VRCObjectPool.OnReturn -= ClientSimObjectPoolHelper.OnReturn;
|
||||
Components.VRCObjectPool.OnSpawn -= ClientSimObjectPoolHelper.OnSpawn;
|
||||
|
||||
Components.VRCObjectSync.OnAwake -= syncedObjectManager.InitializeObjectSync;
|
||||
Components.VRCObjectSync.FlagDiscontinuityHook -= ClientSimObjectSyncHelper.FlagDiscontinuityHook;
|
||||
Components.VRCObjectSync.RespawnHandler -= ClientSimObjectSyncHelper.RespawnObject;
|
||||
Components.VRCObjectSync.TeleportHandler -= ClientSimObjectSyncHelper.TeleportTo;
|
||||
Components.VRCObjectSync.SetGravityHook -= ClientSimObjectSyncHelper.SetUseGravity;
|
||||
Components.VRCObjectSync.SetKinematicHook -= ClientSimObjectSyncHelper.SetIsKinematic;
|
||||
|
||||
VRCPlayerApi._GetPlayerId -= _playerManager.GetPlayerID;
|
||||
VRCPlayerApi._GetPlayerById -= _playerManager.GetPlayerByID;
|
||||
VRCPlayerApi._isMasterDelegate -= _playerManager.IsMaster;
|
||||
VRCPlayerApi._isSuspendedDelegate -= _playerManager.IsSuspended;
|
||||
VRCPlayerApi._isVRCPlusDelegate -= _playerManager.IsVRCPlus;
|
||||
VRCPlayerApi._TakeOwnership -= ClientSimPlayerManager.SetOwner;
|
||||
VRCPlayerApi._IsOwner -= _playerManager.IsOwner;
|
||||
VRCPlayerApi._isInstanceOwnerDelegate -= _playerManager.IsInstanceOwner;
|
||||
|
||||
VRCPlayerApi._EnablePickups -= ClientSimPlayerManager.EnablePickups;
|
||||
VRCPlayerApi._Immobilize -= ClientSimPlayerManager.Immobilize;
|
||||
VRCPlayerApi._TeleportTo -= ClientSimPlayerManager.TeleportTo;
|
||||
VRCPlayerApi._TeleportToOrientation -= ClientSimPlayerManager.TeleportToOrientation;
|
||||
VRCPlayerApi._TeleportToOrientationLerp -= ClientSimPlayerManager.TeleportToOrientationLerp;
|
||||
VRCPlayerApi._PlayHapticEventInHand -= ClientSimPlayerManager.PlayHapticEventInHand;
|
||||
VRCPlayerApi._GetPlayerByGameObject -= ClientSimPlayerManager.GetPlayerByGameObject;
|
||||
VRCPlayerApi._GetPickupInHand -= ClientSimPlayerManager.GetPickupInHand;
|
||||
VRCPlayerApi._GetTrackingData -= ClientSimPlayerManager.GetTrackingData;
|
||||
VRCPlayerApi._GetBonePosition -= ClientSimPlayerManager.GetBonePosition;
|
||||
VRCPlayerApi._GetBoneRotation -= ClientSimPlayerManager.GetBoneRotation;
|
||||
|
||||
VRCPlayerApi._ClearPlayerTags -= ClientSimPlayerManager.ClearPlayerTags;
|
||||
VRCPlayerApi._SetPlayerTag -= ClientSimPlayerManager.SetPlayerTag;
|
||||
VRCPlayerApi._GetPlayerTag -= ClientSimPlayerManager.GetPlayerTag;
|
||||
VRCPlayerApi._GetPlayersWithTag -= ClientSimPlayerManager.GetPlayersWithTag;
|
||||
VRCPlayerApi._SetSilencedToTagged -= ClientSimPlayerManager.SetSilencedToTagged;
|
||||
VRCPlayerApi._SetSilencedToUntagged -= ClientSimPlayerManager.SetSilencedToUntagged;
|
||||
VRCPlayerApi._ClearSilence -= ClientSimPlayerManager.ClearSilence;
|
||||
|
||||
VRCPlayerApi._IsUserInVR -= ClientSimPlayerManager.IsUserInVR;
|
||||
VRCPlayerApi._GetRunSpeed -= ClientSimPlayerManager.GetRunSpeed;
|
||||
VRCPlayerApi._SetRunSpeed -= ClientSimPlayerManager.SetRunSpeed;
|
||||
VRCPlayerApi._GetWalkSpeed -= ClientSimPlayerManager.GetWalkSpeed;
|
||||
VRCPlayerApi._SetWalkSpeed -= ClientSimPlayerManager.SetWalkSpeed;
|
||||
VRCPlayerApi._GetJumpImpulse -= ClientSimPlayerManager.GetJumpImpulse;
|
||||
VRCPlayerApi._SetJumpImpulse -= ClientSimPlayerManager.SetJumpImpulse;
|
||||
VRCPlayerApi._GetStrafeSpeed -= ClientSimPlayerManager.GetStrafeSpeed;
|
||||
VRCPlayerApi._SetStrafeSpeed -= ClientSimPlayerManager.SetStrafeSpeed;
|
||||
VRCPlayerApi._GetVelocity -= ClientSimPlayerManager.GetVelocity;
|
||||
VRCPlayerApi._SetVelocity -= ClientSimPlayerManager.SetVelocity;
|
||||
VRCPlayerApi._GetPosition -= ClientSimPlayerManager.GetPosition;
|
||||
VRCPlayerApi._GetRotation -= ClientSimPlayerManager.GetRotation;
|
||||
VRCPlayerApi._GetGravityStrength -= ClientSimPlayerManager.GetGravityStrength;
|
||||
VRCPlayerApi._SetGravityStrength -= ClientSimPlayerManager.SetGravityStrength;
|
||||
VRCPlayerApi.IsGrounded -= ClientSimPlayerManager.IsGrounded;
|
||||
VRCPlayerApi._UseLegacyLocomotion -= ClientSimPlayerManager.UseLegacyLocomotion;
|
||||
VRCPlayerApi._Respawn -= ClientSimPlayerManager.Respawn;
|
||||
VRCPlayerApi._RespawnWithIndex -= ClientSimPlayerManager.RespawnWithIndex;
|
||||
|
||||
VRCPlayerApi._GetManualAvatarScalingAllowed -= ClientSimPlayerManager.GetManualAvatarScalingAllowed;
|
||||
VRCPlayerApi._SetManualAvatarScalingAllowed -= ClientSimPlayerManager.SetManualAvatarScalingAllowed;
|
||||
VRCPlayerApi._GetAvatarEyeHeightMinimumAsMeters -= ClientSimPlayerManager.GetAvatarEyeHeightMinimumAsMeters;
|
||||
VRCPlayerApi._GetAvatarEyeHeightMaximumAsMeters -= ClientSimPlayerManager.GetAvatarEyeHeightMaximumAsMeters;
|
||||
VRCPlayerApi._SetAvatarEyeHeightMinimumByMeters -= ClientSimPlayerManager.SetAvatarEyeHeightMinimumByMeters;
|
||||
VRCPlayerApi._SetAvatarEyeHeightMaximumByMeters -= ClientSimPlayerManager.SetAvatarEyeHeightMaximumByMeters;
|
||||
VRCPlayerApi._GetAvatarEyeHeightAsMeters -= ClientSimPlayerManager.GetAvatarEyeHeightAsMeters;
|
||||
VRCPlayerApi._SetAvatarEyeHeightByMeters -= ClientSimPlayerManager.SetAvatarEyeHeightByMeters;
|
||||
VRCPlayerApi._SetAvatarEyeHeightByMultiplier -= ClientSimPlayerManager.SetAvatarEyeHeightByMultiplier;
|
||||
|
||||
VRCPlayerApi._CombatSetup -= ClientSimCombatSystemHelper.CombatSetup;
|
||||
VRCPlayerApi._CombatSetMaxHitpoints -= ClientSimCombatSystemHelper.CombatSetMaxHitpoints;
|
||||
VRCPlayerApi._CombatGetCurrentHitpoints -= ClientSimCombatSystemHelper.CombatGetCurrentHitpoints;
|
||||
VRCPlayerApi._CombatSetRespawn -= ClientSimCombatSystemHelper.CombatSetRespawn;
|
||||
VRCPlayerApi._CombatSetDamageGraphic -= ClientSimCombatSystemHelper.CombatSetDamageGraphic;
|
||||
VRCPlayerApi._CombatGetDestructible -= ClientSimCombatSystemHelper.CombatGetDestructible;
|
||||
VRCPlayerApi._CombatSetCurrentHitpoints -= ClientSimCombatSystemHelper.CombatSetCurrentHitpoints;
|
||||
|
||||
VRCPlayerApi._SetAvatarAudioVolumetricRadius -= ClientSimPlayerManager.SetAvatarAudioVolumetricRadius;
|
||||
VRCPlayerApi._SetAvatarAudioNearRadius -= ClientSimPlayerManager.SetAvatarAudioNearRadius;
|
||||
VRCPlayerApi._SetAvatarAudioFarRadius -= ClientSimPlayerManager.SetAvatarAudioFarRadius;
|
||||
VRCPlayerApi._SetAvatarAudioGain -= ClientSimPlayerManager.SetAvatarAudioGain;
|
||||
VRCPlayerApi._SetAvatarAudioForceSpatial -= ClientSimPlayerManager.SetAvatarAudioForceSpatial;
|
||||
VRCPlayerApi._SetAvatarAudioCustomCurve -= ClientSimPlayerManager.SetAvatarAudioCustomCurve;
|
||||
|
||||
VRCPlayerApi._SetVoiceLowpass -= ClientSimPlayerManager.SetVoiceLowpass;
|
||||
VRCPlayerApi._SetVoiceVolumetricRadius -= ClientSimPlayerManager.SetVoiceVolumetricRadius;
|
||||
VRCPlayerApi._SetVoiceDistanceFar -= ClientSimPlayerManager.SetVoiceDistanceFar;
|
||||
VRCPlayerApi._SetVoiceDistanceNear -= ClientSimPlayerManager.SetVoiceDistanceNear;
|
||||
VRCPlayerApi._SetVoiceGain -= ClientSimPlayerManager.SetVoiceGain;
|
||||
|
||||
|
||||
VRC_SpatialAudioSource.Initialize -= ClientSimSpatialAudioHelper.InitializeAudio;
|
||||
|
||||
VRCAVProVideoPlayer.Initialize -= ClientSimAVProVideoStub.InitializePlayer;
|
||||
|
||||
InputManager._EnableObjectHighlight -= highlightManager.EnableObjectHighlight;
|
||||
|
||||
Store._sendProductEvent -= ClientSimStoreManager.SendProductEvent;
|
||||
Store._listPurchases -= ClientSimStoreManager.ListPurchases;
|
||||
Store._listAvailableProducts -= ClientSimStoreManager.ListAvailableProducts;
|
||||
Store._doesPlayerOwnProduct -= ClientSimStoreManager.DoesPlayerOwnProduct;
|
||||
Store._doesAnyPlayerOwnProduct -= ClientSimStoreManager.DoesAnyPlayerOwnProduct;
|
||||
Store._getPlayersWhoOwnProduct -= ClientSimStoreManager.GetPlayersWhoOwnProduct;
|
||||
|
||||
VRCQualitySettings.SetShadowDistanceInternal -= SetShadowDistance;
|
||||
VRCQualitySettings.SetShadowDistanceInternalAll -= SetShadowDistanceAll;
|
||||
VRCQualitySettings.ResetShadowDistanceInternal -= ResetShadowDistance;
|
||||
VRCCameraSettings.OnCameraSettingsChanged -= OnCameraSettingsChanged;
|
||||
|
||||
NetworkCalling.GetAllQueuedEventsProxy -= ClientSimNetworkCalling.GetAllQueuedEventsProxy;
|
||||
NetworkCalling.GetQueuedEventsProxy -= ClientSimNetworkCalling.GetQueuedEventsProxy;
|
||||
NetworkCalling.SendCustomNetworkEventProxy -= ClientSimNetworkCalling.SendCustomNetworkEventProxy;
|
||||
|
||||
VRC.SDK3.Components.VRCOpenMenu._OpenAvatarListingDelegate -= OpenAvatarListing;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Accessors
|
||||
|
||||
internal ClientSimProxyObjects GetProxyObjects()
|
||||
{
|
||||
return _proxyObject;
|
||||
}
|
||||
|
||||
internal ClientSimMenu GetMenu()
|
||||
{
|
||||
return menu;
|
||||
}
|
||||
|
||||
internal IClientSimEventDispatcher GetEventDispatcher()
|
||||
{
|
||||
return _eventDispatcher;
|
||||
}
|
||||
|
||||
internal IClientSimUdonEventSender GetUdonEventSender()
|
||||
{
|
||||
return _udonEventSender;
|
||||
}
|
||||
|
||||
internal IClientSimBlacklistManager GetBlacklistManager()
|
||||
{
|
||||
return _blacklistManager;
|
||||
}
|
||||
|
||||
internal IClientSimUdonManager GetUdonManager()
|
||||
{
|
||||
return _udonManager;
|
||||
}
|
||||
|
||||
internal IClientSimSyncedObjectManager GetSyncedObjectManager()
|
||||
{
|
||||
return syncedObjectManager;
|
||||
}
|
||||
|
||||
internal IClientSimPlayerManager GetPlayerManager()
|
||||
{
|
||||
return _playerManager;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8dfd7f39e425354dbab5d2c2064fe0a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,576 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon.Common;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// System responsible for displaying the menu to the players and updating settings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sends Events:
|
||||
/// - ClientSimMenuStateChangedEvent
|
||||
/// - ClientSimMenuRespawnClickedEvent
|
||||
/// - ClientSimOnPlayerHeightUpdateEvent
|
||||
/// Listens to Events:
|
||||
/// - ClientSimReadyEvent
|
||||
/// - ClientSimOnPlayerMovedEvent
|
||||
/// - ClientSimOnNewMasterEvent
|
||||
/// - ClientSimOnPlayerJoinedEvent
|
||||
/// Listens to Input Events:
|
||||
/// - ToggleMenu
|
||||
/// </remarks>
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimMenu : ClientSimBehaviour, IDisposable
|
||||
{
|
||||
// Property name on UI shaders to set the ZTest mode. Used to make the menu appear on top of everything.
|
||||
private const string GUI_ZTEST_MODE_PROPERTY_NAME = "unity_GUIZTestMode";
|
||||
|
||||
private const string HAS_USER_ACCEPTED_WARNING = "accepted_warning";
|
||||
|
||||
public enum ClientSimDisplayedPage
|
||||
{
|
||||
PAUSE_MENU,
|
||||
WARNING_PAGE,
|
||||
INVALID_SETTINGS_PAGE,
|
||||
DELAYED_START_PAGE,
|
||||
}
|
||||
|
||||
// The method to open the settings window is set from Editor context.
|
||||
// This hook is set on playmode start in ClientSimEditorRuntimeLinker.cs
|
||||
internal static Action openSettingsHook;
|
||||
// This method allows the menu to check the editor only method if ClientSim has all settings properly set
|
||||
internal static Func<bool> checkValidSettingsHook;
|
||||
|
||||
[SerializeField]
|
||||
private GameObject menu;
|
||||
|
||||
public float menuScaleFactor = 0.0035f;
|
||||
|
||||
[SerializeField]
|
||||
private GameObject pauseMenu;
|
||||
[SerializeField]
|
||||
private GameObject warningsPage;
|
||||
[SerializeField]
|
||||
private GameObject invalidSettingsPage;
|
||||
[SerializeField]
|
||||
private GameObject delayStartPage;
|
||||
|
||||
|
||||
[SerializeField]
|
||||
private Toggle tooltipsToggle;
|
||||
[SerializeField]
|
||||
private Toggle reticleToggle;
|
||||
[SerializeField]
|
||||
private Toggle invertMouseToggle;
|
||||
[SerializeField]
|
||||
private Toggle consoleLoggingToggle;
|
||||
[SerializeField]
|
||||
private Slider playerHeightSlider;
|
||||
[SerializeField]
|
||||
private Text playerHeightText;
|
||||
|
||||
[SerializeField]
|
||||
private Text playerNameText;
|
||||
[SerializeField]
|
||||
private Text playerIdText;
|
||||
[SerializeField]
|
||||
private Toggle isMasterToggle;
|
||||
[SerializeField]
|
||||
private Toggle isInstanceOwnerToggle;
|
||||
[SerializeField]
|
||||
private Toggle isVRCPlusToggle;
|
||||
|
||||
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private IClientSimInput _input;
|
||||
private ClientSimSettings _settings;
|
||||
private IClientSimSessionState _sessionState;
|
||||
private IClientSimPlayerHeightManager _heightManager;
|
||||
|
||||
private ClientSimDisplayedPage _displayedPage = ClientSimDisplayedPage.WARNING_PAGE;
|
||||
private bool _menuIsActive;
|
||||
private bool _stackedCameraReady = false;
|
||||
|
||||
private float _playerHeightOriginalMaxvalue;
|
||||
|
||||
private Canvas _menuCanvas;
|
||||
|
||||
public void SetCanvasCamera(Camera cam)
|
||||
{
|
||||
_menuCanvas.worldCamera = cam;
|
||||
}
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
_menuCanvas = menu.GetComponent<Canvas>();
|
||||
_playerHeightOriginalMaxvalue = playerHeightSlider.maxValue;
|
||||
|
||||
}
|
||||
|
||||
public void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimInput input,
|
||||
ClientSimSettings settings,
|
||||
IClientSimSessionState sessionState,
|
||||
IClientSimPlayerHeightManager heightManager)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_input = input;
|
||||
_settings = settings;
|
||||
_sessionState = sessionState;
|
||||
_heightManager = heightManager;
|
||||
|
||||
// Input will be null with incorrect Unity input project settings.
|
||||
_input?.SubscribeToggleMenu(HandleInputMenuToggle);
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
|
||||
_eventDispatcher.Subscribe<ClientSimOnNewMasterEvent>(OnMasterChange);
|
||||
_eventDispatcher.Subscribe<ClientSimReadyEvent>(OnReady);
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerHeightUpdateEvent>(OnPlayerHeightUpdate);
|
||||
_eventDispatcher.Subscribe<ClientSimOnToggleManualScalingEvent>(OnManualScalingToggled);
|
||||
_eventDispatcher.Subscribe<ClientSimStackedCameraReadyEvent>(OnStackedCameraReady);
|
||||
|
||||
playerNameText.text = "";
|
||||
playerIdText.text = "";
|
||||
isMasterToggle.isOn = false;
|
||||
isInstanceOwnerToggle.isOn = settings.isInstanceOwner;
|
||||
isVRCPlusToggle.isOn = settings.isVRCPlus;
|
||||
isVRCPlusToggle.onValueChanged.AddListener(OnVRCPlusToggleChanged);
|
||||
|
||||
UpdateValuesFromSettings();
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.SceneVisibilityManager.instance.Hide(gameObject, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
public ClientSimDisplayedPage GetDisplayedPage()
|
||||
{
|
||||
return _displayedPage;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
SetUIOverlayMaterial();
|
||||
|
||||
bool shouldShowMenu = true;
|
||||
if (checkValidSettingsHook?.Invoke() == false)
|
||||
{
|
||||
// Force open the ClientSim settings window.
|
||||
// When first importing ClientSim and disabling legacy input, all UI menus fail to react to the mouse,
|
||||
// preventing users from clicking the settings window button.
|
||||
OpenSettings();
|
||||
SetDisplayedPage(ClientSimDisplayedPage.INVALID_SETTINGS_PAGE);
|
||||
}
|
||||
else if (_settings.initializationDelay > 0)
|
||||
{
|
||||
SetDisplayedPage(ClientSimDisplayedPage.DELAYED_START_PAGE);
|
||||
}
|
||||
else if (_settings.spawnPlayer)
|
||||
{
|
||||
DisplayInitialPageForPlayer();
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldShowMenu = false;
|
||||
}
|
||||
|
||||
ToggleMenu(shouldShowMenu);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_input?.UnsubscribeToggleMenu(HandleInputMenuToggle);
|
||||
|
||||
_eventDispatcher?.Unsubscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
|
||||
_eventDispatcher?.Unsubscribe<ClientSimOnNewMasterEvent>(OnMasterChange);
|
||||
_eventDispatcher?.Unsubscribe<ClientSimReadyEvent>(OnReady);
|
||||
_eventDispatcher?.Unsubscribe<ClientSimOnPlayerMovedEvent>(OnPlayerMoved);
|
||||
_eventDispatcher?.Unsubscribe<ClientSimOnToggleManualScalingEvent>(OnManualScalingToggled);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Only update the menu settings while the menu is displayed.
|
||||
if (_menuIsActive)
|
||||
{
|
||||
UpdateValuesFromSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetUIOverlayMaterial()
|
||||
{
|
||||
int propertyId = Shader.PropertyToID(GUI_ZTEST_MODE_PROPERTY_NAME);
|
||||
|
||||
// Solution provided from Unity forums:
|
||||
// https://answers.unity.com/questions/878667/world-space-canvas-on-top-of-everything.html
|
||||
Graphic[] graphics = GetComponentsInChildren<Graphic>(true);
|
||||
Dictionary<Material, Material> newMaterialMapping = new Dictionary<Material, Material>();
|
||||
foreach (var graphic in graphics)
|
||||
{
|
||||
Material mat = graphic.materialForRendering;
|
||||
if (mat == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!newMaterialMapping.TryGetValue(mat, out Material updatedMaterial))
|
||||
{
|
||||
updatedMaterial = new Material(mat);
|
||||
newMaterialMapping.Add(mat, updatedMaterial);
|
||||
}
|
||||
updatedMaterial.SetInt(propertyId, (int)UnityEngine.Rendering.CompareFunction.Always);
|
||||
graphic.material = updatedMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDisplayedPage(ClientSimDisplayedPage page)
|
||||
{
|
||||
_displayedPage = page;
|
||||
|
||||
pauseMenu.SetActive(_displayedPage == ClientSimDisplayedPage.PAUSE_MENU);
|
||||
warningsPage.SetActive(_displayedPage == ClientSimDisplayedPage.WARNING_PAGE);
|
||||
delayStartPage.SetActive(_displayedPage == ClientSimDisplayedPage.DELAYED_START_PAGE);
|
||||
invalidSettingsPage.SetActive(_displayedPage == ClientSimDisplayedPage.INVALID_SETTINGS_PAGE);
|
||||
}
|
||||
|
||||
private void DisplayInitialPageForPlayer()
|
||||
{
|
||||
SetDisplayedPage(_sessionState.GetBool(HAS_USER_ACCEPTED_WARNING)
|
||||
? ClientSimDisplayedPage.PAUSE_MENU
|
||||
: ClientSimDisplayedPage.WARNING_PAGE);
|
||||
}
|
||||
|
||||
private void UpdateValuesFromSettings()
|
||||
{
|
||||
tooltipsToggle.isOn = _settings.showTooltips;
|
||||
reticleToggle.isOn = _settings.showDesktopReticle;
|
||||
invertMouseToggle.isOn = _settings.invertMouseLook;
|
||||
consoleLoggingToggle.isOn = _settings.displayLogs;
|
||||
|
||||
float playerHeight = _heightManager.GetAvatarEyeHeightAsMetersClamped();
|
||||
if (!Mathf.Approximately(playerHeight, playerHeightSlider.value))
|
||||
{
|
||||
// the player height slider obeys manual scale restrictions as if through the radial menu
|
||||
ClampPlayerHeightSliderBounds();
|
||||
|
||||
playerHeightSlider.value = playerHeight;
|
||||
playerHeightText.text = playerHeight.ToString("F2");
|
||||
|
||||
_eventDispatcher.SendEvent(new ClientSimOnPlayerHeightUpdateEvent { playerHeight = playerHeight });
|
||||
}
|
||||
}
|
||||
|
||||
private void ClampPlayerHeightSliderBounds()
|
||||
{
|
||||
playerHeightSlider.minValue = _heightManager.GetAvatarEyeHeightMinimumAsMeters();
|
||||
playerHeightSlider.maxValue = _heightManager.GetAvatarEyeHeightMaximumAsMeters();
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
ClientSimSettings.SaveSettings(_settings);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void ToggleMenu(bool isActive)
|
||||
{
|
||||
// Don't allow the menu to be closed if the stacked camera system is not ready.
|
||||
if(!isActive && !_stackedCameraReady)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_menuIsActive = isActive;
|
||||
menu.SetActive(isActive);
|
||||
|
||||
// toggle internal UI camera stack to improve menu performance
|
||||
if (_menuCanvas.worldCamera != null)
|
||||
_menuCanvas.worldCamera.enabled = isActive;
|
||||
|
||||
_eventDispatcher.SendEvent(new ClientSimMenuStateChangedEvent { isMenuOpen = _menuIsActive });
|
||||
|
||||
if (_menuIsActive)
|
||||
{
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerMovedEvent>(OnPlayerMoved);
|
||||
UpdateCanvasLocation();
|
||||
|
||||
// If the user sets the player height value too large through the Settings window,
|
||||
// toggling the menu will clamp the max range to make it more usable again.
|
||||
ClampPlayerHeightSliderBounds();
|
||||
}
|
||||
else
|
||||
{
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnPlayerMovedEvent>(OnPlayerMoved);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO update position based on tracking type. Desktop should always be in front of the camera and VR
|
||||
// should be stationary relative to the playspace position.
|
||||
private void UpdateCanvasLocation()
|
||||
{
|
||||
Camera cam = _menuCanvas.worldCamera;
|
||||
|
||||
// Always use main camera to position the menu if the camera is missing or not enabled.
|
||||
if (cam == null || !cam.enabled || !cam.gameObject.activeInHierarchy)
|
||||
{
|
||||
cam = Camera.main;
|
||||
}
|
||||
|
||||
// TODO handle missing camera better
|
||||
if (cam == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Transform camTransform = cam.transform;
|
||||
menu.transform.localScale = camTransform.lossyScale * menuScaleFactor;
|
||||
|
||||
Vector3 position = camTransform.TransformPoint(Vector3.forward * 2);
|
||||
menu.transform.SetPositionAndRotation(position, camTransform.rotation);
|
||||
}
|
||||
|
||||
#region ClientSim Input
|
||||
|
||||
private void HandleInputMenuToggle(bool value, HandType hand)
|
||||
{
|
||||
// Only handle menu input down, and not on release.
|
||||
if (!value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Users can only change the enabled state when the current page is the normal pause menu.
|
||||
if (_displayedPage != ClientSimDisplayedPage.PAUSE_MENU)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Player is not active, do not allow changing the state of the menu.
|
||||
if (!_settings.spawnPlayer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
// Ignore pressing escape to toggle the menu off. Due to Unity using the escape key as a special key to
|
||||
// remove focus from the game window, it is impossible to recapture it.
|
||||
if (_menuIsActive && (UnityEngine.InputSystem.Keyboard.current?.escapeKey.wasPressedThisFrame ?? false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
ToggleMenu(!_menuIsActive);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ClientSim Events
|
||||
|
||||
private void OnReady(ClientSimReadyEvent readyEvent)
|
||||
{
|
||||
// Disable the menu if the player is not spawned, and the displayed page isn't invalid.
|
||||
if (!_settings.spawnPlayer && _displayedPage != ClientSimDisplayedPage.INVALID_SETTINGS_PAGE)
|
||||
{
|
||||
ToggleMenu(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_displayedPage == ClientSimDisplayedPage.DELAYED_START_PAGE)
|
||||
{
|
||||
DisplayInitialPageForPlayer();
|
||||
UpdateCanvasLocation();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStackedCameraReady(ClientSimStackedCameraReadyEvent readyEvent)
|
||||
{
|
||||
_stackedCameraReady = true;
|
||||
_eventDispatcher.Unsubscribe<ClientSimStackedCameraReadyEvent>(OnStackedCameraReady);
|
||||
if(_settings.hideMenuOnLaunch && _sessionState.GetBool(HAS_USER_ACCEPTED_WARNING))
|
||||
ToggleMenu(false);
|
||||
|
||||
}
|
||||
|
||||
private void OnPlayerJoined(ClientSimOnPlayerJoinedEvent joinEvent)
|
||||
{
|
||||
VRCPlayerApi player = joinEvent.player;
|
||||
if (!player.isLocal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isMasterToggle.isOn = player.isMaster;
|
||||
playerNameText.text = player.displayName;
|
||||
playerIdText.text = player.playerId.ToString();
|
||||
}
|
||||
|
||||
private void OnMasterChange(ClientSimOnNewMasterEvent masterEvent)
|
||||
{
|
||||
isMasterToggle.isOn = Networking.IsMaster;
|
||||
}
|
||||
|
||||
private void OnVRCPlusToggleChanged(bool value)
|
||||
{
|
||||
var localPlayer = Networking.LocalPlayer;
|
||||
if (localPlayer != null)
|
||||
{
|
||||
localPlayer.GetClientSimPlayer().isVRCPlus = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerMoved(ClientSimOnPlayerMovedEvent movedEvent)
|
||||
{
|
||||
UpdateCanvasLocation();
|
||||
}
|
||||
|
||||
private void OnPlayerHeightUpdate(ClientSimOnPlayerHeightUpdateEvent heightEvent)
|
||||
{
|
||||
// if height was set programatically and exceeds a manual scaling limit, set slider to the exceeded limit
|
||||
if (heightEvent.exceedsManualScalingMinimum)
|
||||
playerHeightSlider.SetValueWithoutNotify(_heightManager.GetAvatarEyeHeightMinimumAsMeters());
|
||||
|
||||
else if (heightEvent.exceedsManualScalingMaximum)
|
||||
playerHeightSlider.SetValueWithoutNotify(_heightManager.GetAvatarEyeHeightMaximumAsMeters());
|
||||
|
||||
else
|
||||
playerHeightSlider.SetValueWithoutNotify(heightEvent.playerHeight);
|
||||
|
||||
playerHeightText.text = heightEvent.playerHeight.ToString("F2");
|
||||
ClientSimSettings.Instance.SetInitialPlayerHeight(heightEvent.playerHeight);
|
||||
}
|
||||
|
||||
private void OnManualScalingToggled(ClientSimOnToggleManualScalingEvent toggleEvent)
|
||||
{
|
||||
playerHeightSlider.interactable = toggleEvent.manualScalingAllowed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI Hooks
|
||||
|
||||
[PublicAPI]
|
||||
public void WarningAccepted()
|
||||
{
|
||||
CloseMenu();
|
||||
SetDisplayedPage(ClientSimDisplayedPage.PAUSE_MENU);
|
||||
_sessionState.SetBool(HAS_USER_ACCEPTED_WARNING, true);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void OpenMenu()
|
||||
{
|
||||
ToggleMenu(true);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void CloseMenu()
|
||||
{
|
||||
ToggleMenu(false);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void Respawn()
|
||||
{
|
||||
CloseMenu();
|
||||
_eventDispatcher.SendEvent(new ClientSimMenuRespawnClickedEvent());
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void OpenSettings()
|
||||
{
|
||||
openSettingsHook?.Invoke();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void ExitPlaymode()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorApplication.ExitPlaymode();
|
||||
#endif
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void SpawnRemotePlayer()
|
||||
{
|
||||
ClientSimMain.SpawnRemotePlayer();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void UpdatePlayerHeight(float playerHeight)
|
||||
{
|
||||
if (!_heightManager.GetManualAvatarScalingAllowed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Mathf.Approximately(_heightManager.GetAvatarEyeHeightAsMetersClamped(), playerHeight))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_heightManager.SetAvatarEyeHeightByMeters(playerHeight, true);
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void UpdateShowTooltips(bool showTooltips)
|
||||
{
|
||||
if (_settings.showTooltips == showTooltips)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settings.showTooltips = showTooltips;
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void UpdateShowReticle(bool showReticle)
|
||||
{
|
||||
if (_settings.showDesktopReticle == showReticle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settings.showDesktopReticle = showReticle;
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void UpdateInvertMouseLook(bool invertMouseLook)
|
||||
{
|
||||
if (_settings.invertMouseLook == invertMouseLook)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settings.invertMouseLook = invertMouseLook;
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void UpdateConsoleLogging(bool consoleLogging)
|
||||
{
|
||||
if (_settings.displayLogs == consoleLogging)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settings.displayLogs = consoleLogging;
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcf28ff06eb64c8aa55abe665a48a198
|
||||
timeCreated: 1638825107
|
||||
@ -0,0 +1,107 @@
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public class ClientSimPlayerHeightManager : IClientSimPlayerHeightManager
|
||||
{
|
||||
// used when scale is adjusted via Udon
|
||||
private const float SYSTEM_EYE_HEIGHT_MIN = 0.1f;
|
||||
private const float SYSTEM_EYE_HEIGHT_MAX = 100.0f;
|
||||
|
||||
// used when the user adjusts scale manually
|
||||
private float userEyeHeightMin = 0.2f;
|
||||
private float userEyeHeightMax = 5f;
|
||||
|
||||
// Default avatar height is 1.9 units tall
|
||||
private float playerHeight = ClientSimSettings.Instance.playerStartHeight;
|
||||
private float playerSpawnHeight = ClientSimSettings.Instance.playerStartHeight;
|
||||
private bool manualScalingAllowed = true;
|
||||
private IClientSimEventDispatcher eventDispatcher;
|
||||
private IClientSimUdonEventSender udonEventSender;
|
||||
|
||||
public ClientSimPlayerHeightManager(IClientSimEventDispatcher eventDispatcher, IClientSimUdonEventSender udonEventSender)
|
||||
{
|
||||
this.eventDispatcher = eventDispatcher;
|
||||
this.udonEventSender = udonEventSender;
|
||||
|
||||
eventDispatcher.Subscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
|
||||
}
|
||||
|
||||
~ClientSimPlayerHeightManager()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
eventDispatcher?.Unsubscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
|
||||
|
||||
eventDispatcher = null;
|
||||
udonEventSender = null;
|
||||
}
|
||||
|
||||
private void OnPlayerJoined(ClientSimOnPlayerJoinedEvent joinEvent)
|
||||
{
|
||||
udonEventSender.RunEvent("_onAvatarChanged", ("player", joinEvent.player));
|
||||
udonEventSender.RunEvent("_onAvatarEyeHeightChanged",
|
||||
("player", joinEvent.player),
|
||||
("prevEyeHeightAsMeters", 0f));
|
||||
}
|
||||
|
||||
public bool GetManualAvatarScalingAllowed() => manualScalingAllowed;
|
||||
public void SetManualAvatarScalingAllowed(bool value)
|
||||
{
|
||||
manualScalingAllowed = value;
|
||||
eventDispatcher?.SendEvent(new ClientSimOnToggleManualScalingEvent { manualScalingAllowed = value });
|
||||
}
|
||||
|
||||
public float GetAvatarEyeHeightMinimumAsMeters() => userEyeHeightMin;
|
||||
public void SetAvatarEyeHeightMinimumByMeters(float value)
|
||||
{
|
||||
// min can't go above max, and can't pass system thresholds
|
||||
userEyeHeightMin = Mathf.Clamp(
|
||||
Mathf.Min(value, userEyeHeightMax),
|
||||
SYSTEM_EYE_HEIGHT_MIN,
|
||||
SYSTEM_EYE_HEIGHT_MAX);
|
||||
if (playerHeight < userEyeHeightMin)
|
||||
{
|
||||
SetAvatarEyeHeightByMeters(userEyeHeightMin);
|
||||
}
|
||||
}
|
||||
public float GetAvatarEyeHeightMaximumAsMeters() => userEyeHeightMax;
|
||||
|
||||
public void SetAvatarEyeHeightMaximumByMeters(float value)
|
||||
{
|
||||
// max can't go below min, and can't pass system thresholds
|
||||
userEyeHeightMax = Mathf.Clamp(
|
||||
Mathf.Max(value, userEyeHeightMin),
|
||||
SYSTEM_EYE_HEIGHT_MIN,
|
||||
SYSTEM_EYE_HEIGHT_MAX);
|
||||
if (playerHeight > userEyeHeightMax)
|
||||
{
|
||||
SetAvatarEyeHeightByMeters(userEyeHeightMax);
|
||||
}
|
||||
}
|
||||
public float GetAvatarEyeHeightAsMeters() => playerHeight;
|
||||
public float GetAvatarEyeHeightAsMetersClamped() => Mathf.Clamp(playerHeight, userEyeHeightMin, userEyeHeightMax);
|
||||
public void SetAvatarEyeHeightByMultiplier(float multiplier) => SetAvatarEyeHeightByMeters(playerSpawnHeight * multiplier);
|
||||
public void SetAvatarEyeHeightByMeters(float newHeight, bool isManual = false)
|
||||
{
|
||||
float previousHeight = playerHeight;
|
||||
playerHeight = isManual
|
||||
? Mathf.Clamp(newHeight, userEyeHeightMin, userEyeHeightMax)
|
||||
: Mathf.Clamp(newHeight, SYSTEM_EYE_HEIGHT_MIN, SYSTEM_EYE_HEIGHT_MAX);
|
||||
|
||||
eventDispatcher?.SendEvent(new ClientSimOnPlayerHeightUpdateEvent
|
||||
{
|
||||
playerHeight = playerHeight,
|
||||
exceedsManualScalingMinimum = !isManual && playerHeight < userEyeHeightMin,
|
||||
exceedsManualScalingMaximum = !isManual && playerHeight > userEyeHeightMax
|
||||
});
|
||||
udonEventSender.RunEvent("_onAvatarEyeHeightChanged",
|
||||
("player", Networking.LocalPlayer),
|
||||
("prevEyeHeightAsMeters", previousHeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fa1dfce2cf44ccf84bee1e4679f0404
|
||||
timeCreated: 1706291242
|
||||
@ -0,0 +1,787 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Components;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// System that manages all the players currently in ClientSim.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sends Events:
|
||||
/// - ClientSimOnNewMasterEvent
|
||||
/// - ClientSimOnPlayerJoinedEvent
|
||||
/// - ClientSimOnPlayerLeftEvent
|
||||
/// </remarks>
|
||||
public class ClientSimPlayerManager : IClientSimPlayerManager, IDisposable
|
||||
{
|
||||
private int _masterID = -1;
|
||||
private int _localPlayerID = -1;
|
||||
private int _nextPlayerID = 1;
|
||||
private readonly Dictionary<VRCPlayerApi, int> _playerIDs = new Dictionary<VRCPlayerApi, int>();
|
||||
private readonly Dictionary<int, VRCPlayerApi> _players = new Dictionary<int, VRCPlayerApi>();
|
||||
|
||||
// List of players that have joined before ClientSim has finished initializing.
|
||||
private readonly List<VRCPlayerApi> _waitingPlayers = new List<VRCPlayerApi>();
|
||||
private bool _networkReady = false;
|
||||
|
||||
private VRCPlayerObject[] _playerObjectList;
|
||||
|
||||
private VRCPlayerApi _localPlayer;
|
||||
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private static IClientSimPlayerHeightManager _heightManager;
|
||||
|
||||
public ClientSimPlayerManager(IClientSimEventDispatcher eventDispatcher, IClientSimPlayerHeightManager heightManager)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_heightManager = heightManager;
|
||||
|
||||
// Ensure no other players have been added to the list.
|
||||
VRCPlayerApi.AllPlayers.Clear();
|
||||
}
|
||||
|
||||
~ClientSimPlayerManager()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Prevent sending new events as players are cleared.
|
||||
_eventDispatcher = null;
|
||||
|
||||
// Dispose of all players added by this manager.
|
||||
List<VRCPlayerApi> players = new List<VRCPlayerApi>(_playerIDs.Keys);
|
||||
foreach (VRCPlayerApi player in players)
|
||||
{
|
||||
RemovePlayer(player);
|
||||
}
|
||||
|
||||
_heightManager?.Dispose();
|
||||
}
|
||||
|
||||
private int GetNextPlayerId()
|
||||
{
|
||||
int id = _nextPlayerID;
|
||||
++_nextPlayerID;
|
||||
return id;
|
||||
}
|
||||
|
||||
private void InitializePlayer(ClientSimPlayer clientSimPlayer, VRCPlayerApi player, int playerId)
|
||||
{
|
||||
if (_players.ContainsKey(playerId))
|
||||
{
|
||||
throw new ClientSimException($"PlayerId already assigned to player! {playerId}");
|
||||
}
|
||||
|
||||
this.Log($"Assigning player id {playerId}");
|
||||
|
||||
_playerIDs[player] = playerId;
|
||||
_players[playerId] = player;
|
||||
|
||||
// Adding player to the list makes them valid. This should happen before network ready has been sent.
|
||||
player.AddToList();
|
||||
|
||||
if (_masterID == -1)
|
||||
{
|
||||
this.Log($"Player {playerId} is now master");
|
||||
_masterID = playerId;
|
||||
|
||||
_eventDispatcher?.SendEvent(new ClientSimOnNewMasterEvent
|
||||
{
|
||||
oldMasterPlayer = null,
|
||||
newMasterPlayer = player
|
||||
});
|
||||
}
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
if (ClientSimMain.TryGetInstance(out var instance))
|
||||
clientSimPlayer.SetupPlayerPersistence(
|
||||
instance.GetEventDispatcher(),
|
||||
instance.GetUdonEventSender(),
|
||||
instance.GetBlacklistManager(),
|
||||
instance.GetUdonManager(),
|
||||
instance.GetSyncedObjectManager(),
|
||||
instance.GetPlayerManager()
|
||||
);
|
||||
#endif
|
||||
|
||||
if (_networkReady)
|
||||
{
|
||||
DispatchPlayerJoinedEvent(player);
|
||||
}
|
||||
else
|
||||
{
|
||||
_waitingPlayers.Add(player);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnClientSimReady()
|
||||
{
|
||||
_networkReady = true;
|
||||
foreach (var player in _waitingPlayers)
|
||||
{
|
||||
DispatchPlayerJoinedEvent(player);
|
||||
}
|
||||
|
||||
_waitingPlayers.Clear();
|
||||
}
|
||||
|
||||
private void DispatchPlayerJoinedEvent(VRCPlayerApi player)
|
||||
{
|
||||
_eventDispatcher?.SendEvent(new ClientSimOnPlayerJoinedEvent { player = player });
|
||||
}
|
||||
|
||||
public VRCPlayerApi CreateNewPlayer(bool local, ClientSimPlayer player, string name = "")
|
||||
{
|
||||
int playerID = GetNextPlayerId();
|
||||
string objectName = $"[{playerID}] {(local ? "Local" : "Remote")} Player";
|
||||
player.gameObject.name = objectName;
|
||||
VRCPlayerApi playerApi = new VRCPlayerApi
|
||||
{
|
||||
gameObject = player.gameObject,
|
||||
displayName = string.IsNullOrEmpty(name) ? objectName : name,
|
||||
isLocal = local
|
||||
};
|
||||
|
||||
player.SetPlayer(playerApi);
|
||||
|
||||
InitializePlayer(player, playerApi, playerID);
|
||||
|
||||
if (local)
|
||||
{
|
||||
_localPlayerID = playerID;
|
||||
_localPlayer = playerApi;
|
||||
}
|
||||
Debug.Assert(playerApi.isLocal == local, "ClientSimPlayerManager:CreateNewPlayer New player does not match local settings!");
|
||||
|
||||
return playerApi;
|
||||
}
|
||||
|
||||
public void RemovePlayer(VRCPlayerApi player)
|
||||
{
|
||||
// Master is leaving, pick a new master.
|
||||
if (_masterID == player.playerId)
|
||||
{
|
||||
_masterID = -1;
|
||||
VRCPlayerApi newMaster = null;
|
||||
Debug.Assert(VRCPlayerApi.AllPlayers[0] == player, "ClientSimPlayerManager:RemovePlayer Removing master player who was not first in the list!");
|
||||
// First player is the current master who is leaving.
|
||||
// If there is another player, select them as the new master.
|
||||
if (VRCPlayerApi.AllPlayers.Count > 1)
|
||||
{
|
||||
newMaster = VRCPlayerApi.AllPlayers[1];
|
||||
_masterID = newMaster.playerId;
|
||||
}
|
||||
|
||||
_eventDispatcher?.SendEvent(new ClientSimOnNewMasterEvent
|
||||
{
|
||||
oldMasterPlayer = player,
|
||||
newMasterPlayer = newMaster
|
||||
});
|
||||
}
|
||||
|
||||
_eventDispatcher?.SendEvent(new ClientSimOnPlayerLeftEvent { player = player });
|
||||
|
||||
_playerIDs.Remove(player);
|
||||
_players.Remove(player.playerId);
|
||||
player.RemoveFromList();
|
||||
|
||||
if (player.isLocal)
|
||||
{
|
||||
_localPlayer = null;
|
||||
_localPlayerID = -1;
|
||||
}
|
||||
|
||||
Object.Destroy(player.gameObject);
|
||||
}
|
||||
|
||||
public int GetMasterID()
|
||||
{
|
||||
return _masterID;
|
||||
}
|
||||
|
||||
public VRCPlayerApi GetMaster()
|
||||
{
|
||||
return GetPlayerByID(_masterID);
|
||||
}
|
||||
|
||||
public VRCPlayerApi GetInstanceOwner()
|
||||
{
|
||||
foreach (var player in _players.Values)
|
||||
{
|
||||
if (player.GetClientSimPlayer().isInstanceOwner)
|
||||
{
|
||||
return player;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public VRCPlayerApi LocalPlayer()
|
||||
{
|
||||
return _localPlayer;
|
||||
}
|
||||
|
||||
public VRCPlayerApi GetPlayerByID(int playerID)
|
||||
{
|
||||
_players.TryGetValue(playerID, out VRCPlayerApi player);
|
||||
return player;
|
||||
}
|
||||
|
||||
public int GetPlayerID(VRCPlayerApi player)
|
||||
{
|
||||
if (player == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
_playerIDs.TryGetValue(player, out int playerId);
|
||||
return playerId;
|
||||
}
|
||||
|
||||
public bool IsMaster(VRCPlayerApi player)
|
||||
{
|
||||
return GetPlayerID(player) == _masterID;
|
||||
}
|
||||
|
||||
public bool IsInstanceOwner(VRCPlayerApi player)
|
||||
{
|
||||
return player.GetClientSimPlayer().isInstanceOwner;
|
||||
}
|
||||
|
||||
public bool IsInstanceOwner()
|
||||
{
|
||||
return IsInstanceOwner(_localPlayer);
|
||||
}
|
||||
|
||||
public bool IsLocalPlayerMaster()
|
||||
{
|
||||
return _localPlayerID == _masterID;
|
||||
}
|
||||
|
||||
public bool IsSuspended(VRCPlayerApi player)
|
||||
{
|
||||
return player.GetClientSimPlayer().isSuspended;
|
||||
}
|
||||
|
||||
public bool IsVRCPlus(VRCPlayerApi player)
|
||||
{
|
||||
return player.GetClientSimPlayer().isVRCPlus;
|
||||
}
|
||||
|
||||
public VRCPlayerApi GetOwner(GameObject obj)
|
||||
{
|
||||
// TODO consider SyncMode.None
|
||||
IClientSimSyncable sync = obj.GetComponent<IClientSimSyncable>();
|
||||
|
||||
int playerID = sync != null ? sync.GetOwner() : _masterID;
|
||||
|
||||
if (!_players.TryGetValue(playerID, out VRCPlayerApi player))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return player;
|
||||
}
|
||||
|
||||
public bool IsOwner(VRCPlayerApi player, GameObject obj)
|
||||
{
|
||||
IClientSimSyncable sync = obj.GetComponent<IClientSimSyncable>();
|
||||
int owner = sync == null ? _masterID : sync.GetOwner();
|
||||
return owner == player.playerId;
|
||||
}
|
||||
|
||||
public static void SetOwner(VRCPlayerApi player, GameObject obj)
|
||||
{
|
||||
if (Networking.GetOwner(obj) == player)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IClientSimSyncable[] syncs = obj.GetComponents<IClientSimSyncable>();
|
||||
foreach (IClientSimSyncable sync in syncs)
|
||||
{
|
||||
sync.SetOwner(player.playerId);
|
||||
}
|
||||
|
||||
IClientSimSyncableHandler[] syncHandlers = obj.GetComponents<IClientSimSyncableHandler>();
|
||||
foreach (IClientSimSyncableHandler syncHandler in syncHandlers)
|
||||
{
|
||||
syncHandler.OnOwnershipTransferred(player.playerId);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsUserInVR(VRCPlayerApi player)
|
||||
{
|
||||
return player.GetClientSimPlayer().IsUserVR;
|
||||
}
|
||||
|
||||
public static void EnablePickups(VRCPlayerApi player, bool enabled)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
player.LogWarning($"[VRCPlayerAPI.EnablePickups] EnablePickups for remote players will do nothing. PlayerId: {player.playerId}");
|
||||
return;
|
||||
}
|
||||
|
||||
player.GetClientSimPlayer().pickupData.SetPickupsEnabled(enabled);
|
||||
}
|
||||
|
||||
public static void Immobilize(VRCPlayerApi player, bool immobilized)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
// VRChatBug: Throw an exception to crash the udon program similar to VRChat Client.
|
||||
throw new ClientSimException("[VRCPlayerAPI.Immobilize] You cannot set remote players Immobilized");
|
||||
}
|
||||
|
||||
player.GetClientSimPlayer().locomotionData.SetImmobilized(immobilized);
|
||||
}
|
||||
|
||||
public static void TeleportToOrientationLerp(VRCPlayerApi player, Vector3 position, Quaternion rotation, VRC_SceneDescriptor.SpawnOrientation orientation, bool lerp)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
player.LogWarning($"[VRCPlayerAPI.TeleportTo] Teleporting remote players will do nothing. PlayerId: {player.playerId}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore lerp since there is no networking here
|
||||
player.GetPlayerController().Teleport(position, rotation, orientation == VRC_SceneDescriptor.SpawnOrientation.AlignRoomWithSpawnPoint);
|
||||
}
|
||||
|
||||
public static void TeleportToOrientation(VRCPlayerApi player, Vector3 position, Quaternion rotation, VRC_SceneDescriptor.SpawnOrientation orientation)
|
||||
{
|
||||
TeleportToOrientationLerp(player, position, rotation, orientation, false);
|
||||
}
|
||||
|
||||
public static void TeleportTo(VRCPlayerApi player, Vector3 position, Quaternion rotation)
|
||||
{
|
||||
TeleportToOrientation(player, position, rotation, VRC_SceneDescriptor.SpawnOrientation.Default);
|
||||
}
|
||||
|
||||
public static void Respawn(VRCPlayerApi playerApi)
|
||||
{
|
||||
if (!playerApi.isLocal)
|
||||
{
|
||||
playerApi.LogWarning($"[VRCPlayerApi.Respawn] Respawn for remote players will do nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
playerApi.GetPlayerController().Respawn();
|
||||
}
|
||||
|
||||
public static void RespawnWithIndex(VRCPlayerApi playerApi, int index)
|
||||
{
|
||||
if (!playerApi.isLocal)
|
||||
{
|
||||
playerApi.LogWarning($"[VRCPlayerApi.Respawn] Respawn for remote players will do nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
playerApi.GetPlayerController().Respawn(index);
|
||||
}
|
||||
|
||||
public static void PlayHapticEventInHand(VRCPlayerApi player, VRC_Pickup.PickupHand hand, float duration, float amplitude, float frequency)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
player.LogWarning($"[VRCPlayerAPI.PlayHapticEventInHand] PlayHapticEventInHand for remote players will do nothing. PlayerId: {player.playerId}");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO
|
||||
player.Log($"[VRCPlayerAPI.PlayHapticEventInHand] Playing haptics for player. PlayerId: {player.playerId}, Hand: {hand}, Duration: {duration}, Amplitude: {amplitude}, Frequency: {frequency}");
|
||||
}
|
||||
|
||||
public static VRCPlayerApi GetPlayerByGameObject(GameObject obj)
|
||||
{
|
||||
ClientSimPlayer player = obj.GetComponentInParent<ClientSimPlayer>();
|
||||
if (player != null)
|
||||
{
|
||||
return player.Player;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<VRCPlayerApi> GetAllPlayersWithinRange(Vector3 pos, float radius, int limit = -1)
|
||||
{
|
||||
List<VRCPlayerApi> resultList = new List<VRCPlayerApi>(VRCPlayerApi.AllPlayers.Count);
|
||||
|
||||
if (limit != 0)
|
||||
{
|
||||
float radiusSqr = radius * radius;
|
||||
|
||||
foreach (VRCPlayerApi player in VRCPlayerApi.AllPlayers)
|
||||
{
|
||||
Vector3 playerPosition = player.GetPosition();
|
||||
Vector3 delta = pos - playerPosition;
|
||||
if (delta.sqrMagnitude <= radiusSqr)
|
||||
{
|
||||
resultList.Add(player);
|
||||
|
||||
if (limit > 0 && limit <= resultList.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resultList.Sort(ComparePlayersByDistance);
|
||||
}
|
||||
|
||||
return resultList;
|
||||
|
||||
int ComparePlayersByDistance(VRCPlayerApi a, VRCPlayerApi b)
|
||||
{
|
||||
float aDistSqr = (pos - a.GetPosition()).sqrMagnitude;
|
||||
float bDistSqr = (pos - b.GetPosition()).sqrMagnitude;
|
||||
return aDistSqr.CompareTo(bDistSqr);
|
||||
}
|
||||
}
|
||||
|
||||
public static VRC_Pickup GetPickupInHand(VRCPlayerApi player, VRC_Pickup.PickupHand hand)
|
||||
{
|
||||
return player.GetClientSimPlayer().pickupData.GetPickupInHand(hand);
|
||||
}
|
||||
|
||||
public static VRCPlayerApi.TrackingData GetTrackingData(VRCPlayerApi player, VRCPlayerApi.TrackingDataType trackingDataType)
|
||||
{
|
||||
// Remote players do not have tracking data, so get respective bone
|
||||
if (!player.isLocal)
|
||||
{
|
||||
Vector3 position = Vector3.zero;
|
||||
Quaternion rotation = Quaternion.identity;
|
||||
|
||||
switch (trackingDataType)
|
||||
{
|
||||
case VRCPlayerApi.TrackingDataType.Head:
|
||||
position = GetBonePosition(player, HumanBodyBones.Head);
|
||||
rotation = GetBoneRotation(player, HumanBodyBones.Head);
|
||||
break;
|
||||
case VRCPlayerApi.TrackingDataType.LeftHand:
|
||||
position = GetBonePosition(player, HumanBodyBones.LeftHand);
|
||||
rotation = GetBoneRotation(player, HumanBodyBones.LeftHand);
|
||||
break;
|
||||
case VRCPlayerApi.TrackingDataType.RightHand:
|
||||
position = GetBonePosition(player, HumanBodyBones.RightHand);
|
||||
rotation = GetBoneRotation(player, HumanBodyBones.RightHand);
|
||||
break;
|
||||
case VRCPlayerApi.TrackingDataType.Origin:
|
||||
position = GetPosition(player);
|
||||
rotation = GetRotation(player);
|
||||
break;
|
||||
}
|
||||
return new VRCPlayerApi.TrackingData(position, rotation);
|
||||
}
|
||||
|
||||
return player.GetClientSimPlayer().GetTrackingProvider().GetTrackingData(trackingDataType);
|
||||
}
|
||||
|
||||
#region Player Locomotion
|
||||
|
||||
public static float GetRunSpeed(VRCPlayerApi player)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
// VRChatBug: Throw an exception to crash the udon program similar to VRChat Client.
|
||||
throw new ClientSimException("[VRCPlayerAPI.GetRunSpeed] You cannot get run speed for remote clients!");
|
||||
}
|
||||
return player.GetClientSimPlayer().locomotionData.GetRunSpeed();
|
||||
}
|
||||
|
||||
public static void SetRunSpeed(VRCPlayerApi player, float speed)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
// VRChatBug: Throw an exception to crash the udon program similar to VRChat Client.
|
||||
throw new ClientSimException("[VRCPlayerAPI.SetRunSpeed] You cannot set run speed for remote clients!");
|
||||
}
|
||||
player.GetClientSimPlayer().locomotionData.SetRunSpeed(speed);
|
||||
}
|
||||
|
||||
public static float GetStrafeSpeed(VRCPlayerApi player)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
// VRChatBug: Throw an exception to crash the udon program similar to VRChat Client.
|
||||
throw new ClientSimException("[VRCPlayerAPI.GetStrafeSpeed] You cannot get strafe speed for remote clients!");
|
||||
}
|
||||
return player.GetClientSimPlayer().locomotionData.GetStrafeSpeed();
|
||||
}
|
||||
|
||||
public static void SetStrafeSpeed(VRCPlayerApi player, float speed)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
// VRChatBug: Throw an exception to crash the udon program similar to VRChat Client.
|
||||
throw new ClientSimException("[VRCPlayerAPI.SetStrafeSpeed] You cannot set strafe speed for remote clients!");
|
||||
}
|
||||
player.GetClientSimPlayer().locomotionData.SetStrafeSpeed(speed);
|
||||
}
|
||||
|
||||
public static float GetWalkSpeed(VRCPlayerApi player)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
// VRChatBug: Throw an exception to crash the udon program similar to VRChat Client.
|
||||
throw new ClientSimException("[VRCPlayerAPI.GetWalkSpeed] You cannot get walk speed for remote clients!");
|
||||
}
|
||||
return player.GetClientSimPlayer().locomotionData.GetWalkSpeed();
|
||||
}
|
||||
|
||||
public static void SetWalkSpeed(VRCPlayerApi player, float speed)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
// VRChatBug: Throw an exception to crash the udon program similar to VRChat Client.
|
||||
throw new ClientSimException("[VRCPlayerAPI.SetWalkSpeed] You cannot set walk speed for remote clients!");
|
||||
}
|
||||
player.GetClientSimPlayer().locomotionData.SetWalkSpeed(speed);
|
||||
}
|
||||
|
||||
public static float GetJumpImpulse(VRCPlayerApi player)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
// VRChatBug: Throw an exception to crash the udon program similar to VRChat Client.
|
||||
throw new ClientSimException("[VRCPlayerAPI.GetJumpImpulse] You cannot get jump impulse for remote clients!");
|
||||
}
|
||||
return player.GetClientSimPlayer().locomotionData.GetJump();
|
||||
}
|
||||
|
||||
public static void SetJumpImpulse(VRCPlayerApi player, float jump)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
// VRChatBug: Throw an exception to crash the udon program similar to VRChat Client.
|
||||
throw new ClientSimException("[VRCPlayerAPI.SetJumpImpulse] You cannot set jump impulse for remote clients!");
|
||||
}
|
||||
player.GetClientSimPlayer().locomotionData.SetJump(jump);
|
||||
}
|
||||
|
||||
public static float GetGravityStrength(VRCPlayerApi player)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
// VRChatBug: Throw an exception to crash the udon program similar to VRChat Client.
|
||||
throw new ClientSimException("[VRCPlayerAPI.GetGravityStrength] You cannot get gravity strength for remote clients!");
|
||||
}
|
||||
return player.GetClientSimPlayer().locomotionData.GetGravityStrength();
|
||||
}
|
||||
|
||||
public static void SetGravityStrength(VRCPlayerApi player, float gravity)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
// VRChatBug: Throw an exception to crash the udon program similar to VRChat Client.
|
||||
throw new ClientSimException("[VRCPlayerAPI.SetGravityStrength] You cannot set gravity strength for remote clients!");
|
||||
}
|
||||
player.GetClientSimPlayer().locomotionData.SetGravityStrength(gravity);
|
||||
}
|
||||
|
||||
public static void UseLegacyLocomotion(VRCPlayerApi player)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
player.GetClientSimPlayer().locomotionData.SetUseLegacyLocomotion(true);
|
||||
}
|
||||
|
||||
public static Vector3 GetVelocity(VRCPlayerApi player)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
return player.GetPlayerController().GetVelocity();
|
||||
}
|
||||
|
||||
public static void SetVelocity(VRCPlayerApi player, Vector3 velocity)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
player.GetPlayerController().SetVelocity(velocity);
|
||||
}
|
||||
|
||||
public static Vector3 GetPosition(VRCPlayerApi player)
|
||||
{
|
||||
return player.GetClientSimPlayer().GetPosition();
|
||||
}
|
||||
|
||||
public static Quaternion GetRotation(VRCPlayerApi player)
|
||||
{
|
||||
return player.GetClientSimPlayer().GetRotation();
|
||||
}
|
||||
|
||||
public static bool IsGrounded(VRCPlayerApi player)
|
||||
{
|
||||
if (!player.isLocal)
|
||||
{
|
||||
// TODO verify remote player values when not grounded.
|
||||
return false;
|
||||
}
|
||||
return player.GetPlayerController().IsGrounded();
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
public static Quaternion GetBoneRotation(VRCPlayerApi player, HumanBodyBones bone)
|
||||
{
|
||||
return player.GetClientSimPlayer().GetAvatarDataProvider().GetBoneRotation(bone);
|
||||
}
|
||||
|
||||
public static Vector3 GetBonePosition(VRCPlayerApi player, HumanBodyBones bone)
|
||||
{
|
||||
return player.GetClientSimPlayer().GetAvatarDataProvider().GetBonePosition(bone);
|
||||
}
|
||||
|
||||
#region Player Tags
|
||||
|
||||
public static List<int> GetPlayersWithTag(string tagName, string tagValue)
|
||||
{
|
||||
List<int> players = new List<int>();
|
||||
foreach (var player in VRCPlayerApi.AllPlayers)
|
||||
{
|
||||
if (player.GetClientSimPlayer().tagData.HasPlayerTag(tagName, tagValue))
|
||||
{
|
||||
players.Add(player.playerId);
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
|
||||
public static void ClearPlayerTags(VRCPlayerApi player)
|
||||
{
|
||||
player.LogError("Clearing all player tags. VRCPlayerApi.ClearPlayerTags is a dangerous call, as it will clear all the tags and this might break prefabs that rely on them.");
|
||||
player.GetClientSimPlayer().tagData.ClearPlayerTags();
|
||||
}
|
||||
|
||||
public static void SetPlayerTag(VRCPlayerApi player, string tagName, string tagValue)
|
||||
{
|
||||
player.GetClientSimPlayer().tagData.SetPlayerTag(tagName, tagValue);
|
||||
}
|
||||
|
||||
public static string GetPlayerTag(VRCPlayerApi player, string tagName)
|
||||
{
|
||||
return player.GetClientSimPlayer().tagData.GetPlayerTag(tagName);
|
||||
}
|
||||
|
||||
|
||||
public static void ClearSilence(VRCPlayerApi player)
|
||||
{
|
||||
// TODO?
|
||||
}
|
||||
|
||||
public static void SetSilencedToUntagged(VRCPlayerApi player, int number, string tagName, string tagValue)
|
||||
{
|
||||
// TODO?
|
||||
}
|
||||
|
||||
public static void SetSilencedToTagged(VRCPlayerApi player, int number, string tagName, string tagValue)
|
||||
{
|
||||
// TODO?
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Player Audio
|
||||
|
||||
public static void SetAvatarAudioVolumetricRadius(VRCPlayerApi player, float value)
|
||||
{
|
||||
player.GetClientSimPlayer().audioData.SetAvatarAudioVolumetricRadius(value);
|
||||
}
|
||||
|
||||
public static void SetAvatarAudioNearRadius(VRCPlayerApi player, float value)
|
||||
{
|
||||
player.GetClientSimPlayer().audioData.SetAvatarAudioNearRadius(value);
|
||||
}
|
||||
|
||||
public static void SetAvatarAudioFarRadius(VRCPlayerApi player, float value)
|
||||
{
|
||||
player.GetClientSimPlayer().audioData.SetAvatarAudioFarRadius(value);
|
||||
}
|
||||
|
||||
public static void SetAvatarAudioGain(VRCPlayerApi player, float value)
|
||||
{
|
||||
player.GetClientSimPlayer().audioData.SetAvatarAudioGain(value);
|
||||
}
|
||||
|
||||
public static void SetAvatarAudioForceSpatial(VRCPlayerApi player, bool value)
|
||||
{
|
||||
player.GetClientSimPlayer().audioData.SetAvatarAudioForceSpatial(value);
|
||||
}
|
||||
|
||||
public static void SetAvatarAudioCustomCurve(VRCPlayerApi player, bool value)
|
||||
{
|
||||
player.GetClientSimPlayer().audioData.SetAvatarAudioCustomCurve(value);
|
||||
}
|
||||
|
||||
public static void SetVoiceGain(VRCPlayerApi player, float value) =>
|
||||
player.GetClientSimPlayer().audioData.SetVoiceGain(value);
|
||||
|
||||
public static void SetVoiceDistanceNear(VRCPlayerApi player, float value) =>
|
||||
player.GetClientSimPlayer().audioData.SetVoiceDistanceNear(value);
|
||||
|
||||
public static void SetVoiceDistanceFar(VRCPlayerApi player, float value) =>
|
||||
player.GetClientSimPlayer().audioData.SetVoiceDistanceFar(value);
|
||||
|
||||
public static void SetVoiceVolumetricRadius(VRCPlayerApi player, float value) =>
|
||||
player.GetClientSimPlayer().audioData.SetVoiceVolumetricRadius(value);
|
||||
|
||||
public static void SetVoiceLowpass(VRCPlayerApi player, bool value) =>
|
||||
player.GetClientSimPlayer().audioData.SetVoiceLowpass(value);
|
||||
|
||||
public static float GetVoiceGain(VRCPlayerApi player) =>
|
||||
player.GetClientSimPlayer().audioData.GetVoiceGain();
|
||||
|
||||
public static float GetVoiceDistanceNear(VRCPlayerApi player) =>
|
||||
player.GetClientSimPlayer().audioData.GetVoiceDistanceNear();
|
||||
|
||||
public static float GetVoiceDistanceFar(VRCPlayerApi player) =>
|
||||
player.GetClientSimPlayer().audioData.GetVoiceDistanceFar();
|
||||
|
||||
public static float GetVoiceVolumetricRadius(VRCPlayerApi player) =>
|
||||
player.GetClientSimPlayer().audioData.GetVoiceVolumetricRadius();
|
||||
|
||||
public static bool GetVoiceLowpass(VRCPlayerApi player) =>
|
||||
player.GetClientSimPlayer().audioData.GetVoiceLowpass();
|
||||
|
||||
public static string GetCurrentLanguage()
|
||||
{
|
||||
return ClientSimSettings.Instance.currentLanguage;
|
||||
}
|
||||
|
||||
public static string[] GetAvailableLanguages()
|
||||
{
|
||||
return ClientSimSettings.Instance.availableLanguages;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Player Scaling
|
||||
|
||||
public static bool GetManualAvatarScalingAllowed(VRCPlayerApi _) => _heightManager.GetManualAvatarScalingAllowed();
|
||||
public static void SetManualAvatarScalingAllowed(VRCPlayerApi _, bool value) => _heightManager.SetManualAvatarScalingAllowed(value);
|
||||
public static float GetAvatarEyeHeightMinimumAsMeters(VRCPlayerApi _) => _heightManager.GetAvatarEyeHeightMinimumAsMeters();
|
||||
public static float GetAvatarEyeHeightMaximumAsMeters(VRCPlayerApi _) => _heightManager.GetAvatarEyeHeightMaximumAsMeters();
|
||||
public static float GetAvatarEyeHeightAsMeters(VRCPlayerApi _) => _heightManager.GetAvatarEyeHeightAsMeters();
|
||||
public static void SetAvatarEyeHeightMinimumByMeters(VRCPlayerApi _, float value) => _heightManager.SetAvatarEyeHeightMinimumByMeters(value);
|
||||
public static void SetAvatarEyeHeightMaximumByMeters(VRCPlayerApi _, float value) => _heightManager.SetAvatarEyeHeightMaximumByMeters(value);
|
||||
public static void SetAvatarEyeHeightByMeters(VRCPlayerApi _, float value) => _heightManager.SetAvatarEyeHeightByMeters(value);
|
||||
public static void SetAvatarEyeHeightByMultiplier(VRCPlayerApi _, float value) => _heightManager.SetAvatarEyeHeightByMultiplier(value);
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8313fb7d292ed7c40a0f0e6e06a652df
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,93 @@
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// System that handles spawning new players for ClientSim.
|
||||
/// New players will then be initialized through the PlayerManager.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimPlayerSpawner : ClientSimBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private GameObject localPlayerPrefab;
|
||||
[SerializeField]
|
||||
private GameObject remotePlayerPrefab;
|
||||
|
||||
private IClientSimSceneManager _sceneManager;
|
||||
private IClientSimPlayerManager _playerManager;
|
||||
private IClientSimBlacklistManager _blacklistManager;
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private Transform _parent;
|
||||
|
||||
public void Initialize(
|
||||
IClientSimSceneManager sceneManager,
|
||||
IClientSimPlayerManager playerManager,
|
||||
IClientSimBlacklistManager blacklistManager,
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
Transform parent)
|
||||
{
|
||||
_sceneManager = sceneManager;
|
||||
_playerManager = playerManager;
|
||||
_blacklistManager = blacklistManager;
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public static Vector3 GetRandomPositionAroundSpawn(Vector3 spawnPosition, float radius)
|
||||
{
|
||||
Vector2 randomPosition = Random.insideUnitCircle * radius;
|
||||
return new Vector3(randomPosition.x + spawnPosition.x, spawnPosition.y, randomPosition.y + spawnPosition.z);
|
||||
}
|
||||
|
||||
public ClientSimPlayer SpawnPlayer(string playerName, bool isLocal)
|
||||
{
|
||||
if (!_sceneManager.HasSceneDescriptor())
|
||||
{
|
||||
throw new ClientSimException("Cannot spawn player if there is no world descriptor!");
|
||||
}
|
||||
|
||||
GameObject playerPrefab = isLocal ? localPlayerPrefab : remotePlayerPrefab;
|
||||
if (playerPrefab == null)
|
||||
{
|
||||
throw new ClientSimException("Failed to spawn player! Player prefab was not found.");
|
||||
}
|
||||
|
||||
Transform spawn = _sceneManager.GetSpawnPoint(!isLocal);
|
||||
float radius = _sceneManager.GetSpawnRadius();
|
||||
Vector3 position = GetRandomPositionAroundSpawn(spawn.position, radius);
|
||||
Quaternion rotation = Quaternion.Euler(0, spawn.rotation.eulerAngles.y, 0);
|
||||
|
||||
GameObject playerInstance = Instantiate(playerPrefab, position, rotation, _parent);
|
||||
|
||||
if (_parent == null)
|
||||
{
|
||||
DontDestroyOnLoad(playerInstance);
|
||||
}
|
||||
_blacklistManager.AddObjectAndChildrenToBlackList(playerInstance);
|
||||
|
||||
ClientSimPlayer player = playerInstance.GetComponent<ClientSimPlayer>();
|
||||
if (player == null)
|
||||
{
|
||||
throw new ClientSimException("Failed to spawn player! ClientSimPlayer script was not found.");
|
||||
}
|
||||
|
||||
// PlayerManager will automatically handle sending player join event
|
||||
_playerManager.CreateNewPlayer(isLocal, player, playerName);
|
||||
|
||||
|
||||
if (isLocal)
|
||||
{
|
||||
// Disable player controller until ClientSim is initialized, which is when the player should be able to gain control.
|
||||
playerInstance.SetActive(false);
|
||||
}
|
||||
|
||||
player.SetEventDispatcher(_eventDispatcher);
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79164cc4f26063f4db9016f803a7263d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,21 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// Manager class for holding any proxy object. Currently only the camera proxy provided for the combat system.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimProxyObjects : ClientSimBehaviour, IClientSimProxyObjectProvider
|
||||
{
|
||||
[SerializeField]
|
||||
private Transform cameraProxy;
|
||||
|
||||
public Transform CameraProxy() => cameraProxy;
|
||||
|
||||
public void DestroyProxy()
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a70a3c6d92e24dd489cdd7b4d138a17e
|
||||
timeCreated: 1638830019
|
||||
@ -0,0 +1,190 @@
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
using System.Reflection;
|
||||
using UnityEngine.Rendering.PostProcessing;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper for the VRC_SceneDescriptor.
|
||||
/// </summary>
|
||||
public class ClientSimSceneManager : IClientSimSceneManager
|
||||
{
|
||||
// Using a variable here to allow for testability when not on android platform.
|
||||
private bool _isAndroid =
|
||||
#if UNITY_ANDROID
|
||||
true;
|
||||
#else
|
||||
false;
|
||||
#endif
|
||||
|
||||
// Only used for tests to verify android path in copy camera.
|
||||
internal static ClientSimSceneManager CreateTestInstance(bool isAndroid)
|
||||
{
|
||||
return new ClientSimSceneManager { _isAndroid = isAndroid };
|
||||
}
|
||||
|
||||
|
||||
private int _spawnOrder = 0;
|
||||
private VRC_SceneDescriptor _descriptor => VRC_SceneDescriptor.Instance;
|
||||
|
||||
|
||||
public bool HasSceneDescriptor()
|
||||
{
|
||||
return _descriptor != null;
|
||||
}
|
||||
|
||||
public void SetupCamera(Camera camera)
|
||||
{
|
||||
if (!HasSceneDescriptor())
|
||||
{
|
||||
throw new ClientSimException("Cannot get reference camera when there is no scene descriptor.");
|
||||
}
|
||||
|
||||
Camera refCamera = null;
|
||||
if (_descriptor.ReferenceCamera != null)
|
||||
{
|
||||
refCamera = _descriptor.ReferenceCamera.GetComponent<Camera>();
|
||||
}
|
||||
|
||||
if (refCamera != null)
|
||||
{
|
||||
CopyCameraValues(refCamera, camera);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogWarning("Reference camera is not set in the SceneDescriptor!");
|
||||
|
||||
// Force camera near settings
|
||||
camera.nearClipPlane = Mathf.Clamp(camera.nearClipPlane, 0.01f, 0.05f);
|
||||
}
|
||||
}
|
||||
|
||||
public float GetRespawnHeight()
|
||||
{
|
||||
if (!HasSceneDescriptor())
|
||||
{
|
||||
throw new ClientSimException("Cannot get respawn height when there is no scene descriptor.");
|
||||
}
|
||||
|
||||
return _descriptor.RespawnHeightY;
|
||||
}
|
||||
|
||||
public bool ShouldObjectsDestroyAtRespawnHeight()
|
||||
{
|
||||
if (!HasSceneDescriptor())
|
||||
{
|
||||
throw new ClientSimException("Cannot get respawn height setting when there is no scene descriptor.");
|
||||
}
|
||||
|
||||
return _descriptor.ObjectBehaviourAtRespawnHeight == VRC_SceneDescriptor.RespawnHeightBehaviour.Destroy;
|
||||
}
|
||||
|
||||
public void ResetSpawnOrder()
|
||||
{
|
||||
_spawnOrder = 0;
|
||||
}
|
||||
|
||||
public Transform GetSpawnPoint(bool remote = false)
|
||||
{
|
||||
if (!HasSceneDescriptor())
|
||||
{
|
||||
throw new ClientSimException("Trying to get a Spawn Point but there is no Scene Descriptor. Add a SceneDescriptor or the VRCWorldPrefab to your scene.");
|
||||
}
|
||||
|
||||
if (_descriptor.spawns.Length == 0 || _descriptor.spawns[0] == null)
|
||||
{
|
||||
throw new ClientSimException("Trying to get a Spawn Point but the Scene Descriptor doesn't have one. Add a Transform to the 'Spawns' array in the VRC Scene Descriptor component.");
|
||||
}
|
||||
|
||||
// Remote players always restart the list, so for now, only first spawn
|
||||
if (_descriptor.spawnOrder == VRC_SceneDescriptor.SpawnOrder.First ||
|
||||
_descriptor.spawnOrder == VRC_SceneDescriptor.SpawnOrder.Demo ||
|
||||
remote)
|
||||
{
|
||||
return _descriptor.spawns[0];
|
||||
}
|
||||
if (_descriptor.spawnOrder == VRC_SceneDescriptor.SpawnOrder.Random)
|
||||
{
|
||||
int spawn = Random.Range(0, _descriptor.spawns.Length);
|
||||
return _descriptor.spawns[spawn];
|
||||
}
|
||||
if (_descriptor.spawnOrder == VRC_SceneDescriptor.SpawnOrder.Sequential)
|
||||
{
|
||||
Transform spawn = _descriptor.spawns[_spawnOrder];
|
||||
_spawnOrder = (_spawnOrder + 1) % _descriptor.spawns.Length;
|
||||
return spawn;
|
||||
}
|
||||
|
||||
// Fallback to first spawn point
|
||||
return _descriptor.spawns[0];
|
||||
}
|
||||
|
||||
public Transform GetSpawnPoint(int index)
|
||||
{
|
||||
if (!HasSceneDescriptor())
|
||||
{
|
||||
throw new ClientSimException("Cannot get spawn point when there is no scene descriptor.");
|
||||
}
|
||||
|
||||
if (index < 0 || index >= _descriptor.spawns.Length || _descriptor.spawns[index] == null)
|
||||
{
|
||||
this.LogWarning($"Using spawn point 0 instead of {index}, which is invalid.");
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// Fallback to first spawn point
|
||||
return _descriptor.spawns[index];
|
||||
}
|
||||
|
||||
public float GetSpawnRadius()
|
||||
{
|
||||
if (!HasSceneDescriptor())
|
||||
{
|
||||
throw new ClientSimException("Cannot get spawn radius when there is no scene descriptor.");
|
||||
}
|
||||
|
||||
return _descriptor.spawnRadius;
|
||||
}
|
||||
|
||||
private void CopyCameraValues(Camera refCamera, Camera camera)
|
||||
{
|
||||
if (refCamera == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
camera.farClipPlane = refCamera.farClipPlane;
|
||||
camera.nearClipPlane = Mathf.Clamp(refCamera.nearClipPlane, 0.01f, 0.05f);
|
||||
camera.clearFlags = refCamera.clearFlags;
|
||||
camera.backgroundColor = refCamera.backgroundColor;
|
||||
camera.allowHDR = refCamera.allowHDR;
|
||||
|
||||
PostProcessLayer refPostProcessLayer = refCamera.GetComponent<PostProcessLayer>();
|
||||
if (refPostProcessLayer != null)
|
||||
{
|
||||
if (_isAndroid)
|
||||
{
|
||||
Debug.LogWarning("Post processing is not supported on Android");
|
||||
}
|
||||
else
|
||||
{
|
||||
// VRChatBug: VRChat ignores if the PostProcessingLayer is disabled on the reference camera and always
|
||||
// enables it for the player's camera.
|
||||
PostProcessLayer postProcessLayer = camera.gameObject.AddComponent<PostProcessLayer>();
|
||||
postProcessLayer.volumeLayer = refPostProcessLayer.volumeLayer;
|
||||
|
||||
postProcessLayer.volumeTrigger = refPostProcessLayer.volumeTrigger != refPostProcessLayer.transform
|
||||
? refPostProcessLayer.volumeTrigger
|
||||
: camera.transform;
|
||||
|
||||
// Use reflection to copy over resources : https://github.com/Unity-Technologies/PostProcessing/issues/467
|
||||
FieldInfo resourcesInfo = typeof(PostProcessLayer).GetField("m_Resources", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
PostProcessResources postProcessResources = resourcesInfo.GetValue(refPostProcessLayer) as PostProcessResources;
|
||||
postProcessLayer.Init(postProcessResources);
|
||||
}
|
||||
}
|
||||
refCamera.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1763d0603be7460583cf4e71634eea83
|
||||
timeCreated: 1638892281
|
||||
@ -0,0 +1,28 @@
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public class ClientSimSessionState : IClientSimSessionState
|
||||
{
|
||||
private const string SESSION_KEY_PREFIX = "com.vrchat.clientsim.session";
|
||||
|
||||
private string GetSessionKey(string key)
|
||||
{
|
||||
return $"{SESSION_KEY_PREFIX}.{key}";
|
||||
}
|
||||
|
||||
public bool GetBool(string key)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
return UnityEditor.SessionState.GetBool(GetSessionKey(key), false);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void SetBool(string key, bool value)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.SessionState.SetBool(GetSessionKey(key), value);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f545fcbb2b04e7480e53fd98ae6fbed
|
||||
timeCreated: 1643144734
|
||||
@ -0,0 +1,246 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using VRC.Udon.Common.Interfaces;
|
||||
using VRC.Economy;
|
||||
using VRC.Udon;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UIElements;
|
||||
using VRC.Economy.Editor;
|
||||
#endif
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public class ClientSimStoreManager : IDisposable
|
||||
{
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private static IClientSimUdonEventSender _udonEventSender;
|
||||
private static IClientSimPlayerManager _playerManager;
|
||||
|
||||
public static List<IProduct> WorldProducts;
|
||||
public static Dictionary<string, IProduct> OwnedProducts;
|
||||
|
||||
public ClientSimStoreManager(IClientSimEventDispatcher eventDispatcher, IClientSimUdonEventSender udonEventSender, IClientSimPlayerManager playerManager)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_udonEventSender = udonEventSender;
|
||||
_playerManager = playerManager;
|
||||
|
||||
Subscribe();
|
||||
|
||||
WorldProducts = new List<IProduct>();
|
||||
OwnedProducts = new Dictionary<string, IProduct>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Fetch UdonProducts scriptable objects to add to WorldProducts list
|
||||
UdonProduct[] products = Resources.FindObjectsOfTypeAll<UdonProduct>();
|
||||
if (products.Length != 0)
|
||||
{
|
||||
Dictionary<string, UdonProduct> productsDict = new Dictionary<string, UdonProduct>();
|
||||
foreach (UdonProduct prod in products)
|
||||
{
|
||||
productsDict.Add(AssetDatabase.GetAssetPath(prod), prod);
|
||||
}
|
||||
|
||||
var dependencies = AssetDatabase.GetDependencies(SceneManager.GetActiveScene().path);
|
||||
foreach (string s in dependencies)
|
||||
{
|
||||
productsDict.TryGetValue(s, out UdonProduct prod);
|
||||
if (prod != null) WorldProducts.Add(prod);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
~ClientSimStoreManager()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void Subscribe()
|
||||
{
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
|
||||
WorldProducts.Clear();
|
||||
OwnedProducts.Clear();
|
||||
}
|
||||
|
||||
private void OnPlayerJoined(ClientSimOnPlayerJoinedEvent joinEvent)
|
||||
{
|
||||
if (joinEvent.player.isLocal)
|
||||
{
|
||||
if (WorldProducts.Count == 0) return;
|
||||
List<UdonBehaviour> udonBehaviours = UdonManager.Instance.GetUdonBehavioursInScene();
|
||||
foreach (UdonProduct p in WorldProducts)
|
||||
{
|
||||
if (p.Purchased)
|
||||
{
|
||||
UdonProductStub stub = new UdonProductStub(p, _playerManager.LocalPlayer());
|
||||
OwnedProducts.Add(p.ID, stub);
|
||||
foreach (UdonBehaviour ub in udonBehaviours)
|
||||
{
|
||||
ub.RunEvent("_onPurchaseConfirmed", ("result", stub), ("player", _playerManager.LocalPlayer()), ("purchasedNow", false));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (UdonBehaviour ub in UdonManager.Instance.GetUdonBehavioursInScene())
|
||||
{
|
||||
ub.RunEvent("_onPurchasesLoaded", ("result", OwnedProducts.Values.ToArray()), ("player", _playerManager.LocalPlayer()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// This section hooks into the UdonProductEditor.OnDrawProductGUI event
|
||||
// to modify the custom ScriptableObject editor if the user is running ClientSim
|
||||
[InitializeOnLoadMethod]
|
||||
public static void ModifyUdonProductGUI()
|
||||
{
|
||||
UdonProductEditor.OnDrawProductGUI += OnDrawProductGUI;
|
||||
UdonProductElement.OnCustomGUI += OnDrawProductElementGUI;
|
||||
}
|
||||
public static event EventHandler<UdonProduct> RefreshProductElementButton;
|
||||
private const string stringExpireProduct = "Expire product";
|
||||
private const string stringPurchaseProduct = "Purchase product";
|
||||
private const string stringPurchaseTooltip = "This product (if used in any UdonBehaviours in the scene) will act as if it has already been bought prior to launching playmode, firing the OnPurchaseConfirmed & OnPurchasesLoaded events accordingly.";
|
||||
private const string stringNotUsedInScene = "This product is not used in this scene and therefore cannot be bought or expired.";
|
||||
|
||||
private static bool IsProductUsedInScene(UdonProduct product)
|
||||
{
|
||||
return product != null && WorldProducts != null && WorldProducts.Contains(product);
|
||||
}
|
||||
|
||||
private static void OnDrawProductElementGUI(object sender, VisualElement ve)
|
||||
{
|
||||
UdonProductElement element = (UdonProductElement)sender;
|
||||
UdonProduct product = element.UdonProduct;
|
||||
ve.style.display = DisplayStyle.Flex;
|
||||
ve.SetEnabled(true);
|
||||
|
||||
EditorApplication.playModeStateChanged += change =>
|
||||
{
|
||||
RefreshProductElementButton?.Invoke(sender, product);
|
||||
};
|
||||
|
||||
Button button = new Button();
|
||||
button.text = product.Purchased ? stringExpireProduct : stringPurchaseProduct;
|
||||
button.tooltip = product.Purchased ? stringPurchaseTooltip : string.Empty;
|
||||
button.clicked += () =>
|
||||
{
|
||||
product.Purchased = !product.Purchased;
|
||||
if (EditorApplication.isPlaying && IsProductUsedInScene(product))
|
||||
{
|
||||
if (product.Purchased) PurchaseConfirmed(product, true);
|
||||
else PurchaseExpired(product);
|
||||
}
|
||||
RefreshProductElementButton?.Invoke(sender, product);
|
||||
};
|
||||
ve.Add(button);
|
||||
|
||||
RefreshProductElementButton += (o, args) =>
|
||||
{
|
||||
if (args != product) return;
|
||||
button.text = product.Purchased ? stringExpireProduct : stringPurchaseProduct;
|
||||
if(product.Purchased) UdonProductsManagerWindow.ShowInfoBox(UdonProductsManagerWindow.InfoBoxStatus.Info, stringPurchaseTooltip);
|
||||
button.tooltip = product.Purchased ? stringPurchaseTooltip : string.Empty;
|
||||
ve.SetEnabled(!EditorApplication.isPlaying || IsProductUsedInScene(product));
|
||||
};
|
||||
}
|
||||
|
||||
private static void OnDrawProductGUI(object sender, UdonProduct e)
|
||||
{
|
||||
bool isUsedInScene = e != null && WorldProducts != null && EditorApplication.isPlaying && WorldProducts.Contains(e);
|
||||
|
||||
GUILayout.Label("ClientSim functions", EditorStyles.boldLabel);
|
||||
using (new EditorGUI.DisabledGroupScope(EditorApplication.isPlaying && !isUsedInScene))
|
||||
{
|
||||
if (GUILayout.Button(e.Purchased ? stringExpireProduct : stringPurchaseProduct))
|
||||
{
|
||||
e.Purchased = !e.Purchased;
|
||||
if (EditorApplication.isPlaying && isUsedInScene)
|
||||
{
|
||||
if (e.Purchased) PurchaseConfirmed(e, true);
|
||||
else PurchaseExpired(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!EditorApplication.isPlaying && e.Purchased)
|
||||
EditorGUILayout.HelpBox(stringPurchaseTooltip, MessageType.Info);
|
||||
if (EditorApplication.isPlaying && !isUsedInScene) EditorGUILayout.HelpBox(stringNotUsedInScene, MessageType.Warning);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public static void PurchaseConfirmed(IProduct product, bool purchasedNow)
|
||||
{
|
||||
UdonProductStub stub = new UdonProductStub(product, _playerManager.LocalPlayer());
|
||||
OwnedProducts.Add(product.ID, stub);
|
||||
foreach (UdonBehaviour ub in UdonManager.Instance.GetUdonBehavioursInScene())
|
||||
{
|
||||
ub.RunEvent("_onPurchaseConfirmed", ("result", stub), ("player", _playerManager.LocalPlayer()), ("purchasedNow", purchasedNow));
|
||||
}
|
||||
}
|
||||
|
||||
public static void PurchaseExpired(IProduct product)
|
||||
{
|
||||
IProduct ownedProduct = OwnedProducts[product.ID];
|
||||
OwnedProducts.Remove(product.ID);
|
||||
_udonEventSender.RunEvent("_onPurchaseExpired", ("result", ownedProduct), ("player", _playerManager.LocalPlayer()));
|
||||
}
|
||||
|
||||
public static void SendProductEvent(IUdonEventReceiver behaviour, IProduct product)
|
||||
{
|
||||
if (OwnedProducts.ContainsKey(product.ID))
|
||||
{
|
||||
behaviour.RunEvent("_onProductEvent", ("result", OwnedProducts[product.ID]), ("player", OwnedProducts[product.ID].Buyer));
|
||||
}
|
||||
}
|
||||
|
||||
public static void ListPurchases(IUdonEventReceiver behaviour, VRC.SDKBase.VRCPlayerApi player)
|
||||
{
|
||||
behaviour.RunEvent("_onListPurchases", ("result", OwnedProducts.Values.ToArray()), ("player", _playerManager.LocalPlayer()));
|
||||
}
|
||||
|
||||
public static void ListAvailableProducts(IUdonEventReceiver behaviour)
|
||||
{
|
||||
behaviour.RunEvent("_onListAvailableProducts", ("result", WorldProducts.ToArray()));
|
||||
}
|
||||
|
||||
public static bool DoesPlayerOwnProduct(VRC.SDKBase.VRCPlayerApi player, IProduct product)
|
||||
{
|
||||
return OwnedProducts.ContainsKey(product.ID);
|
||||
}
|
||||
|
||||
public static bool DoesAnyPlayerOwnProduct(IProduct product)
|
||||
{
|
||||
return OwnedProducts.ContainsKey(product.ID);
|
||||
}
|
||||
|
||||
public static VRC.SDKBase.VRCPlayerApi[] GetPlayersWhoOwnProduct(IProduct product)
|
||||
{
|
||||
VRC.SDKBase.VRCPlayerApi[] playerApis;
|
||||
if (OwnedProducts.ContainsKey(product.ID))
|
||||
{
|
||||
playerApis = new VRC.SDKBase.VRCPlayerApi[1] { _playerManager.LocalPlayer() };
|
||||
}
|
||||
else
|
||||
{
|
||||
playerApis = Array.Empty<VRC.SDKBase.VRCPlayerApi>();
|
||||
}
|
||||
return playerApis;
|
||||
}
|
||||
|
||||
public static void ListProductOwners(IUdonEventReceiver behaviour, IProduct product)
|
||||
{
|
||||
behaviour.RunEvent("_onListProductOwners", ("result", product), ("owners", new string[] {"VRCat", "Fred", "VRRat"}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d076b4f4b164393ac22400d6bd84118
|
||||
timeCreated: 1692235633
|
||||
@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Components;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// System that keeps track of all synced objects. Handles changing ownership when players leave
|
||||
/// and also checks for if position synced objects fall below the respawn height.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Listens to Events:
|
||||
/// - ClientSimOnPlayerLeftEvent
|
||||
/// </remarks>
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimSyncedObjectManager : ClientSimBehaviour, IClientSimSyncedObjectManager, IDisposable
|
||||
{
|
||||
// Collection of all synced objects initialized in the scene.
|
||||
private readonly ClientSimObjectCollection<IClientSimSyncable> _syncedObjects =
|
||||
new ClientSimObjectCollection<IClientSimSyncable>();
|
||||
|
||||
// Collection of all position synced objects initialized in the scene.
|
||||
private readonly ClientSimObjectCollection<IClientSimPositionSyncable> _positionSyncedObjects =
|
||||
new ClientSimObjectCollection<IClientSimPositionSyncable>();
|
||||
|
||||
// Collection of all synced playerObjects initialized in the scene.
|
||||
private readonly ClientSimObjectCollection<IClientSimSyncable> _playerObjects =
|
||||
new ClientSimObjectCollection<IClientSimSyncable>();
|
||||
|
||||
// TODO add generic system for saving synced data and restoring it to simulate late joining into a world.
|
||||
// TODO add system to manage udon synced data
|
||||
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private IClientSimSceneManager _sceneManager;
|
||||
private IClientSimPlayerManager _playerManager;
|
||||
|
||||
public void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimSceneManager sceneManager,
|
||||
IClientSimPlayerManager playerManager)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_sceneManager = sceneManager;
|
||||
_playerManager = playerManager;
|
||||
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerLeftEvent>(OnPlayerLeft);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_eventDispatcher?.Unsubscribe<ClientSimOnPlayerLeftEvent>(OnPlayerLeft);
|
||||
}
|
||||
|
||||
public void InitializeObjectSync(VRCObjectSync sync)
|
||||
{
|
||||
// Only allow one object sync helper per object.
|
||||
if (sync.TryGetComponent(out ClientSimObjectSyncHelper syncHelper))
|
||||
{
|
||||
syncHelper.Initialize(sync,this);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientSimObjectSyncHelper helper = sync.gameObject.AddComponent<ClientSimObjectSyncHelper>();
|
||||
helper.Initialize(sync,this);
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializeObjectPool(VRCObjectPool objectPool)
|
||||
{
|
||||
objectPool.gameObject.AddComponent<ClientSimObjectPoolHelper>().Initialize(objectPool, this);
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
ProcessPositionSyncedObjects();
|
||||
}
|
||||
|
||||
private void ProcessPositionSyncedObjects()
|
||||
{
|
||||
_positionSyncedObjects.ProcessAddedAndRemovedObjects();
|
||||
|
||||
// TODO space this out so that there are only x number per frame instead of all every time
|
||||
List<GameObject> objsToDestroy = new List<GameObject>();
|
||||
foreach (IClientSimPositionSyncable sync in _positionSyncedObjects.GetObjects())
|
||||
{
|
||||
if (sync == null)
|
||||
{
|
||||
_positionSyncedObjects.ShouldVerifyObjects();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sync.SyncPosition)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// VRChatBug: The following method will enforce users to use VRCObjectSync's methods for setting
|
||||
// useGravity and isKinematic, but the current SDK does not support the hook to allow this.
|
||||
// Verify Sync properties, eg check if useGravity and isKinematic is properly set.
|
||||
// sync.UpdatePositionSync();
|
||||
|
||||
// Verify if the object is below respawn.
|
||||
Transform syncTransform = sync.GetTransform();
|
||||
if (syncTransform.position.y < _sceneManager.GetRespawnHeight())
|
||||
{
|
||||
if (_sceneManager.ShouldObjectsDestroyAtRespawnHeight())
|
||||
{
|
||||
objsToDestroy.Add(syncTransform.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
sync.Respawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var obj in objsToDestroy)
|
||||
{
|
||||
Destroy(obj);
|
||||
}
|
||||
}
|
||||
|
||||
#region ClientSim Events
|
||||
|
||||
// Handle updating object ownership for all objects the leaving player previously owned.
|
||||
private void OnPlayerLeft(ClientSimOnPlayerLeftEvent leftEvent)
|
||||
{
|
||||
VRCPlayerApi leftPlayer = leftEvent.player;
|
||||
int leftPlayerId = leftPlayer.playerId;
|
||||
VRCPlayerApi masterPlayer = _playerManager.GetMaster();
|
||||
if (masterPlayer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_syncedObjects.ProcessAddedAndRemovedObjects();
|
||||
foreach (IClientSimSyncable sync in _syncedObjects.GetObjects())
|
||||
{
|
||||
if (sync == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sync is Component syncComp)
|
||||
{
|
||||
if(syncComp == null) continue;
|
||||
|
||||
GameObject syncObj = syncComp.gameObject;
|
||||
if (Networking.GetOwner(syncObj)?.playerId == leftPlayerId)
|
||||
{
|
||||
Networking.SetOwner(masterPlayer, syncObj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sync.GetOwner() == leftPlayerId)
|
||||
{
|
||||
sync.SetOwner(masterPlayer.playerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IClientSimSyncedObjectManager
|
||||
|
||||
public void AddSyncedObject(IClientSimSyncable sync)
|
||||
{
|
||||
_syncedObjects.AddObject(sync);
|
||||
_syncedObjects.ProcessAddedAndRemovedObjects();
|
||||
|
||||
if (sync is IClientSimPositionSyncable posSync && posSync.SyncPosition)
|
||||
{
|
||||
_positionSyncedObjects.AddObject(posSync);
|
||||
_positionSyncedObjects.ProcessAddedAndRemovedObjects();
|
||||
}
|
||||
|
||||
if((sync as MonoBehaviour).GetComponentInParent<VRCPlayerObject>() != null)
|
||||
{
|
||||
_playerObjects.AddObject(sync);
|
||||
_playerObjects.ProcessAddedAndRemovedObjects();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveSyncedObject(IClientSimSyncable sync)
|
||||
{
|
||||
_syncedObjects.RemoveObject(sync);
|
||||
_syncedObjects.ProcessAddedAndRemovedObjects();
|
||||
|
||||
if (sync is IClientSimPositionSyncable posSync && posSync.SyncPosition)
|
||||
{
|
||||
_positionSyncedObjects.RemoveObject(posSync);
|
||||
_positionSyncedObjects.ProcessAddedAndRemovedObjects();
|
||||
}
|
||||
|
||||
if((sync as MonoBehaviour).GetComponentInParent<VRCPlayerObject>() != null)
|
||||
{
|
||||
_playerObjects.RemoveObject(sync);
|
||||
_playerObjects.ProcessAddedAndRemovedObjects();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0bd9a5f625c4c04bd9862b62b57f431
|
||||
timeCreated: 1638897183
|
||||
@ -0,0 +1,84 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimTooltip : ClientSimBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private TextMesh tooltipText;
|
||||
|
||||
public IClientSimInteractable Interactable { get; private set; }
|
||||
|
||||
// Both Udon Interacts and Pickups display tooltips at the same location.
|
||||
// Moved here as a helper method to not repeat code.
|
||||
// Tooltip is displayed at the top bound of the object's first collider.
|
||||
public static Vector3 GetToolTipPosition(GameObject obj)
|
||||
{
|
||||
// VRChatBug: Tooltips always ignore the tooltipPlacement transform and instead place the tooltip at the top
|
||||
// of the first collider on the object.
|
||||
Collider interactCollider = obj.GetComponent<Collider>();
|
||||
|
||||
if (interactCollider == null)
|
||||
{
|
||||
return obj.transform.position;
|
||||
}
|
||||
|
||||
// Note due to colliders not updating their bounds property until after a physics update, to prevent extra
|
||||
// calls to Physics.SyncTransforms, the following code approximates the updated center and top extends
|
||||
// position based on the type of collider.
|
||||
|
||||
Transform transform = obj.transform;
|
||||
Vector3 position = transform.position;
|
||||
Quaternion rotation = transform.rotation;
|
||||
Vector3 scale = transform.lossyScale;
|
||||
|
||||
Vector3 colliderCenter = Vector3.zero;
|
||||
if (interactCollider is BoxCollider boxCollider)
|
||||
{
|
||||
colliderCenter = boxCollider.center;
|
||||
}
|
||||
else if (interactCollider is SphereCollider sphereCollider)
|
||||
{
|
||||
colliderCenter = sphereCollider.center;
|
||||
}
|
||||
else if (interactCollider is CapsuleCollider capsuleCollider)
|
||||
{
|
||||
colliderCenter = capsuleCollider.center;
|
||||
}
|
||||
else if (interactCollider is MeshCollider meshCollider)
|
||||
{
|
||||
colliderCenter = meshCollider.sharedMesh.bounds.center;
|
||||
}
|
||||
|
||||
position += rotation * Vector3.Scale(colliderCenter, scale);
|
||||
return position + new Vector3(0, interactCollider.bounds.extents.y, 0);
|
||||
}
|
||||
|
||||
public void UpdateTooltip(Vector3 playerPos, Vector3 up)
|
||||
{
|
||||
Vector3 position = Interactable.GetInteractTextPlacement();
|
||||
|
||||
// Rotate to look towards the player while keeping the proper up direction.
|
||||
// VRChatBug: Build 1160 has this broken again so that rotating the player through stations does not properly rotate tooltips.
|
||||
Quaternion rotation = Quaternion.LookRotation(Vector3.ProjectOnPlane(playerPos - position, up), up);
|
||||
transform.SetPositionAndRotation(position, rotation);
|
||||
}
|
||||
|
||||
public void EnableTooltip(IClientSimInteractable interactable)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
Interactable = interactable;
|
||||
tooltipText.text = Interactable.GetInteractText();
|
||||
}
|
||||
|
||||
public void DisableTooltip()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
Interactable = null;
|
||||
tooltipText.text = "";
|
||||
|
||||
transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99cce027a08452740befb63a4966f0f9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,105 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// Display a tooltip above an interactable.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]
|
||||
[DefaultExecutionOrder(30001)] // Ensure that all tooltip positions are updated at the end of frame.
|
||||
public class ClientSimTooltipManager : ClientSimBehaviour, IClientSimTooltipManager
|
||||
{
|
||||
[SerializeField]
|
||||
private GameObject tooltipPrefab;
|
||||
|
||||
private IClientSimTrackingProvider _trackingProvider;
|
||||
private ClientSimSettings _settings;
|
||||
|
||||
private readonly Queue<ClientSimTooltip> _unusedTooltips = new Queue<ClientSimTooltip>();
|
||||
private readonly List<ClientSimTooltip> _displayedTooltips = new List<ClientSimTooltip>();
|
||||
|
||||
public void Initialize(ClientSimSettings settings, IClientSimTrackingProvider trackingProvider)
|
||||
{
|
||||
_trackingProvider = trackingProvider;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
// TODO expand this to allow passing in any object to display tooltips
|
||||
// (Allow for pickup use text without changing the interactable since the other hand still needs the original text)
|
||||
public void DisplayTooltip(IClientSimInteractable interact)
|
||||
{
|
||||
if (!_settings.showTooltips)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClientSimTooltip tooltip = GetUnusedTooltip();
|
||||
tooltip.EnableTooltip(interact);
|
||||
_displayedTooltips.Add(tooltip);
|
||||
}
|
||||
|
||||
public void DisableTooltip(IClientSimInteractable interact)
|
||||
{
|
||||
// Loop through list backwards to ensure removing doesn't change the order.
|
||||
for (int cur = _displayedTooltips.Count - 1; cur >= 0; --cur)
|
||||
{
|
||||
ClientSimTooltip tooltip = _displayedTooltips[cur];
|
||||
if (tooltip.Interactable == interact)
|
||||
{
|
||||
_displayedTooltips.RemoveAt(cur);
|
||||
DisableTooltip(tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ClientSimTooltip GetUnusedTooltip()
|
||||
{
|
||||
if (_unusedTooltips.Count == 0)
|
||||
{
|
||||
GameObject tooltipObj = Instantiate(tooltipPrefab, transform);
|
||||
return tooltipObj.GetComponent<ClientSimTooltip>();
|
||||
}
|
||||
|
||||
return _unusedTooltips.Dequeue();
|
||||
}
|
||||
|
||||
private void DisableTooltip(ClientSimTooltip tooltip)
|
||||
{
|
||||
tooltip.DisableTooltip();
|
||||
_unusedTooltips.Enqueue(tooltip);
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (_trackingProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Transform playerHead = _trackingProvider.GetTrackingTransform(VRCPlayerApi.TrackingDataType.Head);
|
||||
Vector3 playerPos = playerHead.position;
|
||||
Vector3 playerUp = playerHead.up;
|
||||
// Use playspace up to prevent billboard effect in vr.
|
||||
if (_trackingProvider.IsVR())
|
||||
{
|
||||
playerUp = _trackingProvider.GetTrackingTransform(VRCPlayerApi.TrackingDataType.Origin).up;
|
||||
}
|
||||
|
||||
for (int cur = _displayedTooltips.Count - 1; cur >= 0; --cur)
|
||||
{
|
||||
ClientSimTooltip tooltip = _displayedTooltips[cur];
|
||||
|
||||
// Remove tooltips with destroyed interactables.
|
||||
if (tooltip.Interactable == null)
|
||||
{
|
||||
_displayedTooltips.RemoveAt(cur);
|
||||
DisableTooltip(tooltip);
|
||||
continue;
|
||||
}
|
||||
|
||||
tooltip.UpdateTooltip(playerPos, playerUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3368d7496423784180eac62c9b35e56
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,163 @@
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon;
|
||||
using VRC.Udon.Common.Interfaces;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// System to hold all the current initialized Udon in the scene
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Listens to Events:
|
||||
/// - ClientSimOnPlayerJoinedEvent
|
||||
/// - ClientSimOnPlayerLeftEvent
|
||||
/// - ClientSimOnPlayerRespawnEvent
|
||||
/// </remarks>
|
||||
public class ClientSimUdonManager : IClientSimUdonManager, IDisposable
|
||||
{
|
||||
private IClientSimEventDispatcher _eventDispatcher;
|
||||
private IClientSimSyncedObjectManager _syncedObjectManager;
|
||||
private IClientSimUdonEventSender _udonEventSender;
|
||||
|
||||
private readonly ClientSimObjectCollection<UdonBehaviour> _udonBehaviours =
|
||||
new ClientSimObjectCollection<UdonBehaviour>();
|
||||
|
||||
private bool _isReady = false;
|
||||
|
||||
public ClientSimUdonManager(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimSyncedObjectManager syncedObjectManager,
|
||||
IClientSimUdonEventSender udonEventSender)
|
||||
{
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_syncedObjectManager = syncedObjectManager;
|
||||
_udonEventSender = udonEventSender;
|
||||
|
||||
Subscribe();
|
||||
}
|
||||
|
||||
~ClientSimUdonManager()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void Subscribe()
|
||||
{
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerLeftEvent>(OnPlayerLeft);
|
||||
_eventDispatcher.Subscribe<ClientSimOnPlayerRespawnEvent>(OnPlayerRespawn);
|
||||
_eventDispatcher.Subscribe<ClientSimScreenUpdateEvent>(OnScreenUpdate);
|
||||
_eventDispatcher.Subscribe<ClientSimOnVRCPlusMassGift>(OnVRCPlusMassGift);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnPlayerLeftEvent>(OnPlayerLeft);
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnPlayerRespawnEvent>(OnPlayerRespawn);
|
||||
_eventDispatcher.Unsubscribe<ClientSimScreenUpdateEvent>(OnScreenUpdate);
|
||||
_eventDispatcher.Unsubscribe<ClientSimOnVRCPlusMassGift>(OnVRCPlusMassGift);
|
||||
}
|
||||
|
||||
public void InitUdon(UdonBehaviour behaviour, IUdonProgram program)
|
||||
{
|
||||
ClientSimUdonHelper[] helpers = behaviour.gameObject.GetComponents<ClientSimUdonHelper>();
|
||||
|
||||
foreach (ClientSimUdonHelper helper in helpers)
|
||||
{
|
||||
if(helper.GetUdonBehaviour() == behaviour)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ClientSimUdonHelper helperAdded = behaviour.gameObject.AddComponent<ClientSimUdonHelper>();
|
||||
helperAdded.Initialize(behaviour, this, _syncedObjectManager, _isReady);
|
||||
|
||||
}
|
||||
|
||||
public IEnumerator OnClientSimReady()
|
||||
{
|
||||
_isReady = true;
|
||||
|
||||
_udonBehaviours.ProcessAddedAndRemovedObjects();
|
||||
HashSet<GameObject> objs = new HashSet<GameObject>();
|
||||
foreach (var udonBehavior in _udonBehaviours.GetObjects())
|
||||
{
|
||||
if (udonBehavior == null || objs.Contains(udonBehavior.gameObject))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
objs.Add(udonBehavior.gameObject);
|
||||
|
||||
foreach (var helper in udonBehavior.GetComponents<ClientSimUdonHelper>())
|
||||
{
|
||||
try
|
||||
{
|
||||
helper.OnReady();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.LogError($"{e.Message}\n{e.StackTrace}");
|
||||
this.LogWarning($"Failed to set ready for object: {Tools.GetGameObjectPath(helper.gameObject)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait one frame for all active UdonBehaviours to properly run start.
|
||||
yield return null;
|
||||
}
|
||||
|
||||
#region ClientSimEvent handling
|
||||
|
||||
private void OnPlayerJoined(ClientSimOnPlayerJoinedEvent joinEvent)
|
||||
{
|
||||
_udonEventSender.RunEvent(UdonManager.UDON_EVENT_ONINPUTMETHODCHANGED, ("inputMethod", VRCInputMethod.Keyboard));
|
||||
_udonEventSender.RunEvent(UdonManager.UDON_EVENT_ONLANGUAGECHANGED, ("language", ClientSimSettings.Instance.currentLanguage));
|
||||
_udonEventSender.RunEvent("_onPlayerJoined", ("player", joinEvent.player));
|
||||
}
|
||||
|
||||
private void OnPlayerLeft(ClientSimOnPlayerLeftEvent leftEvent)
|
||||
{
|
||||
_udonEventSender.RunEvent("_onPlayerLeft", ("player", leftEvent.player));
|
||||
}
|
||||
|
||||
private void OnPlayerRespawn(ClientSimOnPlayerRespawnEvent respawnEvent)
|
||||
{
|
||||
_udonEventSender.RunEvent("_onPlayerRespawn", ("player", respawnEvent.player));
|
||||
}
|
||||
|
||||
private void OnScreenUpdate(ClientSimScreenUpdateEvent screenUpdateEvent)
|
||||
{
|
||||
_udonEventSender.RunEvent(UdonManager.UDON_EVENT_ONSCREENUPDATE, ("data", screenUpdateEvent.data));
|
||||
}
|
||||
|
||||
private void OnVRCPlusMassGift(ClientSimOnVRCPlusMassGift giftEvent)
|
||||
{
|
||||
_udonEventSender.RunEvent(UdonManager.UDON_EVENT_ONVRCPLUSMASSGIFT,
|
||||
("gifter", giftEvent.gifter),
|
||||
("numGifts", giftEvent.numGifts));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IClientSimUdonManager
|
||||
|
||||
public void AddUdonBehaviour(UdonBehaviour udonBehaviour)
|
||||
{
|
||||
_udonBehaviours.AddObject(udonBehaviour);
|
||||
}
|
||||
|
||||
public void RemoveUdonBehaviour(UdonBehaviour udonBehaviour)
|
||||
{
|
||||
_udonBehaviours.RemoveObject(udonBehaviour);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 340d1d7e8555a8643b0ab107b14e2a78
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,19 @@
|
||||
using VRC.Udon;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public class ClientSimUdonManagerEventSender : IClientSimUdonEventSender
|
||||
{
|
||||
private readonly UdonManager _udonManager;
|
||||
|
||||
public ClientSimUdonManagerEventSender(UdonManager udonManager)
|
||||
{
|
||||
_udonManager = udonManager;
|
||||
}
|
||||
|
||||
public void RunEvent(string eventName, params (string, object)[] programVariables)
|
||||
{
|
||||
_udonManager.RunEvent(eventName, programVariables);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39cba4c4dd6f4c9686a240bd330a777c
|
||||
timeCreated: 1640270545
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e3fb64cb2751c84fafc391a3fd5c06f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimBlacklistManager
|
||||
{
|
||||
void AddObjectAndChildrenToBlackList(GameObject obj);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7e78ffeb0b3def46a3a33bcb52b1ccc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,10 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimHighlightManager
|
||||
{
|
||||
void EnableObjectHighlight(GameObject obj);
|
||||
void DisableObjectHighlight(GameObject obj);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f56beeb09982c24c9b7164de3faccc4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimInteractiveLayerProvider
|
||||
{
|
||||
LayerMask GetInteractiveLayers();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c004ca50a1e6fdf478a55c1a4e619f98
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,10 @@
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimMousePositionProvider
|
||||
{
|
||||
Vector2 GetMousePosition();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61b8e9d915d2b1245875c146dfa8ab96
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimPlayerHeightManager : IDisposable
|
||||
{
|
||||
bool GetManualAvatarScalingAllowed();
|
||||
float GetAvatarEyeHeightMinimumAsMeters();
|
||||
float GetAvatarEyeHeightMaximumAsMeters();
|
||||
float GetAvatarEyeHeightAsMeters();
|
||||
float GetAvatarEyeHeightAsMetersClamped();
|
||||
void SetManualAvatarScalingAllowed(bool value);
|
||||
void SetAvatarEyeHeightMinimumByMeters(float value);
|
||||
void SetAvatarEyeHeightMaximumByMeters(float value);
|
||||
void SetAvatarEyeHeightByMeters(float newHeight, bool isManual = false);
|
||||
void SetAvatarEyeHeightByMultiplier(float multiplier);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff98d79f6c0f40e2a46e3102881d394b
|
||||
timeCreated: 1706291260
|
||||
@ -0,0 +1,25 @@
|
||||
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Components;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimPlayerManager
|
||||
{
|
||||
VRCPlayerApi CreateNewPlayer(bool local, ClientSimPlayer player, string name = null);
|
||||
void RemovePlayer(VRCPlayerApi player);
|
||||
|
||||
int GetMasterID();
|
||||
VRCPlayerApi GetMaster();
|
||||
VRCPlayerApi LocalPlayer();
|
||||
VRCPlayerApi GetPlayerByID(int playerID);
|
||||
int GetPlayerID(VRCPlayerApi player);
|
||||
bool IsMaster(VRCPlayerApi player);
|
||||
bool IsInstanceOwner(VRCPlayerApi player);
|
||||
bool IsInstanceOwner();
|
||||
bool IsLocalPlayerMaster();
|
||||
VRCPlayerApi GetOwner(GameObject obj);
|
||||
bool IsOwner(VRCPlayerApi player, GameObject obj);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c93456661823b2a4dbab7894ba0c027e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimProxyObjectProvider
|
||||
{
|
||||
Transform CameraProxy();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c782b965c37d2f47a0f17914838fc16
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimSceneManager
|
||||
{
|
||||
bool HasSceneDescriptor();
|
||||
Transform GetSpawnPoint(bool remote);
|
||||
Transform GetSpawnPoint(int index);
|
||||
float GetSpawnRadius();
|
||||
void SetupCamera(Camera camera);
|
||||
float GetRespawnHeight();
|
||||
bool ShouldObjectsDestroyAtRespawnHeight();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7243937d85f17c34ebb98ba8023553a2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimSessionState
|
||||
{
|
||||
bool GetBool(string key);
|
||||
void SetBool(string key, bool value);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b70f8e88d954ae397f2fd3f09c9ac49
|
||||
timeCreated: 1643145513
|
||||
@ -0,0 +1,13 @@
|
||||
using VRC.SDK3.Components;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimSyncedObjectManager
|
||||
{
|
||||
void AddSyncedObject(IClientSimSyncable sync);
|
||||
void RemoveSyncedObject(IClientSimSyncable sync);
|
||||
|
||||
void InitializeObjectSync(VRCObjectSync sync);
|
||||
void InitializeObjectPool(VRCObjectPool objectPool);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46580ba70cfd92e479b924bd1a469c07
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,9 @@
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimTooltipManager
|
||||
{
|
||||
void DisplayTooltip(IClientSimInteractable interact);
|
||||
void DisableTooltip(IClientSimInteractable interact);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b94475b7722141843ba4e3e8795ab65f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,11 @@
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper for sending events to all udon programs.
|
||||
/// Helps in tests without directly referencing UdonManager.
|
||||
/// </summary>
|
||||
public interface IClientSimUdonEventSender
|
||||
{
|
||||
void RunEvent(string eventName, params (string, object)[] programVariables);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7dc60a559e4c412db2226c17f35e1a66
|
||||
timeCreated: 1640270507
|
||||
@ -0,0 +1,10 @@
|
||||
using VRC.Udon;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public interface IClientSimUdonManager
|
||||
{
|
||||
void AddUdonBehaviour(UdonBehaviour udon);
|
||||
void RemoveUdonBehaviour(UdonBehaviour udon);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60d81ad6ae86c9d4db21f724007b16cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user