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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user