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