Files
2026-06-07 16:58:24 +01:00

123 lines
4.1 KiB
C#

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;
}
}
}