Adaug proiectul Unity

This commit is contained in:
2026-11-01 23:39:55 +02:00
parent cb14d9d37d
commit 863eac8f2c
23463 changed files with 5412601 additions and 0 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 29656219f4bec4af4b3720997e749957
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.InputSystem.EnhancedTouch;
namespace UnityEngine.InputSystem.Editor
{
[InitializeOnLoad]
internal static class TouchSimulationEditorInitializer
{
static TouchSimulationEditorInitializer()
{
// We're initializing as part of [InitializeOnLoad]. We don't want to trigger InputSystem
// initialization from the static cctor so delay-execute the registration.
EditorApplication.delayCall += () =>
{
InputSystem.onSettingsChange += OnSettingsChanged;
InputSystem.onBeforeUpdate += ReEnableAfterDomainReload;
};
}
private static void ReEnableAfterDomainReload()
{
OnSettingsChanged();
InputSystem.onBeforeUpdate -= ReEnableAfterDomainReload;
}
private static void OnSettingsChanged()
{
if (InputEditorUserSettings.simulateTouch)
TouchSimulation.Enable();
else
TouchSimulation.Disable();
}
}
[CustomEditor(typeof(TouchSimulation))]
internal class TouchSimulationEditor : UnityEditor.Editor
{
public void OnDisable()
{
new InputComponentEditorAnalytic(InputSystemComponent.TouchSimulation).Send();
}
}
}
#endif // UNITY_EDITOR
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a69b36bd3aae948309bdb0008372a60a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e38cdda355b6f4f8fb66876f964f570e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,239 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
#if UNITY_6000_2_OR_NEWER
using TreeView = UnityEditor.IMGUI.Controls.TreeView<int>;
using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem<int>;
using TreeViewState = UnityEditor.IMGUI.Controls.TreeViewState<int>;
#endif
////TODO: use two columns for treeview and separate name and value
namespace UnityEngine.InputSystem.HID.Editor
{
/// <summary>
/// A window that dumps a raw HID descriptor in a tree view.
/// </summary>
/// <remarks>
/// Not specific to InputDevices of type <see cref="HID"/> so that it can work with
/// any <see cref="InputDevice"/> created for a device using the "HID" interface.
/// </remarks>
internal class HIDDescriptorWindow : EditorWindow, ISerializationCallbackReceiver
{
public static void CreateOrShowExisting(int deviceId, InputDeviceDescription deviceDescription)
{
// See if we have an existing window for the device and if so pop it
// in front.
if (s_OpenWindows != null)
{
for (var i = 0; i < s_OpenWindows.Count; ++i)
{
var existingWindow = s_OpenWindows[i];
if (existingWindow.m_DeviceId == deviceId)
{
existingWindow.Show();
existingWindow.Focus();
return;
}
}
}
// No, so create a new one.
var window = CreateInstance<HIDDescriptorWindow>();
window.InitializeWith(deviceId, deviceDescription);
window.minSize = new Vector2(270, 200);
window.Show();
window.titleContent = new GUIContent("HID Descriptor");
}
public void Awake()
{
AddToList();
}
public void OnDestroy()
{
RemoveFromList();
}
public void OnGUI()
{
if (!m_Initialized)
InitializeWith(m_DeviceId, m_DeviceDescription);
GUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.Label(m_Label, GUILayout.MinWidth(100), GUILayout.ExpandWidth(true));
GUILayout.EndHorizontal();
var rect = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
m_TreeView.OnGUI(rect);
}
private void InitializeWith(int deviceId, InputDeviceDescription deviceDescription)
{
m_DeviceId = deviceId;
m_DeviceDescription = deviceDescription;
m_Initialized = true;
// Set up tree view for HID descriptor.
var hidDescriptor = HID.ReadHIDDeviceDescriptor(ref m_DeviceDescription,
(ref InputDeviceCommand command) => InputRuntime.s_Instance.DeviceCommand(m_DeviceId, ref command));
if (m_TreeViewState == null)
m_TreeViewState = new TreeViewState();
m_TreeView = new HIDDescriptorTreeView(m_TreeViewState, hidDescriptor);
m_TreeView.SetExpanded(1, true);
m_Label = new GUIContent(
$"HID Descriptor for '{deviceDescription.manufacturer} {deviceDescription.product}'");
}
[NonSerialized] private bool m_Initialized;
[NonSerialized] private HIDDescriptorTreeView m_TreeView;
[NonSerialized] private GUIContent m_Label;
[SerializeField] private int m_DeviceId;
[SerializeField] private InputDeviceDescription m_DeviceDescription;
[SerializeField] private TreeViewState m_TreeViewState;
private void AddToList()
{
if (s_OpenWindows == null)
s_OpenWindows = new List<HIDDescriptorWindow>();
if (!s_OpenWindows.Contains(this))
s_OpenWindows.Add(this);
}
private void RemoveFromList()
{
if (s_OpenWindows != null)
s_OpenWindows.Remove(this);
}
private static List<HIDDescriptorWindow> s_OpenWindows;
private class HIDDescriptorTreeView : TreeView
{
private HID.HIDDeviceDescriptor m_Descriptor;
public HIDDescriptorTreeView(TreeViewState state, HID.HIDDeviceDescriptor descriptor)
: base(state)
{
m_Descriptor = descriptor;
Reload();
}
protected override TreeViewItem BuildRoot()
{
var id = 0;
var root = new TreeViewItem
{
id = id++,
depth = -1
};
var item = BuildDeviceItem(m_Descriptor, ref id);
root.AddChild(item);
return root;
}
private TreeViewItem BuildDeviceItem(HID.HIDDeviceDescriptor device, ref int id)
{
var item = new TreeViewItem
{
id = id++,
depth = 0,
displayName = "Device"
};
AddChild(item, string.Format("Vendor ID: 0x{0:X}", device.vendorId), ref id);
AddChild(item, string.Format("Product ID: 0x{0:X}", device.productId), ref id);
AddChild(item, string.Format("Usage Page: 0x{0:X} ({1})", (uint)device.usagePage, device.usagePage), ref id);
AddChild(item, string.Format("Usage: 0x{0:X}", device.usage), ref id);
AddChild(item, "Input Report Size: " + device.inputReportSize, ref id);
AddChild(item, "Output Report Size: " + device.outputReportSize, ref id);
AddChild(item, "Feature Report Size: " + device.featureReportSize, ref id);
// Elements.
if (device.elements != null)
{
var elementCount = device.elements.Length;
var elements = AddChild(item, elementCount + " Elements", ref id);
for (var i = 0; i < elementCount; ++i)
BuildElementItem(i, elements, device.elements[i], ref id);
}
else
AddChild(item, "0 Elements", ref id);
////TODO: collections
return item;
}
private TreeViewItem BuildElementItem(int index, TreeViewItem parent, HID.HIDElementDescriptor element, ref int id)
{
var item = AddChild(parent, string.Format("Element {0} ({1})", index, element.reportType), ref id);
string usagePageString = HID.UsagePageToString(element.usagePage);
string usageString = HID.UsageToString(element.usagePage, element.usage);
AddChild(item, string.Format("Usage Page: 0x{0:X} ({1})", (uint)element.usagePage, usagePageString), ref id);
if (usageString != null)
AddChild(item, string.Format("Usage: 0x{0:X} ({1})", element.usage, usageString), ref id);
else
AddChild(item, string.Format("Usage: 0x{0:X}", element.usage), ref id);
AddChild(item, "Report Type: " + element.reportType, ref id);
AddChild(item, "Report ID: " + element.reportId, ref id);
AddChild(item, "Report Size in Bits: " + element.reportSizeInBits, ref id);
AddChild(item, "Report Bit Offset: " + element.reportOffsetInBits, ref id);
AddChild(item, "Collection Index: " + element.collectionIndex, ref id);
AddChild(item, string.Format("Unit: {0:X}", element.unit), ref id);
AddChild(item, string.Format("Unit Exponent: {0:X}", element.unitExponent), ref id);
AddChild(item, "Logical Min: " + element.logicalMin, ref id);
AddChild(item, "Logical Max: " + element.logicalMax, ref id);
AddChild(item, "Physical Min: " + element.physicalMin, ref id);
AddChild(item, "Physical Max: " + element.physicalMax, ref id);
AddChild(item, "Has Null State?: " + element.hasNullState, ref id);
AddChild(item, "Has Preferred State?: " + element.hasPreferredState, ref id);
AddChild(item, "Is Array?: " + element.isArray, ref id);
AddChild(item, "Is Non-Linear?: " + element.isNonLinear, ref id);
AddChild(item, "Is Relative?: " + element.isRelative, ref id);
AddChild(item, "Is Constant?: " + element.isConstant, ref id);
AddChild(item, "Is Wrapping?: " + element.isWrapping, ref id);
return item;
}
private TreeViewItem AddChild(TreeViewItem parent, string displayName, ref int id)
{
var item = new TreeViewItem
{
id = id++,
depth = parent.depth + 1,
displayName = displayName
};
parent.AddChild(item);
return item;
}
}
public void OnBeforeSerialize()
{
}
public void OnAfterDeserialize()
{
AddToList();
}
}
}
#endif // UNITY_EDITOR
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ae42a3986cf84fe2aa1df4aa984dd806
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,35 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using UnityEngine.InputSystem.Editor;
namespace UnityEngine.InputSystem.HID.Editor
{
/// <summary>
/// Handles Editor-specific initialization for HID support.
/// </summary>
[InitializeOnLoad]
internal static class HIDSupportEditorInitializer
{
private static readonly GUIContent s_HIDDescriptor = new GUIContent("HID Descriptor");
static HIDSupportEditorInitializer()
{
// Add toolbar button to any devices using the "HID" interface. Opens
// a window to browse the HID descriptor of the device.
InputDeviceDebuggerWindow.onToolbarGUI += OnDeviceToolbarGUI;
}
private static void OnDeviceToolbarGUI(InputDevice device)
{
if (device.description.interfaceName == HID.kHIDInterface)
{
if (GUILayout.Button(s_HIDDescriptor, EditorStyles.toolbarButton))
{
HIDDescriptorWindow.CreateOrShowExisting(device.deviceId, device.description);
}
}
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: efc4aca331ba242fab026845289179ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e37b431b8b93e4719910a045d05ba3cb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,7 @@
using System.Runtime.CompilerServices;
using UnityEngine.Scripting;
[assembly: InternalsVisibleTo("UnityEngine.InputForUIVisualizer")]
[assembly: InternalsVisibleTo("Unity.InputSystem.Tests")]
[assembly: InternalsVisibleTo("Unity.InputSystem")]
[assembly: AlwaysLinkAssembly]
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3f8d555e90ab4499890cd182a2d849ce
timeCreated: 1775119500
@@ -0,0 +1,137 @@
#if UNITY_EDITOR && ENABLE_INPUT_SYSTEM && UNITY_2023_2_OR_NEWER
using System.Collections.Generic;
using UnityEditor;
using UnityEngine.InputSystem.Editor;
namespace UnityEngine.InputSystem.Plugins.InputForUI
{
// Unlike InputSystemProvider we want the verifier to register itself directly on domain reload in editor.
[InitializeOnLoad]
internal class InputActionAssetVerifier : ProjectWideActionsAsset.IInputActionAssetVerifier
{
public enum ReportPolicy
{
ReportAll,
SuppressChildErrors
}
// Note: This is intentionally not a constant to avoid dead code warning in tests while this remains
// as a setting type of value.
public static ReportPolicy DefaultReportPolicy = ReportPolicy.SuppressChildErrors;
static InputActionAssetVerifier()
{
// Register an InputActionAsset verifier for this plugin.
ProjectWideActionsAsset.RegisterInputActionAssetVerifier(() => new InputActionAssetVerifier());
InputSystemProvider.SetOnRegisterActions((asset) => { ProjectWideActionsAsset.Verify(asset); });
}
#region ProjectWideActionsAsset.IInputActionAssetVerifier
public void Verify(InputActionAsset asset,
ProjectWideActionsAsset.IReportInputActionAssetVerificationErrors reporter)
{
// We don't want to log warnings if no UI action map is present as we default in this case.
if (asset.FindActionMap("UI", false) == null)
{
return;
}
// Note:
// PWA has initial state check true for "Point" action, DefaultActions do not, does it matter?
//
// Additionally note that "Submit" and "Cancel" are indirectly expected to be of Button action type.
// This is not available in UI configuration, but InputActionRebindingExtensions suggests this.
//
// Additional "LeftClick" has initial state check set in PWA, but not "MiddleClick" and "RightClick".
// Is this intentional? Are requirements different?
var context = new Context(asset, reporter, DefaultReportPolicy);
context.Verify(actionNameOrId: InputSystemProvider.Actions.PointAction, actionType: InputActionType.PassThrough, expectedControlType: nameof(Vector2));
context.Verify(actionNameOrId: InputSystemProvider.Actions.MoveAction, actionType: InputActionType.PassThrough, expectedControlType: nameof(Vector2));
context.Verify(actionNameOrId: InputSystemProvider.Actions.SubmitAction, actionType: InputActionType.Button, expectedControlType: "Button");
context.Verify(actionNameOrId: InputSystemProvider.Actions.CancelAction, actionType: InputActionType.Button, expectedControlType: "Button");
context.Verify(actionNameOrId: InputSystemProvider.Actions.LeftClickAction, actionType: InputActionType.PassThrough, expectedControlType: "Button");
context.Verify(actionNameOrId: InputSystemProvider.Actions.MiddleClickAction, actionType: InputActionType.PassThrough, expectedControlType: "Button");
context.Verify(actionNameOrId: InputSystemProvider.Actions.RightClickAction, actionType: InputActionType.PassThrough, expectedControlType: "Button");
context.Verify(actionNameOrId: InputSystemProvider.Actions.ScrollWheelAction, actionType: InputActionType.PassThrough, expectedControlType: nameof(Vector2));
}
#endregion
private struct Context
{
const string errorSuffix = "The Input System's runtime UI integration relies on certain required input action definitions, some of which are missing. This means some runtime UI input may not work correctly. See <a href=\"https://docs.unity3d.com/Packages/com.unity.inputsystem@latest/index.html?subfolder=/manual/UISupport.html#required-actions-for-ui\">Input System Manual - UI Support</a> for guidance on required actions for UI integration or see <a href=\"https://docs.unity3d.com/Packages/com.unity.inputsystem@latest/index.html?subfolder=/manual/ProjectWideActions.html#the-default-actions\">how to revert to defaults</a>.";
public Context(InputActionAsset asset,
ProjectWideActionsAsset.IReportInputActionAssetVerificationErrors reporter,
ReportPolicy policy)
{
this.asset = asset;
this.missingPaths = new HashSet<string>();
this.reporter = reporter;
this.policy = policy;
}
private string GetAssetReference()
{
var path = AssetDatabase.GetAssetPath(asset);
return string.IsNullOrEmpty(path) ? asset.name : path;
}
private void ActionMapWarning(string actionMap, string problem)
{
reporter.Report($"InputActionMap with path '{actionMap}' in asset \"{GetAssetReference()}\" {problem}. {errorSuffix}");
}
private void ActionWarning(string actionNameOrId, string problem)
{
reporter.Report($"InputAction with path '{actionNameOrId}' in asset \"{GetAssetReference()}\" {problem}. {errorSuffix}");
}
public void Verify(string actionNameOrId, InputActionType actionType, string expectedControlType)
{
var action = asset.FindAction(actionNameOrId);
if (action == null)
{
const string kCouldNotBeFound = "could not be found";
// Check if the map (if any) exists
var noMapOrMapExists = true;
var index = actionNameOrId.IndexOf('/');
if (index > 0)
{
var path = actionNameOrId.Substring(0, index);
if (asset.FindActionMap(path) == null)
{
if (missingPaths == null)
missingPaths = new HashSet<string>(1);
if (missingPaths.Add(path))
ActionMapWarning(path, kCouldNotBeFound);
noMapOrMapExists = false;
}
}
if (!noMapOrMapExists && policy == ReportPolicy.SuppressChildErrors)
return;
ActionWarning(actionNameOrId, kCouldNotBeFound);
}
else if (action.bindings.Count == 0)
ActionWarning(actionNameOrId, "do not have any configured bindings");
else if (action.type != actionType)
ActionWarning(actionNameOrId, $"has 'type' set to '{nameof(InputActionType)}.{action.type}', but '{nameof(InputActionType)}.{actionType}' was expected");
else if (!string.IsNullOrEmpty(expectedControlType) && !string.IsNullOrEmpty(action.expectedControlType) && action.expectedControlType != expectedControlType)
ActionWarning(actionNameOrId, $"has 'expectedControlType' set to '{action.expectedControlType}', but '{expectedControlType}' was expected");
}
private readonly InputActionAsset asset;
private readonly ProjectWideActionsAsset.IReportInputActionAssetVerificationErrors reporter;
private HashSet<string> missingPaths; // Avoids generating multiple warnings around missing map
private ReportPolicy policy;
}
}
}
#endif // UNITY_EDITOR && ENABLE_INPUT_SYSTEM && UNITY_2023_2_OR_NEWER
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1fe35221cd5f4ca9b9df947d34e7e1f0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,19 @@
{
"name": "Unity.InputSystem.ForUI.Editor",
"rootNamespace": "",
"references": [
"Unity.InputSystem",
"Unity.InputSystem.ForUI"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 319e0e95bdd5841b7b1808ef2f0ffd7f
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f027141a193544959a71baea4a57d3f7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,34 @@
#if UNITY_EDITOR && (PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI)
using UnityEditor;
using UnityEditor.AnimatedValues;
using UnityEngine.InputSystem.Editor;
namespace UnityEngine.InputSystem.OnScreen
{
[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
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 62acf18b6b5d144bea1cbe618187f44d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,83 @@
#if UNITY_EDITOR && (PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI)
using UnityEditor;
using UnityEditor.AnimatedValues;
using UnityEngine.InputSystem.Editor;
namespace UnityEngine.InputSystem.OnScreen
{
[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 ==
OnScreenStick.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
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 839e0066c5f854ddda076a4ff2f80a7b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
#if UNITY_EDITOR
using UnityEditor;
namespace UnityEngine.InputSystem.OnScreen
{
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: 91a3b2b6adad844ed9b680b378d8485a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 95a3b96623aff409fad313b971f84743
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,666 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine.InputSystem.Users;
using UnityEngine.InputSystem.Utilities;
#if UNITY_INPUT_SYSTEM_ENABLE_UI
using UnityEngine.InputSystem.UI;
using UnityEngine.InputSystem.UI.Editor;
#endif
////TODO: detect if new input system isn't enabled and provide UI to enable it
#pragma warning disable 0414
namespace UnityEngine.InputSystem.Editor
{
/// <summary>
/// A custom inspector for the <see cref="PlayerInput"/> component.
/// </summary>
[CustomEditor(typeof(PlayerInput))]
internal class PlayerInputEditor : UnityEditor.Editor
{
public const string kDefaultInputActionsAssetPath =
"Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/PlayerInput/DefaultInputActions.inputactions";
public void OnEnable()
{
InputActionImporter.onImport += Refresh;
InputUser.onChange += OnUserChange;
// Look up properties.
m_ActionsProperty = serializedObject.FindProperty(nameof(PlayerInput.m_Actions));
m_DefaultControlSchemeProperty = serializedObject.FindProperty(nameof(PlayerInput.m_DefaultControlScheme));
m_NeverAutoSwitchControlSchemesProperty = serializedObject.FindProperty(nameof(PlayerInput.m_NeverAutoSwitchControlSchemes));
m_DefaultActionMapProperty = serializedObject.FindProperty(nameof(PlayerInput.m_DefaultActionMap));
m_NotificationBehaviorProperty = serializedObject.FindProperty(nameof(PlayerInput.m_NotificationBehavior));
m_CameraProperty = serializedObject.FindProperty(nameof(PlayerInput.m_Camera));
m_ActionEventsProperty = serializedObject.FindProperty(nameof(PlayerInput.m_ActionEvents));
m_DeviceLostEventProperty = serializedObject.FindProperty(nameof(PlayerInput.m_DeviceLostEvent));
m_DeviceRegainedEventProperty = serializedObject.FindProperty(nameof(PlayerInput.m_DeviceRegainedEvent));
m_ControlsChangedEventProperty = serializedObject.FindProperty(nameof(PlayerInput.m_ControlsChangedEvent));
#if UNITY_INPUT_SYSTEM_ENABLE_UI
m_UIInputModuleProperty = serializedObject.FindProperty(nameof(PlayerInput.m_UIInputModule));
#endif
}
public void OnDisable()
{
new InputComponentEditorAnalytic(InputSystemComponent.PlayerInput).Send();
new PlayerInputEditorAnalytic(this).Send();
}
public void OnDestroy()
{
InputActionImporter.onImport -= Refresh;
InputUser.onChange -= OnUserChange;
}
private void Refresh()
{
////FIXME: doesn't seem like we're picking up the results of the latest import
m_ActionAssetInitialized = false;
Repaint();
}
private void OnUserChange(InputUser user, InputUserChange change, InputDevice device)
{
Repaint();
}
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
// Action config section.
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_ActionsProperty);
var actionsWereChanged = false;
// Check for if we're using project-wide actions to raise a warning message.
if (m_ActionsProperty.objectReferenceValue != null)
{
InputActionAsset actions = m_ActionsProperty.objectReferenceValue as InputActionAsset;
if (actions == InputSystem.actions)
{
EditorGUILayout.HelpBox("Project-wide actions asset is not recommended to be used with Player " +
"Input because it is a singleton reference and all actions maps are enabled by default.\r\n" +
"You should manually disable all action maps on Start() and " +
"manually enable the default action map.",
MessageType.Warning);
}
}
var assetChanged = CheckIfActionAssetChanged();
// initialize the editor component if the asset has changed or if it has not been initialized yet
#if UNITY_6000_4_OR_NEWER
if (EditorGUI.EndChangeCheck() || !m_ActionAssetInitialized || assetChanged || m_ActionAssetEntityId == EntityId.None)
#else
if (EditorGUI.EndChangeCheck() || !m_ActionAssetInitialized || assetChanged || m_ActionAssetInstanceID == 0)
#endif
{
InitializeEditorComponent(assetChanged);
actionsWereChanged = true;
}
++EditorGUI.indentLevel;
if (m_ControlSchemeOptions != null && m_ControlSchemeOptions.Length > 1) // Don't show if <Any> is the only option.
{
// Default control scheme picker.
Color currentBg = GUI.backgroundColor;
// if the invalid DefaultControlSchemeName is selected set the popup draw the BG color in red
if (m_InvalidDefaultControlSchemeName != null && m_SelectedDefaultControlScheme == 1)
GUI.backgroundColor = Color.red;
var rect = EditorGUILayout.GetControlRect();
var label = EditorGUI.BeginProperty(rect, m_DefaultControlSchemeText, m_DefaultControlSchemeProperty);
var selected = EditorGUI.Popup(rect, label, m_SelectedDefaultControlScheme, m_ControlSchemeOptions);
EditorGUI.EndProperty();
if (selected != m_SelectedDefaultControlScheme)
{
if (selected == 0)
{
m_DefaultControlSchemeProperty.stringValue = null;
}
// if there is an invalid default scheme name it will be at rank 1.
// we use m_InvalidDefaultControlSchemeName to prevent usage of the string with "name<Not Found>"
else if (m_InvalidDefaultControlSchemeName != null && selected == 1)
{
m_DefaultControlSchemeProperty.stringValue = m_InvalidDefaultControlSchemeName;
}
else
{
m_DefaultControlSchemeProperty.stringValue = m_ControlSchemeOptions[selected].text;
}
m_SelectedDefaultControlScheme = selected;
}
// Restore the initial color
GUI.backgroundColor = currentBg;
rect = EditorGUILayout.GetControlRect();
label = EditorGUI.BeginProperty(rect, m_AutoSwitchText, m_NeverAutoSwitchControlSchemesProperty);
var neverAutoSwitchValueOld = m_NeverAutoSwitchControlSchemesProperty.boolValue;
var neverAutoSwitchValueNew = !EditorGUI.Toggle(rect, label, !neverAutoSwitchValueOld);
EditorGUI.EndProperty();
if (neverAutoSwitchValueOld != neverAutoSwitchValueNew)
{
m_NeverAutoSwitchControlSchemesProperty.boolValue = neverAutoSwitchValueNew;
serializedObject.ApplyModifiedProperties();
}
}
if (m_ActionMapOptions != null && m_ActionMapOptions.Length > 0)
{
// Default action map picker.
var rect = EditorGUILayout.GetControlRect();
var label = EditorGUI.BeginProperty(rect, m_DefaultActionMapText, m_DefaultActionMapProperty);
var selected = EditorGUI.Popup(rect, label, m_SelectedDefaultActionMap,
m_ActionMapOptions);
EditorGUI.EndProperty();
if (selected != m_SelectedDefaultActionMap)
{
if (selected == 0)
{
m_DefaultActionMapProperty.stringValue = null;
}
else
{
// Use ID rather than name.
var asset = (InputActionAsset)m_ActionsProperty.objectReferenceValue;
var actionMap = asset.FindActionMap(m_ActionMapOptions[selected].text);
if (actionMap != null)
m_DefaultActionMapProperty.stringValue = actionMap.id.ToString();
}
m_SelectedDefaultActionMap = selected;
}
}
--EditorGUI.indentLevel;
DoHelpCreateAssetUI();
#if UNITY_INPUT_SYSTEM_ENABLE_UI
// UI config section.
if (m_UIPropertyText == null)
m_UIPropertyText = EditorGUIUtility.TrTextContent("UI Input Module", m_UIInputModuleProperty.GetTooltip());
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_UIInputModuleProperty, m_UIPropertyText);
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
if (m_UIInputModuleProperty.objectReferenceValue != null)
{
var uiModule = m_UIInputModuleProperty.objectReferenceValue as InputSystemUIInputModule;
if (m_ActionsProperty.objectReferenceValue != null && uiModule.actionsAsset != m_ActionsProperty.objectReferenceValue)
{
EditorGUILayout.HelpBox("The referenced InputSystemUIInputModule is configured using different input actions than this PlayerInput. They should match if you want to synchronize PlayerInput actions to the UI input.", MessageType.Warning);
if (GUILayout.Button(m_FixInputModuleText))
InputSystemUIInputModuleEditor.ReassignActions(uiModule, m_ActionsProperty.objectReferenceValue as InputActionAsset);
}
}
#endif
// Camera section.
if (m_CameraPropertyText == null)
m_CameraPropertyText = EditorGUIUtility.TrTextContent("Camera", m_CameraProperty.GetTooltip());
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_CameraProperty, m_CameraPropertyText);
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
// Notifications/event section.
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_NotificationBehaviorProperty, m_NotificationBehaviorText);
if (EditorGUI.EndChangeCheck() || actionsWereChanged || !m_NotificationBehaviorInitialized)
OnNotificationBehaviorChange();
switch ((PlayerNotifications)m_NotificationBehaviorProperty.intValue)
{
case PlayerNotifications.SendMessages:
case PlayerNotifications.BroadcastMessages:
Debug.Assert(m_SendMessagesHelpText != null);
EditorGUILayout.HelpBox(m_SendMessagesHelpText);
break;
case PlayerNotifications.InvokeUnityEvents:
m_EventsGroupUnfolded = EditorGUILayout.Foldout(m_EventsGroupUnfolded, m_EventsGroupText, toggleOnLabelClick: true);
if (m_EventsGroupUnfolded)
{
// Action events. Group by action map.
if (m_ActionNames != null)
{
using (new EditorGUI.IndentLevelScope())
{
for (var n = 0; n < m_NumActionMaps; ++n)
{
// Skip action maps that have no names (case 1317735).
if (m_ActionMapNames[n] == null)
continue;
m_ActionMapEventsUnfolded[n] = EditorGUILayout.Foldout(m_ActionMapEventsUnfolded[n],
m_ActionMapNames[n], toggleOnLabelClick: true);
using (new EditorGUI.IndentLevelScope())
{
if (m_ActionMapEventsUnfolded[n])
{
for (var i = 0; i < m_ActionNames.Length; ++i)
{
if (m_ActionMapIndices[i] != n)
continue;
EditorGUILayout.PropertyField(m_ActionEventsProperty.GetArrayElementAtIndex(i), m_ActionNames[i]);
}
}
}
}
}
}
// Misc events.
EditorGUILayout.PropertyField(m_DeviceLostEventProperty);
EditorGUILayout.PropertyField(m_DeviceRegainedEventProperty);
EditorGUILayout.PropertyField(m_ControlsChangedEventProperty);
}
break;
}
// Miscellaneous buttons.
DoUtilityButtonsUI();
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
// Debug UI.
if (EditorApplication.isPlaying)
DoDebugUI();
}
// This checks changes that are not captured by BeginChangeCheck/EndChangeCheck.
// One such case is when the user triggers a "Reset" on the component.
bool CheckIfActionAssetChanged()
{
var obj = m_ActionsProperty.objectReferenceValue;
if (obj == null)
return false;
#if UNITY_6000_4_OR_NEWER
EntityId assetEntityId = obj.GetEntityId();
bool result = assetEntityId != m_ActionAssetEntityId && m_ActionAssetEntityId != EntityId.None;
m_ActionAssetEntityId = assetEntityId;
return result;
#else
int assetInstanceID = obj.GetInstanceID();
// if the m_ActionAssetInstanceID is 0 the PlayerInputEditor has not been initialized yet, but the asset did not change
bool result = assetInstanceID != m_ActionAssetInstanceID && m_ActionAssetInstanceID != 0;
m_ActionAssetInstanceID = (int)assetInstanceID;
return result;
#endif
}
private void DoHelpCreateAssetUI()
{
if (m_ActionsProperty.objectReferenceValue != null)
{
// All good. We already have an asset.
return;
}
EditorGUILayout.HelpBox("There are no input actions associated with this input component yet. Click the button below to create "
+ "a new set of input actions or drag an existing input actions asset into the field above.", MessageType.Info);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button(m_CreateActionsText, EditorStyles.miniButton, GUILayout.MaxWidth(120)))
{
// Request save file location.
var defaultFileName = Application.productName;
var fileName = EditorUtility.SaveFilePanel("Create Input Actions Asset", "Assets", defaultFileName,
InputActionAsset.Extension);
////TODO: take current Supported Devices into account when creating this
// Create and import asset and open editor.
if (!string.IsNullOrEmpty(fileName))
{
if (!fileName.StartsWith(Application.dataPath))
{
Debug.LogError($"Path must be located in Assets/ folder (got: '{fileName}')");
EditorGUILayout.EndHorizontal();
return;
}
if (!fileName.EndsWith("." + InputActionAsset.Extension))
fileName += "." + InputActionAsset.Extension;
// Load default actions and update all GUIDs.
var defaultActionsText = File.ReadAllText(kDefaultInputActionsAssetPath);
var newActions = InputActionAsset.FromJson(defaultActionsText);
foreach (var map in newActions.actionMaps)
{
map.m_Id = Guid.NewGuid().ToString();
foreach (var action in map.actions)
action.m_Id = Guid.NewGuid().ToString();
}
newActions.name = Path.GetFileNameWithoutExtension(fileName);
var newActionsText = newActions.ToJson();
// Write it out and tell the asset DB to pick it up.
File.WriteAllText(fileName, newActionsText);
// Import the new asset
var relativePath = "Assets/" + fileName.Substring(Application.dataPath.Length + 1);
AssetDatabase.ImportAsset(relativePath, ImportAssetOptions.ForceSynchronousImport);
// Load imported object.
var importedObject = AssetDatabase.LoadAssetAtPath<InputActionAsset>(relativePath);
// Set it on the PlayerInput component.
m_ActionsProperty.objectReferenceValue = importedObject;
serializedObject.ApplyModifiedProperties();
// Open the asset.
AssetDatabase.OpenAsset(importedObject);
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Separator();
}
private void DoUtilityButtonsUI()
{
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button(m_OpenSettingsText, EditorStyles.miniButton))
InputSettingsProvider.Open();
if (GUILayout.Button(m_OpenDebuggerText, EditorStyles.miniButton))
InputDebuggerWindow.CreateOrShow();
EditorGUILayout.EndHorizontal();
}
private void DoDebugUI()
{
var playerInput = (PlayerInput)target;
if (!playerInput.user.valid)
return;
////TODO: show actions when they happen
var user = playerInput.user.index.ToString();
var controlScheme = playerInput.user.controlScheme?.name;
var devices = string.Join(", ", playerInput.user.pairedDevices);
EditorGUILayout.Space();
EditorGUILayout.LabelField(m_DebugText, EditorStyles.boldLabel);
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.LabelField("User", user);
EditorGUILayout.LabelField("Control Scheme", controlScheme);
EditorGUILayout.LabelField("Devices", devices);
EditorGUI.EndDisabledGroup();
}
private void OnNotificationBehaviorChange()
{
Debug.Assert(m_ActionAssetInitialized);
serializedObject.ApplyModifiedProperties();
var notificationBehavior = (PlayerNotifications)m_NotificationBehaviorProperty.intValue;
switch (notificationBehavior)
{
// Create text that lists all the messages sent by the component.
case PlayerNotifications.BroadcastMessages:
case PlayerNotifications.SendMessages:
{
var builder = new StringBuilder();
builder.Append("Will ");
if (notificationBehavior == PlayerNotifications.BroadcastMessages)
builder.Append("BroadcastMessage()");
else
builder.Append("SendMessage()");
builder.Append(" to GameObject: ");
builder.Append(PlayerInput.DeviceLostMessage);
builder.Append(", ");
builder.Append(PlayerInput.DeviceRegainedMessage);
builder.Append(", ");
builder.Append(PlayerInput.ControlsChangedMessage);
var playerInput = (PlayerInput)target;
var asset = playerInput.m_Actions;
if (asset != null)
{
foreach (var action in asset)
{
builder.Append(", On");
builder.Append(CSharpCodeHelpers.MakeTypeName(action.name));
}
}
m_SendMessagesHelpText = new GUIContent(builder.ToString());
break;
}
case PlayerNotifications.InvokeUnityEvents:
{
var playerInput = (PlayerInput)target;
bool areEventsDirty = (playerInput.m_DeviceLostEvent == null) || (playerInput.m_DeviceRegainedEvent == null) || (playerInput.m_ControlsChangedEvent == null);
playerInput.m_DeviceLostEvent ??= new PlayerInput.DeviceLostEvent();
playerInput.m_DeviceRegainedEvent ??= new PlayerInput.DeviceRegainedEvent();
playerInput.m_ControlsChangedEvent ??= new PlayerInput.ControlsChangedEvent();
if (areEventsDirty)
{
serializedObject.Update();
// Force action refresh.
m_ActionAssetInitialized = false;
Refresh();
}
break;
}
}
m_NotificationBehaviorInitialized = true;
}
private void InitializeEditorComponent(bool assetChanged)
{
serializedObject.ApplyModifiedProperties();
m_ActionAssetInitialized = true;
var playerInput = (PlayerInput)target;
var asset = (InputActionAsset)m_ActionsProperty.objectReferenceValue;
if (assetChanged)
m_SelectedDefaultActionMap = -1;
if (asset == null)
{
m_ControlSchemeOptions = null;
m_ActionMapOptions = null;
m_ActionNames = null;
m_SelectedDefaultControlScheme = -1;
m_InvalidDefaultControlSchemeName = null;
return;
}
// If we're sending Unity events, read out the event list.
if ((PlayerNotifications)m_NotificationBehaviorProperty.intValue ==
PlayerNotifications.InvokeUnityEvents)
{
////FIXME: this should preserve the same order that we have in the asset
var newActionNames = new List<GUIContent>();
var newActionEvents = new List<PlayerInput.ActionEvent>();
var newActionMapIndices = new List<int>();
m_NumActionMaps = 0;
m_ActionMapNames = null;
void AddEntry(InputAction action, PlayerInput.ActionEvent actionEvent)
{
newActionNames.Add(new GUIContent(action.name));
newActionEvents.Add(actionEvent);
var actionMapIndex = asset.actionMaps.IndexOfReference(action.actionMap);
newActionMapIndices.Add(actionMapIndex);
if (actionMapIndex >= m_NumActionMaps)
m_NumActionMaps = actionMapIndex + 1;
ArrayHelpers.PutAtIfNotSet(ref m_ActionMapNames, actionMapIndex,
() => new GUIContent(action.actionMap.name));
}
// Bring over any action events that we already have and that are still in the asset.
var oldActionEvents = playerInput.m_ActionEvents;
if (oldActionEvents != null)
{
foreach (var entry in oldActionEvents)
{
var guid = entry.actionId;
var action = asset.FindAction(guid);
if (action != null)
AddEntry(action, entry);
}
}
// Add any new actions.
foreach (var action in asset)
{
// Skip if it was already in there.
if (oldActionEvents != null && oldActionEvents.Any(x => x.actionId == action.id.ToString()))
continue;
////FIXME: adds bindings to the name
AddEntry(action, new PlayerInput.ActionEvent(action.id, action.ToString()));
}
m_ActionNames = newActionNames.ToArray();
m_ActionMapIndices = newActionMapIndices.ToArray();
Array.Resize(ref m_ActionMapEventsUnfolded, m_NumActionMaps);
playerInput.m_ActionEvents = newActionEvents.ToArray();
}
// Read out control schemes.
var selectedDefaultControlScheme = playerInput.defaultControlScheme;
m_InvalidDefaultControlSchemeName = null;
m_SelectedDefaultControlScheme = 0;
////TODO: sort alphabetically and ensure that the order is the same in the schemes editor
var controlSchemesNames = asset.controlSchemes.Select(cs => cs.name).ToList();
// try to find the selected Default Control Scheme
if (!string.IsNullOrEmpty(selectedDefaultControlScheme))
{
// +1 since <Any> will be the first in the list
m_SelectedDefaultControlScheme = 1 + controlSchemesNames.FindIndex(name => string.Compare(name, selectedDefaultControlScheme,
StringComparison.InvariantCultureIgnoreCase) == 0);
// if not found, will insert the invalid name next to <Any>
if (m_SelectedDefaultControlScheme == 0)
{
m_InvalidDefaultControlSchemeName = selectedDefaultControlScheme;
m_SelectedDefaultControlScheme = 1;
controlSchemesNames.Insert(0, $"{selectedDefaultControlScheme}{L10n.Tr("<Not Found>")}");
}
}
else
{
playerInput.defaultControlScheme = null;
}
m_ControlSchemeOptions = new GUIContent[controlSchemesNames.Count + 1];
m_ControlSchemeOptions[0] = new GUIContent(EditorGUIUtility.TrTextContent("<Any>"));
for (var i = 0; i < controlSchemesNames.Count; ++i)
{
m_ControlSchemeOptions[i + 1] = new GUIContent(controlSchemesNames[i]);
}
// Read out action maps.
var selectedDefaultActionMap = !string.IsNullOrEmpty(playerInput.defaultActionMap)
? asset.FindActionMap(playerInput.defaultActionMap)
: null;
m_SelectedDefaultActionMap = (asset.actionMaps.Count > 0 && m_SelectedDefaultActionMap == -1) ? 1 : 0;
var actionMaps = asset.actionMaps;
m_ActionMapOptions = new GUIContent[actionMaps.Count + 1];
m_ActionMapOptions[0] = new GUIContent(EditorGUIUtility.TrTextContent("<None>"));
////TODO: sort alphabetically
for (var i = 0; i < actionMaps.Count; ++i)
{
var actionMap = actionMaps[i];
m_ActionMapOptions[i + 1] = new GUIContent(actionMap.name);
if (selectedDefaultActionMap != null && actionMap == selectedDefaultActionMap)
m_SelectedDefaultActionMap = i + 1;
}
if (m_SelectedDefaultActionMap <= 0)
playerInput.defaultActionMap = null;
else
playerInput.defaultActionMap = m_ActionMapOptions[m_SelectedDefaultActionMap].text;
serializedObject.Update();
}
[SerializeField] private bool m_EventsGroupUnfolded;
[SerializeField] private bool[] m_ActionMapEventsUnfolded;
[NonSerialized] private readonly GUIContent m_CreateActionsText = EditorGUIUtility.TrTextContent("Create Actions...");
[NonSerialized] private readonly GUIContent m_FixInputModuleText = EditorGUIUtility.TrTextContent("Fix UI Input Module");
[NonSerialized] private readonly GUIContent m_OpenSettingsText = EditorGUIUtility.TrTextContent("Open Input Settings");
[NonSerialized] private readonly GUIContent m_OpenDebuggerText = EditorGUIUtility.TrTextContent("Open Input Debugger");
[NonSerialized] private readonly GUIContent m_EventsGroupText =
EditorGUIUtility.TrTextContent("Events", "UnityEvents triggered by the PlayerInput component");
[NonSerialized] private readonly GUIContent m_NotificationBehaviorText =
EditorGUIUtility.TrTextContent("Behavior",
"Determine how notifications should be sent when an input-related event associated with the player happens.");
[NonSerialized] private readonly GUIContent m_DefaultControlSchemeText =
EditorGUIUtility.TrTextContent("Default Scheme", "Which control scheme to try by default. If not set, PlayerInput "
+ "will simply go through all control schemes in the action asset and try one after the other. If set, PlayerInput will try "
+ "the given scheme first but if using that fails (e.g. when not required devices are missing) will fall back to trying the other "
+ "control schemes in order.");
[NonSerialized] private readonly GUIContent m_DefaultActionMapText =
EditorGUIUtility.TrTextContent("Default Map", "Action map to enable by default. If not set, no actions will be enabled by default.");
[NonSerialized] private readonly GUIContent m_AutoSwitchText =
EditorGUIUtility.TrTextContent("Auto-Switch",
"By default, when there is only a single PlayerInput, the player "
+ "is allowed to freely switch between control schemes simply by starting to use a different device. By toggling this property off, this "
+ "behavior is disabled and even with a single player, the player will stay locked onto the explicitly selected control scheme. Note "
+ "that you can still change control schemes explicitly through the PlayerInput API.\n\nWhen there are multiple PlayerInputs in the game, auto-switching is disabled automatically regardless of the value of this property.");
[NonSerialized] private readonly GUIContent m_DebugText = EditorGUIUtility.TrTextContent("Debug");
[NonSerialized] private GUIContent m_UIPropertyText;
[NonSerialized] private GUIContent m_CameraPropertyText;
[NonSerialized] private GUIContent m_SendMessagesHelpText;
[NonSerialized] private GUIContent[] m_ActionNames;
[NonSerialized] private GUIContent[] m_ActionMapNames;
[NonSerialized] private int[] m_ActionMapIndices;
[NonSerialized] private int m_NumActionMaps;
[NonSerialized] private int m_SelectedDefaultControlScheme;
[NonSerialized] private string m_InvalidDefaultControlSchemeName;
[NonSerialized] private GUIContent[] m_ControlSchemeOptions;
[NonSerialized] private int m_SelectedDefaultActionMap;
[NonSerialized] private GUIContent[] m_ActionMapOptions;
[NonSerialized] private SerializedProperty m_ActionsProperty;
[NonSerialized] private SerializedProperty m_DefaultControlSchemeProperty;
[NonSerialized] private SerializedProperty m_DefaultActionMapProperty;
[NonSerialized] private SerializedProperty m_NeverAutoSwitchControlSchemesProperty;
[NonSerialized] private SerializedProperty m_NotificationBehaviorProperty;
#if UNITY_INPUT_SYSTEM_ENABLE_UI
[NonSerialized] private SerializedProperty m_UIInputModuleProperty;
#endif
[NonSerialized] private SerializedProperty m_ActionEventsProperty;
[NonSerialized] private SerializedProperty m_CameraProperty;
[NonSerialized] private SerializedProperty m_DeviceLostEventProperty;
[NonSerialized] private SerializedProperty m_DeviceRegainedEventProperty;
[NonSerialized] private SerializedProperty m_ControlsChangedEventProperty;
[NonSerialized] private bool m_NotificationBehaviorInitialized;
[NonSerialized] private bool m_ActionAssetInitialized;
#if UNITY_6000_4_OR_NEWER
[NonSerialized] private EntityId m_ActionAssetEntityId;
#else
[NonSerialized] private int m_ActionAssetInstanceID;
#endif
}
}
#endif // UNITY_EDITOR
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 78dcfd26eada049b0840e276bd029712
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,269 @@
#if UNITY_EDITOR
using System;
using System.Linq;
using UnityEditor;
using UnityEngine.InputSystem.Users;
namespace UnityEngine.InputSystem.Editor
{
/// <summary>
/// Custom inspector for <see cref="PlayerInputManager"/>.
/// </summary>
[CustomEditor(typeof(PlayerInputManager))]
internal class PlayerInputManagerEditor : UnityEditor.Editor
{
public void OnEnable()
{
InputUser.onChange += OnUserChange;
}
public void OnDisable()
{
new InputComponentEditorAnalytic(InputSystemComponent.PlayerInputManager).Send();
new PlayerInputManagerEditorAnalytic(this).Send();
}
public void OnDestroy()
{
InputUser.onChange -= OnUserChange;
}
private void OnUserChange(InputUser user, InputUserChange change, InputDevice device)
{
Repaint();
}
public override void OnInspectorGUI()
{
////TODO: cache properties
EditorGUI.BeginChangeCheck();
DoNotificationSectionUI();
EditorGUILayout.Space();
DoJoinSectionUI();
EditorGUILayout.Space();
DoSplitScreenSectionUI();
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
if (EditorApplication.isPlaying)
DoDebugUI();
}
private void DoNotificationSectionUI()
{
var notificationBehaviorProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_NotificationBehavior));
EditorGUILayout.PropertyField(notificationBehaviorProperty);
switch ((PlayerNotifications)notificationBehaviorProperty.intValue)
{
case PlayerNotifications.SendMessages:
if (m_SendMessagesHelpText == null)
m_SendMessagesHelpText = EditorGUIUtility.TrTextContent(
$"Will SendMessage() to GameObject: " + string.Join(",", PlayerInputManager.messages));
EditorGUILayout.HelpBox(m_SendMessagesHelpText);
break;
case PlayerNotifications.BroadcastMessages:
if (m_BroadcastMessagesHelpText == null)
m_BroadcastMessagesHelpText = EditorGUIUtility.TrTextContent(
$"Will BroadcastMessage() to GameObject: " + string.Join(",", PlayerInputManager.messages));
EditorGUILayout.HelpBox(m_BroadcastMessagesHelpText);
break;
case PlayerNotifications.InvokeUnityEvents:
m_EventsExpanded = EditorGUILayout.Foldout(m_EventsExpanded, m_EventsLabel, toggleOnLabelClick: true);
if (m_EventsExpanded)
{
var playerJoinedEventProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_PlayerJoinedEvent));
var playerLeftEventProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_PlayerLeftEvent));
EditorGUILayout.PropertyField(playerJoinedEventProperty);
EditorGUILayout.PropertyField(playerLeftEventProperty);
}
break;
}
}
private void DoJoinSectionUI()
{
EditorGUILayout.LabelField(m_JoiningGroupLabel, EditorStyles.boldLabel);
// Join behavior
var joinBehaviorProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_JoinBehavior));
EditorGUILayout.PropertyField(joinBehaviorProperty);
if ((PlayerJoinBehavior)joinBehaviorProperty.intValue != PlayerJoinBehavior.JoinPlayersManually)
{
++EditorGUI.indentLevel;
// Join action.
if ((PlayerJoinBehavior)joinBehaviorProperty.intValue ==
PlayerJoinBehavior.JoinPlayersWhenJoinActionIsTriggered)
{
var joinActionProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_JoinAction));
EditorGUILayout.PropertyField(joinActionProperty);
}
// Player prefab.
var playerPrefabProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_PlayerPrefab));
EditorGUILayout.PropertyField(playerPrefabProperty);
ValidatePlayerPrefab(joinBehaviorProperty, playerPrefabProperty);
--EditorGUI.indentLevel;
}
// Enabled-by-default.
var allowJoiningProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_AllowJoining));
if (m_AllowingJoiningLabel == null)
m_AllowingJoiningLabel = new GUIContent("Joining Enabled By Default", allowJoiningProperty.GetTooltip());
EditorGUILayout.PropertyField(allowJoiningProperty, m_AllowingJoiningLabel);
// Max player count.
var maxPlayerCountProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_MaxPlayerCount));
if (m_EnableMaxPlayerCountLabel == null)
m_EnableMaxPlayerCountLabel = EditorGUIUtility.TrTextContent("Limit Number of Players", maxPlayerCountProperty.GetTooltip());
if (maxPlayerCountProperty.intValue > 0)
m_MaxPlayerCountEnabled = true;
m_MaxPlayerCountEnabled = EditorGUILayout.Toggle(m_EnableMaxPlayerCountLabel, m_MaxPlayerCountEnabled);
if (m_MaxPlayerCountEnabled)
{
++EditorGUI.indentLevel;
if (maxPlayerCountProperty.intValue < 0)
maxPlayerCountProperty.intValue = 1;
EditorGUILayout.PropertyField(maxPlayerCountProperty);
--EditorGUI.indentLevel;
}
else
maxPlayerCountProperty.intValue = -1;
}
private static void ValidatePlayerPrefab(SerializedProperty joinBehaviorProperty,
SerializedProperty playerPrefabProperty)
{
if ((PlayerJoinBehavior)joinBehaviorProperty.intValue != PlayerJoinBehavior.JoinPlayersWhenButtonIsPressed)
return;
if (playerPrefabProperty.objectReferenceValue == null)
return;
var playerInput = ((GameObject)playerPrefabProperty.objectReferenceValue)
.GetComponentInChildren<PlayerInput>();
if (playerInput == null)
{
EditorGUILayout.HelpBox("No PlayerInput component found in player prefab.", MessageType.Info);
return;
}
if (playerInput.actions == null)
{
EditorGUILayout.HelpBox("PlayerInput component has no input action asset assigned.", MessageType.Info);
return;
}
if (playerInput.actions.controlSchemes.Any(c => c.deviceRequirements.Count > 0) == false)
EditorGUILayout.HelpBox("Join Players When Button Is Pressed behavior will not work when the Input Action Asset " +
"assigned to the PlayerInput component has no required devices in any control scheme.",
MessageType.Info);
}
private void DoSplitScreenSectionUI()
{
EditorGUILayout.LabelField(m_SplitScreenGroupLabel, EditorStyles.boldLabel);
// Split-screen toggle.
var splitScreenProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_SplitScreen));
if (m_SplitScreenLabel == null)
m_SplitScreenLabel = new GUIContent("Enable Split-Screen", splitScreenProperty.GetTooltip());
EditorGUILayout.PropertyField(splitScreenProperty, m_SplitScreenLabel);
if (!splitScreenProperty.boolValue)
return;
++EditorGUI.indentLevel;
// Maintain-aspect-ratio toggle.
var maintainAspectRatioProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_MaintainAspectRatioInSplitScreen));
if (m_MaintainAspectRatioLabel == null)
m_MaintainAspectRatioLabel =
new GUIContent("Maintain Aspect Ratio", maintainAspectRatioProperty.GetTooltip());
EditorGUILayout.PropertyField(maintainAspectRatioProperty, m_MaintainAspectRatioLabel);
// Fixed-number toggle.
var fixedNumberProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_FixedNumberOfSplitScreens));
if (m_EnableFixedNumberOfSplitScreensLabel == null)
m_EnableFixedNumberOfSplitScreensLabel = EditorGUIUtility.TrTextContent("Set Fixed Number", fixedNumberProperty.GetTooltip());
if (fixedNumberProperty.intValue > 0)
m_FixedNumberOfSplitScreensEnabled = true;
m_FixedNumberOfSplitScreensEnabled = EditorGUILayout.Toggle(m_EnableFixedNumberOfSplitScreensLabel,
m_FixedNumberOfSplitScreensEnabled);
if (m_FixedNumberOfSplitScreensEnabled)
{
++EditorGUI.indentLevel;
if (fixedNumberProperty.intValue < 0)
fixedNumberProperty.intValue = 4;
if (m_FixedNumberOfSplitScreensLabel == null)
m_FixedNumberOfSplitScreensLabel = EditorGUIUtility.TrTextContent("Number of Screens",
fixedNumberProperty.tooltip);
EditorGUILayout.PropertyField(fixedNumberProperty, m_FixedNumberOfSplitScreensLabel);
--EditorGUI.indentLevel;
}
else
{
fixedNumberProperty.intValue = -1;
}
// Split-screen area.
var splitScreenAreaProperty = serializedObject.FindProperty(nameof(PlayerInputManager.m_SplitScreenRect));
if (m_SplitScreenAreaLabel == null)
m_SplitScreenAreaLabel = new GUIContent("Screen Rectangle", splitScreenAreaProperty.GetTooltip());
EditorGUILayout.PropertyField(splitScreenAreaProperty, m_SplitScreenAreaLabel);
--EditorGUI.indentLevel;
}
private void DoDebugUI()
{
EditorGUILayout.Space();
EditorGUILayout.LabelField(m_DebugLabel, EditorStyles.boldLabel);
EditorGUI.BeginDisabledGroup(true);
var players = PlayerInput.all;
if (players.Count == 0)
{
EditorGUILayout.LabelField("No Players");
}
else
{
foreach (var player in players)
{
var str = player.gameObject.name;
if (player.splitScreenIndex != -1)
str += $" (Screen #{player.splitScreenIndex})";
EditorGUILayout.LabelField("Player #" + player.playerIndex, str);
}
}
EditorGUI.EndDisabledGroup();
}
[SerializeField] private bool m_EventsExpanded;
[SerializeField] private bool m_MaxPlayerCountEnabled;
[SerializeField] private bool m_FixedNumberOfSplitScreensEnabled;
[NonSerialized] private readonly GUIContent m_JoiningGroupLabel = EditorGUIUtility.TrTextContent("Joining");
[NonSerialized] private readonly GUIContent m_SplitScreenGroupLabel = EditorGUIUtility.TrTextContent("Split-Screen");
[NonSerialized] private readonly GUIContent m_EventsLabel = EditorGUIUtility.TrTextContent("Events");
[NonSerialized] private readonly GUIContent m_DebugLabel = EditorGUIUtility.TrTextContent("Debug");
[NonSerialized] private GUIContent m_SendMessagesHelpText;
[NonSerialized] private GUIContent m_BroadcastMessagesHelpText;
[NonSerialized] private GUIContent m_AllowingJoiningLabel;
[NonSerialized] private GUIContent m_SplitScreenLabel;
[NonSerialized] private GUIContent m_MaintainAspectRatioLabel;
[NonSerialized] private GUIContent m_SplitScreenAreaLabel;
[NonSerialized] private GUIContent m_FixedNumberOfSplitScreensLabel;
[NonSerialized] private GUIContent m_EnableMaxPlayerCountLabel;
[NonSerialized] private GUIContent m_EnableFixedNumberOfSplitScreensLabel;
}
}
#endif // UNITY_EDITOR
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6247927155f224aa5a61c15611fcd34a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0a40837a6cb5749b69436d6839711335
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,765 @@
#if UNITY_EDITOR && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Editor;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.Steam.Editor
{
/// <summary>
/// Converts input actions to and from Steam IGA file format.
/// </summary>
/// <remarks>
/// The idea behind this converter is to enable users to use Unity's action editor to set up actions
/// for their game and the be able, when targeting desktops through Steam, to convert the game's actions
/// to a Steam VDF file that allows using the Steam Controller API with the game.
///
/// The generated VDF file is meant to allow editing by hand in order to add localization strings or
/// apply Steam-specific settings that cannot be inferred from Unity input actions.
/// </remarks>
public static class SteamIGAConverter
{
/// <summary>
/// Generate C# code for an <see cref="InputDevice"/> derived class that exposes the controls
/// for the actions found in the given Steam IGA description.
/// </summary>
/// <param name="vdf"></param>
/// <param name="namespaceAndClassName"></param>
/// <returns></returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Justification = "TODO: Refactor later.")]
public static string GenerateInputDeviceFromSteamIGA(string vdf, string namespaceAndClassName)
{
if (string.IsNullOrEmpty(vdf))
throw new ArgumentNullException("vdf");
if (string.IsNullOrEmpty(namespaceAndClassName))
throw new ArgumentNullException("namespaceAndClassName");
// Parse VDF.
var parsedVdf = ParseVDF(vdf);
var actions = (Dictionary<string, object>)((Dictionary<string, object>)parsedVdf["In Game Actions"])["actions"];
// Determine class and namespace name.
var namespaceName = "";
var className = "";
var indexOfLastDot = namespaceAndClassName.LastIndexOf('.');
if (indexOfLastDot != -1)
{
namespaceName = namespaceAndClassName.Substring(0, indexOfLastDot);
className = namespaceAndClassName.Substring(indexOfLastDot + 1);
}
else
{
className = namespaceAndClassName;
}
var stateStructName = className + "State";
var builder = new StringBuilder();
builder.Append("// THIS FILE HAS BEEN AUTO-GENERATED\n");
builder.Append("#if (UNITY_EDITOR || UNITY_STANDALONE) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT\n");
builder.Append("using UnityEngine;\n");
builder.Append("using UnityEngine.InputSystem;\n");
builder.Append("using UnityEngine.InputSystem.Controls;\n");
builder.Append("using UnityEngine.InputSystem.Layouts;\n");
builder.Append("using UnityEngine.InputSystem.LowLevel;\n");
builder.Append("using UnityEngine.InputSystem.Utilities;\n");
builder.Append("using UnityEngine.InputSystem.Steam;\n");
builder.Append("#if UNITY_EDITOR\n");
builder.Append("using UnityEditor;\n");
builder.Append("#endif\n");
builder.Append("\n");
if (!string.IsNullOrEmpty(namespaceName))
{
builder.Append("namespace ");
builder.Append(namespaceName);
builder.Append("\n{\n");
}
// InitializeOnLoad attribute.
builder.Append("#if UNITY_EDITOR\n");
builder.Append("[InitializeOnLoad]\n");
builder.Append("#endif\n");
// Control layout attribute.
builder.Append("[InputControlLayout(stateType = typeof(");
builder.Append(stateStructName);
builder.Append("))]\n");
// Class declaration.
builder.Append("public class ");
builder.Append(className);
builder.Append(" : SteamController\n");
builder.Append("{\n");
// Device matcher.
builder.Append(" private static InputDeviceMatcher deviceMatcher\n");
builder.Append(" {\n");
builder.Append(" get { return new InputDeviceMatcher().WithInterface(\"Steam\").WithProduct(\"");
builder.Append(className);
builder.Append("\"); }\n");
builder.Append(" }\n");
// Static constructor.
builder.Append('\n');
builder.Append("#if UNITY_EDITOR\n");
builder.Append(" static ");
builder.Append(className);
builder.Append("()\n");
builder.Append(" {\n");
builder.Append(" InputSystem.RegisterLayout<");
builder.Append(className);
builder.Append(">(matches: deviceMatcher);\n");
builder.Append(" }\n");
builder.Append("#endif\n");
// RuntimeInitializeOnLoadMethod.
// NOTE: Not relying on static ctor here. See il2cpp bug 1014293.
builder.Append('\n');
builder.Append(" [RuntimeInitializeOnLoadMethod(loadType: RuntimeInitializeLoadType.BeforeSceneLoad)]\n");
builder.Append(" private static void RuntimeInitializeOnLoad()\n");
builder.Append(" {\n");
builder.Append(" InputSystem.RegisterLayout<");
builder.Append(className);
builder.Append(">(matches: deviceMatcher);\n");
builder.Append(" }\n");
// Control properties.
builder.Append('\n');
foreach (var setEntry in actions)
{
var setEntryProperties = (Dictionary<string, object>)setEntry.Value;
// StickPadGyros.
var stickPadGyros = (Dictionary<string, object>)setEntryProperties["StickPadGyro"];
foreach (var entry in stickPadGyros)
{
var entryProperties = (Dictionary<string, object>)entry.Value;
var isStick = entryProperties.ContainsKey("input_mode") && (string)entryProperties["input_mode"] == "joystick_move";
builder.Append(" [InputControl]\n");
builder.Append(
$" public {(isStick ? "StickControl" : "Vector2Control")} {CSharpCodeHelpers.MakeIdentifier(entry.Key)} {{ get; protected set; }}\n");
}
// Buttons.
var buttons = (Dictionary<string, object>)setEntryProperties["Button"];
foreach (var entry in buttons)
{
builder.Append(" [InputControl]\n");
builder.Append(
$" public ButtonControl {CSharpCodeHelpers.MakeIdentifier(entry.Key)} {{ get; protected set; }}\n");
}
// AnalogTriggers.
var analogTriggers = (Dictionary<string, object>)setEntryProperties["AnalogTrigger"];
foreach (var entry in analogTriggers)
{
builder.Append(" [InputControl]\n");
builder.Append(
$" public AxisControl {CSharpCodeHelpers.MakeIdentifier(entry.Key)} {{ get; protected set; }}\n");
}
}
// FinishSetup method.
builder.Append('\n');
builder.Append(" protected override void FinishSetup()\n");
builder.Append(" {\n");
builder.Append(" base.FinishSetup();\n");
foreach (var setEntry in actions)
{
var setEntryProperties = (Dictionary<string, object>)setEntry.Value;
// StickPadGyros.
var stickPadGyros = (Dictionary<string, object>)setEntryProperties["StickPadGyro"];
foreach (var entry in stickPadGyros)
{
var entryProperties = (Dictionary<string, object>)entry.Value;
var isStick = entryProperties.ContainsKey("input_mode") && (string)entryProperties["input_mode"] == "joystick_move";
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)} = GetChildControl<{(isStick ? "StickControl" : "Vector2Control")}>(\"{entry.Key}\");\n");
}
// Buttons.
var buttons = (Dictionary<string, object>)setEntryProperties["Button"];
foreach (var entry in buttons)
{
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)} = GetChildControl<ButtonControl>(\"{entry.Key}\");\n");
}
// AnalogTriggers.
var analogTriggers = (Dictionary<string, object>)setEntryProperties["AnalogTrigger"];
foreach (var entry in analogTriggers)
{
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)} = GetChildControl<AxisControl>(\"{entry.Key}\");\n");
}
}
builder.Append(" }\n");
// ResolveSteamActions method.
builder.Append('\n');
builder.Append(" protected override void ResolveSteamActions(ISteamControllerAPI api)\n");
builder.Append(" {\n");
foreach (var setEntry in actions)
{
var setEntryProperties = (Dictionary<string, object>)setEntry.Value;
// Set handle.
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(setEntry.Key)}SetHandle = api.GetActionSetHandle(\"{setEntry.Key}\");\n");
// StickPadGyros.
var stickPadGyros = (Dictionary<string, object>)setEntryProperties["StickPadGyro"];
foreach (var entry in stickPadGyros)
{
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle = api.GetAnalogActionHandle(\"{entry.Key}\");\n");
}
// Buttons.
var buttons = (Dictionary<string, object>)setEntryProperties["Button"];
foreach (var entry in buttons)
{
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle = api.GetDigitalActionHandle(\"{entry.Key}\");\n");
}
// AnalogTriggers.
var analogTriggers = (Dictionary<string, object>)setEntryProperties["AnalogTrigger"];
foreach (var entry in analogTriggers)
{
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle = api.GetAnalogActionHandle(\"{entry.Key}\");\n");
}
}
builder.Append(" }\n");
// Handle cache fields.
builder.Append('\n');
foreach (var setEntry in actions)
{
var setEntryProperties = (Dictionary<string, object>)setEntry.Value;
// Set handle.
builder.Append(
$" public SteamHandle<InputActionMap> {CSharpCodeHelpers.MakeIdentifier(setEntry.Key)}SetHandle {{ get; private set; }}\n");
// StickPadGyros.
var stickPadGyros = (Dictionary<string, object>)setEntryProperties["StickPadGyro"];
foreach (var entry in stickPadGyros)
{
builder.Append(
$" public SteamHandle<InputAction> {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle {{ get; private set; }}\n");
}
// Buttons.
var buttons = (Dictionary<string, object>)setEntryProperties["Button"];
foreach (var entry in buttons)
{
builder.Append(
$" public SteamHandle<InputAction> {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle {{ get; private set; }}\n");
}
// AnalogTriggers.
var analogTriggers = (Dictionary<string, object>)setEntryProperties["AnalogTrigger"];
foreach (var entry in analogTriggers)
{
builder.Append(
$" public SteamHandle<InputAction> {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle {{ get; private set; }}\n");
}
}
// steamActionSets property.
builder.Append('\n');
builder.Append(" private SteamActionSetInfo[] m_ActionSets;\n");
builder.Append(" public override ReadOnlyArray<SteamActionSetInfo> steamActionSets\n");
builder.Append(" {\n");
builder.Append(" get\n");
builder.Append(" {\n");
builder.Append(" if (m_ActionSets == null)\n");
builder.Append(" m_ActionSets = new[]\n");
builder.Append(" {\n");
foreach (var setEntry in actions)
{
builder.Append(string.Format(
" new SteamActionSetInfo {{ name = \"{0}\", handle = {1}SetHandle }},\n",
setEntry.Key,
CSharpCodeHelpers.MakeIdentifier(setEntry.Key)));
}
builder.Append(" };\n");
builder.Append(" return new ReadOnlyArray<SteamActionSetInfo>(m_ActionSets);\n");
builder.Append(" }\n");
builder.Append(" }\n");
// Update method.
builder.Append('\n');
builder.Append(" protected override unsafe void Update(ISteamControllerAPI api)\n");
builder.Append(" {\n");
builder.Append($" {stateStructName} state;\n");
var currentButtonBit = 0;
foreach (var setEntry in actions)
{
var setEntryProperties = (Dictionary<string, object>)setEntry.Value;
// StickPadGyros.
var stickPadGyros = (Dictionary<string, object>)setEntryProperties["StickPadGyro"];
foreach (var entry in stickPadGyros)
{
builder.Append(string.Format(" state.{0} = api.GetAnalogActionData(steamControllerHandle, {0}Handle).position;\n",
CSharpCodeHelpers.MakeIdentifier(entry.Key)));
}
// Buttons.
var buttons = (Dictionary<string, object>)setEntryProperties["Button"];
foreach (var entry in buttons)
{
builder.Append(
$" if (api.GetDigitalActionData(steamControllerHandle, {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle).pressed)\n");
builder.Append($" state.buttons[{currentButtonBit / 8}] |= {currentButtonBit % 8};\n");
++currentButtonBit;
}
// AnalogTriggers.
var analogTriggers = (Dictionary<string, object>)setEntryProperties["AnalogTrigger"];
foreach (var entry in analogTriggers)
{
builder.Append(string.Format(" state.{0} = api.GetAnalogActionData(steamControllerHandle, {0}Handle).position.x;\n",
CSharpCodeHelpers.MakeIdentifier(entry.Key)));
}
}
builder.Append(" InputSystem.QueueStateEvent(this, state);\n");
builder.Append(" }\n");
builder.Append("}\n");
if (!string.IsNullOrEmpty(namespaceName))
builder.Append("}\n");
// State struct.
builder.Append("public unsafe struct ");
builder.Append(stateStructName);
builder.Append(" : IInputStateTypeInfo\n");
builder.Append("{\n");
builder.Append(" public FourCC format\n");
builder.Append(" {\n");
builder.Append(" get {\n");
////TODO: handle class names that are shorter than 4 characters
////TODO: uppercase characters
builder.Append(
$" return new FourCC('{className[0]}', '{className[1]}', '{className[2]}', '{className[3]}');\n");
builder.Append(" }\n");
builder.Append(" }\n");
builder.Append("\n");
var totalButtonCount = 0;
foreach (var setEntry in actions)
{
var setEntryProperties = (Dictionary<string, object>)setEntry.Value;
// Buttons.
var buttons = (Dictionary<string, object>)setEntryProperties["Button"];
var buttonCount = buttons.Count;
if (buttonCount > 0)
{
foreach (var entry in buttons)
{
builder.Append(
$" [InputControl(name = \"{entry.Key}\", layout = \"Button\", bit = {totalButtonCount})]\n");
++totalButtonCount;
}
}
}
if (totalButtonCount > 0)
{
var byteCount = (totalButtonCount + 7) / 8;
builder.Append(" public fixed byte buttons[");
builder.Append(byteCount.ToString());
builder.Append("];\n");
}
foreach (var setEntry in actions)
{
var setEntryProperties = (Dictionary<string, object>)setEntry.Value;
// StickPadGyros.
var stickPadGyros = (Dictionary<string, object>)setEntryProperties["StickPadGyro"];
foreach (var entry in stickPadGyros)
{
var entryProperties = (Dictionary<string, object>)entry.Value;
var isStick = entryProperties.ContainsKey("input_mode") && (string)entryProperties["input_mode"] == "joystick_move";
builder.Append(
$" [InputControl(name = \"{entry.Key}\", layout = \"{(isStick ? "Stick" : "Vector2")}\")]\n");
builder.Append($" public Vector2 {CSharpCodeHelpers.MakeIdentifier(entry.Key)};\n");
}
// AnalogTriggers.
var analogTriggers = (Dictionary<string, object>)setEntryProperties["AnalogTrigger"];
foreach (var entry in analogTriggers)
{
builder.Append($" [InputControl(name = \"{entry.Key}\", layout = \"Axis\")]\n");
builder.Append($" public float {CSharpCodeHelpers.MakeIdentifier(entry.Key)};\n");
}
}
builder.Append("}\n");
builder.Append("#endif\n");
return builder.ToString();
}
/// <summary>
/// Convert an .inputactions asset to Steam VDF format.
/// </summary>
/// <param name="asset"></param>
/// <param name="locale"></param>
/// <returns>A string in Steam VDF format describing "In Game Actions" corresponding to the actions in
/// <paramref name="asset"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="asset"/> is null.</exception>
public static string ConvertInputActionsToSteamIGA(InputActionAsset asset, string locale = "english")
{
if (asset == null)
throw new ArgumentNullException("asset");
return ConvertInputActionsToSteamIGA(asset.actionMaps, locale: locale);
}
public static string ConvertInputActionsToSteamIGA(IEnumerable<InputActionMap> actionMaps, string locale = "english")
{
if (actionMaps == null)
throw new ArgumentNullException("actionMaps");
var localizationStrings = new Dictionary<string, string>();
var builder = new StringBuilder();
builder.Append("\"In Game Actions\"\n");
builder.Append("{\n");
// Add actions.
builder.Append("\t\"actions\"\n");
builder.Append("\t{\n");
// Add each action map.
foreach (var actionMap in actionMaps)
{
var actionMapName = actionMap.name;
var actionMapIdentifier = CSharpCodeHelpers.MakeIdentifier(actionMapName);
builder.Append("\t\t\"");
builder.Append(actionMapName);
builder.Append("\"\n");
builder.Append("\t\t{\n");
// Title.
builder.Append("\t\t\t\"title\"\t\"#Set_");
builder.Append(actionMapIdentifier);
builder.Append("\"\n");
localizationStrings["Set_" + actionMapIdentifier] = actionMapName;
// StickPadGyro actions.
builder.Append("\t\t\t\"StickPadGyro\"\n");
builder.Append("\t\t\t{\n");
foreach (var action in actionMap.actions.Where(x => GetSteamControllerInputType(x) == "StickPadGyro"))
ConvertInputActionToVDF(action, builder, localizationStrings);
builder.Append("\t\t\t}\n");
// AnalogTrigger actions.
builder.Append("\t\t\t\"AnalogTrigger\"\n");
builder.Append("\t\t\t{\n");
foreach (var action in actionMap.actions.Where(x => GetSteamControllerInputType(x) == "AnalogTrigger"))
ConvertInputActionToVDF(action, builder, localizationStrings);
builder.Append("\t\t\t}\n");
// Button actions.
builder.Append("\t\t\t\"Button\"\n");
builder.Append("\t\t\t{\n");
foreach (var action in actionMap.actions.Where(x => GetSteamControllerInputType(x) == "Button"))
ConvertInputActionToVDF(action, builder, localizationStrings);
builder.Append("\t\t\t}\n");
builder.Append("\t\t}\n");
}
builder.Append("\t}\n");
// Add localizations.
builder.Append("\t\"localization\"\n");
builder.Append("\t{\n");
builder.Append("\t\t\"");
builder.Append(locale);
builder.Append("\"\n");
builder.Append("\t\t{\n");
foreach (var entry in localizationStrings)
{
builder.Append("\t\t\t\"");
builder.Append(entry.Key);
builder.Append("\"\t\"");
builder.Append(entry.Value);
builder.Append("\"\n");
}
builder.Append("\t\t}\n");
builder.Append("\t}\n");
builder.Append("}\n");
return builder.ToString();
}
private static void ConvertInputActionToVDF(InputAction action, StringBuilder builder, Dictionary<string, string> localizationStrings)
{
builder.Append("\t\t\t\t\"");
builder.Append(action.name);
var mapIdentifier = CSharpCodeHelpers.MakeIdentifier(action.actionMap.name);
var actionIdentifier = CSharpCodeHelpers.MakeIdentifier(action.name);
var titleId = "Action_" + mapIdentifier + "_" + actionIdentifier;
localizationStrings[titleId] = action.name;
// StickPadGyros are objects. Everything else is just strings.
var inputType = GetSteamControllerInputType(action);
if (inputType == "StickPadGyro")
{
builder.Append("\"\n");
builder.Append("\t\t\t\t{\n");
// Title.
builder.Append("\t\t\t\t\t\"title\"\t\"#");
builder.Append(titleId);
builder.Append("\"\n");
// Decide on "input_mode". Assume "absolute_mouse" by default and take
// anything built on StickControl as "joystick_move".
var inputMode = "absolute_mouse";
var controlType = EditorInputControlLayoutCache.TryGetLayout(action.expectedControlType).type;
if (typeof(StickControl).IsAssignableFrom(controlType))
inputMode = "joystick_move";
builder.Append("\t\t\t\t\t\"input_mode\"\t\"");
builder.Append(inputMode);
builder.Append("\"\n");
builder.Append("\t\t\t\t}\n");
}
else
{
builder.Append("\"\t\"");
builder.Append(titleId);
builder.Append("\"\n");
}
}
public static string GetSteamControllerInputType(InputAction action)
{
if (action == null)
throw new ArgumentNullException("action");
// Make sure we have an expected control layout.
var expectedControlLayout = action.expectedControlType;
if (string.IsNullOrEmpty(expectedControlLayout))
throw new ArgumentException($"Cannot determine Steam input type for action '{action}' that has no associated expected control layout",
nameof(action));
// Try to fetch the layout.
var layout = EditorInputControlLayoutCache.TryGetLayout(expectedControlLayout);
if (layout == null)
throw new ArgumentException($"Cannot determine Steam input type for action '{action}'; cannot find layout '{expectedControlLayout}'", nameof(action));
// Map our supported control types.
var controlType = layout.type;
if (typeof(ButtonControl).IsAssignableFrom(controlType))
return "Button";
if (typeof(InputControl<float>).IsAssignableFrom(controlType))
return "AnalogTrigger";
if (typeof(Vector2Control).IsAssignableFrom(controlType))
return "StickPadGyro";
// Everything else throws.
throw new ArgumentException($"Cannot determine Steam input type for action '{action}'; layout '{expectedControlLayout}' with control type '{ controlType.Name}' has no known representation in the Steam controller API", nameof(action));
}
public static Dictionary<string, object> ParseVDF(string vdf)
{
var parser = new VDFParser(vdf);
return parser.Parse();
}
private struct VDFParser
{
public string vdf;
public int length;
public int position;
public VDFParser(string vdf)
{
this.vdf = vdf;
length = vdf.Length;
position = 0;
}
public Dictionary<string, object> Parse()
{
var result = new Dictionary<string, object>();
ParseKeyValuePair(result);
SkipWhitespace();
if (position < length)
throw new InvalidOperationException($"Parse error at {position} in '{vdf}'; not expecting any more input");
return result;
}
private bool ParseKeyValuePair(Dictionary<string, object> result)
{
var key = ParseString();
if (key.isEmpty)
return false;
SkipWhitespace();
if (position == length)
throw new InvalidOperationException($"Expecting value or object at position {position} in '{vdf}'");
var nextChar = vdf[position];
if (nextChar == '"')
{
var value = ParseString();
result[key.ToString()] = value.ToString();
}
else if (nextChar == '{')
{
var value = ParseObject();
result[key.ToString()] = value;
}
else
{
throw new InvalidOperationException($"Expecting value or object at position {position} in '{vdf}'");
}
return true;
}
private Substring ParseString()
{
SkipWhitespace();
if (position == length || vdf[position] != '"')
return new Substring();
++position;
var startPos = position;
while (position < length && vdf[position] != '"')
++position;
var endPos = position;
if (position < length)
++position;
return new Substring(vdf, startPos, endPos - startPos);
}
private Dictionary<string, object> ParseObject()
{
SkipWhitespace();
if (position == length || vdf[position] != '{')
return null;
var result = new Dictionary<string, object>();
++position;
while (position < length)
{
if (!ParseKeyValuePair(result))
break;
}
SkipWhitespace();
if (position == length || vdf[position] != '}')
throw new InvalidOperationException($"Expecting '}}' at position {position} in '{vdf}'");
++position;
return result;
}
private void SkipWhitespace()
{
while (position < length && char.IsWhiteSpace(vdf[position]))
++position;
}
}
[MenuItem("Assets/Steam/Export to Steam In-Game Actions File...", true)]
private static bool IsExportContextMenuItemEnabled()
{
return Selection.activeObject is InputActionAsset;
}
[MenuItem("Assets/Steam/Export to Steam In-Game Actions File...")]
private static void ExportContextMenuItem()
{
var selectedAsset = (InputActionAsset)Selection.activeObject;
// Determine default .vdf file name.
var defaultVDFName = "";
var directory = "";
var assetPath = AssetDatabase.GetAssetPath(selectedAsset);
if (!string.IsNullOrEmpty(assetPath))
{
defaultVDFName = Path.GetFileNameWithoutExtension(assetPath) + ".vdf";
directory = Path.GetDirectoryName(assetPath);
}
// Ask for save location.
var fileName = EditorUtility.SaveFilePanel("Export Steam In-Game Actions File", directory, defaultVDFName, "vdf");
if (!string.IsNullOrEmpty(fileName))
{
var text = ConvertInputActionsToSteamIGA(selectedAsset);
File.WriteAllText(fileName, text);
AssetDatabase.Refresh();
}
}
[MenuItem("Assets/Steam/Generate Unity Input Device...", true)]
private static bool IsGenerateContextMenuItemEnabled()
{
// VDF files have no associated importer and so come in as DefaultAssets.
if (!(Selection.activeObject is DefaultAsset))
return false;
var assetPath = AssetDatabase.GetAssetPath(Selection.activeObject);
if (!string.IsNullOrEmpty(assetPath) && Path.GetExtension(assetPath) == ".vdf")
return true;
return false;
}
////TODO: support setting class and namespace name
[MenuItem("Assets/Steam/Generate Unity Input Device...")]
private static void GenerateContextMenuItem()
{
var selectedAsset = Selection.activeObject;
var assetPath = AssetDatabase.GetAssetPath(selectedAsset);
if (string.IsNullOrEmpty(assetPath))
{
Debug.LogError("Cannot determine source asset path");
return;
}
var defaultClassName = Path.GetFileNameWithoutExtension(assetPath);
var defaultFileName = defaultClassName + ".cs";
var defaultDirectory = Path.GetDirectoryName(assetPath);
// Ask for save location.
var fileName = EditorUtility.SaveFilePanel("Generate C# Input Device Class", defaultDirectory, defaultFileName, "cs");
if (string.IsNullOrEmpty(fileName))
return;
// Load VDF file text.
var vdf = File.ReadAllText(assetPath);
// Generate and write output.
var className = Path.GetFileNameWithoutExtension(fileName);
var text = GenerateInputDeviceFromSteamIGA(vdf, className);
File.WriteAllText(fileName, text);
AssetDatabase.Refresh();
}
}
}
#endif // UNITY_EDITOR && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bb4d45fbf0aa84484b251242fdb21800
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 03672f9d7e73f4134820c35a3cd46a86
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,223 @@
#if UNITY_INPUT_SYSTEM_ENABLE_UI && UNITY_EDITOR
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.EventSystems;
using UnityEngine.InputSystem.Editor;
////TODO: add button to automatically set up gamepad mouse cursor support
namespace UnityEngine.InputSystem.UI.Editor
{
[CustomEditor(typeof(InputSystemUIInputModule))]
[InitializeOnLoad]
internal class InputSystemUIInputModuleEditor : UnityEditor.Editor
{
// ISX-1966 - It's unclear if this initializer will work correctly with CoreCLR and needs to be investigated.
static InputSystemUIInputModuleEditor()
{
#if UNITY_6000_0_OR_NEWER && ENABLE_INPUT_SYSTEM
InputModuleComponentFactory.SetInputModuleComponentOverride(
go => ObjectFactory.AddComponent<InputSystemUIInputModule>(go));
#endif
}
private static InputActionReference GetActionReferenceFromAssets(InputActionReference[] actions, params string[] actionNames)
{
foreach (var actionName in actionNames)
{
foreach (var action in actions)
{
if (action.action != null && string.Compare(action.action.name, actionName, StringComparison.InvariantCultureIgnoreCase) == 0)
return action;
}
}
return null;
}
private static InputActionReference[] GetAllAssetReferencesFromAssetDatabase(InputActionAsset actions)
{
if (actions == null)
return null;
var path = AssetDatabase.GetAssetPath(actions);
var assets = AssetDatabase.LoadAllAssetsAtPath(path);
return assets.Where(asset => asset is InputActionReference)
.Cast<InputActionReference>()
.OrderBy(x => x.name)
.ToArray();
}
private static readonly string[] s_ActionNames =
{
"Point",
"LeftClick",
"MiddleClick",
"RightClick",
"ScrollWheel",
"Move",
"Submit",
"Cancel",
"TrackedDevicePosition",
"TrackedDeviceOrientation"
};
private static readonly string[] s_ActionNiceNames =
{
"Point",
"Left Click",
"Middle Click",
"Right Click",
"Scroll Wheel",
"Move",
"Submit",
"Cancel",
"Tracked Position",
"Tracked Orientation"
};
private SerializedProperty[] m_ReferenceProperties;
private SerializedProperty m_ActionsAsset;
private InputActionReference[] m_AvailableActionReferencesInAssetDatabase;
private string[] m_AvailableActionsInAssetNames;
private bool m_AdvancedFoldoutState;
private string MakeActionReferenceNameUsableInGenericMenu(string name)
{
// Ugly hack: GenericMenu interprets "/" as a submenu path. But luckily, "/" is not the only slash we have in Unicode.
return name.Replace("/", "\uFF0F");
}
public void OnEnable()
{
var numActions = s_ActionNames.Length;
m_ReferenceProperties = new SerializedProperty[numActions];
for (var i = 0; i < numActions; i++)
m_ReferenceProperties[i] = serializedObject.FindProperty($"m_{s_ActionNames[i]}Action");
m_ActionsAsset = serializedObject.FindProperty("m_ActionsAsset");
m_AvailableActionReferencesInAssetDatabase = GetAllAssetReferencesFromAssetDatabase(m_ActionsAsset.objectReferenceValue as InputActionAsset);
m_AvailableActionsInAssetNames = new[] { "None" }
.Concat(m_AvailableActionReferencesInAssetDatabase?.Select(x => MakeActionReferenceNameUsableInGenericMenu(x.name)) ?? new string[0]).ToArray();
}
public void OnDisable()
{
new InputComponentEditorAnalytic(InputSystemComponent.InputSystemUIInputModule).Send();
}
public static void ReassignActions(InputSystemUIInputModule module, InputActionAsset action)
{
module.actionsAsset = action;
var assets = GetAllAssetReferencesFromAssetDatabase(action);
if (assets != null)
{
module.point = GetActionReferenceFromAssets(assets, module.point?.action?.name, "Point", "MousePosition", "Mouse Position");
module.leftClick = GetActionReferenceFromAssets(assets, module.leftClick?.action?.name, "Click", "LeftClick", "Left Click");
module.rightClick = GetActionReferenceFromAssets(assets, module.rightClick?.action?.name, "RightClick", "Right Click", "ContextClick", "Context Click", "ContextMenu", "Context Menu");
module.middleClick = GetActionReferenceFromAssets(assets, module.middleClick?.action?.name, "MiddleClick", "Middle Click");
module.scrollWheel = GetActionReferenceFromAssets(assets, module.scrollWheel?.action?.name, "ScrollWheel", "Scroll Wheel", "Scroll", "Wheel");
module.move = GetActionReferenceFromAssets(assets, module.move?.action?.name, "Navigate", "Move");
module.submit = GetActionReferenceFromAssets(assets, module.submit?.action?.name, "Submit");
module.cancel = GetActionReferenceFromAssets(assets, module.cancel?.action?.name, "Cancel", "Esc", "Escape");
module.trackedDevicePosition = GetActionReferenceFromAssets(assets, module.trackedDevicePosition?.action?.name, "TrackedDevicePosition", "Position");
module.trackedDeviceOrientation = GetActionReferenceFromAssets(assets, module.trackedDeviceOrientation?.action?.name, "TrackedDeviceOrientation", "Orientation");
}
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_ActionsAsset);
if (EditorGUI.EndChangeCheck())
{
var actions = m_ActionsAsset.objectReferenceValue as InputActionAsset;
if (actions != null)
{
serializedObject.ApplyModifiedProperties();
ReassignActions(target as InputSystemUIInputModule, actions);
serializedObject.Update();
}
// reinitialize action types
OnEnable();
}
var numActions = s_ActionNames.Length;
if ((m_AvailableActionReferencesInAssetDatabase != null && m_AvailableActionReferencesInAssetDatabase.Length > 0) || m_ActionsAsset.objectReferenceValue == null)
{
for (var i = 0; i < numActions; i++)
{
// find the input action reference from the asset that matches the input action reference from the
// InputSystemUIInputModule that is currently selected. Note we can't use reference equality of the
// two InputActionReference objects here because in ReassignActions above, we create new instances
// every time it runs.
var index = IndexOfInputActionInAsset(
((InputActionReference)m_ReferenceProperties[i]?.objectReferenceValue)?.action);
EditorGUI.BeginChangeCheck();
index = EditorGUILayout.Popup(s_ActionNiceNames[i], index, m_AvailableActionsInAssetNames);
if (EditorGUI.EndChangeCheck())
m_ReferenceProperties[i].objectReferenceValue =
index > 0 ? m_AvailableActionReferencesInAssetDatabase[index - 1] : null;
}
}
else
{
// Somehow we have an asset but no asset references from the database, pull out references manually and show them in read only UI
EditorGUILayout.HelpBox("Showing fields as read-only because current action asset seems to be created by a script and assigned programmatically.", MessageType.Info);
EditorGUI.BeginDisabledGroup(true);
for (var i = 0; i < numActions; i++)
{
var retrievedName = "None";
if (m_ReferenceProperties[i].objectReferenceValue != null &&
(m_ReferenceProperties[i].objectReferenceValue is InputActionReference reference))
retrievedName = MakeActionReferenceNameUsableInGenericMenu(reference.ToDisplayName());
EditorGUILayout.Popup(s_ActionNiceNames[i], 0, new[] {retrievedName});
}
EditorGUI.EndDisabledGroup();
}
m_AdvancedFoldoutState = EditorGUILayout.Foldout(m_AdvancedFoldoutState, new GUIContent("Advanced"), true);
if (m_AdvancedFoldoutState)
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_CursorLockBehavior"),
EditorGUIUtility.TrTextContent("Cursor Lock Behavior",
$"Controls the origin point of UI raycasts when the cursor is locked. {InputSystemUIInputModule.CursorLockBehavior.OutsideScreen} " +
$"is the default behavior and will force the raycast to miss all objects. {InputSystemUIInputModule.CursorLockBehavior.ScreenCenter} " +
$"will cast the ray from the center of the screen."));
if (GUI.changed)
serializedObject.ApplyModifiedProperties();
}
private int IndexOfInputActionInAsset(InputAction inputAction)
{
// return 0 instead of -1 here because the zero-th index refers to the 'None' binding.
if (inputAction == null)
return 0;
if (m_AvailableActionReferencesInAssetDatabase == null)
return 0;
var index = 0;
for (var j = 0; j < m_AvailableActionReferencesInAssetDatabase.Length; j++)
{
if (m_AvailableActionReferencesInAssetDatabase[j].action != null &&
m_AvailableActionReferencesInAssetDatabase[j].action == inputAction)
{
index = j + 1;
break;
}
}
return index;
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f0800c72aa1894e478bb749990855f9e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,57 @@
#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_ENABLE_UI
using System;
using UnityEditor;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.Editor;
namespace UnityEngine.InputSystem.UI.Editor
{
// The only purpose of the Input System suppying a custom editor for the UI StandaloneInputModule is to guide users to using
// the Input System's InputSystemUIInputModule instead.
[CustomEditor(typeof(StandaloneInputModule))]
internal class StandaloneInputModuleModuleEditor : UnityEditor.Editor
{
SerializedProperty enableNativePlatformBackendsForNewInputSystem;
SerializedProperty disableOldInputManagerSupport;
public void OnEnable()
{
var allPlayerSettings = Resources.FindObjectsOfTypeAll<PlayerSettings>();
if (allPlayerSettings.Length > 0)
{
var playerSettings = Resources.FindObjectsOfTypeAll<PlayerSettings>()[0];
var so = new SerializedObject(playerSettings);
enableNativePlatformBackendsForNewInputSystem = so.FindProperty("enableNativePlatformBackendsForNewInputSystem");
disableOldInputManagerSupport = so.FindProperty("disableOldInputManagerSupport");
}
}
public override void OnInspectorGUI()
{
// We assume that if these properties don't exist (ie are null), then that's because the new Input System has become the default.
if (enableNativePlatformBackendsForNewInputSystem == null || enableNativePlatformBackendsForNewInputSystem.boolValue)
{
if (disableOldInputManagerSupport == null || disableOldInputManagerSupport.boolValue)
EditorGUILayout.HelpBox("You are using StandaloneInputModule, which uses the old InputManager. You are using the new InputSystem, and have the old InputManager disabled. StandaloneInputModule will not work. Click the button below to replace this component with a InputSystemUIInputModule, which uses the new InputSystem.", MessageType.Error);
else
EditorGUILayout.HelpBox("You are using StandaloneInputModule, which uses the old InputManager. You also have the new InputSystem enabled in your project. Click the button below to replace this component with a InputSystemUIInputModule, which uses the new InputSystem (recommended).", MessageType.Info);
if (GUILayout.Button("Replace with InputSystemUIInputModule"))
{
var go = ((StandaloneInputModule)target).gameObject;
Undo.DestroyObjectImmediate(target);
Undo.AddComponent<InputSystemUIInputModule>(go);
return;
}
GUILayout.Space(10);
}
base.OnInspectorGUI();
}
public void OnDisable()
{
new InputComponentEditorAnalytic(InputSystemComponent.StandaloneInputModule).Send();
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bd2cb6115f94b4c129408ea68717f9c3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b67ddb076aa7b4301b132a1012ba666d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,573 @@
#if UNITY_EDITOR
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using UnityEditor;
using UnityEngine.InputSystem.LowLevel;
namespace UnityEngine.InputSystem
{
/// <summary>
/// Adds support for processing input-related messages sent from the <c>Unite Remote</c> app.
/// </summary>
/// <remarks>
/// A hook in the Unity runtime allows us to observe messages received from the remote (see Modules/GenericRemoteEditor).
/// We get the binary blob of each message and a shot at processing the message instead of
/// the native code doing it.
/// </remarks>
internal static class UnityRemoteSupport
{
public static bool isConnected => s_State.connected;
private static Func<IntPtr, bool> s_RegisteredNativeHandler;
/// <summary>
/// When set (by tests), this delegate receives messages instead of the native path.
/// Cleared in <see cref="ResetGlobalState"/>.
/// </summary>
internal static Func<IntPtr, bool> s_TestMessageDispatch;
/// <summary>
/// Set whenever gyro commands are forwarded to the editor native API (for test observability).
/// </summary>
internal static bool? unityRemoteGyroEnabled;
/// <summary>
/// Set whenever the gyro update interval is forwarded to the editor native API (for test observability).
/// </summary>
internal static float? unityRemoteGyroUpdateInterval;
/// <summary>
/// Used by tests that run with a test runtime; the test fixture installs
/// <see cref="s_TestMessageDispatch"/> to deliver messages into <see cref="ProcessMessageFromUnityRemote"/>.
/// </summary>
internal static Func<IntPtr, bool> GetMessageHandlerForTesting() => ProcessMessageFromUnityRemote;
public static void Initialize()
{
SetNativeMessageHandler(ProcessMessageFromUnityRemote);
InputSystem.onSettingsChange += OnSettingsChanged;
}
private static void OnSettingsChanged()
{
if (InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kDisableUnityRemoteSupport))
{
SetNativeMessageHandler(null);
if (s_State.connected)
Disconnect();
}
else
SetNativeMessageHandler(ProcessMessageFromUnityRemote);
}
private static MethodInfo GetUnityRemoteAPIMethod(string methodName)
{
var editorAssembly = typeof(EditorApplication).Assembly;
var genericRemoteClass = editorAssembly.GetType("UnityEditor.Remote.GenericRemote");
if (genericRemoteClass == null)
return null;
return genericRemoteClass.GetMethod(methodName);
}
private static void SetNativeMessageHandler(Func<IntPtr, bool> handler)
{
if (s_RegisteredNativeHandler != null)
{
var removeMethod = GetUnityRemoteAPIMethod("RemoveMessageHandler");
removeMethod?.Invoke(null, new object[] { s_RegisteredNativeHandler });
}
s_RegisteredNativeHandler = handler;
if (handler != null)
{
var addMethod = GetUnityRemoteAPIMethod("AddMessageHandler");
addMethod?.Invoke(null, new object[] { handler });
}
}
private static void SetNativeGyroEnabled(bool value)
{
unityRemoteGyroEnabled = value;
var setMethod = GetUnityRemoteAPIMethod("SetGyroEnabled");
setMethod?.Invoke(null, new object[] { value });
}
private static void SetNativeGyroUpdateInterval(float interval)
{
unityRemoteGyroUpdateInterval = interval;
var setMethod = GetUnityRemoteAPIMethod("SetGyroUpdateInterval");
setMethod?.Invoke(null, new object[] { interval });
}
private static unsafe bool ProcessMessageFromUnityRemote(IntPtr messageData)
{
var messageHeader = (MessageHeader*)messageData;
switch (messageHeader->type)
{
case (byte)MessageType.Hello:
if (s_State.connected)
break;
if (InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kDisableUnityRemoteSupport))
break;
// Install handlers.
s_State.deviceChangeHandler = OnDeviceChange;
s_State.deviceCommandHandler = OnDeviceCommand; ////REVIEW: We really should have a way of installing a handler just for a specific device.
InputSystem.onDeviceChange += s_State.deviceChangeHandler;
InputSystem.onDeviceCommand += s_State.deviceCommandHandler;
// Add devices.
s_State.touchscreen = InputSystem.AddDevice<Touchscreen>();
s_State.touchscreen.m_DeviceFlags |= InputDevice.DeviceFlags.Remote;
s_State.accelerometer = InputSystem.AddDevice<Accelerometer>();
s_State.accelerometer.m_DeviceFlags |= InputDevice.DeviceFlags.Remote;
// Gryo etc. added only when we receive GyroSettingsMessage.
s_State.connected = true;
Debug.Log("Unity Remote connected to input!");
break;
case (byte)MessageType.Goodbye:
if (!s_State.connected)
break;
Disconnect();
Debug.Log("Unity Remote disconnected from input!");
break;
case (byte)MessageType.Options:
var optionsMessage = (OptionsMessage*)messageData;
s_State.screenSize = DetermineScreenSize(optionsMessage->dimension1, optionsMessage->dimension2);
break;
case (byte)MessageType.TouchInput:
if (s_State.touchscreen == null)
break;
// Android Remote seems to not be sending the last two fields (azimuthAngle and attitudeAngle).
if (messageHeader->length < 56)
break;
var touchMessage = (TouchInputMessage*)messageData;
var phase = TouchPhase.None;
switch (touchMessage->phase)
{
case (int)UnityEngine.TouchPhase.Began: phase = TouchPhase.Began; break;
case (int)UnityEngine.TouchPhase.Canceled: phase = TouchPhase.Canceled; break;
case (int)UnityEngine.TouchPhase.Ended: phase = TouchPhase.Ended; break;
case (int)UnityEngine.TouchPhase.Moved: phase = TouchPhase.Moved; break;
// Ignore stationary.
}
if (phase == default)
break;
InputSystem.QueueStateEvent(s_State.touchscreen, new TouchState
{
touchId = touchMessage->id + 1,
phase = phase,
position = MapRemoteTouchCoordinatesToLocal(new Vector2(touchMessage->positionX, touchMessage->positionY)),
radius = new Vector2(touchMessage->radius, touchMessage->radius),
pressure = touchMessage->pressure
});
break;
case (byte)MessageType.GyroSettings:
var gyroSettingsMessage = (GyroSettingsMessage*)messageData;
if (!s_State.gyroInitialized)
{
// Message itself indicates presence of a gyro. Add the devices.
s_State.gyroscope = InputSystem.AddDevice<Gyroscope>();
s_State.attitude = InputSystem.AddDevice<AttitudeSensor>();
s_State.gravity = InputSystem.AddDevice<GravitySensor>();
s_State.linearAcceleration = InputSystem.AddDevice<LinearAccelerationSensor>();
s_State.gyroscope.m_DeviceFlags |= InputDevice.DeviceFlags.Remote;
s_State.attitude.m_DeviceFlags |= InputDevice.DeviceFlags.Remote;
s_State.gravity.m_DeviceFlags |= InputDevice.DeviceFlags.Remote;
s_State.linearAcceleration.m_DeviceFlags |= InputDevice.DeviceFlags.Remote;
s_State.gyroInitialized = true;
}
// Disable them if they are not currently enabled.
if (gyroSettingsMessage->enabled == 0)
{
InputSystem.DisableDevice(s_State.gyroscope);
InputSystem.DisableDevice(s_State.attitude);
InputSystem.DisableDevice(s_State.gravity);
InputSystem.DisableDevice(s_State.linearAcceleration);
}
else
{
s_State.gyroEnabled = true;
}
s_State.gyroUpdateInterval = gyroSettingsMessage->receivedGyroUpdateInternal;
break;
case (byte)MessageType.GyroInput:
var gyroInputMessage = (GyroInputMessage*)messageData;
if (s_State.attitude != null && s_State.attitude.enabled)
{
InputSystem.QueueStateEvent(s_State.attitude, new AttitudeState
{
attitude = new Quaternion(gyroInputMessage->attitudeX, gyroInputMessage->attitudeY, gyroInputMessage->attitudeZ,
gyroInputMessage->attitudeW)
});
}
if (s_State.gyroscope != null && s_State.gyroscope.enabled)
{
InputSystem.QueueStateEvent(s_State.gyroscope, new GyroscopeState
{
angularVelocity = new Vector3(gyroInputMessage->rotationRateX, gyroInputMessage->rotationRateY,
gyroInputMessage->rotationRateZ)
});
}
if (s_State.gravity != null && s_State.gravity.enabled)
{
InputSystem.QueueStateEvent(s_State.gravity, new GravityState
{
gravity = new Vector3(gyroInputMessage->gravityX, gyroInputMessage->gravityY,
gyroInputMessage->gravityZ)
});
}
if (s_State.linearAcceleration != null && s_State.linearAcceleration.enabled)
{
InputSystem.QueueStateEvent(s_State.linearAcceleration, new LinearAccelerationState
{
acceleration = new Vector3(gyroInputMessage->userAccelerationX, gyroInputMessage->userAccelerationY,
gyroInputMessage->userAccelerationZ)
});
}
break;
case (byte)MessageType.AccelerometerInput:
if (s_State.accelerometer == null)
break;
var accelerometerMessage = (AccelerometerInputMessage*)messageData;
InputSystem.QueueStateEvent(s_State.accelerometer, new AccelerometerState
{
acceleration = new Vector3(accelerometerMessage->accelerationX, accelerometerMessage->accelerationY,
accelerometerMessage->accelerationZ)
});
break;
}
return false;
}
private static void Disconnect()
{
InputSystem.RemoveDevice(s_State.touchscreen);
InputSystem.RemoveDevice(s_State.accelerometer);
if (s_State.gyroscope != null)
InputSystem.RemoveDevice(s_State.gyroscope);
if (s_State.attitude != null)
InputSystem.RemoveDevice(s_State.attitude);
if (s_State.gravity != null)
InputSystem.RemoveDevice(s_State.gravity);
if (s_State.linearAcceleration != null)
InputSystem.RemoveDevice(s_State.linearAcceleration);
ResetGlobalState();
}
private static void OnDeviceChange(InputDevice device, InputDeviceChange change)
{
switch (change)
{
case InputDeviceChange.Removed:
// Deal with someone manually removing one of our devices.
if (device == s_State.accelerometer)
s_State.accelerometer = null;
else if (device == s_State.attitude)
s_State.accelerometer = null;
else if (device == s_State.gravity)
s_State.gravity = null;
else if (device == s_State.gyroscope)
s_State.gyroscope = null;
else if (device == s_State.touchscreen)
s_State.touchscreen = null;
else if (device == s_State.linearAcceleration)
s_State.linearAcceleration = null;
break;
case InputDeviceChange.Enabled:
case InputDeviceChange.Disabled:
// If it's any of our devices that make up the remote gyro,
// send a message to the remote.
if (device == s_State.attitude || device == s_State.gravity || device == s_State.gyroscope ||
device == s_State.linearAcceleration)
{
SyncGyroEnabledInRemote();
}
break;
}
}
private static unsafe long? OnDeviceCommand(InputDevice device, InputDeviceCommand* command)
{
if (device != s_State.attitude && device != s_State.gyroscope && device != s_State.gravity &&
device != s_State.linearAcceleration)
return null;
if (command->type == SetSamplingFrequencyCommand.Type)
{
s_State.gyroUpdateInterval = ((SetSamplingFrequencyCommand*)command)->frequency;
SetNativeGyroUpdateInterval(s_State.gyroUpdateInterval);
return InputDeviceCommand.GenericSuccess;
}
if (command->type == QuerySamplingFrequencyCommand.Type)
{
((QuerySamplingFrequencyCommand*)command)->frequency = s_State.gyroUpdateInterval;
return InputDeviceCommand.GenericSuccess;
}
return InputDeviceCommand.GenericFailure;
}
private static void SyncGyroEnabledInRemote()
{
var enabled = (s_State.attitude?.enabled ?? false) || (s_State.gravity?.enabled ?? false) ||
(s_State.gyroscope?.enabled ?? false) || (s_State.linearAcceleration?.enabled ?? false);
if (enabled != s_State.gyroEnabled)
{
s_State.gyroEnabled = enabled;
SetNativeGyroEnabled(enabled);
}
}
// This is taken from HandleOptionsMessage() in GenericRemote.cpp.
private static Vector2 DetermineScreenSize(int dimension1, int dimension2)
{
const float kMaxPixels = 640 * 480; // limit the resolution to VGA
float screenPixels = dimension1 * dimension2;
var divider = (int)Mathf.Ceil(Mathf.Sqrt(screenPixels / kMaxPixels));
if (divider == 0)
return default;
var hdim1 = dimension1 / divider;
var hdim2 = dimension2 / divider;
// GetConfigValue is private. Reflect around it.
var getConfigValueMethod = typeof(EditorSettings).GetMethod("GetConfigValue");
if (getConfigValueMethod != null && "Normal".Equals(getConfigValueMethod.Invoke(null, new[] { "UnityRemoteResolution" })))
{
hdim1 = dimension1;
hdim2 = dimension2;
}
if (hdim1 >= 1 && hdim2 >= 1)
return new Vector2(dimension1, dimension2);
return default;
}
private static Vector2 MapRemoteTouchCoordinatesToLocal(Vector2 position)
{
var screenSizeRemote = s_State.screenSize;
var screenSizeLocal = InputRuntime.s_Instance.screenSize;
return new Vector2(
position.x / screenSizeRemote.x * screenSizeLocal.x,
position.y = position.y / screenSizeRemote.y * screenSizeLocal.y);
}
// See Editor/Src/RemoteInput/GenericRemote.cpp
internal enum MessageType : byte
{
Invalid = 0,
Hello = 1,
Options = 2,
GyroSettings = 3,
DeviceOrientation = 4,
DeviceFeatures = 5,
TouchInput = 10,
AccelerometerInput = 11,
TrackBallInput = 12,
Key = 13,
GyroInput = 14,
MousePresence = 15,
JoystickInput = 16,
JoystickNames = 17,
WebCamDeviceList = 20,
WebCamStream = 21,
LocationServiceData = 30,
CompassData = 31,
Goodbye = 32,
Reserved = 255,
}
internal interface IUnityRemoteMessage
{
byte staticType { get; }
}
[StructLayout(LayoutKind.Explicit, Size = 5)]
internal struct MessageHeader
{
// Unfortunately, the header has an odd 5 byte length and everything
// coming after it is misaligned. Reason is that native reads is as a stream
// and wants to pack tightly.
[FieldOffset(0)] public byte type;
[FieldOffset(1)] public int length;
}
[StructLayout(LayoutKind.Explicit)]
internal unsafe struct HelloMessage : IUnityRemoteMessage
{
[FieldOffset(0)] public MessageHeader header;
[FieldOffset(5)] public uint protocolIdLength;
[FieldOffset(9)] public fixed char protocolId[11];
[FieldOffset(20)] public int protocolVersion;
public byte staticType => (byte)MessageType.Hello;
public static HelloMessage Create()
{
var msg = default(HelloMessage);
msg.protocolIdLength = 11;
msg.protocolId[0] = 'U';
msg.protocolId[1] = 'n';
msg.protocolId[2] = 'i';
msg.protocolId[3] = 't';
msg.protocolId[4] = 'y';
msg.protocolId[5] = 'R';
msg.protocolId[6] = 'e';
msg.protocolId[7] = 'm';
msg.protocolId[8] = 'o';
msg.protocolId[9] = 't';
msg.protocolId[10] = 'e';
msg.protocolVersion = 0;
return msg;
}
}
[StructLayout(LayoutKind.Explicit)]
internal struct OptionsMessage : IUnityRemoteMessage
{
[FieldOffset(0)] public MessageHeader header;
[FieldOffset(5)] public int dimension1;
[FieldOffset(9)] public int dimension2;
public byte staticType => (byte)MessageType.Options;
}
[StructLayout(LayoutKind.Explicit)]
internal struct GoodbyeMessage : IUnityRemoteMessage
{
[FieldOffset(0)] public MessageHeader header;
public byte staticType => (byte)MessageType.Goodbye;
}
[StructLayout(LayoutKind.Explicit)]
internal struct TouchInputMessage : IUnityRemoteMessage
{
[FieldOffset(0)] public MessageHeader header;
[FieldOffset(5)] public float positionX;
[FieldOffset(9)] public float positionY;
[FieldOffset(13)] public ulong frame;
[FieldOffset(21)] public int id;
[FieldOffset(25)] public int phase;
[FieldOffset(29)] public int tapCount;
[FieldOffset(33)] public float radius;
[FieldOffset(37)] public float radiusVariance;
[FieldOffset(41)] public int type;
[FieldOffset(45)] public float pressure;
[FieldOffset(49)] public float maximumPossiblePressure;
[FieldOffset(53)] public float azimuthAngle;
[FieldOffset(57)] public float altitudeAngle;
public byte staticType => (byte)MessageType.TouchInput;
}
[StructLayout(LayoutKind.Explicit)]
internal struct GyroSettingsMessage : IUnityRemoteMessage
{
[FieldOffset(0)] public MessageHeader header;
[FieldOffset(5)] public int enabled;
[FieldOffset(9)] public float receivedGyroUpdateInternal;
public byte staticType => (byte)MessageType.GyroSettings;
}
[StructLayout(LayoutKind.Explicit)]
internal struct GyroInputMessage : IUnityRemoteMessage
{
[FieldOffset(0)] public MessageHeader header;
[FieldOffset(5)] public float rotationRateX;
[FieldOffset(9)] public float rotationRateY;
[FieldOffset(13)] public float rotationRateZ;
[FieldOffset(17)] public float rotationRateUnbiasedX;
[FieldOffset(21)] public float rotationRateUnbiasedY;
[FieldOffset(25)] public float rotationRateUnbiasedZ;
[FieldOffset(29)] public float gravityX;
[FieldOffset(33)] public float gravityY;
[FieldOffset(37)] public float gravityZ;
[FieldOffset(41)] public float userAccelerationX;
[FieldOffset(45)] public float userAccelerationY;
[FieldOffset(49)] public float userAccelerationZ;
[FieldOffset(53)] public float attitudeX;
[FieldOffset(57)] public float attitudeY;
[FieldOffset(61)] public float attitudeZ;
[FieldOffset(65)] public float attitudeW;
public byte staticType => (byte)MessageType.GyroInput;
}
[StructLayout(LayoutKind.Explicit)]
internal struct AccelerometerInputMessage : IUnityRemoteMessage
{
[FieldOffset(0)] public MessageHeader header;
[FieldOffset(5)] public float accelerationX;
[FieldOffset(9)] public float accelerationY;
[FieldOffset(13)] public float accelerationZ;
[FieldOffset(17)] public float deltaTime;
public byte staticType => (byte)MessageType.AccelerometerInput;
}
private struct State
{
public bool connected;
public bool gyroInitialized;
public bool gyroEnabled;
public float gyroUpdateInterval;
public Vector2 screenSize;
public Action<InputDevice, InputDeviceChange> deviceChangeHandler;
public InputDeviceCommandDelegate deviceCommandHandler;
// Devices that we create for receiving input from the remote.
public Touchscreen touchscreen;
public Accelerometer accelerometer;
public Gyroscope gyroscope;
public AttitudeSensor attitude;
public GravitySensor gravity;
public LinearAccelerationSensor linearAcceleration;
}
private static State s_State;
////TODO: hook this into Hakan's new cleanup mechanism
internal static void ResetGlobalState()
{
if (s_State.deviceChangeHandler != null)
InputSystem.onDeviceChange -= s_State.deviceChangeHandler;
if (s_State.deviceCommandHandler != null)
InputSystem.onDeviceCommand -= s_State.deviceCommandHandler;
s_State = default;
s_TestMessageDispatch = null;
unityRemoteGyroEnabled = null;
unityRemoteGyroUpdateInterval = null;
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 156cc514fbe5442381b68a445ee3bf6a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0210636210b964e4cb07b817e5ce6481
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,34 @@
#if UNITY_EDITOR
using System;
using UnityEditor;
namespace UnityEngine.InputSystem
{
internal class InputSettingsiOSProvider
{
[NonSerialized] private SerializedProperty m_MotionUsageEnabled;
[NonSerialized] private SerializedProperty m_MotionUsageDescription;
private GUIContent m_MotionUsageContent;
private GUIContent m_MotionUsageDescriptionContent;
public InputSettingsiOSProvider(SerializedObject parent)
{
var prefix = "m_iOSSettings.m_MotionUsage";
m_MotionUsageEnabled = parent.FindProperty(prefix + ".m_Enabled");
m_MotionUsageDescription = parent.FindProperty(prefix + ".m_Description");
m_MotionUsageContent = new GUIContent("Motion Usage", "Enables Motion Usage for the app, required for sensors like Step Counter. This also adds 'Privacy - Motion Usage Description' entry to Info.plist");
m_MotionUsageDescriptionContent = new GUIContent(" Description", "Describe why the app wants to access the device's Motion Usage sensor.");
}
public void OnGUI()
{
EditorGUILayout.PropertyField(m_MotionUsageEnabled, m_MotionUsageContent);
EditorGUI.BeginDisabledGroup(!m_MotionUsageEnabled.boolValue);
EditorGUILayout.PropertyField(m_MotionUsageDescription, m_MotionUsageDescriptionContent);
EditorGUI.EndDisabledGroup();
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6e6208eb562aa4ce7be0e1760d82a313
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,39 @@
#if UNITY_EDITOR
using System.IO;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
using UnityEngine.InputSystem;
namespace UnityEngine.InputSystem
{
internal class iOSPostProcessBuild
{
[PostProcessBuild]
public static void UpdateInfoPList(BuildTarget buildTarget, string pathToBuiltProject)
{
if (buildTarget != BuildTarget.iOS)
return;
var settings = InputSystem.settings.iOS;
if (!settings.motionUsage.enabled)
return;
var plistPath = pathToBuiltProject + "/Info.plist";
var contents = File.ReadAllText(plistPath);
var description = InputSystem.settings.iOS.motionUsage.usageDescription;
#if UNITY_IOS || UNITY_TVOS
var plist = new UnityEditor.iOS.Xcode.PlistDocument();
plist.ReadFromString(contents);
var root = plist.root;
var buildKey = "NSMotionUsageDescription";
if (root[buildKey] != null)
Debug.LogWarning($"{buildKey} is already present in Info.plist, the value will be overwritten.");
root.SetString(buildKey, description);
File.WriteAllText(plistPath, plist.WriteToString());
#endif
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 536defedfe4153e4cabde208f78a818d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: