UI 자동화를 위해 바인딩 기능 구현
- 유니티 에셋 인증 오류로 meta 재생성
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
#if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
#endif
|
||||
|
||||
////TODO: custom icon for OnScreenButton component
|
||||
|
||||
namespace UnityEngine.InputSystem.OnScreen
|
||||
{
|
||||
/// <summary>
|
||||
/// A button that is visually represented on-screen and triggered by touch or other pointer
|
||||
/// input.
|
||||
/// </summary>
|
||||
[AddComponentMenu("Input/On-Screen Button")]
|
||||
[HelpURL(InputSystem.kDocUrl + "/manual/OnScreen.html#on-screen-buttons")]
|
||||
public class OnScreenButton : OnScreenControl, IPointerDownHandler, IPointerUpHandler
|
||||
{
|
||||
public void OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
SendValueToControl(0.0f);
|
||||
}
|
||||
|
||||
public void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
SendValueToControl(1.0f);
|
||||
}
|
||||
|
||||
////TODO: pressure support
|
||||
/*
|
||||
/// <summary>
|
||||
/// If true, the button's value is driven from the pressure value of touch or pen input.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This essentially allows having trigger-like buttons as on-screen controls.
|
||||
/// </remarks>
|
||||
[SerializeField] private bool m_UsePressure;
|
||||
*/
|
||||
|
||||
[InputControl(layout = "Button")]
|
||||
[SerializeField]
|
||||
private string m_ControlPath;
|
||||
|
||||
protected override string controlPathInternal
|
||||
{
|
||||
get => m_ControlPath;
|
||||
set => m_ControlPath = value;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[UnityEditor.CustomEditor(typeof(OnScreenButton))]
|
||||
internal class OnScreenButtonEditor : UnityEditor.Editor
|
||||
{
|
||||
private UnityEditor.SerializedProperty m_ControlPathInternal;
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
m_ControlPathInternal = serializedObject.FindProperty(nameof(OnScreenButton.m_ControlPath));
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
new InputComponentEditorAnalytic(InputSystemComponent.OnScreenButton).Send();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
// Current implementation has UGUI dependencies (ISXB-915, ISXB-916)
|
||||
UGUIOnScreenControlEditorUtils.ShowWarningIfNotPartOfCanvasHierarchy((OnScreenButton)target);
|
||||
|
||||
UnityEditor.EditorGUILayout.PropertyField(m_ControlPathInternal);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d54531977ecb194c95e2d3aa7a5d72a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,369 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.Users;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
////REVIEW: should we make this ExecuteInEditMode?
|
||||
|
||||
////TODO: handle display strings for this in some form; shouldn't display generic gamepad binding strings, for example, for OSCs
|
||||
|
||||
////TODO: give more control over when an OSC creates a new devices; going simply by name of layout only is inflexible
|
||||
|
||||
////TODO: make this survive domain reloads
|
||||
|
||||
////TODO: allow feeding into more than one control
|
||||
|
||||
namespace UnityEngine.InputSystem.OnScreen
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for on-screen controls.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The set of on-screen controls together forms a device. A control layout
|
||||
/// is automatically generated from the set and a device using the layout is
|
||||
/// added to the system when the on-screen controls are enabled.
|
||||
///
|
||||
/// The layout that the generated layout is based on is determined by the
|
||||
/// control paths chosen for each on-screen control. If, for example, an
|
||||
/// on-screen control chooses the 'a' key from the "Keyboard" layout as its
|
||||
/// path, a device layout is generated that is based on the "Keyboard" layout
|
||||
/// and the on-screen control becomes the 'a' key in that layout.
|
||||
///
|
||||
/// If a <see cref="GameObject"/> has multiple on-screen controls that reference different
|
||||
/// types of device layouts (e.g. one control references 'buttonWest' on
|
||||
/// a gamepad and another references 'leftButton' on a mouse), then a device
|
||||
/// is created for each type referenced by the setup.
|
||||
///
|
||||
/// The <see cref="OnScreenControl"/> works by simulating events from the device specified in the <see cref="OnScreenControl.controlPath"/>
|
||||
/// property. Some parts of the Input System, such as the <see cref="PlayerInput"/> component, can be set up to
|
||||
/// auto-switch <see cref="PlayerInput.neverAutoSwitchControlSchemes"/> to a new device when input from them is detected.
|
||||
/// When a device is switched, any currently running inputs from the previously active device are cancelled.
|
||||
///
|
||||
/// To avoid this situation, you need to ensure, depending on your case, that the Mouse, Pen, Touchsceen and/or XRController devices are not used in a concurent
|
||||
/// control schemes of the simulated device.
|
||||
/// </remarks>
|
||||
public abstract class OnScreenControl : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The control path (see <see cref="InputControlPath"/>) for the control that the on-screen
|
||||
/// control will feed input into.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A device will be created from the device layout referenced by the control path (see
|
||||
/// <see cref="InputControlPath.TryGetDeviceLayout"/>). The path is then used to look up
|
||||
/// <see cref="control"/> on the device. The resulting control will be fed values from
|
||||
/// the on-screen control.
|
||||
///
|
||||
/// Multiple on-screen controls sharing the same device layout will together create a single
|
||||
/// virtual device. If, for example, one component uses <c>"<Gamepad>/buttonSouth"</c>
|
||||
/// and another uses <c>"<Gamepad>/leftStick"</c> as the control path, a single
|
||||
/// <see cref="Gamepad"/> will be created and the first component will feed data to
|
||||
/// <see cref="Gamepad.buttonSouth"/> and the second component will feed data to
|
||||
/// <see cref="Gamepad.leftStick"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="InputControlPath"/>
|
||||
public string controlPath
|
||||
{
|
||||
get => controlPathInternal;
|
||||
set
|
||||
{
|
||||
controlPathInternal = value;
|
||||
if (isActiveAndEnabled)
|
||||
SetupInputControl();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The actual control that is fed input from the on-screen control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only valid while the on-screen control is enabled. Otherwise, it is <c>null</c>. Also,
|
||||
/// if no <see cref="controlPath"/> has been set, this will remain <c>null</c> even if the component is enabled.
|
||||
/// </remarks>
|
||||
public InputControl control => m_Control;
|
||||
|
||||
private InputControl m_Control;
|
||||
private OnScreenControl m_NextControlOnDevice;
|
||||
private InputEventPtr m_InputEventPtr;
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for the <see cref="controlPath"/> of the component. Must be implemented by subclasses.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Moving the definition of how the control path is stored into subclasses allows them to
|
||||
/// apply their own <see cref="InputControlAttribute"/> attributes to them and thus set their
|
||||
/// own layout filters.
|
||||
/// </remarks>
|
||||
protected abstract string controlPathInternal { get; set; }
|
||||
|
||||
private void SetupInputControl()
|
||||
{
|
||||
Debug.Assert(m_Control == null, "InputControl already initialized");
|
||||
Debug.Assert(m_NextControlOnDevice == null, "Previous InputControl has not been properly uninitialized (m_NextControlOnDevice still set)");
|
||||
Debug.Assert(!m_InputEventPtr.valid, "Previous InputControl has not been properly uninitialized (m_InputEventPtr still set)");
|
||||
|
||||
// Nothing to do if we don't have a control path.
|
||||
var path = controlPathInternal;
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return;
|
||||
|
||||
// Determine what type of device to work with.
|
||||
var layoutName = InputControlPath.TryGetDeviceLayout(path);
|
||||
if (layoutName == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Cannot determine device layout to use based on control path '{path}' used in {GetType().Name} component",
|
||||
this);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to find existing on-screen device that matches.
|
||||
var internedLayoutName = new InternedString(layoutName);
|
||||
var deviceInfoIndex = -1;
|
||||
for (var i = 0; i < s_OnScreenDevices.length; ++i)
|
||||
{
|
||||
////FIXME: this does not take things such as different device usages into account
|
||||
if (s_OnScreenDevices[i].device.m_Layout == internedLayoutName)
|
||||
{
|
||||
deviceInfoIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have a matching one, create a new one.
|
||||
InputDevice device;
|
||||
if (deviceInfoIndex == -1)
|
||||
{
|
||||
// Try to create device.
|
||||
try
|
||||
{
|
||||
device = InputSystem.AddDevice(layoutName);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Could not create device with layout '{layoutName}' used in '{GetType().Name}' component");
|
||||
Debug.LogException(exception);
|
||||
return;
|
||||
}
|
||||
InputSystem.AddDeviceUsage(device, "OnScreen");
|
||||
|
||||
// Create event buffer.
|
||||
var buffer = StateEvent.From(device, out var eventPtr, Allocator.Persistent);
|
||||
|
||||
// Add to list.
|
||||
deviceInfoIndex = s_OnScreenDevices.Append(new OnScreenDeviceInfo
|
||||
{
|
||||
eventPtr = eventPtr,
|
||||
buffer = buffer,
|
||||
device = device,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
device = s_OnScreenDevices[deviceInfoIndex].device;
|
||||
}
|
||||
|
||||
// Try to find control on device.
|
||||
m_Control = InputControlPath.TryFindControl(device, path);
|
||||
if (m_Control == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Cannot find control with path '{path}' on device of type '{layoutName}' referenced by component '{GetType().Name}'",
|
||||
this);
|
||||
|
||||
// Remove the device, if we just created one.
|
||||
if (s_OnScreenDevices[deviceInfoIndex].firstControl == null)
|
||||
{
|
||||
s_OnScreenDevices[deviceInfoIndex].Destroy();
|
||||
s_OnScreenDevices.RemoveAt(deviceInfoIndex);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
m_InputEventPtr = s_OnScreenDevices[deviceInfoIndex].eventPtr;
|
||||
|
||||
// We have all we need. Permanently add us.
|
||||
s_OnScreenDevices[deviceInfoIndex] =
|
||||
s_OnScreenDevices[deviceInfoIndex].AddControl(this);
|
||||
}
|
||||
|
||||
protected void SendValueToControl<TValue>(TValue value)
|
||||
where TValue : struct
|
||||
{
|
||||
if (m_Control == null)
|
||||
return;
|
||||
|
||||
if (!(m_Control is InputControl<TValue> control))
|
||||
throw new ArgumentException(
|
||||
$"The control path {controlPath} yields a control of type {m_Control.GetType().Name} which is not an InputControl with value type {typeof(TValue).Name}", nameof(value));
|
||||
|
||||
////FIXME: this gives us a one-frame lag (use InputState.Change instead?)
|
||||
m_InputEventPtr.internalTime = InputRuntime.s_Instance.currentTime;
|
||||
control.WriteValueIntoEvent(value, m_InputEventPtr);
|
||||
InputSystem.QueueEvent(m_InputEventPtr);
|
||||
}
|
||||
|
||||
protected void SentDefaultValueToControl()
|
||||
{
|
||||
if (m_Control == null)
|
||||
return;
|
||||
|
||||
////FIXME: this gives us a one-frame lag (use InputState.Change instead?)
|
||||
m_InputEventPtr.internalTime = InputRuntime.s_Instance.currentTime;
|
||||
m_Control.ResetToDefaultStateInEvent(m_InputEventPtr);
|
||||
InputSystem.QueueEvent(m_InputEventPtr);
|
||||
}
|
||||
|
||||
// Used by PlayerInput auto switch for scheme to prevent using Pointer device.
|
||||
internal static bool HasAnyActive => s_nbActiveInstances != 0;
|
||||
private static int s_nbActiveInstances = 0;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
++s_nbActiveInstances;
|
||||
SetupInputControl();
|
||||
if (m_Control == null)
|
||||
return;
|
||||
// if we are in single player and if it the first active switch to the target device.
|
||||
if (s_nbActiveInstances == 1 &&
|
||||
PlayerInput.isSinglePlayer)
|
||||
{
|
||||
var firstPlayer = PlayerInput.GetPlayerByIndex(0);
|
||||
if (firstPlayer?.neverAutoSwitchControlSchemes == false)
|
||||
{
|
||||
var devices = firstPlayer.devices;
|
||||
bool deviceFound = false;
|
||||
// skip is the device is already part of the current scheme
|
||||
foreach (var device in devices)
|
||||
{
|
||||
if (m_Control.device.deviceId == device.deviceId)
|
||||
{
|
||||
deviceFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!deviceFound)
|
||||
{
|
||||
firstPlayer.SwitchCurrentControlScheme(m_Control.device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
--s_nbActiveInstances;
|
||||
if (m_Control == null)
|
||||
return;
|
||||
|
||||
var device = m_Control.device;
|
||||
for (var i = 0; i < s_OnScreenDevices.length; ++i)
|
||||
{
|
||||
if (s_OnScreenDevices[i].device != device)
|
||||
continue;
|
||||
|
||||
var deviceInfo = s_OnScreenDevices[i].RemoveControl(this);
|
||||
if (deviceInfo.firstControl == null)
|
||||
{
|
||||
// We're the last on-screen control on this device. Remove the device.
|
||||
s_OnScreenDevices[i].Destroy();
|
||||
s_OnScreenDevices.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
s_OnScreenDevices[i] = deviceInfo;
|
||||
|
||||
// We're keeping the device but we're disabling the on-screen representation
|
||||
// for one of its controls. If the control isn't in default state, reset it
|
||||
// to that now. This is what ensures that if, for example, OnScreenButton is
|
||||
// disabled after OnPointerDown, we reset its button control to zero even
|
||||
// though we will not see an OnPointerUp.
|
||||
if (!m_Control.CheckStateIsAtDefault())
|
||||
SentDefaultValueToControl();
|
||||
}
|
||||
|
||||
m_Control = null;
|
||||
m_InputEventPtr = new InputEventPtr();
|
||||
Debug.Assert(m_NextControlOnDevice == null);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private struct OnScreenDeviceInfo
|
||||
{
|
||||
public InputEventPtr eventPtr;
|
||||
public NativeArray<byte> buffer;
|
||||
public InputDevice device;
|
||||
public OnScreenControl firstControl;
|
||||
|
||||
public OnScreenDeviceInfo AddControl(OnScreenControl control)
|
||||
{
|
||||
control.m_NextControlOnDevice = firstControl;
|
||||
firstControl = control;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OnScreenDeviceInfo RemoveControl(OnScreenControl control)
|
||||
{
|
||||
if (firstControl == control)
|
||||
firstControl = control.m_NextControlOnDevice;
|
||||
else
|
||||
{
|
||||
for (OnScreenControl current = firstControl.m_NextControlOnDevice, previous = firstControl;
|
||||
current != null; previous = current, current = current.m_NextControlOnDevice)
|
||||
{
|
||||
if (current != control)
|
||||
continue;
|
||||
|
||||
previous.m_NextControlOnDevice = current.m_NextControlOnDevice;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
control.m_NextControlOnDevice = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if (buffer.IsCreated)
|
||||
buffer.Dispose();
|
||||
if (device != null)
|
||||
InputSystem.RemoveDevice(device);
|
||||
device = null;
|
||||
buffer = new NativeArray<byte>();
|
||||
}
|
||||
}
|
||||
|
||||
private static InlinedArray<OnScreenDeviceInfo> s_OnScreenDevices;
|
||||
|
||||
internal string GetWarningMessage()
|
||||
{
|
||||
return $"{GetType()} needs to be attached as a child to a UI Canvas and have a RectTransform component to function properly.";
|
||||
}
|
||||
}
|
||||
|
||||
internal static class UGUIOnScreenControlUtils
|
||||
{
|
||||
public static RectTransform GetCanvasRectTransform(Transform transform)
|
||||
{
|
||||
var parentTransform = transform.parent;
|
||||
return parentTransform != null ? transform.parent.GetComponentInParent<RectTransform>() : null;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal static class UGUIOnScreenControlEditorUtils
|
||||
{
|
||||
public static void ShowWarningIfNotPartOfCanvasHierarchy(OnScreenControl target)
|
||||
{
|
||||
if (UGUIOnScreenControlUtils.GetCanvasRectTransform(target.transform) == null)
|
||||
UnityEditor.EditorGUILayout.HelpBox(target.GetWarningMessage(), UnityEditor.MessageType.Warning);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92ae37f8da4c25141a0c43e136275e46
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,596 @@
|
||||
#if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditor.AnimatedValues;
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
#endif
|
||||
////TODO: custom icon for OnScreenStick component
|
||||
|
||||
namespace UnityEngine.InputSystem.OnScreen
|
||||
{
|
||||
/// <summary>
|
||||
/// A stick control displayed on screen and moved around by touch or other pointer
|
||||
/// input.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <see cref="OnScreenStick"/> works by simulating events from the device specified in the <see cref="OnScreenControl.controlPath"/>
|
||||
/// property. Some parts of the Input System, such as the <see cref="PlayerInput"/> component, can be set up to
|
||||
/// auto-switch <see cref="PlayerInput.neverAutoSwitchControlSchemes"/> to a new device when input from them is detected.
|
||||
/// When a device is switched, any currently running inputs from the previously active device are cancelled.
|
||||
/// In the case of <see cref="OnScreenStick"/>, this can mean that the <see cref="IPointerUpHandler.OnPointerUp"/> method will be called
|
||||
/// and the stick will jump back to center, even though the pointer input has not physically been released.
|
||||
///
|
||||
/// To avoid this situation, set the <see cref="useIsolatedInputActions"/> property to true. This will create a set of local
|
||||
/// Input Actions to drive the stick that are not cancelled when device switching occurs.
|
||||
/// You might also need to ensure, depending on your case, that the Mouse, Pen, Touchsceen and/or XRController devices are not used in a concurent
|
||||
/// control schemes of the simulated device.
|
||||
/// </remarks>
|
||||
[AddComponentMenu("Input/On-Screen Stick")]
|
||||
[HelpURL(InputSystem.kDocUrl + "/manual/OnScreen.html#on-screen-sticks")]
|
||||
public class OnScreenStick : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler
|
||||
{
|
||||
private const string kDynamicOriginClickable = "DynamicOriginClickable";
|
||||
|
||||
/// <summary>
|
||||
/// Callback to handle OnPointerDown UI events.
|
||||
/// </summary>
|
||||
public void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
if (m_UseIsolatedInputActions)
|
||||
return;
|
||||
|
||||
if (eventData == null)
|
||||
throw new System.ArgumentNullException(nameof(eventData));
|
||||
|
||||
BeginInteraction(eventData.position, eventData.pressEventCamera);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback to handle OnDrag UI events.
|
||||
/// </summary>
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (m_UseIsolatedInputActions)
|
||||
return;
|
||||
|
||||
if (eventData == null)
|
||||
throw new System.ArgumentNullException(nameof(eventData));
|
||||
|
||||
MoveStick(eventData.position, eventData.pressEventCamera);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback to handle OnPointerUp UI events.
|
||||
/// </summary>
|
||||
public void OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
if (m_UseIsolatedInputActions)
|
||||
return;
|
||||
|
||||
EndInteraction();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (m_UseIsolatedInputActions)
|
||||
{
|
||||
// avoid allocations every time the pointer down event fires by allocating these here
|
||||
// and re-using them
|
||||
m_RaycastResults = new List<RaycastResult>();
|
||||
m_PointerEventData = new PointerEventData(EventSystem.current);
|
||||
|
||||
// if the pointer actions have no bindings (the default), add some
|
||||
if (m_PointerDownAction == null || m_PointerDownAction.bindings.Count == 0)
|
||||
{
|
||||
if (m_PointerDownAction == null)
|
||||
m_PointerDownAction = new InputAction(type: InputActionType.PassThrough);
|
||||
// ensure PassThrough mode
|
||||
else if (m_PointerDownAction.m_Type != InputActionType.PassThrough)
|
||||
m_PointerDownAction.m_Type = InputActionType.PassThrough;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
InputExitPlayModeAnalytic.suppress = true;
|
||||
#endif
|
||||
m_PointerDownAction.AddBinding("<Mouse>/leftButton");
|
||||
m_PointerDownAction.AddBinding("<Pen>/tip");
|
||||
m_PointerDownAction.AddBinding("<Touchscreen>/touch*/press");
|
||||
m_PointerDownAction.AddBinding("<XRController>/trigger");
|
||||
#if UNITY_EDITOR
|
||||
InputExitPlayModeAnalytic.suppress = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (m_PointerMoveAction == null || m_PointerMoveAction.bindings.Count == 0)
|
||||
{
|
||||
if (m_PointerMoveAction == null)
|
||||
m_PointerMoveAction = new InputAction();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
InputExitPlayModeAnalytic.suppress = true;
|
||||
#endif
|
||||
m_PointerMoveAction.AddBinding("<Mouse>/position");
|
||||
m_PointerMoveAction.AddBinding("<Pen>/position");
|
||||
m_PointerMoveAction.AddBinding("<Touchscreen>/touch*/position");
|
||||
#if UNITY_EDITOR
|
||||
InputExitPlayModeAnalytic.suppress = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
m_PointerDownAction.performed += OnPointerChanged;
|
||||
m_PointerDownAction.Enable();
|
||||
m_PointerMoveAction.Enable();
|
||||
}
|
||||
|
||||
// Unable to setup elements according to settings if a RectTransform is not available (ISXB-915, ISXB-916).
|
||||
if (!(transform is RectTransform))
|
||||
return;
|
||||
|
||||
m_StartPos = ((RectTransform)transform).anchoredPosition;
|
||||
|
||||
if (m_Behaviour != Behaviour.ExactPositionWithDynamicOrigin) return;
|
||||
|
||||
m_PointerDownPos = m_StartPos;
|
||||
|
||||
var dynamicOrigin = new GameObject(kDynamicOriginClickable, typeof(Image));
|
||||
dynamicOrigin.transform.SetParent(transform);
|
||||
var image = dynamicOrigin.GetComponent<Image>();
|
||||
image.color = new Color(1, 1, 1, 0);
|
||||
var rectTransform = (RectTransform)dynamicOrigin.transform;
|
||||
rectTransform.sizeDelta = new Vector2(m_DynamicOriginRange * 2, m_DynamicOriginRange * 2);
|
||||
rectTransform.localScale = new Vector3(1, 1, 0);
|
||||
rectTransform.anchoredPosition3D = Vector3.zero;
|
||||
|
||||
image.sprite = SpriteUtilities.CreateCircleSprite(16, new Color32(255, 255, 255, 255));
|
||||
image.alphaHitTestMinimumThreshold = 0.5f;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (m_UseIsolatedInputActions)
|
||||
{
|
||||
m_PointerDownAction.performed -= OnPointerChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void BeginInteraction(Vector2 pointerPosition, Camera uiCamera)
|
||||
{
|
||||
var canvasRectTransform = UGUIOnScreenControlUtils.GetCanvasRectTransform(transform);
|
||||
if (canvasRectTransform == null)
|
||||
{
|
||||
Debug.LogError(GetWarningMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_Behaviour)
|
||||
{
|
||||
case Behaviour.RelativePositionWithStaticOrigin:
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRectTransform, pointerPosition, uiCamera, out m_PointerDownPos);
|
||||
break;
|
||||
case Behaviour.ExactPositionWithStaticOrigin:
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRectTransform, pointerPosition, uiCamera, out m_PointerDownPos);
|
||||
MoveStick(pointerPosition, uiCamera);
|
||||
break;
|
||||
case Behaviour.ExactPositionWithDynamicOrigin:
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRectTransform, pointerPosition, uiCamera, out var pointerDown);
|
||||
m_PointerDownPos = ((RectTransform)transform).anchoredPosition = pointerDown;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveStick(Vector2 pointerPosition, Camera uiCamera)
|
||||
{
|
||||
var canvasRectTransform = UGUIOnScreenControlUtils.GetCanvasRectTransform(transform);
|
||||
if (canvasRectTransform == null)
|
||||
{
|
||||
Debug.LogError(GetWarningMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRectTransform, pointerPosition, uiCamera, out var position);
|
||||
var delta = position - m_PointerDownPos;
|
||||
|
||||
switch (m_Behaviour)
|
||||
{
|
||||
case Behaviour.RelativePositionWithStaticOrigin:
|
||||
delta = Vector2.ClampMagnitude(delta, movementRange);
|
||||
((RectTransform)transform).anchoredPosition = (Vector2)m_StartPos + delta;
|
||||
break;
|
||||
|
||||
case Behaviour.ExactPositionWithStaticOrigin:
|
||||
delta = position - (Vector2)m_StartPos;
|
||||
delta = Vector2.ClampMagnitude(delta, movementRange);
|
||||
((RectTransform)transform).anchoredPosition = (Vector2)m_StartPos + delta;
|
||||
break;
|
||||
|
||||
case Behaviour.ExactPositionWithDynamicOrigin:
|
||||
delta = Vector2.ClampMagnitude(delta, movementRange);
|
||||
((RectTransform)transform).anchoredPosition = m_PointerDownPos + delta;
|
||||
break;
|
||||
}
|
||||
|
||||
var newPos = new Vector2(delta.x / movementRange, delta.y / movementRange);
|
||||
SendValueToControl(newPos);
|
||||
}
|
||||
|
||||
private void EndInteraction()
|
||||
{
|
||||
((RectTransform)transform).anchoredPosition = m_PointerDownPos = m_StartPos;
|
||||
SendValueToControl(Vector2.zero);
|
||||
}
|
||||
|
||||
private void OnPointerDown(InputAction.CallbackContext ctx)
|
||||
{
|
||||
if (m_IsIsolationActive) { return; }
|
||||
Debug.Assert(EventSystem.current != null);
|
||||
|
||||
var screenPosition = Vector2.zero;
|
||||
TouchControl touchControl = null;
|
||||
if (ctx.control?.parent is TouchControl touch)
|
||||
{
|
||||
touchControl = touch;
|
||||
screenPosition = touch.position.ReadValue();
|
||||
}
|
||||
else if (ctx.control?.device is Pointer pointer)
|
||||
{
|
||||
screenPosition = pointer.position.ReadValue();
|
||||
}
|
||||
|
||||
m_PointerEventData.position = screenPosition;
|
||||
EventSystem.current.RaycastAll(m_PointerEventData, m_RaycastResults);
|
||||
if (m_RaycastResults.Count == 0)
|
||||
return;
|
||||
|
||||
var stickSelected = false;
|
||||
foreach (var result in m_RaycastResults)
|
||||
{
|
||||
if (result.gameObject != gameObject) continue;
|
||||
|
||||
stickSelected = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!stickSelected)
|
||||
return;
|
||||
|
||||
BeginInteraction(screenPosition, GetCameraFromCanvas());
|
||||
if (touchControl != null)
|
||||
{
|
||||
m_TouchControl = touchControl;
|
||||
m_PointerMoveAction.ApplyBindingOverride($"{touchControl.path}/position", path: "<Touchscreen>/touch*/position");
|
||||
}
|
||||
|
||||
m_PointerMoveAction.performed += OnPointerMove;
|
||||
m_IsIsolationActive = true;
|
||||
}
|
||||
|
||||
private void OnPointerChanged(InputAction.CallbackContext ctx)
|
||||
{
|
||||
if (ctx.control.IsPressed())
|
||||
OnPointerDown(ctx);
|
||||
else
|
||||
OnPointerUp(ctx);
|
||||
}
|
||||
|
||||
private void OnPointerMove(InputAction.CallbackContext ctx)
|
||||
{
|
||||
// only pointer devices are allowed
|
||||
Debug.Assert(ctx.control?.device is Pointer);
|
||||
Vector2 screenPosition;
|
||||
|
||||
// If it's a finger take the value from the finger that initiated the change
|
||||
if (m_TouchControl != null)
|
||||
{
|
||||
// if the finger is up ignore the move
|
||||
if (m_TouchControl.isInProgress == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
screenPosition = m_TouchControl.position.ReadValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
screenPosition = ((Pointer)ctx.control.device).position.ReadValue();
|
||||
}
|
||||
|
||||
MoveStick(screenPosition, GetCameraFromCanvas());
|
||||
}
|
||||
|
||||
private void OnPointerUp(InputAction.CallbackContext ctx)
|
||||
{
|
||||
if (!m_IsIsolationActive) return;
|
||||
|
||||
// if it's a finger ensure that is the one that get released
|
||||
if (m_TouchControl != null)
|
||||
{
|
||||
if (m_TouchControl.isInProgress) return;
|
||||
m_PointerMoveAction.ApplyBindingOverride(null, path: "<Touchscreen>/touch*/position");
|
||||
m_TouchControl = null;
|
||||
}
|
||||
|
||||
EndInteraction();
|
||||
m_PointerMoveAction.performed -= OnPointerMove;
|
||||
m_IsIsolationActive = false;
|
||||
}
|
||||
|
||||
private Camera GetCameraFromCanvas()
|
||||
{
|
||||
var canvas = GetComponentInParent<Canvas>();
|
||||
var renderMode = canvas?.renderMode;
|
||||
if (renderMode == RenderMode.ScreenSpaceOverlay
|
||||
|| (renderMode == RenderMode.ScreenSpaceCamera && canvas?.worldCamera == null))
|
||||
return null;
|
||||
|
||||
return canvas?.worldCamera ?? Camera.main;
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
// This will not produce meaningful results unless we have a rect transform (ISXB-915, ISXB-916).
|
||||
var parentRectTransform = transform.parent as RectTransform;
|
||||
if (parentRectTransform == null)
|
||||
return;
|
||||
|
||||
Gizmos.matrix = parentRectTransform.localToWorldMatrix;
|
||||
|
||||
var startPos = parentRectTransform.anchoredPosition;
|
||||
if (Application.isPlaying)
|
||||
startPos = m_StartPos;
|
||||
|
||||
Gizmos.color = new Color32(84, 173, 219, 255);
|
||||
|
||||
var center = startPos;
|
||||
if (Application.isPlaying && m_Behaviour == Behaviour.ExactPositionWithDynamicOrigin)
|
||||
center = m_PointerDownPos;
|
||||
|
||||
DrawGizmoCircle(center, m_MovementRange);
|
||||
|
||||
if (m_Behaviour != Behaviour.ExactPositionWithDynamicOrigin) return;
|
||||
|
||||
Gizmos.color = new Color32(158, 84, 219, 255);
|
||||
DrawGizmoCircle(startPos, m_DynamicOriginRange);
|
||||
}
|
||||
|
||||
private void DrawGizmoCircle(Vector2 center, float radius)
|
||||
{
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
var radians = i / 32f * Mathf.PI * 2;
|
||||
var nextRadian = (i + 1) / 32f * Mathf.PI * 2;
|
||||
Gizmos.DrawLine(
|
||||
new Vector3(center.x + Mathf.Cos(radians) * radius, center.y + Mathf.Sin(radians) * radius, 0),
|
||||
new Vector3(center.x + Mathf.Cos(nextRadian) * radius, center.y + Mathf.Sin(nextRadian) * radius, 0));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDynamicOriginClickableArea()
|
||||
{
|
||||
var dynamicOriginTransform = transform.Find(kDynamicOriginClickable);
|
||||
if (dynamicOriginTransform)
|
||||
{
|
||||
var rectTransform = (RectTransform)dynamicOriginTransform;
|
||||
rectTransform.sizeDelta = new Vector2(m_DynamicOriginRange * 2, m_DynamicOriginRange * 2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The distance from the onscreen control's center of origin, around which the control can move.
|
||||
/// </summary>
|
||||
public float movementRange
|
||||
{
|
||||
get => m_MovementRange;
|
||||
set => m_MovementRange = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the circular region where the onscreen control may have it's origin placed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This only applies if <see cref="behaviour"/> is set to <see cref="Behaviour.ExactPositionWithDynamicOrigin"/>.
|
||||
/// When the first press is within this region, then the control will appear at that position and have it's origin of motion placed there.
|
||||
/// Otherwise, if pressed outside of this region the control will ignore it.
|
||||
/// This property defines the radius of the circular region. The center point being defined by the component position in the scene.
|
||||
/// </remarks>
|
||||
public float dynamicOriginRange
|
||||
{
|
||||
get => m_DynamicOriginRange;
|
||||
set
|
||||
{
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
if (m_DynamicOriginRange != value)
|
||||
{
|
||||
m_DynamicOriginRange = value;
|
||||
UpdateDynamicOriginClickableArea();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents stick interactions from getting cancelled due to device switching.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is useful for scenarios where the active device switches automatically
|
||||
/// based on the most recently actuated device. A common situation where this happens is
|
||||
/// when using a <see cref="PlayerInput"/> component with Auto-switch set to true. Imagine
|
||||
/// a mobile game where an on-screen stick simulates the left stick of a gamepad device.
|
||||
/// When the on-screen stick is moved, the Input System will see an input event from a gamepad
|
||||
/// and switch the active device to it. This causes any active actions to be cancelled, including
|
||||
/// the pointer action driving the on screen stick, which results in the stick jumping back to
|
||||
/// the center as though it had been released.
|
||||
///
|
||||
/// In isolated mode, the actions driving the stick are not cancelled because they are
|
||||
/// unique Input Action instances that don't share state with any others.
|
||||
/// </remarks>
|
||||
public bool useIsolatedInputActions
|
||||
{
|
||||
get => m_UseIsolatedInputActions;
|
||||
set => m_UseIsolatedInputActions = value;
|
||||
}
|
||||
|
||||
[FormerlySerializedAs("movementRange")]
|
||||
[SerializeField]
|
||||
[Min(0)]
|
||||
private float m_MovementRange = 50;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Defines the circular region where the onscreen control may have it's origin placed.")]
|
||||
[Min(0)]
|
||||
private float m_DynamicOriginRange = 100;
|
||||
|
||||
[InputControl(layout = "Vector2")]
|
||||
[SerializeField]
|
||||
private string m_ControlPath;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Choose how the onscreen stick will move relative to it's origin and the press position.\n\n" +
|
||||
"RelativePositionWithStaticOrigin: The control's center of origin is fixed. " +
|
||||
"The control will begin un-actuated at it's centered position and then move relative to the pointer or finger motion.\n\n" +
|
||||
"ExactPositionWithStaticOrigin: The control's center of origin is fixed. The stick will immediately jump to the " +
|
||||
"exact position of the click or touch and begin tracking motion from there.\n\n" +
|
||||
"ExactPositionWithDynamicOrigin: The control's center of origin is determined by the initial press position. " +
|
||||
"The stick will begin un-actuated at this center position and then track the current pointer or finger position.")]
|
||||
private Behaviour m_Behaviour;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Set this to true to prevent cancellation of pointer events due to device switching. Cancellation " +
|
||||
"will appear as the stick jumping back and forth between the pointer position and the stick center.")]
|
||||
private bool m_UseIsolatedInputActions;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The action that will be used to detect pointer down events on the stick control. Note that if no bindings " +
|
||||
"are set, default ones will be provided.")]
|
||||
private InputAction m_PointerDownAction;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The action that will be used to detect pointer movement on the stick control. Note that if no bindings " +
|
||||
"are set, default ones will be provided.")]
|
||||
private InputAction m_PointerMoveAction;
|
||||
|
||||
private Vector3 m_StartPos;
|
||||
private Vector2 m_PointerDownPos;
|
||||
|
||||
[NonSerialized]
|
||||
private List<RaycastResult> m_RaycastResults;
|
||||
[NonSerialized]
|
||||
private PointerEventData m_PointerEventData;
|
||||
[NonSerialized]
|
||||
private TouchControl m_TouchControl;
|
||||
[NonSerialized]
|
||||
private bool m_IsIsolationActive;
|
||||
|
||||
protected override string controlPathInternal
|
||||
{
|
||||
get => m_ControlPath;
|
||||
set => m_ControlPath = value;
|
||||
}
|
||||
|
||||
/// <summary>Defines how the onscreen stick will move relative to it's origin and the press position.</summary>
|
||||
public Behaviour behaviour
|
||||
{
|
||||
get => m_Behaviour;
|
||||
set => m_Behaviour = value;
|
||||
}
|
||||
|
||||
/// <summary>Defines how the onscreen stick will move relative to it's center of origin and the press position.</summary>
|
||||
public enum Behaviour
|
||||
{
|
||||
/// <summary>The control's center of origin is fixed in the scene.
|
||||
/// The control will begin un-actuated at it's centered position and then move relative to the press motion.</summary>
|
||||
RelativePositionWithStaticOrigin,
|
||||
|
||||
/// <summary>The control's center of origin is fixed in the scene.
|
||||
/// The control may begin from an actuated position to ensure it is always tracking the current press position.</summary>
|
||||
ExactPositionWithStaticOrigin,
|
||||
|
||||
/// <summary>The control's center of origin is determined by the initial press position.
|
||||
/// The control will begin unactuated at this center position and then track the current press position.</summary>
|
||||
ExactPositionWithDynamicOrigin
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(OnScreenStick))]
|
||||
internal class OnScreenStickEditor : UnityEditor.Editor
|
||||
{
|
||||
private AnimBool m_ShowDynamicOriginOptions;
|
||||
private AnimBool m_ShowIsolatedInputActions;
|
||||
|
||||
private SerializedProperty m_UseIsolatedInputActions;
|
||||
private SerializedProperty m_Behaviour;
|
||||
private SerializedProperty m_ControlPathInternal;
|
||||
private SerializedProperty m_MovementRange;
|
||||
private SerializedProperty m_DynamicOriginRange;
|
||||
private SerializedProperty m_PointerDownAction;
|
||||
private SerializedProperty m_PointerMoveAction;
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
m_ShowDynamicOriginOptions = new AnimBool(false);
|
||||
m_ShowIsolatedInputActions = new AnimBool(false);
|
||||
|
||||
m_UseIsolatedInputActions = serializedObject.FindProperty(nameof(OnScreenStick.m_UseIsolatedInputActions));
|
||||
|
||||
m_Behaviour = serializedObject.FindProperty(nameof(OnScreenStick.m_Behaviour));
|
||||
m_ControlPathInternal = serializedObject.FindProperty(nameof(OnScreenStick.m_ControlPath));
|
||||
m_MovementRange = serializedObject.FindProperty(nameof(OnScreenStick.m_MovementRange));
|
||||
m_DynamicOriginRange = serializedObject.FindProperty(nameof(OnScreenStick.m_DynamicOriginRange));
|
||||
m_PointerDownAction = serializedObject.FindProperty(nameof(OnScreenStick.m_PointerDownAction));
|
||||
m_PointerMoveAction = serializedObject.FindProperty(nameof(OnScreenStick.m_PointerMoveAction));
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
// Report analytics
|
||||
new InputComponentEditorAnalytic(InputSystemComponent.OnScreenStick).Send();
|
||||
new OnScreenStickEditorAnalytic(this).Send();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
// Current implementation has UGUI dependencies (ISXB-915, ISXB-916)
|
||||
UGUIOnScreenControlEditorUtils.ShowWarningIfNotPartOfCanvasHierarchy((OnScreenStick)target);
|
||||
|
||||
EditorGUILayout.PropertyField(m_MovementRange);
|
||||
EditorGUILayout.PropertyField(m_ControlPathInternal);
|
||||
EditorGUILayout.PropertyField(m_Behaviour);
|
||||
|
||||
m_ShowDynamicOriginOptions.target = ((OnScreenStick)target).behaviour ==
|
||||
Behaviour.ExactPositionWithDynamicOrigin;
|
||||
if (EditorGUILayout.BeginFadeGroup(m_ShowDynamicOriginOptions.faded))
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_DynamicOriginRange);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
((OnScreenStick)target).UpdateDynamicOriginClickableArea();
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUILayout.EndFadeGroup();
|
||||
|
||||
EditorGUILayout.PropertyField(m_UseIsolatedInputActions);
|
||||
m_ShowIsolatedInputActions.target = m_UseIsolatedInputActions.boolValue;
|
||||
if (EditorGUILayout.BeginFadeGroup(m_ShowIsolatedInputActions.faded))
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(m_PointerDownAction);
|
||||
EditorGUILayout.PropertyField(m_PointerMoveAction);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUILayout.EndFadeGroup();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9d677f1681015749b15c436eec6d880
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
#if UNITY_EDITOR || UNITY_ANDROID || UNITY_IOS || UNITY_TVOS || UNITY_WSA || UNITY_VISIONOS
|
||||
namespace UnityEngine.InputSystem.OnScreen
|
||||
{
|
||||
/// <summary>
|
||||
/// Support for various forms of on-screen controls.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// On-screen input visually represents control elements either through (potentially) built-in
|
||||
/// mechanisms like <see cref="OnScreenKeyboard"/> or through manually arranged control setups
|
||||
/// in the form of <see cref="OnScreenControl">OnScreenControls</see>.
|
||||
/// </remarks>
|
||||
#if UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
static class OnScreenSupport
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
////TODO: OnScreenKeyboard support
|
||||
//InputSystem.RegisterLayout<OnScreenKeyboard>();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74405005f3013c2498103a79211ba93b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user