diff --git a/.signature b/.signature new file mode 100644 index 00000000..267ace45 --- /dev/null +++ b/.signature @@ -0,0 +1 @@ +{"timestamp":1737020322,"signature":"PdimvrUBR29LsZgyaI/7Bm+nzjuugBdQ5zBtKxEw9A+PqIv09hUHibJabnoksz19QA3zR2eOGcDPxmZg5Uy1ELSJX54gDmo0KuofPBA8lswUT/PSDlqijS64IfdCt7QuYq2BGq7U7HHQuOjPBLWqkJE4LhLiKu5SssSjZ0ikME3SrwthwNDgt2LTMO+niM7DPxiKukiVaZlmCJPL+kZeBD5YqZ89O3IGGn/t3w/YsX32X2Iq3BirCWpwKpKjFo7vyglcPTyoPx2J7KAYk280OFuq3FwkF0bTjgYOcbN1v/8i4Xu4qIcNrAvAxTYhSJo++iyaUEqoqd40cK2EjNUDDnCRi9mIIMZmD5stB9nehq9+GN+x1Ne6Al6dYGTkVBxnz9WiVPz75JI2DW8B4nLBMVFuOg9+FRT0eZZcn73x5VwXBIl26F+18ZYKpTJVuY3FvTXJ7G7dRwIvuo/RVB8yuyu8jE1m1QC35W+7yeIOKSqCAvqwZF0gDXIXMPzQVyig","publicKey":"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQm9qQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FZOEFNSUlCaWdLQ0FZRUFzdUhXYUhsZ0I1cVF4ZEJjTlJKSAordHR4SmoxcVY1NTdvMlZaRE1XaXhYRVBkRTBEMVFkT1JIRXNSS1RscmplUXlERU83ZlNQS0ZwZ1A3MU5TTnJCCkFHM2NFSU45aHNQVDhOVmllZmdWem5QTkVMenFkVmdEbFhpb2VpUnV6OERKWFgvblpmU1JWKytwbk9ySTRibG4KS0twelJlNW14OTc1SjhxZ1FvRktKT0NNRlpHdkJMR2MxSzZZaEIzOHJFODZCZzgzbUovWjBEYkVmQjBxZm13cgo2ZDVFUXFsd0E5Y3JZT1YyV1VpWXprSnBLNmJZNzRZNmM1TmpBcEFKeGNiaTFOaDlRVEhUcU44N0ZtMDF0R1ZwCjVNd1pXSWZuYVRUemEvTGZLelR5U0pka0tldEZMVGdkYXpMYlpzUEE2aHBSK0FJRTJhc0tLTi84UUk1N3UzU2cKL2xyMnZKS1IvU2l5eEN1Q20vQWJkYnJMbXk0WjlSdm1jMGdpclA4T0lLQWxBRWZ2TzV5Z2hSKy8vd1RpTFlzUQp1SllDM0V2UE16ZGdKUzdGR2FscnFLZzlPTCsxVzROY05yNWdveVdSUUJ0cktKaWlTZEJVWmVxb0RvSUY5NHpCCndGbzJJT1JFdXFqcU51M3diMWZIM3p1dGdtalFra3IxVjJhd3hmcExLWlROQWdNQkFBRT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg"} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ab76b9..0a7323db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,50 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. Due to package verification, the latest version below is the unpublished version and the date is meaningless. however, it has to be formatted properly to pass verification tests. +## [1.12.0] - 2025-01-15 + +### Fixed +- Fixed an issue causing the Action context menu to not show on right click when right clicking an action in the Input Action Editor [ISXB-1134](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1134). +- Reverted changes from 0ddd534d8 (ISXB-746) which introduced a regression [ISXB-1127](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1127). +- Fixed `ArgumentNullException: Value cannot be null.` during the migration of Project-wide Input Actions from `InputManager.asset` to `InputSystem_Actions.inputactions` asset which lead do the lost of the configuration [ISXB-1105](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1105). +- Fixed pointerId staying the same when simultaneously releasing and then pressing in the same frame on mobile using touch. [ISXB-1006](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-845). +- Fixed ISubmitHandler.OnSubmit event processing when operating in Manual Update mode (ISXB-1141). +- Fixed Rename mode is not entered and name is autocompleted to default when creating a new Action Map on 2022.3. [ISXB-1151](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1151). +- Fixed unexpected control scheme switch when using `OnScreenControl` and pointer based schemes which registed "Cancel" event on every frame.[ISXB-656](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-656). +- Fixed an issue with The "Add Control Scheme..." popup window so that it now persists until any changes are explicitly Saved or Cancelled [case ISXB-1131](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1131). +- Fixed missing documentation for source generated Input Action Assets. This is now generated as part of the source code generation step when "Generate C# Class" is checked in the importer inspector settings. +- Fixed pasting into an empty map list raising an exception. [ISXB-1150](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1150) +- Fixed pasting bindings into empty Input Action asset. [ISXB-1180](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1180) +- Fixed missing '&' symbol in Control Scheme dropdown on Windows platform. [ISXB-1109](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1109) +- Fixed icon scaling in Input Actions window. +- Fixed an issue where removing the InputSystem package could lead to invalid input handling settings. +- Fixed `ArgumentOutOfRangeException` when adding a new Control Scheme with any Device selected. [ISXB-1129](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1129) +- Fixed a CS0105 compiler warning due to duplicate using statement in test source code (ISXB-1247). +- Fixed tooltip support in the UI Toolkit version of the Input Actions Asset editor. +- Fixed documentation to clarify bindings with modifiers `overrideModifiersNeedToBePressedFirst` configuration [ISXB-806](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-806). +- Fixed an issue in `Samples/Visualizers/GamepadVisualizer.unity` sample where the visualization wouldn't handle device disconnects or current device changes properly (ISXB-1243). +- Fixed an issue when displaying Serialized InputAction's Processor properties inside the Inspector window. [ISXB-1269](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1269) +- Fixed an issue with default device selection when adding new Control Scheme. +- Fixed an issue where action map delegates were not updated when the asset already assigned to the PlayerInput component were changed [ISXB-711](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-711). +- Fixed Action properties edition in the UI Toolkit version of the Input Actions Asset editor. [ISXB-1277](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1277) +- Fixed an editor crash caused by input debugger device state window reusing cached state when reconnecting Stadia controller. [ISXB-658](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-658) +- Fixed an issue where batch jobs would fail with "Error: Error building Player because scripts are compiling" if a source generated .inputactions asset is out of sync with its generated source code (ISXB-1300). +- Fixed multiple `OnScreenStick` Components that does not work together when using them simultaneously in isolation mode. [ISXB-813](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-813) +- Fixed an issue in input actions editor window that caused certain fields in custom input composite bindings to require multiple clicks to action / focus. [ISXB-1171](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1171) +- Fixed an editor/player hang in `InputSystemUIInputModule` due to an infinite loop. This was caused by the assumption that `RemovePointerAtIndex` would _always_ successfully remove the pointer, which is not the case with touch based pointers. [ISXB-1258](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1258) + +### Changed +- Changed location of the link xml file (code stripping rules), from a temporary directory to the project Library folder (ISX-2140). +- Added back the InputManager to InputSystem project-wide asset migration code with performance improvement (ISX-2086). +- Changed `OnScreenControl` to automaticaly switch, in Single Player with autoswitch enabled, to the target device control scheme when the first component is enabled to prevent bad interactions when it start. +- Changed paremeter `overrideModifiersNeedToBePressedFirst` to obsolete for `ButtonWithOneModifier`, `ButtonWithTwoModifiers`, `OneModifierComposite` and `TwoModifiersComposite` in favour the new `modifiersOrder` parameter which is more explicit. +- Changed `Samples/Visualizers/GamepadVisualizer.unity` to visualize the control values of the current device instead of the first device. + +### Added +- Added new API `InputSystem.settings.useIMGUIEditorForAssets` that should be used in custom `InputParameterEditor` that use both IMGUI and UI Toolkit. +- Added ProfilerMakers to `InputAction.Enable()` and `InputActionMap.ResolveBindings()` to enable gathering of profiling data. +- Added throwing an error message when trying to use the Input System package on console without the extension package installed. + ## [1.11.2] - 2024-10-16 ### Fixed @@ -17,14 +61,12 @@ however, it has to be formatted properly to pass verification tests. - Fixed "AnalyticsResult" errors on consoles [ISXB-1107] - Fixed wrong `Display Index` value for touchscreen events.[ISXB-1101](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1101) - Fixed event handling when using Fixed Update processing where WasPressedThisFrame could appear to true for consecutive frames [ISXB-1006](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1006) +- Removed a redundant warning when using fallback code to parse a HID descriptor. (UUM-71260) ### Added - Added the display of the device flag `CanRunInBackground` in device debug view. - Added analytics for programmatic `InputAction` setup via `InputActionSetupExtensions` when exiting play-mode. -### Fixed -- Removed a redundant warning when using fallback code to parse a HID descriptor. (UUM-71260) - ### Changed - Removed the InputManager to InputSystem project-wide asset migration code for performance improvement (ISX-2086) @@ -38,6 +80,7 @@ however, it has to be formatted properly to pass verification tests. - Fixed potential crash on Mac when using stale references to deleted InputDevice objects [ISXB-606](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-606). - Fixed conditional compilation for non-editor analytics on platforms not enabling analytics. - Fixed simulated touch input not working with PlayerInput component [ISXB-483](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-483). +- Fixed unused PenState information to determine the displayIndex on platforms providing it. (PLAT-10123) ### Changed - Renamed editor Resources directories to PackageResources to fix package validation warnings. diff --git a/DocCodeSamples.Tests.meta b/DocCodeSamples.Tests.meta new file mode 100644 index 00000000..b137e3ae --- /dev/null +++ b/DocCodeSamples.Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c0f26b555bfc14351a9f8ec342fd6ae8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DocCodeSamples.Tests/DocCodeSamples.asmdef b/DocCodeSamples.Tests/DocCodeSamples.asmdef new file mode 100644 index 00000000..6e8b6248 --- /dev/null +++ b/DocCodeSamples.Tests/DocCodeSamples.asmdef @@ -0,0 +1,18 @@ +{ + "name": "Unity.InputSystem.DocCodeSamples", + "rootNamespace": "", + "references": [ + "GUID:75469ad4d38634e559750d17036d5f7c" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [], + "autoReferenced": false, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/DocCodeSamples.Tests/DocCodeSamples.asmdef.meta b/DocCodeSamples.Tests/DocCodeSamples.asmdef.meta new file mode 100644 index 00000000..213c7c30 --- /dev/null +++ b/DocCodeSamples.Tests/DocCodeSamples.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 41b01d3964f844d8b43923c18b3a9a6f +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DocCodeSamples.Tests/GamepadExample.cs b/DocCodeSamples.Tests/GamepadExample.cs new file mode 100644 index 00000000..1ba384dd --- /dev/null +++ b/DocCodeSamples.Tests/GamepadExample.cs @@ -0,0 +1,53 @@ +using UnityEngine; +using UnityEngine.InputSystem; + +namespace DocCodeSamples.Tests +{ + internal class GamepadExample : MonoBehaviour + { + void Start() + { + // Print all connected gamepads + Debug.Log(string.Join("\n", Gamepad.all)); + } + + void Update() + { + var gamepad = Gamepad.current; + + // No gamepad connected. + if (gamepad == null) + { + return; + } + + // Check if "Button North" was pressed this frame + if (gamepad.buttonNorth.wasPressedThisFrame) + { + Debug.Log("Button North was pressed"); + } + + // Check if the button control is being continuously actuated and read its value + if (gamepad.rightTrigger.IsActuated()) + { + Debug.Log("Right trigger value: " + gamepad.rightTrigger.ReadValue()); + } + + // Read left stick value and perform some code based on the value + Vector2 move = gamepad.leftStick.ReadValue(); + { + // Use the Vector2 move for the game logic here + } + + // Creating haptic feedback while "Button South" is pressed and stopping it when released. + if (gamepad.buttonSouth.wasPressedThisFrame) + { + gamepad.SetMotorSpeeds(0.2f, 1.0f); + } + else if (gamepad.buttonSouth.wasReleasedThisFrame) + { + gamepad.ResetHaptics(); + } + } + } +} diff --git a/DocCodeSamples.Tests/GamepadExample.cs.meta b/DocCodeSamples.Tests/GamepadExample.cs.meta new file mode 100644 index 00000000..2b7c0d0a --- /dev/null +++ b/DocCodeSamples.Tests/GamepadExample.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 898672c95e554f2fb492125d78b11af2 +timeCreated: 1733401360 \ No newline at end of file diff --git a/DocCodeSamples.Tests/GamepadHapticsExample.cs b/DocCodeSamples.Tests/GamepadHapticsExample.cs new file mode 100644 index 00000000..b1c41464 --- /dev/null +++ b/DocCodeSamples.Tests/GamepadHapticsExample.cs @@ -0,0 +1,48 @@ +using UnityEngine; +using UnityEngine.InputSystem; + +namespace DocCodeSamples.Tests +{ + internal class GamepadHapticsExample : MonoBehaviour + { + bool hapticsArePaused = false; + + void Update() + { + var gamepad = Gamepad.current; + + // No gamepad connected, no need to continue. + if (gamepad == null) + return; + + float leftTrigger = gamepad.leftTrigger.ReadValue(); + float rightTrigger = gamepad.rightTrigger.ReadValue(); + + // Only set motor speeds if haptics were not paused and if trigger is actuated. + // Both triggers must be actuated past 0.2f to start haptics. + if (!hapticsArePaused && + (gamepad.leftTrigger.IsActuated() || gamepad.rightTrigger.IsActuated())) + gamepad.SetMotorSpeeds( + leftTrigger < 0.2f ? 0.0f : leftTrigger, + rightTrigger < 0.2f ? 0.0f : rightTrigger); + + // Toggle haptics "playback" when "Button South" is pressed. + // Notice that if you release the triggers after pausing, + // and press the button again, haptics will resume. + if (gamepad.buttonSouth.wasPressedThisFrame) + { + if (hapticsArePaused) + gamepad.ResumeHaptics(); + else + gamepad.PauseHaptics(); + + hapticsArePaused = !hapticsArePaused; + } + + // Notice that if you release the triggers after pausing, + // and press the Start button, haptics will be reset. + if (gamepad.startButton.wasPressedThisFrame) + gamepad.ResetHaptics(); + } + } +} diff --git a/DocCodeSamples.Tests/GamepadHapticsExample.cs.meta b/DocCodeSamples.Tests/GamepadHapticsExample.cs.meta new file mode 100644 index 00000000..42a61400 --- /dev/null +++ b/DocCodeSamples.Tests/GamepadHapticsExample.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3bbc200178984676a2dcb977a2fe3bae +timeCreated: 1733400387 \ No newline at end of file diff --git a/DocCodeSamples.Tests/InputSystemUIInputModuleAssignActionsExample.cs b/DocCodeSamples.Tests/InputSystemUIInputModuleAssignActionsExample.cs new file mode 100644 index 00000000..886f1d61 --- /dev/null +++ b/DocCodeSamples.Tests/InputSystemUIInputModuleAssignActionsExample.cs @@ -0,0 +1,42 @@ +#if UNITY_INPUT_SYSTEM_ENABLE_UI + +using UnityEngine; +using UnityEngine.InputSystem.UI; + +namespace DocCodeSamples.Tests +{ + internal class InputSystemUIInputModuleAssignActionsExample : MonoBehaviour + { + // Reference to the InputSystemUIInputModule component, needs to be provided in the Inspector + public InputSystemUIInputModule uiModule; + + void Start() + { + // Assign default actions + AssignActions(); + } + + void AssignActions() + { + if (uiModule != null) + uiModule.AssignDefaultActions(); + else + Debug.LogError("InputSystemUIInputModule not found."); + } + + void UnassignActions() + { + if (uiModule != null) + uiModule.UnassignActions(); + else + Debug.LogError("InputSystemUIInputModule not found."); + } + + void OnDestroy() + { + // Unassign actions when the object is destroyed + UnassignActions(); + } + } +} +#endif diff --git a/DocCodeSamples.Tests/InputSystemUIInputModuleAssignActionsExample.cs.meta b/DocCodeSamples.Tests/InputSystemUIInputModuleAssignActionsExample.cs.meta new file mode 100644 index 00000000..0dfa4a76 --- /dev/null +++ b/DocCodeSamples.Tests/InputSystemUIInputModuleAssignActionsExample.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 026e1117180341c1bf7847a2cc61f75b +timeCreated: 1733488542 \ No newline at end of file diff --git a/Documentation~/Images/TimingEventGrouping.png b/Documentation~/Images/TimingEventGrouping.png new file mode 100644 index 00000000..1b574dfb Binary files /dev/null and b/Documentation~/Images/TimingEventGrouping.png differ diff --git a/Documentation~/Images/TimingFastFPS.png b/Documentation~/Images/TimingFastFPS.png new file mode 100644 index 00000000..f106071d Binary files /dev/null and b/Documentation~/Images/TimingFastFPS.png differ diff --git a/Documentation~/Images/TimingInputsPerFrame.png b/Documentation~/Images/TimingInputsPerFrame.png new file mode 100644 index 00000000..4358a0f9 Binary files /dev/null and b/Documentation~/Images/TimingInputsPerFrame.png differ diff --git a/Documentation~/Images/TimingSlowFPS.png b/Documentation~/Images/TimingSlowFPS.png new file mode 100644 index 00000000..c8d216f8 Binary files /dev/null and b/Documentation~/Images/TimingSlowFPS.png differ diff --git a/Documentation~/Images/TimingUnprocessedTime.png b/Documentation~/Images/TimingUnprocessedTime.png new file mode 100644 index 00000000..20615cd7 Binary files /dev/null and b/Documentation~/Images/TimingUnprocessedTime.png differ diff --git a/Documentation~/TableOfContents.md b/Documentation~/TableOfContents.md index 305e0dfa..7ce97522 100644 --- a/Documentation~/TableOfContents.md +++ b/Documentation~/TableOfContents.md @@ -25,6 +25,13 @@ * [Events](Events.md) * [Layouts](Layouts.md) * [User Management](UserManagement.md) + * [Timing and latency](timing-and-latency.md) + * [Input events queue](timing-input-events-queue.md) + * [Select an input processing mode](timing-select-mode.md) + * [Optimize for dynamic update](timing-optimize-dynamic-update.md) + * [Optimize for fixed update](timing-optimize-fixed-update.md) + * [Avoid missed or duplicate events](timing-missed-duplicate-events.md) + * [Mixed timing scenarios](timing-mixed-scenarios.md) * [Supported Input Devices](SupportedDevices.md) * [Pointers](Pointers.md) * [Touch support](Touch.md) diff --git a/Documentation~/UISupport.md b/Documentation~/UISupport.md index 8e196f82..a9040952 100644 --- a/Documentation~/UISupport.md +++ b/Documentation~/UISupport.md @@ -32,6 +32,7 @@ The three main UI solutions are **UI Toolkit**, **Unity UI**, and **IMGUI**. The - From Unity 2023.2 and onwards, the UI actions defined in the default [project-wide actions](./ProjectWideActions.md) directly map to UI Toolkit input. You do not need to use the UI Input Module component.

- In versions of Unity prior to 2023.2, you must use the UI Input Module component to define which actions are passed through from the Input System to the UI. +- Refer to UI Toolkit [Runtime UI event system and input handling](https://docs.unity3d.com/Manual/UIE-Runtime-Event-System.html) for more information on how to configure UI Toolkit input. **For [**Unity UI**](https://docs.unity3d.com/Packages/com.unity.ugui@latest), also known as "uGUI" (a GameObject and Component style UI solution):** diff --git a/Documentation~/timing-and-latency.md b/Documentation~/timing-and-latency.md new file mode 100644 index 00000000..2b3d2b8b --- /dev/null +++ b/Documentation~/timing-and-latency.md @@ -0,0 +1,21 @@ +--- +uid: timing-latency +--- +# Timing and Latency + +Input Timing refers to the topic of exactly when the Input System receives and processes input from devices. + +Latency is the amount of time between the user providing some input, and the user receiving a response to that input. For example, the time between a button press and your game’s character moving on-screen. In fast-paced input scenarios such as action games, even tiny delays between the user's input and your game responding can be noticeable and affect the feel of your gameplay. + +In addition to the effects of latency, timing can affect one-off discrete events such as when a button press starts or finishes. Checking for these at the wrong time can result in missed or duplicate events. + +To minimize input latency, and to avoid missed or duplicate events, it helps to understand how the Input System processes events in relation to Unity's frame updates, physics updates, and fixed updates. This will help you make decisions about how to read and respond to input in your game or app. + +| **Topic** | **Description** | +| :------------------------------ | :------------------------------- | +| **[Input events queue](timing-input-events-queue.md)** | Understand how and when the Input System receives and processes input from devices. | +| **[Select an input processing mode](timing-select-mode.md)** | How to select an appropriate **Update Mode** which controls when the Input System processes queued input events. | +| **[Optimize for dynamic update](timing-optimize-dynamic-update.md)** | How to optimize input for use in `Update` calls. | +| **[Optimize for fixed update](timing-optimize-fixed-update.md)** | How to optimize input for use in `FixedUpdate` calls. | +| **[Avoid missed or duplicate events](timing-missed-duplicate-events.md)** | How to avoid missing or duplicated discrete input events like when a button was pressed or released. | +| **[Mixed timing scenarios](timing-mixed-scenarios.md)** | How to optimize and avoid problems when using input in both `Update` and `FixedUpdate` calls. | diff --git a/Documentation~/timing-input-events-queue.md b/Documentation~/timing-input-events-queue.md new file mode 100644 index 00000000..cb05a3a4 --- /dev/null +++ b/Documentation~/timing-input-events-queue.md @@ -0,0 +1,29 @@ +# The input events queue + +The Input System receives information from hardware input devices as a stream of events. These events represent either system events received from the input device, or snapshots in time based on frequent samples from the device. + +The incoming events are stored in a queue, and by default are processed each frame. Input controls which have discrete (on/off) states, such as a button on a gamepad or mouse, generate corresponding single discrete events when they change state. Input controls with a range of motion, for example a stick or trigger on a gamepad that can be gradually moved over a period of time, generate a stream of individual events in rapid succession that approximates the smooth change in value. + +![image alt text](./Images/TimingEventGrouping.png) + +*A diagram showing an example of events coming from a smoothly changing analog input such as a gamepad stick over a period of four frames. In this example, the events are occurring faster than the game’s frame rate, which means multiple events are received each frame.* + +Because a device can cause events at times when the input system can't process them (for example, during the rendering phase of the player loop), these events are placed in an incoming queue, and the input system processes them in batches at frequent intervals. + +## Input Event Queue Processing + +Unity’s player loop repeats at frequent intervals depending on how fast your game or app is running. The player loop repeats once per frame, and performs the Update and FixedUpdate calls. However, the player loop in your game or app usually runs at a rate that's different to the rate of incoming events from input controls, which tend to have their own rate of operation. + +This means that each time an Update cycle occurs while a user is moving an input control, there's likely to be a queue of events representing the gradual change in values that occurred between the last frame and the current frame. This queue is processed at the beginning of the next Update or FixedUpdate, depending on which [**Input Update Mode**](timing-select-mode.md) you're using. + +## Event grouping and processing + +To pass through these incoming input events to your code, the Input System groups and processes them at a specific time within the [Player Loop](https://docs.unity3d.com/Manual/ExecutionOrder.html). This is either just before the next `Update` if the Input Update Mode is set to **Process Events In Dynamic Update**, or just before the next `FixedUpdate` if the Input Mode is set to **Process Events in Fixed Update**. + +The following diagram shows how each batch of events (labeled with letters) is processed at the start of the subsequent frame, when the Input Update mode is set to **Process Events in Dynamic Update**. Events (**a**), (**b**) & (**c**) occurred during frame 1, and so are processed at the start of frame 2, before frame 2’s Update() call. Events (**d**), (**e**), (**f**) & (**g**) occurred during frame 2, and so are processed at the start of frame 3. Events (**h**) & (**i**) occur during frame 3, and so are processed at the start of frame 4. + +![image alt text](./Images/TimingInputsPerFrame.png) + +This means that the exact time that your code receives the events isn't the same as the time the event was received by the input system in Unity. For this reason, when you're using event callbacks (as opposed to polling), the Input System includes time stamps for each event so that you can know when each event was generated. + +For example, event (**d**) in the diagram is received from the device by the input system at time 0.012 s, but the time at which your code receives the event callback is at the start of the next frame, at about 0.03 s. It retains its timestamp of 0.012 s, but it, along with events (**e**), (**f**), and (**g**), are all processed by your code at almost exactly the same time, each marked with their own time stamps. diff --git a/Documentation~/timing-missed-duplicate-events.md b/Documentation~/timing-missed-duplicate-events.md new file mode 100644 index 00000000..d8ac7992 --- /dev/null +++ b/Documentation~/timing-missed-duplicate-events.md @@ -0,0 +1,9 @@ +# Avoid missed or duplicate discrete events + +Discrete events are simple on/off events that occur when a user presses or releases a control such as a gamepad button, key, mouse, or touch press. This is in contrast to continuously changing values like those from gamepad stick movement. You can poll for these types of discrete event by using [`WasPressedThisFrame`](../api/UnityEngine.InputSystem.InputAction.html#UnityEngine_InputSystem_InputAction_WasPressedThisFrame_) or [`WasReleasedThisFrame`](../api/UnityEngine.InputSystem.InputAction.html#UnityEngine_InputSystem_InputAction_WasReleasedThisFrame_). However, you can get incorrect results such as missing an event or appearing to receive multiple, if you check for them at the wrong time. + +If your Update Mode is set to **Process in FixedUpdate**, you must ensure that you only use [`WasPressedThisFrame`](../api/UnityEngine.InputSystem.InputAction.html#UnityEngine_InputSystem_InputAction_WasPressedThisFrame_) or [`WasReleasedThisFrame`](../api/UnityEngine.InputSystem.InputAction.html#UnityEngine_InputSystem_InputAction_WasReleasedThisFrame_) in **FixedUpdate** calls. Using them in Update might either miss events, or returns true across multiple consecutive frames depending on whether the frame rate is running slower or faster than the fixed time step. + +Conversely, if your Update Mode is set to **process in Dynamic Update**, you must ensure that you only use [`WasPressedThisFrame`](../api/UnityEngine.InputSystem.InputAction.html#UnityEngine_InputSystem_InputAction_WasPressedThisFrame_) or [`WasReleasedThisFrame`](../api/UnityEngine.InputSystem.InputAction.html#UnityEngine_InputSystem_InputAction_WasReleasedThisFrame_) in `Update` calls. Using them in `FixedUpdate` might either miss events, or return true across multiple consecutive frames depending on whether the fixed time step is running slower or faster than your game’s frame rate. + +If you find that you're missing events that should have been detected, or are receiving multiple events for what should have been a single press or release of a control, the reason is probably that you either have your Input Update Mode set to the wrong setting, or that you're reading the state of these events in the wrong `Update` or `FixedUpdate` call. diff --git a/Documentation~/timing-mixed-scenarios.md b/Documentation~/timing-mixed-scenarios.md new file mode 100644 index 00000000..787a48c8 --- /dev/null +++ b/Documentation~/timing-mixed-scenarios.md @@ -0,0 +1,92 @@ +# Mixed timing scenarios with fixed and dynamic input + +There are some situations where you might set the Update Mode **process in Dynamic Update** even when using input code in `FixedUpdate`, to minimize input latency, as described in the [previous section](./timing-optimize-fixed-update.md). + +In this situation, for discrete events you must ensure that you use [`WasPressedThisFrame`](../api/UnityEngine.InputSystem.InputAction.html#UnityEngine_InputSystem_InputAction_WasPressedThisFrame_) or [`WasReleasedThisFrame`](../api/UnityEngine.InputSystem.InputAction.html#UnityEngine_InputSystem_InputAction_WasReleasedThisFrame_) in `Update`, and pass through a variable to your `FixedUpdate` code to indicate the event happened. There may still be some latency between the frame in which the event occurred, and the next `FixedUpdate` call. + +For example: + +```c# +using UnityEngine; +using UnityEngine.InputSystem; + +public class ExampleScript : MonoBehaviour +{ + InputAction jumpAction; + bool jumpPressed; + + private void Start() + { + jumpAction = InputSystem.actions.FindAction("Jump"); + } + + private void Update() + { + // read discrete jump pressed event here: + if (jumpAction.WasPressedThisFrame()) + { + // set this variable to true, for use in FixedUpdate + jumpPressed = true; + } + } + + void FixedUpdate() + { + if (jumpPressed) + { + // apply jump physics here + + // set the variable to false so that the jump pressed physics are only applied once + jumpPressed = false; + } + } +} +``` + +## Minimum latency in mixed timing scenarios + +A technique to give the user the feel of absolute minimum latency while still using `FixedUpdate` is to respond as fast as possible in `Update` giving some visual feedback, but also respond to that same input in `FixedUpdate` for your physics system code. For example, you could display the start of a "jump" animation immediately in `Update`, while applying physics to correspond with the "jump" animation in the next available `FixedUpdate` which might come slightly later. + +In this scenario, set your Update Mode **Process events in Dynamic Update** which gives you the fastest response in your `Update` call. However for the reasons mentioned in the previous section, this might mean you miss discrete events if you use methods like [`WasPressedThisFrame`](../api/UnityEngine.InputSystem.InputAction.html#UnityEngine_InputSystem_InputAction_WasPressedThisFrame_) in your `FixedUpdate` call. To avoid this problem, use a variable to pass through the pressed/released state of the discrete event from the event handler to your FixedUpdate call, and then clear it once your FixedUpdate code has acted on it. For example: + +```c# +using UnityEngine; +using UnityEngine.InputSystem; + +public class ExampleScript : MonoBehaviour +{ + InputAction jumpAction; + bool jumpPressed; + + private void Start() + { + jumpAction = InputSystem.actions.FindAction("Jump"); + } + + private void Update() + { + // at high FPS, it’s fastest to read actions here: + + // read discrete jump pressed event here: + if (jumpAction.WasPressedThisFrame()) + { + // start jump animation here + + // set this variable to true, for use in FixedUpdate + jumpPressed = true; + + } + } + + void FixedUpdate() + { + if (jumpPressed) + { + // apply jump physics here + + // set the variable to false so that the jump pressed physics are only applied once + jumpPressed = false; + } + } +} +``` diff --git a/Documentation~/timing-optimize-dynamic-update.md b/Documentation~/timing-optimize-dynamic-update.md new file mode 100644 index 00000000..0e131410 --- /dev/null +++ b/Documentation~/timing-optimize-dynamic-update.md @@ -0,0 +1,24 @@ +# Optimize for dynamic update (non-physics) scenarios + +If you're not working with the physics system or using `FixedUpdate`, always set the input system to process input in sync with the frame rate and `Update()` calls. This is the default setting, but to check or set this, go to **Project Settings** \> **Input System Package** \> **Input Settings**, and set **Update Mode** to **Process Events in Dynamic Update**. + +You can use either a Polling or Event-driven approach to read and process input each frame. You can find out more about Polling or Event-driven approaches in [Responding To Actions](RespondingToActions.html). Whether you choose polling or event-driven, as long as you have your Update Mode set to **Process Events in Dynamic Update**, you receive the latest events and values at the start of each frame. + +## Polling technique + +Poll input in `Update` and use those values to control your game in `Update`. If there were multiple events after the last frame completed (for example, multiple changing position values of a continuously moving gamepad stick), polling gives you the most recently processed value which is often fine for most scenarios. This approach is often called **sample-and-hold** and is a form of down-sampling, because individual subframe information is discarded. For example, in the scenario shown in the diagram below, polling the input on frame 3 gives the value for event (**g**), while the values for events (**d**) (**e**) and (**f**) are discarded. + +![image alt text](./Images/TimingInputsPerFrame.png) + +Also use `Update` to poll for discrete on/off state changes using API such as [`WasPressedThisFrame`](../api/UnityEngine.InputSystem.InputAction.html#UnityEngine_InputSystem_InputAction_WasPressedThisFrame_) and **WasReleasedThisFrame**. + +> [!NOTE] +> The input system doesn't detect multiple discrete on/off events that happen in a single frame when you use the poll driven approach. Multiple discrete on/off events might happen if your game is running at a low frame rate and a user repeatedly presses a button very rapidly, or if the user is using a type of game controller with a rapid "auto fire" mode. The polling technique also can't detect the order of multiple buttons were pressed on the same frame. Use event-driven input if you require this information. + +### Event-driven technique + +All events that occurred since the last frame are immediately processed before the current frame's `Update`, in the order that they were received. For continuously changing values (for example, multiple changing position values of a continuously moving gamepad stick), you might receive multiple events per frame with backdated timestamps indicating when they occurred between the last frame and the start of the current frame. + +You can read and store input values from the input events in your event handler methods, and use those values to control your game in `Update`. + +For example, in the scenario shown in the previous diagram, at the start of frame 3, you receive events for (**d**), (**e**), (**f**), and (**g**) and can process all of them in your game code. diff --git a/Documentation~/timing-optimize-fixed-update.md b/Documentation~/timing-optimize-fixed-update.md new file mode 100644 index 00000000..7bcc1852 --- /dev/null +++ b/Documentation~/timing-optimize-fixed-update.md @@ -0,0 +1,112 @@ +# Optimize for fixed-timestep or physics-based scenarios + +If you are working with the physics system or using `FixedUpdate` to control your game in a scenario where a small amount of input latency is acceptable (for example, a few frames), the simplest approach is to set the [input system update mode](./timing-select-mode.md) to **Process Events in Fixed Update**. This means your input code in `FixedUpdate` will operate as expected. + +To get the minimum possible latency from the Input System and minimize lag, set the input system update mode to **Process Events in Dynamic Update**. However in doing this, you must understand how to avoid the problems which can arise when using this strategy. Although it might seem incorrect if you have code in `FixedUpdate`, for most cases, this approach minimizes lag compared with processing events in Fixed Update. The reasons for this are explained in detail on this page. Having a good understanding of how input is processed in each mode allows you to make your own decision about how best to process input for your particular project. + +## Input in Fixed Update mode + +When you set the input system update mode to **Process Events in Fixed Update**, input events are processed in groups according to whether their timestamp falls within the current fixed time step. There might be none, one, or multiple `FixedUpdate` calls processed per frame depending on how fast the game's frame rate is updating compared with the fixed update time step period. + +If your game’s frame rate is running faster than the fixed time step period, you will have either zero or one `FixedUpdate` call per frame, depending whether the previous fixed time step has completed or not. If your game’s frame rate is running slower than the fixed time step period, you will have one or more `FixedUpdate` calls per frame \- as many as are required to catch up with the number of completed fixed time step periods that have elapsed. + +You can learn more about how the player loop processes fixed update time steps in [Time and Framerate Management](https://docs.unity3d.com/Manual/TimeFrameManagement.html). + +It’s important to understand that Fixed Update provides a simulation of code running at fixed time deltas (the fixed time step), however it does not actually run at regular time intervals. Instead, at the start of each frame loop, Unity will run as many Fixed Update steps as is needed to catch-up to the current frame’s time. If a whole fixed time step hasn't completed yet, no Fixed Update steps occur. If more than one whole fixed time step has elapsed, more than one Fixed Update step occurs. This means that on each frame, the `FixedUpdate` method can be called a variable number of times (or even not at all) depending on how much time has elapsed since the last frame and the value set for the Fixed Update time step. + + +### When the frame rate runs faster than fixed time step duration + +![image alt text](./Images/TimingFastFPS.png) + +This diagram shows the frame rate running faster than the fixed update time step rate. Time progresses to the right, each frame is numbered, and shows its `Update` call at the start of the frame in orange. The fixed time step here is 0.02 seconds (50 times per second), and the game is running faster, at about 80 frames per second. In this situation there are some frames with one fixed update call, and some frames with none, depending on whether a full fixed update time step has completed by the time the frame starts. The fixed time step periods are marked with letters A, B, C, D, E, and the frames in which their corresponding fixed update calls occur are marked in green. The fixed update call for time step A occurs at the start of frame 4, the FixedUpdate call for time step B occurs at the start of frame 7, and so on. + +### When the frame rate runs slower than the fixed time step duration + +![image alt text](./Images/TimingSlowFPS.png) + +This diagram shows the opposite scenario, when the fixed update cycle is running faster than the frame rate. The fixed time step here is 0.01 seconds (100 times per second), and the game frame rate is running slower, at about 40 frames per second. In this situation most frames have multiple fixed update calls before each update call, the number depending on how many whole update time steps have elapsed since the previous frame. The fixed update time step periods are marked with letters A, B, C, and so on, and frames in which their corresponding fixed update calls occur are marked in green. The fixed update call for time step A and B occurs at the start of frame 2, the fixed update call for frames C, D & E occur at the start of frame 3, and so on. + + +In both types of situation, whether the frame rate is running either faster or slower than the fixed time step, the start of the frame usually occurs somewhere part-way through a fixed time step interval. This means a portion of the most recent fixed time step period occurs before the frame, and some during the frame. Partially elapsed fixed time step periods like this aren't processed until the frame after they have fully elapsed. + +## Implications for input lag in fixed time step mode + +Because input that occurs during partially elapsed time steps isn't processed until the frame after the time step has fully completed, this has implications for increased input lag in fixed time step mode. There's almost always some amount of unprocessed time left between the end of the last fixed time step, and the start of the next frame. This means it's possible for input events to occur within that unprocessed time. + +An example of an input event occurring during such unprocessed time is shown in this diagram: + +![image alt text](./Images/TimingUnprocessedTime.png) + +This diagram shows the frame rate running faster than the fixed update time step rate. Each frame is numbered (1 to 7), and shows its `Update` call at the start of the frame in orange. The fixed time step here is 0.02s (50 steps per second), and the game is running faster, at about 80 frames per second. In this situation there are some frames with one fixed update call, and some frames with none. + +The diagram shows an input event (shown as a blue dot) that occurs during some unprocessed time during frame 3. Although the event occurs during frame 3, it's not processed in the fixed update input processing step at the start of frame 4 because it didn't occur during fixed time step (**A**). Instead, it occurred during fixed time step (**B**), which is processed at the start of frame 7 \- the first frame to occur after fixed time step (**B**) has completely elapsed. + +This has the counterintuitive effect that the processing of input on frame 4 actually ignores some input that has already occurred on frame 3, because it's only processing events that occurred in the last complete fixed time step: (**A**). + +## Minimize latency when using fixed update code + +To minimize input latency in input code in `FixedUpdate` calls, set the input system update mode to **Process Events in Dynamic Update**, which eliminates the problem of unprocessed time described previously. You can then use an event-driven or polling technique to read your input without missing events that occurred after the last fixed timestep but before the current frame. + +However, the **Process Events in Dynamic Update** mode might introduce the problem of missed or duplicate discrete events, such as attempting to read whether a button was pressed in a given frame. If you use this strategy, you must understand how to [avoid missed or duplicate events](./timing-missed-duplicate-events.md) in mixed timing scenarios requiring fixed and dynamic input. + +### Event-driven input with fixed update code + +For event-driven input, where the [Player Input component](./PlayerInput.md) calls events in your code, you should store the input values in variables which you can then read in your `FixedUpdate` call. For example: + +``` +using UnityEngine; +using UnityEngine.InputSystem; + +public class ExampleScript : MonoBehaviour +{ + Vector2 moveInputValue; + Rigidbody rigidBody; + public float moveForce = 10; + + private void Start() + { + rigidBody = GetComponent(); + } + + public void OnMove(InputAction.CallbackContext context) + { + // in the event callback, we store the input value + moveInputValue = context.ReadValue(); + } + + private void FixedUpdate() + { + // in fixed update, we use the stored value for + // applying physics forces + rigidBody.AddForce(moveInputValue * moveForce); + } +} +``` + +### Polling input with fixed update code + +For code where you're polling input action values, you can read the values directly from `FixedUpdate`: + +``` +using UnityEngine; +using UnityEngine.InputSystem; + +public class ExampleScript : MonoBehaviour +{ + InputAction moveAction; + Rigidbody rigidBody; + public float moveForce = 10; + + private void Start() + { + moveAction = InputSystem.actions.FindAction("move"); + } + + private void FixedUpdate() + { + Vector2 moveInputValue = moveAction.ReadValue(); + rigidBody.AddForce(moveInputValue * moveForce); + } +} +``` diff --git a/Documentation~/timing-select-mode.md b/Documentation~/timing-select-mode.md new file mode 100644 index 00000000..895fd0af --- /dev/null +++ b/Documentation~/timing-select-mode.md @@ -0,0 +1,15 @@ +# Select an appropriate input processing mode + +The Input System **Update Mode** controls when the input system processes queued input events. + +You can find and change the Update Mode by going to **Project Settings** \> **Input System Package** \> **Input Settings** \> **Update Mode**. + +The choice of Update Mode that best suits your project relates to whether you're using Update or FixedUpdate to respond to input events. You should choose this based on the specifics of the game you're making. You can read more about Update and FixedUpdate in [Time and Framerate Management](https://docs.unity3d.com/Manual/TimeFrameManagement.html). + +## When a small amount of latency is not an issue + +In cases where a small amount of input latency (a few frames) isn't an issue, set the update mode to match where you read your input. If your input code is in `Update` (usually non-physics-based scenarios), use **Process Events in Dynamic Update**. If your input code is in `FixedUpdate` (usually physics-based scenarios), use **Process Events in Fixed Update**. + +## When minimum latency is a necessity + +In cases where minimum latency is a necessity, set the update mode to **Process Events in Dynamic Update**, even if you're using code in FixedUpdate to apply physics forces based on input. This strategy comes with some additional issues that you must be aware of. Refer to the section [Optimizing for fixed-timestep scenarios](timing-optimize-fixed-update.md) for more information. diff --git a/InputSystem/Actions/Composites/AxisComposite.cs b/InputSystem/Actions/Composites/AxisComposite.cs index 9c9074e4..48ae2a7c 100644 --- a/InputSystem/Actions/Composites/AxisComposite.cs +++ b/InputSystem/Actions/Composites/AxisComposite.cs @@ -220,7 +220,7 @@ internal class AxisCompositeEditor : InputParameterEditor public override void OnGUI() { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return; + if (!InputSystem.settings.useIMGUIEditorForAssets) return; #endif target.whichSideWins = (AxisComposite.WhichSideWins)EditorGUILayout.EnumPopup(m_WhichAxisWinsLabel, target.whichSideWins); } diff --git a/InputSystem/Actions/Composites/ButtonWithOneModifier.cs b/InputSystem/Actions/Composites/ButtonWithOneModifier.cs index bca39360..75d6f333 100644 --- a/InputSystem/Actions/Composites/ButtonWithOneModifier.cs +++ b/InputSystem/Actions/Composites/ButtonWithOneModifier.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.Utilities; @@ -79,16 +80,72 @@ public class ButtonWithOneModifier : InputBindingComposite /// still trigger. Default is false. /// /// - /// By default, is required to be in pressed state before or at the same time that + /// By default, if the setting is enabled, + /// is required to be in pressed state before or at the same time that /// goes into pressed state for the composite as a whole to trigger. This means that binding to, for example, Shift+B, /// the shift key has to be pressed before pressing the B key. This is the behavior usually expected with /// keyboard shortcuts. /// /// This parameter can be used to bypass this behavior and allow any timing between and . /// The only requirement is for them both to concurrently be in pressed state. + /// + /// To don't depends on the setting please consider using instead. /// + [Tooltip("Obsolete please use modifiers Order. If enabled, this will override the Input Consumption setting, allowing the modifier keys to be pressed after the button and the composite will still trigger.")] + [Obsolete("Use ModifiersOrder.Unordered with 'modifiersOrder' instead")] public bool overrideModifiersNeedToBePressedFirst; + /// + /// Determines how a modifiers keys need to be pressed in order or not. + /// + public enum ModifiersOrder + { + /// + /// By default, if the setting is enabled, + /// is required to be in pressed state before or at the same time that + /// goes into pressed state for the composite as a whole to trigger. This means that binding to, for example, Shift+B, + /// the shift key has to be pressed before pressing the B key. This is the behavior usually expected with + /// keyboard shortcuts. + /// + /// If the setting is disabled, + /// modifiers can be pressed after the button and the composite will still trigger. + /// + Default = 0, + + /// + /// is required to be in pressed state before or at the same + /// time that goes into pressed state for the composite as a whole to trigger. This means that binding to, + /// for example, Ctrl+B, the ctrl key have to be pressed before pressing the B key. + /// This is the behavior usually expected with keyboard shortcuts. + /// + Ordered = 1, + + /// + /// can be pressed after + /// and the composite will still trigger. The only requirement is for all of them to concurrently be in pressed state. + /// + Unordered = 2 + } + + /// + /// If set to Ordered or Unordered, the built-in logic to determine if modifiers need to be pressed first is overridden. + /// + /// + /// By default, if the setting is enabled, + /// is required to be in pressed state before or at the same time that + /// goes into pressed state for the composite as a whole to trigger. This means that binding to, for example, Shift+B, + /// the shift key has to be pressed before pressing the B key. This is the behavior usually expected with + /// keyboard shortcuts. + /// + /// If the setting is disabled, + /// modifiers can be pressed after the button and the composite will still trigger. + /// + /// This parameter can be used to bypass this behavior and enforce the timing order or allow any timing between and . + /// The only requirement is for them both to concurrently be in pressed state. + /// + [Tooltip("By default it follows the Input Consumption setting to determine if the modifers keys need to be pressed first.")] + public ModifiersOrder modifiersOrder = ModifiersOrder.Default; + /// /// Return the value of the part if is pressed. Otherwise /// return 0. @@ -107,7 +164,7 @@ private bool ModifierIsPressed(ref InputBindingCompositeContext context) { var modifierDown = context.ReadValueAsButton(modifier); - if (modifierDown && !overrideModifiersNeedToBePressedFirst) + if (modifierDown && modifiersOrder == ModifiersOrder.Ordered) { var timestamp = context.GetPressTime(button); var timestamp1 = context.GetPressTime(modifier); @@ -130,8 +187,17 @@ public override float EvaluateMagnitude(ref InputBindingCompositeContext context protected override void FinishSetup(ref InputBindingCompositeContext context) { - if (!overrideModifiersNeedToBePressedFirst) - overrideModifiersNeedToBePressedFirst = !InputSystem.settings.shortcutKeysConsumeInput; + if (modifiersOrder == ModifiersOrder.Default) + { + // Legacy. We need to reference the obsolete member here so temporarily + // turn off the warning. +#pragma warning disable CS0618 + if (overrideModifiersNeedToBePressedFirst) +#pragma warning restore CS0618 + modifiersOrder = ModifiersOrder.Unordered; + else + modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; + } } } } diff --git a/InputSystem/Actions/Composites/ButtonWithTwoModifiers.cs b/InputSystem/Actions/Composites/ButtonWithTwoModifiers.cs index 3d930d07..fcce4700 100644 --- a/InputSystem/Actions/Composites/ButtonWithTwoModifiers.cs +++ b/InputSystem/Actions/Composites/ButtonWithTwoModifiers.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.Utilities; @@ -94,16 +95,71 @@ public class ButtonWithTwoModifiers : InputBindingComposite /// and the composite will still trigger. Default is false. /// /// - /// By default, and are required to be in pressed state before or at the same + /// By default, if the setting is enabled, + /// and are required to be in pressed state before or at the same /// time that goes into pressed state for the composite as a whole to trigger. This means that binding to, - /// for example, Ctrl+Shift+B, the ctrl shift keys have to be pressed before pressing the B key. + /// for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, before pressing the B key. /// This is the behavior usually expected with keyboard shortcuts. /// /// This parameter can be used to bypass this behavior and allow any timing between , , /// and . The only requirement is for all of them to concurrently be in pressed state. + /// + /// To don't depends on the setting please consider using instead. /// + [Tooltip("Obsolete please use modifiers Order. If enabled, this will override the Input Consumption setting, allowing the modifier keys to be pressed after the button and the composite will still trigger.")] + [Obsolete("Use ModifiersOrder.Unordered with 'modifiersOrder' instead")] public bool overrideModifiersNeedToBePressedFirst; + /// + /// Determines how a modifiers keys need to be pressed in order or not. + /// + public enum ModifiersOrder + { + /// + /// By default, if the setting is enabled, + /// and are required to be in pressed state before or at the same + /// time that goes into pressed state for the composite as a whole to trigger. This means that binding to, + /// for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, before pressing the B key. + /// This is the behavior usually expected with keyboard shortcuts. + /// + /// If the setting is disabled, + /// modifiers can be pressed after the button and the composite will still trigger. + /// + Default = 0, + + /// + /// and are required to be in pressed state before or at the same + /// time that goes into pressed state for the composite as a whole to trigger. This means that binding to, + /// for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, before pressing the B key. + /// This is the behavior usually expected with keyboard shortcuts. + /// + Ordered = 1, + + /// + /// and/or can be pressed after + /// and the composite will still trigger. The only requirement is for all of them to concurrently be in pressed state. + /// + Unordered = 2 + } + + /// + /// If set to Ordered or Unordered, the built-in logic to determine if modifiers need to be pressed first is overridden. + /// + /// + /// By default, if the setting is enabled, + /// and are required to be in pressed state before or at the same + /// time that goes into pressed state for the composite as a whole to trigger. This means that binding to, + /// for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, before pressing the B key. + /// This is the behavior usually expected with keyboard shortcuts. + /// + /// If the setting is disabled, + /// modifiers can be pressed after the button and the composite will still trigger. + /// + /// This field allows you to explicitly override this default inference. + /// + [Tooltip("By default it follows the Input Consumption setting to determine if the modifers keys need to be pressed first.")] + public ModifiersOrder modifiersOrder = ModifiersOrder.Default; + /// /// Return the value of the part while both and /// are pressed. Otherwise return 0. @@ -122,7 +178,7 @@ private bool ModifiersArePressed(ref InputBindingCompositeContext context) { var modifiersDown = context.ReadValueAsButton(modifier1) && context.ReadValueAsButton(modifier2); - if (modifiersDown && !overrideModifiersNeedToBePressedFirst) + if (modifiersDown && modifiersOrder == ModifiersOrder.Ordered) { var timestamp = context.GetPressTime(button); var timestamp1 = context.GetPressTime(modifier1); @@ -146,8 +202,17 @@ public override float EvaluateMagnitude(ref InputBindingCompositeContext context protected override void FinishSetup(ref InputBindingCompositeContext context) { - if (!overrideModifiersNeedToBePressedFirst) - overrideModifiersNeedToBePressedFirst = !InputSystem.settings.shortcutKeysConsumeInput; + if (modifiersOrder == ModifiersOrder.Default) + { + // Legacy. We need to reference the obsolete member here so temporarily + // turn off the warning. +#pragma warning disable CS0618 + if (overrideModifiersNeedToBePressedFirst) +#pragma warning restore CS0618 + modifiersOrder = ModifiersOrder.Unordered; + else + modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; + } } } } diff --git a/InputSystem/Actions/Composites/OneModifierComposite.cs b/InputSystem/Actions/Composites/OneModifierComposite.cs index dfd7f648..15c72014 100644 --- a/InputSystem/Actions/Composites/OneModifierComposite.cs +++ b/InputSystem/Actions/Composites/OneModifierComposite.cs @@ -88,7 +88,8 @@ public class OneModifierComposite : InputBindingComposite /// Default value is false. /// /// - /// By default, if is bound to only s, then the composite requires + /// By default, if the setting is enabled, + /// if is bound to only s, then the composite requires /// to be pressed before pressing . This means that binding to, for example, /// Ctrl+B, the ctrl keys have to be pressed before pressing the B key. This is the behavior usually expected /// with keyboard shortcuts. @@ -100,9 +101,69 @@ public class OneModifierComposite : InputBindingComposite /// This field allows you to explicitly override this default inference and make it so that regardless of what /// is bound to, any press sequence is acceptable. For the example binding to Ctrl+B, it would mean that pressing B and /// only then pressing Ctrl will still trigger the binding. + /// + /// To don't depends on the setting please consider using instead. /// + [Tooltip("Obsolete please use modifiers Order. If enabled, this will override the Input Consumption setting, allowing the modifier keys to be pressed after the button and the composite will still trigger.")] + [Obsolete("Use ModifiersOrder.Unordered with 'modifiersOrder' instead")] public bool overrideModifiersNeedToBePressedFirst; + /// + /// Determines how a modifiers keys need to be pressed in order or not. + /// + public enum ModifiersOrder + { + /// + /// By default, if the setting is enabled, + /// if is bound to only s, then the composite requires + /// to be pressed before pressing . This means that binding to, for example, + /// Ctrl+B, the ctrl keys have to be pressed before pressing the B key. This is the behavior usually expected + /// with keyboard shortcuts. + /// + /// If the setting is disabled, + /// modifiers can be pressed after the button and the composite will still trigger. + /// + Default = 0, + + /// + /// if is bound to only s, then the composite requires + /// to be pressed before pressing . This means that binding to, for example, + /// Ctrl+B, the ctrl key have to be pressed before pressing the B key. This is the behavior usually expected + /// with keyboard shortcuts. + /// + Ordered = 1, + + /// + /// can be pressed after + /// and the composite will still trigger. The only requirement is for all of them to concurrently be in pressed state. + /// + Unordered = 2 + } + + /// + /// If set to Ordered or Unordered, the built-in logic to determine if modifiers need to be pressed first is overridden. + /// + /// + /// By default, if the setting is enabled, + /// if is bound to only s, then the composite requires + /// to be pressed before pressing . This means that binding to, for example, + /// Ctrl+B, the ctrl keys have to be pressed before pressing the B key. This is the behavior usually expected + /// with keyboard shortcuts. + /// + /// If the setting is disabled, + /// modifiers can be pressed after the button and the composite will still trigger. + /// + /// However, when binding, for example, Ctrl+MouseDelta, it should be possible to press ctrl at any time. The default + /// logic will automatically detect the difference between this binding and the button binding in the example above and behave + /// accordingly. + /// + /// This field allows you to explicitly override this default inference and make the order mandatory or make it so that regardless of what + /// is bound to, any press sequence is acceptable. For the example binding to Ctrl+B, it would mean that pressing B and + /// only then pressing Ctrl will still trigger the binding. + /// + [Tooltip("By default it follows the Input Consumption setting to determine if the modifers keys need to be pressed first.")] + public ModifiersOrder modifiersOrder = ModifiersOrder.Default; + private int m_ValueSizeInBytes; private Type m_ValueType; private bool m_BindingIsButton; @@ -128,7 +189,7 @@ private bool ModifierIsPressed(ref InputBindingCompositeContext context) var modifierDown = context.ReadValueAsButton(modifier); // When the modifiers are gating a button, we require the modifiers to be pressed *first*. - if (modifierDown && m_BindingIsButton && !overrideModifiersNeedToBePressedFirst) + if (modifierDown && m_BindingIsButton && modifiersOrder == ModifiersOrder.Ordered) { var timestamp = context.GetPressTime(binding); var timestamp1 = context.GetPressTime(modifier); @@ -144,8 +205,17 @@ protected override void FinishSetup(ref InputBindingCompositeContext context) { DetermineValueTypeAndSize(ref context, binding, out m_ValueType, out m_ValueSizeInBytes, out m_BindingIsButton); - if (!overrideModifiersNeedToBePressedFirst) - overrideModifiersNeedToBePressedFirst = !InputSystem.settings.shortcutKeysConsumeInput; + if (modifiersOrder == ModifiersOrder.Default) + { + // Legacy. We need to reference the obsolete member here so temporarily + // turn off the warning. +#pragma warning disable CS0618 + if (overrideModifiersNeedToBePressedFirst) +#pragma warning restore CS0618 + modifiersOrder = ModifiersOrder.Unordered; + else + modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; + } } public override object ReadValueAsObject(ref InputBindingCompositeContext context) diff --git a/InputSystem/Actions/Composites/TwoModifiersComposite.cs b/InputSystem/Actions/Composites/TwoModifiersComposite.cs index b4f8d928..8044a628 100644 --- a/InputSystem/Actions/Composites/TwoModifiersComposite.cs +++ b/InputSystem/Actions/Composites/TwoModifiersComposite.cs @@ -90,9 +90,10 @@ public class TwoModifiersComposite : InputBindingComposite /// Default value is false. /// /// - /// By default, if is bound to only s, then the composite requires + /// By default, if the setting is enabled, + /// if is bound to only s, then the composite requires /// both and to be pressed before pressing . - /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed + /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, /// before pressing the B key. This is the behavior usually expected with keyboard shortcuts. /// /// However, when binding, for example, Ctrl+Shift+MouseDelta, it should be possible to press ctrl and shift @@ -102,9 +103,66 @@ public class TwoModifiersComposite : InputBindingComposite /// This field allows you to explicitly override this default inference and make it so that regardless of what /// is bound to, any press sequence is acceptable. For the example binding to Ctrl+Shift+B, it would mean that pressing /// B and only then pressing Ctrl and Shift will still trigger the binding. + /// + /// To don't depends on the setting please consider using instead. /// + [Tooltip("Obsolete please use modifiers Order. If enabled, this will override the Input Consumption setting, allowing the modifier keys to be pressed after the button and the composite will still trigger.")] + [Obsolete("Use ModifiersOrder.Unordered with 'modifiersOrder' instead")] public bool overrideModifiersNeedToBePressedFirst; + /// + /// Determines how a modifiers keys need to be pressed in order or not. + /// + public enum ModifiersOrder + { + /// + /// By default, if the setting is enabled, + /// if is bound to only s, then the composite requires + /// both and to be pressed before pressing . + /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, + /// before pressing the B key. This is the behavior usually expected with keyboard shortcuts. + /// + /// If the setting is disabled, + /// modifiers can be pressed after the button and the composite will still trigger. + /// + Default = 0, + + /// + /// if is bound to only s, then the composite requires + /// both and to be pressed before pressing . + /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, + /// before pressing the B key. This is the behavior usually expected with keyboard shortcuts. + /// + Ordered = 1, + + /// + /// and/or can be pressed after + /// and the composite will still trigger. The only requirement is for all of them to concurrently be in pressed state. + /// + Unordered = 2 + } + + /// + /// If set to Ordered or Unordered, the built-in logic to determine if modifiers need to be pressed first is overridden. + /// + /// + /// By default, if the setting is enabled, + /// if is bound to only s, then the composite requires + /// both and to be pressed before pressing . + /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, + /// before pressing the B key. This is the behavior usually expected with keyboard shortcuts. + /// + /// If the setting is disabled, + /// modifiers can be pressed after the button and the composite will still trigger. + /// + /// This field allows you to explicitly override this default inference and make the order mandatory or make it so that regardless of what + /// is bound to, any press sequence is acceptable. For the example binding to Ctrl+Shift+B, it would mean that pressing + /// B and only then pressing Ctrl and Shift will still trigger the binding. + /// + /// + [Tooltip("By default it follows the Input Consumption setting to determine if the modifers keys need to be pressed first.")] + public ModifiersOrder modifiersOrder = ModifiersOrder.Default; + /// /// Type of values read from controls bound to . /// @@ -140,7 +198,7 @@ private bool ModifiersArePressed(ref InputBindingCompositeContext context) var modifiersDown = context.ReadValueAsButton(modifier1) && context.ReadValueAsButton(modifier2); // When the modifiers are gating a button, we require the modifiers to be pressed *first*. - if (modifiersDown && m_BindingIsButton && !overrideModifiersNeedToBePressedFirst) + if (modifiersDown && m_BindingIsButton && modifiersOrder == ModifiersOrder.Ordered) { var timestamp = context.GetPressTime(binding); var timestamp1 = context.GetPressTime(modifier1); @@ -157,8 +215,17 @@ protected override void FinishSetup(ref InputBindingCompositeContext context) { OneModifierComposite.DetermineValueTypeAndSize(ref context, binding, out m_ValueType, out m_ValueSizeInBytes, out m_BindingIsButton); - if (!overrideModifiersNeedToBePressedFirst) - overrideModifiersNeedToBePressedFirst = !InputSystem.settings.shortcutKeysConsumeInput; + if (modifiersOrder == ModifiersOrder.Default) + { + // Legacy. We need to reference the obsolete member here so temporarily + // turn off the warning. +#pragma warning disable CS0618 + if (overrideModifiersNeedToBePressedFirst) +#pragma warning restore CS0618 + modifiersOrder = ModifiersOrder.Unordered; + else + modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; + } } public override object ReadValueAsObject(ref InputBindingCompositeContext context) diff --git a/InputSystem/Actions/Composites/Vector2Composite.cs b/InputSystem/Actions/Composites/Vector2Composite.cs index dd7a4626..2f970dda 100644 --- a/InputSystem/Actions/Composites/Vector2Composite.cs +++ b/InputSystem/Actions/Composites/Vector2Composite.cs @@ -145,7 +145,7 @@ public override Vector2 ReadValue(ref InputBindingCompositeContext context) var rightIsPressed = context.ReadValueAsButton(right); // Legacy. We need to reference the obsolete member here so temporarily - // turn of the warning. + // turn off the warning. #pragma warning disable CS0618 if (!normalize) // Was on by default. mode = Mode.Digital; @@ -200,7 +200,7 @@ internal class Vector2CompositeEditor : InputParameterEditor public override void OnGUI() { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return; + if (!InputSystem.settings.useIMGUIEditorForAssets) return; #endif target.mode = (Vector2Composite.Mode)EditorGUILayout.EnumPopup(m_ModeLabel, target.mode); } diff --git a/InputSystem/Actions/Composites/Vector3Composite.cs b/InputSystem/Actions/Composites/Vector3Composite.cs index 0e2626e4..d9a0e510 100644 --- a/InputSystem/Actions/Composites/Vector3Composite.cs +++ b/InputSystem/Actions/Composites/Vector3Composite.cs @@ -180,7 +180,7 @@ internal class Vector3CompositeEditor : InputParameterEditor public override void OnGUI() { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return; + if (!InputSystem.settings.useIMGUIEditorForAssets) return; #endif target.mode = (Vector3Composite.Mode)EditorGUILayout.EnumPopup(m_ModeLabel, target.mode); } diff --git a/InputSystem/Actions/InputAction.cs b/InputSystem/Actions/InputAction.cs index f1964659..d4c1dd41 100644 --- a/InputSystem/Actions/InputAction.cs +++ b/InputSystem/Actions/InputAction.cs @@ -1,5 +1,6 @@ using System; using Unity.Collections.LowLevel.Unsafe; +using Unity.Profiling; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.Utilities; @@ -681,6 +682,12 @@ public bool wantsInitialStateCheck } } + /// + /// ProfilerMarker for measuring the enabling/disabling of InputActions. + /// + static readonly ProfilerMarker k_InputActionEnableProfilerMarker = new ProfilerMarker("InputAction.Enable"); + static readonly ProfilerMarker k_InputActionDisableProfilerMarker = new ProfilerMarker("InputAction.Disable"); + /// /// Construct an unnamed, free-standing action that is not part of any map or asset /// and has no bindings. Bindings can be added with public void Enable() { - if (enabled) - return; + using (k_InputActionEnableProfilerMarker.Auto()) + { + if (enabled) + return; - // For singleton actions, we create an internal-only InputActionMap - // private to the action. - var map = GetOrCreateActionMap(); + // For singleton actions, we create an internal-only InputActionMap + // private to the action. + var map = GetOrCreateActionMap(); - // First time we're enabled, find all controls. - map.ResolveBindingsIfNecessary(); + // First time we're enabled, find all controls. + map.ResolveBindingsIfNecessary(); - // Go live. - map.m_State.EnableSingleAction(this); + // Go live. + map.m_State.EnableSingleAction(this); + } } /// @@ -928,10 +938,13 @@ public void Enable() /// public void Disable() { - if (!enabled) - return; + using (k_InputActionDisableProfilerMarker.Auto()) + { + if (!enabled) + return; - m_ActionMap.m_State.DisableSingleAction(this); + m_ActionMap.m_State.DisableSingleAction(this); + } } ////REVIEW: is *not* cloning IDs here really the right thing to do? @@ -1075,19 +1088,22 @@ public unsafe object ReadValueAsObject() /// Returns the current level of control actuation (usually [0..1]) or -1 if /// the control is actuated but does not support computing magnitudes. /// + /// /// Magnitudes do not make sense for all types of controls. Controls that have no meaningful magnitude /// will return -1 when calling this method. Any negative magnitude value should be considered an invalid value. - ///
+ ///
+ /// /// The magnitude returned by an action is usually determined by the /// that triggered the action, i.e. by the - /// control referenced from . - ///
+ /// control referenced from . See and + /// for additional information. + ///
+ /// /// However, if the binding that triggered is a composite, then the composite /// will determine the magnitude and not the individual control that triggered. /// Instead, the value of the control that triggered the action will be fed into the composite magnitude calculation. + /// ///
- /// - /// public unsafe float GetControlMagnitude() { var state = GetOrCreateActionMap().m_State; @@ -1186,6 +1202,16 @@ public unsafe bool IsInProgress() return false; } + private int ExpectedFrame() + { + // Used by the WasThisFrame() methods below. + // When processing events manually the event processing will happen one frame later. + // + int frameOffset = InputSystem.settings.updateMode == InputSettings.UpdateMode.ProcessEventsManually ? 1 : 0; + int expectedFrame = Time.frameCount - frameOffset; + return expectedFrame; + } + /// /// Returns true if the action's value crossed the press threshold (see ) /// at any point in the frame. @@ -1236,7 +1262,7 @@ public unsafe bool WasPressedThisFrame() { var actionStatePtr = &state.actionStates[m_ActionIndexInState]; var currentUpdateStep = InputUpdate.s_UpdateStepCount; - return actionStatePtr->pressedInUpdate == currentUpdateStep && currentUpdateStep != default && actionStatePtr->frame == Time.frameCount; + return actionStatePtr->pressedInUpdate == currentUpdateStep && currentUpdateStep != default && actionStatePtr->frame == ExpectedFrame(); } return false; @@ -1285,7 +1311,7 @@ public unsafe bool WasReleasedThisFrame() { var actionStatePtr = &state.actionStates[m_ActionIndexInState]; var currentUpdateStep = InputUpdate.s_UpdateStepCount; - return actionStatePtr->releasedInUpdate == currentUpdateStep && currentUpdateStep != default && actionStatePtr->frame == Time.frameCount; + return actionStatePtr->releasedInUpdate == currentUpdateStep && currentUpdateStep != default && actionStatePtr->frame == ExpectedFrame(); } return false; @@ -1344,7 +1370,7 @@ public unsafe bool WasPerformedThisFrame() { var actionStatePtr = &state.actionStates[m_ActionIndexInState]; var currentUpdateStep = InputUpdate.s_UpdateStepCount; - return actionStatePtr->lastPerformedInUpdate == currentUpdateStep && currentUpdateStep != default && actionStatePtr->frame == Time.frameCount; + return actionStatePtr->lastPerformedInUpdate == currentUpdateStep && currentUpdateStep != default && actionStatePtr->frame == ExpectedFrame(); } return false; @@ -1356,13 +1382,16 @@ public unsafe bool WasPerformedThisFrame() /// /// True if the action completed this frame. /// + /// /// Although is technically a phase, this method does not consider disabling /// the action while the action is in to be "completed". - /// + /// + /// /// This method is different from in that it depends directly on the /// interaction(s) driving the action (including the default interaction if no specific interaction /// has been added to the action or binding). - /// + /// + /// /// For example, let's say the action is bound to the space bar and that the binding has a /// assigned to it. In the frame where the space bar /// is pressed, will be true (because the button/key is now pressed) @@ -1373,7 +1402,8 @@ public unsafe bool WasPerformedThisFrame() /// the phase will change to and stay and /// will be true for one frame as it meets the duration threshold. Once released, WasCompletedThisFrame will be true /// (because the action is no longer performed) and only in the frame where the hold transitioned away from Performed. - /// + /// + /// /// For another example where the action could be considered pressed but also completed, let's say the action /// is bound to the thumbstick and that the binding has a Sector interaction from the XR Interaction Toolkit assigned /// to it such that it only performs in the forward sector area past a button press threshold. In the frame where the @@ -1385,11 +1415,21 @@ public unsafe bool WasPerformedThisFrame() /// the thumbstick was no longer within the forward sector. For more details about the Sector interaction, see /// SectorInteraction /// in the XR Interaction Toolkit Scripting API documentation. - ///
+ ///
+ /// /// Unlike , which will reset when the action goes back to waiting /// state, this property will stay true for the duration of the current frame (that is, until the next /// runs) as long as the action was completed at least once. - /// + /// + /// + /// This method will disregard whether the action is currently enabled or disabled. It will keep returning + /// true for the duration of the frame even if the action was subsequently disabled in the frame. + /// + /// + /// The meaning of "frame" is either the current "dynamic" update (MonoBehaviour.Update) or the current + /// fixed update (MonoBehaviour.FixedUpdate) depending on the value of the setting. + /// + ///
/// /// /// var teleport = playerInput.actions["Teleport"]; @@ -1399,13 +1439,6 @@ public unsafe bool WasPerformedThisFrame() /// StopTeleport(); /// /// - /// - /// This method will disregard whether the action is currently enabled or disabled. It will keep returning - /// true for the duration of the frame even if the action was subsequently disabled in the frame. - /// - /// The meaning of "frame" is either the current "dynamic" update (MonoBehaviour.Update) or the current - /// fixed update (MonoBehaviour.FixedUpdate) depending on the value of the setting. - /// /// /// /// @@ -1417,7 +1450,7 @@ public unsafe bool WasCompletedThisFrame() { var actionStatePtr = &state.actionStates[m_ActionIndexInState]; var currentUpdateStep = InputUpdate.s_UpdateStepCount; - return actionStatePtr->lastCompletedInUpdate == currentUpdateStep && currentUpdateStep != default && actionStatePtr->frame == Time.frameCount; + return actionStatePtr->lastCompletedInUpdate == currentUpdateStep && currentUpdateStep != default && actionStatePtr->frame == ExpectedFrame(); } return false; @@ -1781,8 +1814,88 @@ internal int BindingIndexOnMapToBindingIndexOnAction(int indexOfBindingOnMap) /// Information provided to action callbacks about what triggered an action. ///
/// - /// This struct should not be held on to past the duration of the callback. + /// + /// The callback context represents the current state of an associated with the callback + /// and provides information associated with the bound , its value, and its + /// . + /// + /// + /// The callback context provides you with a way to consume events (push-based input) as part of an update when using + /// input action callback notifications. For example, , + /// , rather than relying on + /// pull-based reading. Also see + /// Responding To Actions for additional information on differences between callbacks and polling. + /// + /// + /// Use this struct to read the current input value through any of the read-method overloads: + /// , , + /// or (unsafe). If you don't know the expected value type, + /// you might need to check before reading the value. + /// + /// + /// Use the property to get the current phase of the associated action or + /// evaluate it directly using any of the convenience methods , , + /// . + /// + /// + /// To obtain information about the current timestamp of the associated event, or to check when the event + /// started, use or respectively. + /// + /// + /// You should not use or keep this struct outside of the callback. + /// /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// using UnityEngine.InputSystem.Interactions; + /// + /// public class MyController : MonoBehaviour + /// { + /// [SerializeField] InputActionReference move; + /// [SerializeField] InputActionReference fire; + /// + /// void Awake() + /// { + /// /// Receive notifications when move or fire actions are performed + /// move.action.performed += MovePerformed; + /// fire.action.performed += FirePerformed; + /// } + /// + /// void OnEnable() + /// { + /// /// Enable actions as part of enabling this behavior. + /// move.action.Enable(); + /// fire.action.Enable(); + /// } + /// + /// void OnDisable() + /// { + /// /// Disable actions as part of disabling this behavior. + /// move.action.Disable(); + /// fire.action.Disable(); + /// } + /// + /// void MovePerformed(InputAction.CallbackContext context) + /// { + /// /// Read the current 2D vector value reported by the associated input action. + /// var direction = context.ReadValue<Vector2>(); + /// Debug.Log("Move: " + direction * Time.deltaTime); + /// } + /// + /// void FirePerformed(InputAction.CallbackContext context) + /// { + /// /// If underlying interaction is a slow-tap fire charged projectile, otherwise fire regular + /// /// projectile. + /// if (context.interaction is SlowTapInteraction) + /// Debug.Log("Fire charged projectile"); + /// else + /// Debug.Log("Fire projectile"); + /// } + /// } + /// + /// /// /// /// @@ -1822,34 +1935,32 @@ public unsafe InputActionPhase phase /// /// Whether the has just been started. /// - /// If true, the action was just started. + /// If true, the action was just started. /// public bool started => phase == InputActionPhase.Started; /// /// Whether the has just been performed. /// - /// If true, the action was just performed. + /// If true, the action was just performed. /// public bool performed => phase == InputActionPhase.Performed; /// /// Whether the has just been canceled. /// - /// If true, the action was just canceled. + /// If true, the action was just canceled. /// public bool canceled => phase == InputActionPhase.Canceled; /// - /// The action that got triggered. + /// The associated action that triggered the callback. /// - /// Action that got triggered. public InputAction action => m_State?.GetActionOrNull(bindingIndex); /// /// The control that triggered the action. /// - /// Control that triggered the action. /// /// In case of a composite binding, this is the control of the composite that activated the /// composite as a whole. For example, in case of a WASD-style binding, it could be the W key. @@ -1867,18 +1978,34 @@ public unsafe InputActionPhase phase /// The interaction that triggered the action or null if the binding that triggered does not /// have any particular interaction set on it. ///
- /// Interaction that triggered the callback. /// /// /// - /// void FirePerformed(InputAction.CallbackContext context) + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// using UnityEngine.InputSystem.Interactions; + /// + /// class Example : MonoBehaviour /// { - /// // If SlowTap interaction was performed, perform a charged - /// // firing. Otherwise, fire normally. - /// if (context.interaction is SlowTapInteraction) - /// FireChargedProjectile(); - /// else - /// FireNormalProjectile(); + /// public InputActionReference fire; + /// + /// public void Awake() + /// { + /// fire.action.performed += FirePerformed; + /// } + /// + /// void OnEnable() => fire.action.Enable(); + /// void OnDisable() => fire.action.Disable(); + /// + /// void FirePerformed(InputAction.CallbackContext context) + /// { + /// /// If SlowTap interaction was performed, perform a charged + /// /// firing. Otherwise, fire normally. + /// if (context.interaction is SlowTapInteraction) + /// Debug.Log("Fire charged projectile"); + /// else + /// Debug.Log("Fire projectile"); + /// } /// } /// /// @@ -1901,9 +2028,10 @@ public IInputInteraction interaction /// /// The time at which the action got triggered. /// - /// Time relative to Time.realtimeSinceStartup at which - /// the action got triggered. /// + /// Time is relative to at which the action got + /// triggered. + /// /// This is usually determined by the timestamp of the input event that activated a control /// bound to the action. What this means is that this is normally not the /// value of Time.realtimeSinceStartup when the input system calls the @@ -1922,10 +2050,8 @@ public unsafe double time } /// - /// Time at which the action was started. + /// Time at which the action was with relation to Time.realtimeSinceStartup. /// - /// Value relative to Time.realtimeSinceStartup when the action - /// changed to . /// /// This is only relevant for actions that go through distinct a /// cycle as driven by interactions. @@ -1947,11 +2073,10 @@ public unsafe double startTime /// /// Time difference between and . /// - /// Difference between and . /// /// This property can be used, for example, to determine how long a button /// was held down. - /// + /// /// /// /// // Let's create a button action bound to the A button @@ -1976,22 +2101,18 @@ public unsafe double startTime /// }; /// /// - /// public double duration => time - startTime; /// /// Type of value returned by and expected /// by . /// - /// Type of object returned when reading a value. /// /// The type of value returned by an action is usually determined by the /// that triggered the action, i.e. by the - /// control referenced from . - /// - /// However, if the binding that triggered is a composite, then the composite - /// will determine values and not the individual control that triggered (that - /// one just feeds values into the composite). + /// control referenced from . However, if the binding that + /// triggered is a composite, then the composite will determine values and + /// not the individual control that triggered (that one just feeds values into the composite). /// /// /// @@ -1999,21 +2120,24 @@ public unsafe double startTime public Type valueType => m_State?.GetValueType(bindingIndex, controlIndex); /// - /// Size of values returned by . + /// Size of values returned by in bytes. /// - /// Size of value returned when reading. /// + /// /// All input values passed around by the system are required to be "blittable", /// i.e. they cannot contain references, cannot be heap objects themselves, and /// must be trivially mem-copyable. This means that any value can be read out /// and retained in a raw byte buffer. - /// + /// + /// /// The value of this property determines how many bytes will be written /// by . + /// + /// + /// This property indirectly maps to the value of or + /// . + /// /// - /// - /// - /// public int valueSizeInBytes { get @@ -2026,16 +2150,15 @@ public int valueSizeInBytes } /// - /// Read the value of the action as a raw byte buffer. This allows reading - /// values without having to know value types but also, unlike , - /// without allocating GC heap memory. + /// Read the value of the action as a raw byte buffer. /// /// Memory buffer to read the value into. /// Size of buffer allocated at . Must be /// at least . - /// is null. - /// is too small. /// + /// This allows reading values without having to know value types but also, + /// unlike , without allocating GC heap memory. + /// /// /// /// // Read a Vector2 using the raw memory ReadValue API. @@ -2051,7 +2174,8 @@ public int valueSizeInBytes /// } /// /// - /// + /// is null. + /// is too small. /// /// /// @@ -2075,17 +2199,53 @@ public unsafe void ReadValue(void* buffer, int bufferSize) } /// - /// Read the value of the action. + /// Read the current value of the associated action. /// /// Type of value to read. This must correspond to the /// expected by either or, if it is a composite, by the /// in use. - /// The value read from the action. + /// The current value of type associated with the action. /// The given type /// does not match the value type expected by the control or binding composite. /// - /// + /// /// + /// + /// The following example shows how to read the current value of a specific type: + /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class Example : MonoBehaviour + /// { + /// public InputActionReference move; + /// + /// void Awake() + /// { + /// if (move.action != null) + /// { + /// move.action.performed += context => + /// { + /// // Note: Assumes the underlying value type is Vector2. + /// Debug.Log($"Value is: {context.ReadValue<Vector2>()}"); + /// }; + /// } + /// } + /// + /// void OnEnable() + /// { + /// move.action.Enable(); + /// } + /// + /// void OnDisable() + /// { + /// move.action.Disable(); + /// } + /// } + /// + /// public TValue ReadValue() where TValue : struct { @@ -2110,6 +2270,39 @@ public TValue ReadValue() /// of the button will be taken into account (if set). If there is no custom button press point, the /// global will be used. /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class Example : MonoBehaviour + /// { + /// public InputActionReference fire; + /// + /// void Awake() + /// { + /// if (fire.action != null) + /// { + /// fire.action.performed += context => + /// { + /// // ReadValueAsButton attempts to interpret the value as a button. + /// Debug.Log($"Is firing: {context.ReadValueAsButton()}"); + /// }; + /// } + /// } + /// + /// void OnEnable() + /// { + /// fire.action.Enable(); + /// } + /// + /// void OnDisable() + /// { + /// fire.action.Disable(); + /// } + /// } + /// + /// /// /// public bool ReadValueAsButton() @@ -2121,15 +2314,52 @@ public bool ReadValueAsButton() } /// - /// Same as except that it is not necessary to - /// know the type of value at compile time. + /// Same as except that it is not necessary to know the type of the value + /// at compile time. /// /// The current value from the binding that triggered the action or null if the action /// is not currently in progress. /// - /// This method allocates GC heap memory. Using it during normal gameplay will lead + /// This method allocates GC heap memory due to boxing. Using it during normal gameplay will lead /// to frame-rate instabilities. /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class Example : MonoBehaviour + /// { + /// public InputActionReference move; + /// + /// void Awake() + /// { + /// if (move.action != null) + /// { + /// move.action.performed += context => + /// { + /// // ReadValueAsObject allows reading the associated value as a boxed reference type. + /// object obj = context.ReadValueAsObject(); + /// if (obj is Vector2) + /// Debug.Log($"Current value is Vector2 type: {obj}"); + /// else + /// Debug.Log($"Current value is of another type: {context.valueType}"); + /// }; + /// } + /// } + /// + /// void OnEnable() + /// { + /// move.action.Enable(); + /// } + /// + /// void OnDisable() + /// { + /// move.action.Disable(); + /// } + /// } + /// + /// /// /// public object ReadValueAsObject() @@ -2143,6 +2373,37 @@ public object ReadValueAsObject() /// Return a string representation of the context useful for debugging. /// /// String representation of the context. + /// + /// The following example illustrates how to log callback context to console when a callback is received + /// for debugging purposes: + /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class Example : MonoBehaviour + /// { + /// public InputActionReference move; + /// + /// void Awake() + /// { + /// if (move.action != null) + /// { + /// move.action.performed += context => + /// { + /// // Outputs the associated callback context in its textual representation which may + /// // be useful for debugging purposes. + /// Debug.Log(context.ToString()); + /// }; + /// } + /// } + /// + /// void OnEnable() => move.action.Enable(); + /// void OnDisable() => move.action.Disable(); + /// } + /// + /// public override string ToString() { return $"{{ action={action} phase={phase} time={time} control={control} value={ReadValueAsObject()} interaction={interaction} }}"; diff --git a/InputSystem/Actions/InputActionMap.cs b/InputSystem/Actions/InputActionMap.cs index 8918757d..e5b5f2cb 100644 --- a/InputSystem/Actions/InputActionMap.cs +++ b/InputSystem/Actions/InputActionMap.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Unity.Collections; +using Unity.Profiling; using UnityEngine.InputSystem.Utilities; ////REVIEW: given we have the global ActionPerformed callback, do we really need the per-map callback? @@ -313,6 +314,11 @@ public event Action actionTriggered remove => m_ActionCallbacks.RemoveCallback(value); } + /// + /// ProfilerMarker to measure how long it takes to resolve bindings. + /// + static readonly ProfilerMarker k_ResolveBindingsProfilerMarker = new ProfilerMarker("InputActionMap.ResolveBindings"); + /// /// Construct an action map with default values. /// @@ -1299,100 +1305,104 @@ internal bool ResolveBindingsIfNecessary() /// internal void ResolveBindings() { - // Make sure that if we trigger callbacks as part of disabling and re-enabling actions, - // we don't trigger a re-resolve while we're already resolving bindings. - using (InputActionRebindingExtensions.DeferBindingResolution()) + using (k_ResolveBindingsProfilerMarker.Auto()) { - // In case we have actions that are currently enabled, we temporarily retain the - // UnmanagedMemory of our InputActionState so that we can sync action states after - // we have re-resolved bindings. - var oldMemory = new InputActionState.UnmanagedMemory(); - try + // Make sure that if we trigger callbacks as part of disabling and re-enabling actions, + // we don't trigger a re-resolve while we're already resolving bindings. + using (InputActionRebindingExtensions.DeferBindingResolution()) { - OneOrMore> actionMaps; + // In case we have actions that are currently enabled, we temporarily retain the + // UnmanagedMemory of our InputActionState so that we can sync action states after + // we have re-resolved bindings. + var oldMemory = new InputActionState.UnmanagedMemory(); + try + { + OneOrMore> actionMaps; - // Start resolving. - var resolver = new InputBindingResolver(); + // Start resolving. + var resolver = new InputBindingResolver(); - // If we're part of an asset, we share state and thus binding resolution with - // all maps in the asset. - var needFullResolve = m_State == null; - if (m_Asset != null) - { - actionMaps = m_Asset.actionMaps; - Debug.Assert(actionMaps.Count > 0, "Asset referred to by action map does not have action maps"); + // If we're part of an asset, we share state and thus binding resolution with + // all maps in the asset. + var needFullResolve = m_State == null; + if (m_Asset != null) + { + actionMaps = m_Asset.actionMaps; + Debug.Assert(actionMaps.Count > 0, "Asset referred to by action map does not have action maps"); - // If there's a binding mask set on the asset, apply it. - resolver.bindingMask = m_Asset.m_BindingMask; + // If there's a binding mask set on the asset, apply it. + resolver.bindingMask = m_Asset.m_BindingMask; - foreach (var map in actionMaps) + foreach (var map in actionMaps) + { + needFullResolve |= map.bindingResolutionNeedsFullReResolve; + map.needToResolveBindings = false; + map.bindingResolutionNeedsFullReResolve = false; + map.controlsForEachActionInitialized = false; + } + } + else { - needFullResolve |= map.bindingResolutionNeedsFullReResolve; - map.needToResolveBindings = false; - map.bindingResolutionNeedsFullReResolve = false; - map.controlsForEachActionInitialized = false; + // Standalone action map (possibly a hidden one created for a singleton action). + // Gets its own private state. + + actionMaps = this; + needFullResolve |= bindingResolutionNeedsFullReResolve; + needToResolveBindings = false; + bindingResolutionNeedsFullReResolve = false; + controlsForEachActionInitialized = false; } - } - else - { - // Standalone action map (possibly a hidden one created for a singleton action). - // Gets its own private state. - - actionMaps = this; - needFullResolve |= bindingResolutionNeedsFullReResolve; - needToResolveBindings = false; - bindingResolutionNeedsFullReResolve = false; - controlsForEachActionInitialized = false; - } - // If we already have a state, re-use the arrays we have already allocated. - // NOTE: We will install the arrays on the very same InputActionState instance below. In the - // case where we didn't have to grow the arrays, we should end up with zero GC allocations - // here. - var hasEnabledActions = false; - InputControlList activeControls = default; - if (m_State != null) - { - // Grab a clone of the current memory. We clone because disabling all the actions - // in the map will alter the memory state and we want the state before we start - // touching it. - oldMemory = m_State.memory.Clone(); + // If we already have a state, re-use the arrays we have already allocated. + // NOTE: We will install the arrays on the very same InputActionState instance below. In the + // case where we didn't have to grow the arrays, we should end up with zero GC allocations + // here. + var hasEnabledActions = false; + InputControlList activeControls = default; + if (m_State != null) + { + // Grab a clone of the current memory. We clone because disabling all the actions + // in the map will alter the memory state and we want the state before we start + // touching it. + oldMemory = m_State.memory.Clone(); - m_State.PrepareForBindingReResolution(needFullResolve, ref activeControls, ref hasEnabledActions); + m_State.PrepareForBindingReResolution(needFullResolve, ref activeControls, ref hasEnabledActions); - // Reuse the arrays we have so that we can avoid managed memory allocations, if possible. - resolver.StartWithPreviousResolve(m_State, isFullResolve: needFullResolve); + // Reuse the arrays we have so that we can avoid managed memory allocations, if possible. + resolver.StartWithPreviousResolve(m_State, isFullResolve: needFullResolve); - // Throw away old memory. - m_State.memory.Dispose(); - } + // Throw away old memory. + m_State.memory.Dispose(); + } - // Resolve all maps in the asset. - foreach (var map in actionMaps) - resolver.AddActionMap(map); + // Resolve all maps in the asset. + foreach (var map in actionMaps) + resolver.AddActionMap(map); - // Install state. - if (m_State == null) - { - m_State = new InputActionState(); - m_State.Initialize(resolver); - } - else - { - m_State.ClaimDataFrom(resolver); + // Install state. + if (m_State == null) + { + m_State = new InputActionState(); + m_State.Initialize(resolver); + } + else + { + m_State.ClaimDataFrom(resolver); + } + + if (m_Asset != null) + { + foreach (var map in actionMaps) + map.m_State = m_State; + m_Asset.m_SharedStateForAllMaps = m_State; + } + + m_State.FinishBindingResolution(hasEnabledActions, oldMemory, activeControls, isFullResolve: needFullResolve); } - if (m_Asset != null) + finally { - foreach (var map in actionMaps) - map.m_State = m_State; - m_Asset.m_SharedStateForAllMaps = m_State; + oldMemory.Dispose(); } - - m_State.FinishBindingResolution(hasEnabledActions, oldMemory, activeControls, isFullResolve: needFullResolve); - } - finally - { - oldMemory.Dispose(); } } } diff --git a/InputSystem/Actions/InputActionState.cs b/InputSystem/Actions/InputActionState.cs index cd17fdd9..79cfa1b8 100644 --- a/InputSystem/Actions/InputActionState.cs +++ b/InputSystem/Actions/InputActionState.cs @@ -1476,43 +1476,37 @@ private void ProcessControlStateChange(int mapIndex, int controlIndex, int bindi // If the binding is part of a composite, check for interactions on the composite // itself and give them a first shot at processing the value change. var haveInteractionsOnComposite = false; - var compositeAlreadyTriggered = false; if (bindingStatePtr->isPartOfComposite) { var compositeBindingIndex = bindingStatePtr->compositeOrCompositeBindingIndex; var compositeBindingPtr = &bindingStates[compositeBindingIndex]; - // If the composite has already been triggered from the very same event set a flag so it isn't triggered again. + // If the composite has already been triggered from the very same event, ignore it. // Example: KeyboardState change that includes both A and W key state changes and we're looking // at a WASD composite binding. There's a state change monitor on both the A and the W // key and thus the manager will notify us individually of both changes. However, we // want to perform the action only once. - // NOTE: Do NOT ignore this Event, we still need finish processing the individual button states. - if (!ShouldIgnoreInputOnCompositeBinding(compositeBindingPtr, eventPtr)) - { - // Update magnitude for composite. - var compositeIndex = bindingStates[compositeBindingIndex].compositeOrCompositeBindingIndex; - var compositeContext = new InputBindingCompositeContext - { - m_State = this, - m_BindingIndex = compositeBindingIndex - }; - trigger.magnitude = composites[compositeIndex].EvaluateMagnitude(ref compositeContext); - memory.compositeMagnitudes[compositeIndex] = trigger.magnitude; + if (ShouldIgnoreInputOnCompositeBinding(compositeBindingPtr, eventPtr)) + return; - // Run through interactions on composite. - var interactionCountOnComposite = compositeBindingPtr->interactionCount; - if (interactionCountOnComposite > 0) - { - haveInteractionsOnComposite = true; - ProcessInteractions(ref trigger, - compositeBindingPtr->interactionStartIndex, - interactionCountOnComposite); - } - } - else + // Update magnitude for composite. + var compositeIndex = bindingStates[compositeBindingIndex].compositeOrCompositeBindingIndex; + var compositeContext = new InputBindingCompositeContext + { + m_State = this, + m_BindingIndex = compositeBindingIndex + }; + trigger.magnitude = composites[compositeIndex].EvaluateMagnitude(ref compositeContext); + memory.compositeMagnitudes[compositeIndex] = trigger.magnitude; + + // Run through interactions on composite. + var interactionCountOnComposite = compositeBindingPtr->interactionCount; + if (interactionCountOnComposite > 0) { - compositeAlreadyTriggered = true; + haveInteractionsOnComposite = true; + ProcessInteractions(ref trigger, + compositeBindingPtr->interactionStartIndex, + interactionCountOnComposite); } } @@ -1521,31 +1515,21 @@ private void ProcessControlStateChange(int mapIndex, int controlIndex, int bindi // one of higher magnitude) or may even lead us to switch to processing a different binding // (e.g. when an input of previously greater magnitude has now fallen below the level of another // ongoing input with now higher magnitude). - // - // If Composite has already been triggered, skip this step; it's unnecessary and could also - // cause a processing issue if we switch to another binding. - var isConflictingInput = false; - if (!compositeAlreadyTriggered) - { - isConflictingInput = IsConflictingInput(ref trigger, actionIndex); - bindingStatePtr = &bindingStates[trigger.bindingIndex]; // IsConflictingInput may switch us to a different binding. - } + var isConflictingInput = IsConflictingInput(ref trigger, actionIndex); + bindingStatePtr = &bindingStates[trigger.bindingIndex]; // IsConflictingInput may switch us to a different binding. // Process button presses/releases. - // We MUST execute this processing even if Composite has already been triggered to ensure button states - // are properly updated (ISXB-746) if (!isConflictingInput) ProcessButtonState(ref trigger, actionIndex, bindingStatePtr); // If we have interactions, let them do all the processing. The presence of an interaction // essentially bypasses the default phase progression logic of an action. - // Interactions are skipped if compositeAlreadyTriggered is set. var interactionCount = bindingStatePtr->interactionCount; if (interactionCount > 0 && !bindingStatePtr->isPartOfComposite) { ProcessInteractions(ref trigger, bindingStatePtr->interactionStartIndex, interactionCount); } - else if (!haveInteractionsOnComposite && !isConflictingInput && !compositeAlreadyTriggered) + else if (!haveInteractionsOnComposite && !isConflictingInput) { ProcessDefaultInteraction(ref trigger, actionIndex); } diff --git a/InputSystem/Actions/Interactions/HoldInteraction.cs b/InputSystem/Actions/Interactions/HoldInteraction.cs index d05942b0..79e057da 100644 --- a/InputSystem/Actions/Interactions/HoldInteraction.cs +++ b/InputSystem/Actions/Interactions/HoldInteraction.cs @@ -125,7 +125,7 @@ protected override void OnEnable() public override void OnGUI() { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return; + if (!InputSystem.settings.useIMGUIEditorForAssets) return; #endif m_PressPointSetting.OnGUI(); m_DurationSetting.OnGUI(); diff --git a/InputSystem/Actions/Interactions/MultiTapInteraction.cs b/InputSystem/Actions/Interactions/MultiTapInteraction.cs index 8e5c3f14..35768ca1 100644 --- a/InputSystem/Actions/Interactions/MultiTapInteraction.cs +++ b/InputSystem/Actions/Interactions/MultiTapInteraction.cs @@ -197,7 +197,7 @@ protected override void OnEnable() public override void OnGUI() { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return; + if (!InputSystem.settings.useIMGUIEditorForAssets) return; #endif target.tapCount = EditorGUILayout.IntField(m_TapCountLabel, target.tapCount); m_TapDelaySetting.OnGUI(); diff --git a/InputSystem/Actions/Interactions/PressInteraction.cs b/InputSystem/Actions/Interactions/PressInteraction.cs index 136e6e51..33cdb0bc 100644 --- a/InputSystem/Actions/Interactions/PressInteraction.cs +++ b/InputSystem/Actions/Interactions/PressInteraction.cs @@ -214,7 +214,7 @@ protected override void OnEnable() public override void OnGUI() { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return; + if (!InputSystem.settings.useIMGUIEditorForAssets) return; #endif EditorGUILayout.HelpBox(s_HelpBoxText); target.behavior = (PressBehavior)EditorGUILayout.EnumPopup(s_PressBehaviorLabel, target.behavior); diff --git a/InputSystem/Actions/Interactions/SlowTapInteraction.cs b/InputSystem/Actions/Interactions/SlowTapInteraction.cs index a4c41fe2..5309ab7a 100644 --- a/InputSystem/Actions/Interactions/SlowTapInteraction.cs +++ b/InputSystem/Actions/Interactions/SlowTapInteraction.cs @@ -89,7 +89,7 @@ protected override void OnEnable() public override void OnGUI() { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return; + if (!InputSystem.settings.useIMGUIEditorForAssets) return; #endif m_DurationSetting.OnGUI(); m_PressPointSetting.OnGUI(); diff --git a/InputSystem/Actions/Interactions/TapInteraction.cs b/InputSystem/Actions/Interactions/TapInteraction.cs index b5079ca0..b76a7807 100644 --- a/InputSystem/Actions/Interactions/TapInteraction.cs +++ b/InputSystem/Actions/Interactions/TapInteraction.cs @@ -103,7 +103,7 @@ protected override void OnEnable() public override void OnGUI() { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return; + if (!InputSystem.settings.useIMGUIEditorForAssets) return; #endif m_DurationSetting.OnGUI(); m_PressPointSetting.OnGUI(); diff --git a/InputSystem/AssemblyInfo.cs b/InputSystem/AssemblyInfo.cs index ace16cbf..b15c0de9 100644 --- a/InputSystem/AssemblyInfo.cs +++ b/InputSystem/AssemblyInfo.cs @@ -16,7 +16,7 @@ public static partial class InputSystem // Keep this in sync with "Packages/com.unity.inputsystem/package.json". // NOTE: Unfortunately, System.Version doesn't use semantic versioning so we can't include // "-preview" suffixes here. - internal const string kAssemblyVersion = "1.11.2"; - internal const string kDocUrl = "https://docs.unity3d.com/Packages/com.unity.inputsystem@1.11"; + internal const string kAssemblyVersion = "1.12.0"; + internal const string kDocUrl = "https://docs.unity3d.com/Packages/com.unity.inputsystem@1.12"; } } diff --git a/InputSystem/Controls/ButtonControl.cs b/InputSystem/Controls/ButtonControl.cs index fe28ffe3..e04f4ebf 100644 --- a/InputSystem/Controls/ButtonControl.cs +++ b/InputSystem/Controls/ButtonControl.cs @@ -10,7 +10,7 @@ namespace UnityEngine.InputSystem.Controls /// An axis that has a trigger point beyond which it is considered to be pressed. /// /// - /// By default stored as a single bit. In that format, buttons will only yield 0 + /// By default, stored as a single bit. In that format, buttons will only yield 0 /// and 1 as values. However, buttons return are s and /// yield full floating-point values and may thus have a range of values. See /// for how button presses on such buttons are handled. @@ -34,7 +34,6 @@ public class ButtonControl : AxisControl /// /// The minimum value the button has to reach for it to be considered pressed. /// - /// Button press threshold. /// /// The button is considered pressed, if it has a value equal to or greater than /// this value. @@ -46,6 +45,9 @@ public class ButtonControl : AxisControl /// /// /// + /// using UnityEngine; + /// using UnityEngine.InputSystem.Controls; + /// /// public class MyDevice : InputDevice /// { /// [InputControl(parameters = "pressPoint=0.234")] @@ -56,25 +58,37 @@ public class ButtonControl : AxisControl /// /// /// - /// - /// - /// public float pressPoint = -1; /// /// Return if set, otherwise return . /// - /// Effective value to use for press point thresholds. - /// public float pressPointOrDefault => pressPoint > 0 ? pressPoint : s_GlobalDefaultButtonPressPoint; /// - /// Default-initialize the control. + /// Default-initialize the button control. /// /// - /// The default format for the control is . - /// The control's minimum value is set to 0 and the maximum value to 1. + /// The default format for the button control is . + /// The button control's minimum value is set to 0 and the maximum value to 1. /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem.Controls; + /// + /// public class ButtonControlExample : MonoBehaviour + /// { + /// void Start() + /// { + /// var myButton = new ButtonControl(); + /// } + /// + /// //... + /// } + /// + /// + /// public ButtonControl() { m_StateBlock.format = InputStateBlock.FormatBit; @@ -85,10 +99,39 @@ public ButtonControl() /// /// Whether the given value would be considered pressed for this button. /// - /// Value for the button. + /// Value to check for if the button would be considered pressed or not. /// True if crosses the threshold to be considered pressed. - /// - /// + /// + /// The default format for the control is . + /// The control's minimum value is set to 0 and the maximum value to 1. + /// See and for the (default) press point. + /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem.Controls; + /// + /// public class IsValueConsideredPressedExample : MonoBehaviour + /// { + /// void Start() + /// { + /// var myButton = new ButtonControl(); + /// var valueToTest = 0.5f; + /// + /// if (myButton.IsValueConsideredPressed(valueToTest)) + /// { + /// Debug.Log("myButton is considered pressed at: " + valueToTest.ToString()); + /// } + /// else + /// { + /// Debug.Log("myButton is not considered pressed at: " + valueToTest.ToString()); + /// } + /// } + /// + /// //... + /// } + /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public new bool IsValueConsideredPressed(float value) { @@ -98,10 +141,9 @@ public ButtonControl() /// /// Whether the button is currently pressed. /// - /// True if button is currently pressed. /// /// A button is considered pressed if its value is equal to or greater - /// than its button press threshold (). + /// than its button press threshold (, ). /// /// /// You can use this to read whether specific keys are currently pressed by using isPressed on keys, as shown in the following examples: @@ -149,9 +191,6 @@ public ButtonControl() /// ]]> /// /// - /// - /// - /// public bool isPressed { get @@ -200,7 +239,6 @@ private void BeginTestingForFramePresses(bool currentlyPressed, bool pressedLast /// /// Whether the press started this frame. /// - /// True if the current press of the button started this frame. /// /// The first time this function - or wasReleasedThisFrame - are called, it's possible that extremely fast /// inputs (or very slow frame update times) will result in presses/releases being missed. @@ -262,12 +300,12 @@ public bool wasPressedThisFrame /// /// Whether the press ended this frame. /// - /// True if the current press of the button ended this frame. /// /// _Note_: The Input System identifies keys by physical layout, not according to the current language mapping of the keyboard. To query the name of the key according to the language mapping, use . /// /// /// An example showing the use of this property on a gamepad button and a keyboard key: + /// /// /// using UnityEngine; /// using UnityEngine.InputSystem; @@ -276,8 +314,8 @@ public bool wasPressedThisFrame /// { /// void Update() /// { - /// bool buttonPressed = Gamepad.current.aButton.wasReleasedThisFrame; - /// bool spaceKeyPressed = Keyboard.current.spaceKey.wasReleasedThisFrame; + /// bool buttonReleased = Gamepad.current.aButton.wasReleasedThisFrame; + /// bool spaceKeyReleased = Keyboard.current.spaceKey.wasReleasedThisFrame; /// } /// } /// diff --git a/InputSystem/Controls/InputControl.cs b/InputSystem/Controls/InputControl.cs index 1f5ec6ad..4634d67a 100644 --- a/InputSystem/Controls/InputControl.cs +++ b/InputSystem/Controls/InputControl.cs @@ -77,7 +77,7 @@ namespace UnityEngine.InputSystem /// which has APIs specific to the type of value of the control (e.g. . /// /// The following example demonstrates various common operations performed on input controls: - /// + /// /// /// /// // Look up dpad/up control on current gamepad. @@ -99,10 +99,7 @@ namespace UnityEngine.InputSystem /// leftStickHistory.Enable(); /// /// - /// - /// - /// - /// + /// /// /// /// @@ -122,11 +119,8 @@ public abstract class InputControl /// Lookup of names is case-insensitive. /// /// This is set from the name of the control in the layout. + /// See , , and . /// - /// - /// - /// - /// public string name => m_Name; ////TODO: protect against empty strings @@ -146,8 +140,9 @@ public abstract class InputControl /// For nested controls, the display name will include the display names of all parent controls, /// i.e. the display name will fully identify the control on the device. For example, the display /// name for the left D-Pad button on a gamepad is "D-Pad Left" and not just "Left". + /// + /// There is a short name version, see . /// - /// public string displayName { get @@ -176,9 +171,8 @@ public string displayName /// For nested controls, the short display name will include the short display names of all parent controls, /// that is, the display name will fully identify the control on the device. For example, the display /// name for the left D-Pad button on a gamepad is "D-Pad \u2190" and not just "\u2190". Note that if a parent - /// control has no short name, its long name will be used instead. + /// control has no short name, its long name will be used instead. See . /// - /// public string shortDisplayName { get @@ -205,11 +199,11 @@ public string shortDisplayName /// /// Allocates on first hit. Paths are not created until someone asks for them. /// + /// For more details on the path see . + /// /// /// Example: "/gamepad/leftStick/x" /// - /// - /// public string path { get @@ -221,7 +215,7 @@ public string path } /// - /// Layout the control is based on. + /// Layout name for the control it is based on. /// /// /// This is the layout name rather than a reference to an as @@ -243,25 +237,27 @@ public string path /// /// /// This is the root of the control hierarchy. For the device at the root, this - /// will point to itself. + /// will point to itself (See ). /// - /// public InputDevice device => m_Device; /// - /// The immediate parent of the control or null if the control has no parent - /// (which, once fully constructed) will only be the case for InputDevices). + /// The immediate parent of the control. /// - /// + /// + /// The immediate parent of the control or null if the control has no parent + /// (which, once fully constructed, will only be the case for InputDevices). + /// See the related field. + /// public InputControl parent => m_Parent; /// - /// List of immediate children. + /// List of immediate child controls below this. /// /// /// Does not allocate. + /// See the related field. /// - /// public ReadOnlyArray children => new ReadOnlyArray(m_Device.m_ChildrenForEachControl, m_ChildStartIndex, m_ChildCount); @@ -281,34 +277,38 @@ public string path /// control to use for certain standardized situation without having to know the particulars of /// the device or platform. /// - /// - /// - /// // Bind to any control which is tagged with the "Back" usage on any device. - /// var backAction = new InputAction(binding: "*/{Back}"); - /// - /// - /// /// Note that usages on devices work slightly differently than usages of controls on devices. /// They are also queried through this property but unlike the usages of controls, the set of /// usages of a device can be changed dynamically as the role of the device changes. For details, /// see . Controls, on the other hand, /// can currently only be assigned usages through layouts ( /// or ). + /// + /// can be used to add a device. + /// can be used to remove a device. /// - /// - /// - /// - /// - /// - /// + /// + /// + /// // Bind to any control which is tagged with the "Back" usage on any device. + /// var backAction = new InputAction(binding: "*/{Back}"); + /// + /// public ReadOnlyArray usages => new ReadOnlyArray(m_Device.m_UsagesForEachControl, m_UsageStartIndex, m_UsageCount); - // List of alternate names for the control. + /// + /// List of alternate names for the control. + /// + /// + /// List of aliased alternate names for the control. + /// An example of an alias would be 'North' for the 'Triangle' button on a Playstation pad (or 'Y' button on Xbox pad). + /// public ReadOnlyArray aliases => new ReadOnlyArray(m_Device.m_AliasesForEachControl, m_AliasStartIndex, m_AliasCount); - // Information about where the control stores its state. + /// + /// Information about where the control stores its state, such as format, offset and size. + /// public InputStateBlock stateBlock => m_StateBlock; /// @@ -380,9 +380,10 @@ internal set /// input from an actual physical control whereas "<Gamepad>/leftStick/left" /// represents input from a made-up control. If, however, the "left" button is the only /// viable pick, it will be accepted. + /// + /// A control layout will specify if it is synthetic using . + /// See . /// - /// - /// public bool synthetic { get => (m_ControlFlags & ControlFlags.IsSynthetic) != 0; @@ -398,8 +399,11 @@ internal set /// /// Fetch a control from the control's hierarchy by name. /// + /// A control path. See . /// - /// Note that path matching is case-insensitive. + /// Note that matching is case-insensitive. + /// (see ). + /// An alternative method is . /// /// /// @@ -409,9 +413,6 @@ internal set /// /// /// cannot be found. - /// - /// - /// public InputControl this[string path] { get @@ -430,30 +431,34 @@ public InputControl this[string path] /// Type of values produced by the control. /// /// This is the type of values that are returned when reading the current value of a control - /// or when reading a value of a control from an event. + /// or when reading a value of a control from an event with . + /// The size can be determined with . /// - /// - /// public abstract Type valueType { get; } /// /// Size in bytes of values that the control returns. /// - /// + /// + /// The type can be determined with . + /// public abstract int valueSizeInBytes { get; } /// /// Compute an absolute, normalized magnitude value that indicates the extent to which the control /// is actuated. Shortcut for . /// - /// Amount of actuation of the control or -1 if it cannot be determined. - /// - /// + /// + /// Amount of actuation of the control or -1 if it cannot be determined. + /// public float magnitude => EvaluateMagnitude(); /// - /// Return a string representation of the control useful for debugging. + /// Return a string representation of the control. /// + /// + /// Return a string representation of the control. Useful for debugging. + /// /// A string representation of the control. public override string ToString() { @@ -503,23 +508,48 @@ public unsafe float EvaluateMagnitude() /// Compute an absolute, normalized magnitude value that indicates the extent to which the control /// is actuated in the given state. /// + /// + /// Magnitudes do not make sense for all types of controls. For example, for a control that represents + /// an enumeration of values (such as ), there is no meaningful + /// linear ordering of values (one could derive a linear ordering through the actual enum values but + /// their assignment may be entirely arbitrary; it is unclear whether a state of + /// has a higher or lower "magnitude" as a state of ). + /// + /// Controls that have no meaningful magnitude will return -1 when calling this method. Any negative + /// return value should be considered an invalid value. + /// /// State containing the control's . /// Amount of actuation of the control or -1 if it cannot be determined. /// - /// public virtual unsafe float EvaluateMagnitude(void* statePtr) { return -1; } + /// + /// Read the control's final, processed value from the given buffer and return the value as an object. + /// + /// Buffer to read the value from. + /// Size of in bytes, which must be large enough to store the value. + /// The control's value as stored in . + /// + /// Read the control's final, processed value from the given buffer and return the value as an object. + /// + /// This method allocates GC memory and should not be used during normal gameplay operation. + /// + /// is null. + /// is smaller than the size of the value to be read. + /// public abstract unsafe object ReadValueFromBufferAsObject(void* buffer, int bufferSize); /// /// Read the control's final, processed value from the given state and return the value as an object. /// - /// + /// State to read the value for the control from. /// The control's value as stored in . /// + /// Read the control's final, processed value from the given state and return the value as an object. + /// /// This method allocates GC memory and should not be used during normal gameplay operation. /// /// is null. @@ -542,7 +572,7 @@ public virtual unsafe float EvaluateMagnitude(void* statePtr) /// /// Read a value from the given memory and store it as state. /// - /// Memory containing value. + /// Memory containing value, to store into the state. /// Size of in bytes. Must be at least . /// State containing the control's . Will receive the state /// as converted from the given value. @@ -564,9 +594,9 @@ public virtual unsafe void WriteValueFromBufferIntoState(void* bufferPtr, int bu /// /// Read a value object and store it as state in the given memory. /// - /// Value for the control. + /// Value for the control to store in the state. /// State containing the control's . Will receive - /// the state state as converted from the given value. + /// the state as converted from the given value. /// /// Writing values will NOT apply processors to the given value. This can mean that when reading a value /// from a control after it has been written to its state, the resulting value differs from what was @@ -611,6 +641,8 @@ public virtual unsafe void WriteValueFromObjectIntoState(object value, void* sta /// Note that if the given path matches multiple child controls, only the first control /// encountered in the search will be returned. /// + /// This method is equivalent to calling . + /// /// /// /// // Returns the leftStick control of the current gamepad. @@ -625,9 +657,6 @@ public virtual unsafe void WriteValueFromObjectIntoState(object value, void* sta /// Gamepad.current.TryGetChildControl("*stick"); /// /// - /// - /// This method is equivalent to calling . - /// public InputControl TryGetChildControl(string path) { if (string.IsNullOrEmpty(path)) @@ -635,6 +664,19 @@ public InputControl TryGetChildControl(string path) return InputControlPath.TryFindChild(this, path); } + /// + /// Try to find a child control matching the given path. + /// + /// A control path. See . + /// The type of control to locate. + /// The first direct or indirect child control that matches the given + /// or null if no control was found to match. + /// is null or empty. + /// No control found with the specified type. + /// + /// Note that if the given path matches multiple child controls, only the first control + /// encountered in the search will be returned. + /// public TControl TryGetChildControl(string path) where TControl : InputControl { @@ -653,6 +695,19 @@ public TControl TryGetChildControl(string path) return controlOfType; } + /// + /// Find a child control matching the given path. + /// + /// A control path. See . + /// The first direct or indirect child control that matches the given + /// or null if no control was found to match. + /// is null or empty. + /// No control found with the specified type. + /// The control cannot be found. + /// + /// Note that if the given path matches multiple child controls, only the first control + /// encountered in the search will be returned. + /// public InputControl GetChildControl(string path) { if (string.IsNullOrEmpty(path)) @@ -665,6 +720,20 @@ public InputControl GetChildControl(string path) return control; } + /// + /// Find a child control matching the given path. + /// + /// A control path. See . + /// The type of control to locate. + /// The first direct or indirect child control that matches the given + /// or null if no control was found to match. + /// is null or empty. + /// No control found with the specified type. + /// The control cannot be found. + /// + /// Note that if the given path matches multiple child controls, only the first control + /// encountered in the search will be returned. + /// public TControl GetChildControl(string path) where TControl : InputControl { @@ -677,6 +746,12 @@ public TControl GetChildControl(string path) return controlOfType; } + /// + /// Constructor for the InputControl + /// + /// + /// Constructor for the InputControl + /// protected InputControl() { // Set defaults for state block setup. Subclasses may override. @@ -689,7 +764,7 @@ protected InputControl() /// /// This method can be overridden to perform control- or device-specific setup work. The most /// common use case is for looking up child controls and storing them in local getters. - /// + /// /// /// /// public class MyDevice : InputDevice @@ -706,7 +781,6 @@ protected InputControl() /// } /// /// - /// protected virtual void FinishSetup() { } @@ -723,9 +797,12 @@ protected virtual void FinishSetup() /// /// This method should be called if you are accessing cached data set up by /// . - /// + /// /// /// + /// using UnityEngine.InputSystem; + /// using UnityEngine.InputSystem.Utilities; + /// /// // Let's say your device has an associated orientation which it can be held with /// // and you want to surface both as a property and as a usage on the device. /// // Whenever your backend code detects a change in orientation, it should send @@ -779,7 +856,6 @@ protected virtual void FinishSetup() /// } /// /// - /// /// protected void RefreshConfigurationIfNeeded() { @@ -790,18 +866,98 @@ protected void RefreshConfigurationIfNeeded() } } + /// + /// Refresh the configuration of the control. This is used to update the control's state (e.g. Keyboard Layout or display Name of Keys). + /// + /// + /// The system will call this method automatically whenever a change is made to one of the control's configuration properties. + /// This method is only relevant if you are implementing your own devices or new + /// types of controls which are fetching configuration data from the devices (such + /// as which is fetching display names for individual keys + /// from the underlying platform). + /// See . + /// + /// + /// + /// using UnityEngine.InputSystem; + /// using UnityEngine.InputSystem.Utilities; + /// + /// public class MyDevice : InputDevice + /// { + /// public enum Orientation + /// { + /// Horizontal, + /// Vertical, + /// } + /// private Orientation m_Orientation; + /// private static InternedString s_Vertical = new InternedString("Vertical"); + /// private static InternedString s_Horizontal = new InternedString("Horizontal"); + /// + /// public Orientation orientation + /// { + /// get + /// { + /// // Call RefreshOrientation if the configuration of the device has been + /// // invalidated since last time we initialized m_Orientation. + /// // Calling RefreshConfigurationIfNeeded() is sufficient in most cases, RefreshConfiguration() forces the refresh. + /// RefreshConfiguration(); + /// return m_Orientation; + /// } + /// } + /// protected override void RefreshConfiguration() + /// { + /// // Set Orientation back to horizontal. Alternatively fetch from device. + /// m_Orientation = Orientation.Horizontal; + /// // Reflect the orientation on the device. + /// switch (m_Orientation) + /// { + /// case Orientation.Vertical: + /// InputSystem.RemoveDeviceUsage(this, s_Horizontal); + /// InputSystem.AddDeviceUsage(this, s_Vertical); + /// break; + /// + /// case Orientation.Horizontal: + /// InputSystem.RemoveDeviceUsage(this, s_Vertical); + /// InputSystem.AddDeviceUsage(this, s_Horizontal); + /// break; + /// } + /// } + /// } + /// + /// protected virtual void RefreshConfiguration() { } ////TODO: drop protected access + /// + /// Information about a memory region storing input state. + /// protected internal InputStateBlock m_StateBlock; ////REVIEW: shouldn't these sit on the device? + /// + /// The state data buffer for the device. + /// + /// + /// The state data buffer for the device. + /// protected internal unsafe void* currentStatePtr => InputStateBuffers.GetFrontBufferForDevice(GetDeviceIndex()); + /// + /// The state data buffer for the device from the previous frame. + /// + /// + /// The state data buffer for the device from the previous frame. + /// protected internal unsafe void* previousFrameStatePtr => InputStateBuffers.GetBackBufferForDevice(GetDeviceIndex()); + /// + /// The default state data buffer + /// + /// + /// Buffer that has state for each device initialized with default values. + /// protected internal unsafe void* defaultStatePtr => InputStateBuffers.s_DefaultStateBuffer; /// @@ -816,8 +972,9 @@ protected virtual void RefreshConfiguration() /// that is noise will be masked out whereas all state that isn't will come through unmodified. In other words, /// any bit that is set in the noise mask indicates that the corresponding bit in the control's state memory /// is noise. + /// + /// A control can be marked as . /// - /// protected internal unsafe void* noiseMaskPtr => InputStateBuffers.s_NoiseMaskBuffer; /// @@ -826,7 +983,7 @@ protected virtual void RefreshConfiguration() /// /// Once a device has been added to the system, its state block will get allocated /// in the global state buffers and the offset of the device's state block will - /// get baked into all of the controls on the device. This property always returns + /// get baked into all the controls on the device. This property always returns /// the "unbaked" offset. /// protected internal uint stateOffsetRelativeToDeviceRoot @@ -872,13 +1029,15 @@ protected internal uint stateOffsetRelativeToDeviceRoot internal FourCC m_OptimizedControlDataType; /// + /// The type of the state memory associated with the control. + /// + /// /// For some types of control you can safely read/write state memory directly /// which is much faster than calling ReadUnprocessedValueFromState/WriteValueIntoState. /// This method returns a type that you can use for reading/writing the control directly, - /// or it returns InputStateBlock.kFormatInvalid if it's not possible for this type of control. - /// - /// - /// For example, AxisControl might be a "float" in state memory, and if no processing is applied during reading (e.g. no invert/scale/etc), + /// or it returns if it's not possible for this type of control. + /// + /// For example, AxisControl might be a "float" in state memory, and if no processing is applied during reading (e.g. no invert/scale/etc), /// then you could read it as float in memory directly without calling ReadUnprocessedValueFromState, which is faster. /// Additionally, if you have a Vector3Control which uses 3 AxisControls as consecutive floats in memory, /// you can cast the Vector3Control state memory directly to Vector3 without calling ReadUnprocessedValueFromState on x/y/z axes. @@ -890,20 +1049,33 @@ protected internal uint stateOffsetRelativeToDeviceRoot public FourCC optimizedControlDataType => m_OptimizedControlDataType; /// - /// Calculates and returns a optimized data type that can represent a control's value in memory directly. + /// Calculates and returns an optimized data type that can represent a control's value in memory directly. + /// + /// /// The value then is cached in . /// This method is for internal use only, you should not call this from your own code. - /// + /// + /// + /// An optimized data type that can represent a control's value in memory directly. + /// protected virtual FourCC CalculateOptimizedControlDataType() { return InputStateBlock.kFormatInvalid; } /// - /// Apply built-in parameters changes (e.g. , others), recompute for impacted controls and clear cached value. + /// Apply built-in parameters changes. /// /// + /// Apply built-in parameters changes (e.g. , others). + /// Recompute for impacted controls and clear cached value /// + /// + /// + /// Gamepad.all[0].leftTrigger.WriteValueIntoState(0.5f, Gamepad.all[0].currentStatePtr); + /// Gamepad.all[0].ApplyParameterChanges(); + /// + /// public void ApplyParameterChanges() { // First we go through all children of our own hierarchy @@ -1144,14 +1316,16 @@ internal virtual IEnumerable GetProcessors() public abstract class InputControl : InputControl where TValue : struct { + /// public override Type valueType => typeof(TValue); + /// public override int valueSizeInBytes => UnsafeUtility.SizeOf(); /// /// Returns the current value of the control after processors have been applied. /// - /// The controls current value. + /// The controls current value. /// /// This can only be called on devices that have been added to the system (). /// @@ -1168,7 +1342,7 @@ public abstract class InputControl : InputControl /// /// Also note that this property returns the result as ref readonly. If custom control states are in use, i.e. /// any controls not shipped with the Input System package, be careful of accidental defensive copies - /// . + /// https://docs.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code#avoid-defensive-copies. /// /// public ref readonly TValue value @@ -1290,7 +1464,7 @@ internal unsafe ref readonly TValue unprocessedValue /// in the processor and set it to . /// /// To improve debugging try setting "PARANOID_READ_VALUE_CACHING_CHECKS" internal feature flag to check if cache value is still consistent. - /// . + /// https://docs.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code#avoid-defensive-copies. /// /// public TValue ReadValue() @@ -1328,6 +1502,16 @@ public TValue ReadDefaultValue() } } + /// + /// Get the control's default value. + /// + /// State containing the control's . + /// The control's default value. + /// + /// This is not necessarily equivalent to default(TValue). A control's default value is determined + /// by reading its value from the default state () which in turn + /// is determined from settings in the control's registered layout (). + /// public unsafe TValue ReadValueFromState(void* statePtr) { if (statePtr == null) @@ -1363,11 +1547,28 @@ public unsafe TValue ReadUnprocessedValueFromStateWithCaching(void* statePtr) return statePtr == currentStatePtr ? unprocessedValue : ReadUnprocessedValueFromState(statePtr); } + /// + /// Read value from control + /// + /// The controls current value. + /// + /// This is the locally cached value. Use to get the uncached version. + /// + /// public TValue ReadUnprocessedValue() { return unprocessedValue; } + /// + /// Read value from provided . + /// + /// State pointer to read from. + /// The controls current value. + /// + /// Read value from provided without any caching. + /// + /// public abstract unsafe TValue ReadUnprocessedValueFromState(void* statePtr); /// @@ -1395,6 +1596,7 @@ public override unsafe void ReadValueFromStateIntoBuffer(void* statePtr, void* b UnsafeUtility.MemCpy(bufferPtr, valuePtr, numBytes); } + /// public override unsafe void WriteValueFromBufferIntoState(void* bufferPtr, int bufferSize, void* statePtr) { if (bufferPtr == null) @@ -1432,6 +1634,20 @@ public override unsafe void WriteValueFromObjectIntoState(object value, void* st WriteValueIntoState(valueOfType, statePtr); } + /// + /// Write a value into state at the given memory. + /// + /// Value for the control to store in the state. + /// State containing the control's . Will receive + /// the state as converted from the given value. + /// + /// Writing values will NOT apply processors to the given value. This can mean that when reading a value + /// from a control after it has been written to its state, the resulting value differs from what was + /// written. + /// + /// The control does not support writing. This is the case, for + /// example, that compute values (such as the magnitude of a vector). + /// public virtual unsafe void WriteValueIntoState(TValue value, void* statePtr) { ////REVIEW: should we be able to even tell from layouts which controls support writing and which don't? @@ -1470,6 +1686,12 @@ private static unsafe bool CompareValue(ref TValue firstValue, ref TValue second return UnsafeUtility.MemCmp(firstValuePtr, secondValuePtr, UnsafeUtility.SizeOf()) != 0; } + /// + /// Compared values in state buffers. + /// + /// The first state buffer to read value from. + /// The second state buffer to read value from. + /// True if the buffer values match. False if they differ. public override unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr) { ////REVIEW: should we first compare state here? if there's no change in state, there can be no change in value and we can skip the rest @@ -1480,6 +1702,14 @@ public override unsafe bool CompareValue(void* firstStatePtr, void* secondStateP return CompareValue(ref firstValue, ref secondValue); } + /// + /// Applies all control processors to the passed value. + /// + /// value to run processors on. + /// The processed value. + /// + /// Applies all control processors to the passed value. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue ProcessValue(TValue value) { @@ -1490,7 +1720,7 @@ public TValue ProcessValue(TValue value) /// /// Applies all control processors to the passed value. /// - /// + /// value to run processors on. /// /// Use this overload when your state struct is large to avoid creating copies of the state. /// @@ -1572,6 +1802,7 @@ internal override IEnumerable GetProcessors() internal bool evaluateProcessorsEveryRead = false; + /// protected override void FinishSetup() { foreach (var processor in m_ProcessorStack) diff --git a/InputSystem/Controls/KeyControl.cs b/InputSystem/Controls/KeyControl.cs index 6e8c5f8b..de58183f 100644 --- a/InputSystem/Controls/KeyControl.cs +++ b/InputSystem/Controls/KeyControl.cs @@ -58,7 +58,7 @@ public int scanCode return m_ScanCode; } } - + /// protected override void RefreshConfiguration() { // Wipe our last cached set of data (if any). diff --git a/InputSystem/Controls/Processors/AxisDeadzoneProcessor.cs b/InputSystem/Controls/Processors/AxisDeadzoneProcessor.cs index 88881ab6..d9c33718 100644 --- a/InputSystem/Controls/Processors/AxisDeadzoneProcessor.cs +++ b/InputSystem/Controls/Processors/AxisDeadzoneProcessor.cs @@ -94,7 +94,7 @@ protected override void OnEnable() public override void OnGUI() { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return; + if (!InputSystem.settings.useIMGUIEditorForAssets) return; #endif m_MinSetting.OnGUI(); m_MaxSetting.OnGUI(); diff --git a/InputSystem/Controls/Processors/StickDeadzoneProcessor.cs b/InputSystem/Controls/Processors/StickDeadzoneProcessor.cs index 63bfd187..b0115b97 100644 --- a/InputSystem/Controls/Processors/StickDeadzoneProcessor.cs +++ b/InputSystem/Controls/Processors/StickDeadzoneProcessor.cs @@ -83,7 +83,7 @@ protected override void OnEnable() public override void OnGUI() { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return; + if (!InputSystem.settings.useIMGUIEditorForAssets) return; #endif m_MinSetting.OnGUI(); m_MaxSetting.OnGUI(); diff --git a/InputSystem/Devices/Gamepad.cs b/InputSystem/Devices/Gamepad.cs index 1a6a98c1..3841c4d7 100644 --- a/InputSystem/Devices/Gamepad.cs +++ b/InputSystem/Devices/Gamepad.cs @@ -102,7 +102,6 @@ public struct GamepadState : IInputStateTypeInfo /// /// Button bit mask. /// - /// Button bit mask. /// /// /// @@ -132,20 +131,20 @@ public struct GamepadState : IInputStateTypeInfo public uint buttons; /// - /// Left stick position. Each axis goes from -1 to 1 with - /// 0 being center position. + /// A 2D vector representing the current position of the left stick on a gamepad. /// - /// Left stick position. + /// Each axis of the 2D vector's range goes from -1 to 1. 0 represents the stick in its center position, and -1 or 1 represents the the stick pushed to its extent in each direction along the axis. /// [InputControl(layout = "Stick", usage = "Primary2DMotion", processors = "stickDeadzone", displayName = "Left Stick", shortDisplayName = "LS")] [FieldOffset(4)] public Vector2 leftStick; /// - /// Right stick position. Each axis from -1 to 1 with - /// 0 being center position. + /// A 2D vector representing the current position of the right stick on a gamepad. /// - /// Right stick position. + /// Each axis of the 2D vector's range goes from -1 to 1. + /// 0 represents the stick in its center position. + /// -1 or 1 represents the stick pushed to its extent in each direction along the axis. /// [InputControl(layout = "Stick", usage = "Secondary2DMotion", processors = "stickDeadzone", displayName = "Right Stick", shortDisplayName = "RS")] [FieldOffset(12)] @@ -154,18 +153,22 @@ public struct GamepadState : IInputStateTypeInfo ////REVIEW: should left and right trigger get deadzones? /// - /// Position of the left trigger. Goes from 0 (not pressed) to 1 (fully pressed). + /// The current position of the left trigger on a gamepad. /// - /// Position of left trigger. + /// The value's range goes from 0 to 1. + /// 0 represents the trigger in its neutral position. + /// 1 represents the trigger in its fully pressed position. /// [InputControl(layout = "Button", format = "FLT", usage = "SecondaryTrigger", displayName = "Left Trigger", shortDisplayName = "LT")] [FieldOffset(20)] public float leftTrigger; /// - /// Position of the right trigger. Goes from 0 (not pressed) to 1 (fully pressed). + /// The current position of the right trigger on a gamepad. /// - /// Position of right trigger. + /// The value's range goes from 0 to 1. + /// 0 represents the trigger in its neutral position. + /// 1 represents the trigger in its fully pressed position. /// [InputControl(layout = "Button", format = "FLT", usage = "SecondaryTrigger", displayName = "Right Trigger", shortDisplayName = "RT")] [FieldOffset(24)] @@ -174,7 +177,7 @@ public struct GamepadState : IInputStateTypeInfo /// /// State format tag for GamepadState. /// - /// Returns "GPAD". + /// Holds the format tag for GamepadState ("GPAD") public FourCC format => Format; /// @@ -399,109 +402,96 @@ namespace UnityEngine.InputSystem /// to be mapped correctly and consistently. If, based on the set of supported devices available /// to the input system, this cannot be guaranteed, a given device is usually represented as a /// generic or as just a plain instead. - /// + /// /// - /// - /// // Show all gamepads in the system. - /// Debug.Log(string.Join("\n", Gamepad.all)); - /// - /// // Check whether the X button on the current gamepad is pressed. - /// if (Gamepad.current.xButton.wasPressedThisFrame) - /// Debug.Log("Pressed"); - /// - /// // Rumble the left motor on the current gamepad slightly. - /// Gamepad.current.SetMotorSpeeds(0.2f, 0. - /// + /// /// - /// + /// + /// + /// + /// + /// + /// [InputControlLayout(stateType = typeof(GamepadState), isGenericTypeOfDevice = true)] public class Gamepad : InputDevice, IDualMotorRumble { /// /// The left face button of the gamepad. /// - /// Control representing the X/Square face button. /// - /// On an Xbox controller, this is the X button and on the PS4 controller, this is the - /// square button. + /// Control representing the X/Square face button. + /// On an Xbox controller, this is the and on the PS4 controller, this is the + /// . /// - /// - /// public ButtonControl buttonWest { get; protected set; } /// /// The top face button of the gamepad. /// - /// Control representing the Y/Triangle face button. /// - /// On an Xbox controller, this is the Y button and on the PS4 controller, this is the - /// triangle button. + /// Control representing the Y/Triangle face button. + /// On an Xbox controller, this is the and on the PS4 controller, this is the + /// . /// - /// - /// public ButtonControl buttonNorth { get; protected set; } /// /// The bottom face button of the gamepad. /// - /// Control representing the A/Cross face button. /// - /// On an Xbox controller, this is the A button and on the PS4 controller, this is the - /// cross button. + /// Control representing the A/Cross face button. + /// On an Xbox controller, this is the and on the PS4 controller, this is the + /// . /// - /// - /// public ButtonControl buttonSouth { get; protected set; } /// /// The right face button of the gamepad. /// - /// Control representing the B/Circle face button. /// - /// On an Xbox controller, this is the B button and on the PS4 controller, this is the - /// circle button. + /// Control representing the B/Circle face button. + /// On an Xbox controller, this is the and on the PS4 controller, this is the + /// . /// - /// - /// public ButtonControl buttonEast { get; protected set; } /// /// The button that gets triggered when is pressed down. /// - /// Control representing a click with the left stick. + /// Control representing a click with the left stick. public ButtonControl leftStickButton { get; protected set; } /// /// The button that gets triggered when is pressed down. /// - /// Control representing a click with the right stick. + /// Control representing a click with the right stick. public ButtonControl rightStickButton { get; protected set; } /// /// The right button in the middle section of the gamepad (called "menu" on Xbox /// controllers and "options" on PS4 controllers). /// - /// Control representing the right button in midsection. + /// Control representing the right button in midsection. public ButtonControl startButton { get; protected set; } /// /// The left button in the middle section of the gamepad (called "view" on Xbox /// controllers and "share" on PS4 controllers). /// - /// Control representing the left button in midsection. + /// Control representing the left button in midsection. public ButtonControl selectButton { get; protected set; } /// /// The 4-way directional pad on the gamepad. /// - /// Control representing the d-pad. + /// Control representing the d-pad. public DpadControl dpad { get; protected set; } /// /// The left shoulder/bumper button that sits on top of . /// - /// Control representing the left shoulder button. /// + /// Control representing the left shoulder button. /// On Xbox controllers, this is usually called "left bumper" whereas on PS4 /// controllers, this button is referred to as "L1". /// @@ -510,8 +500,8 @@ public class Gamepad : InputDevice, IDualMotorRumble /// /// The right shoulder/bumper button that sits on top of . /// - /// Control representing the right shoulder button. /// + /// Control representing the right shoulder button. /// On Xbox controllers, this is usually called "right bumper" whereas on PS4 /// controllers, this button is referred to as "R1". /// @@ -520,20 +510,19 @@ public class Gamepad : InputDevice, IDualMotorRumble /// /// The left thumbstick on the gamepad. /// - /// Control representing the left thumbstick. + /// Control representing the left thumbstick. public StickControl leftStick { get; protected set; } /// /// The right thumbstick on the gamepad. /// - /// Control representing the right thumbstick. + /// Control representing the right thumbstick. public StickControl rightStick { get; protected set; } /// /// The left trigger button sitting below . /// - /// Control representing the left trigger button. - /// + /// Control representing the left trigger button. /// On PS4 controllers, this button is referred to as "L2". /// public ButtonControl leftTrigger { get; protected set; } @@ -541,8 +530,7 @@ public class Gamepad : InputDevice, IDualMotorRumble /// /// The right trigger button sitting below . /// - /// Control representing the right trigger button. - /// + /// Control representing the right trigger button. /// On PS4 controllers, this button is referred to as "R2". /// public ButtonControl rightTrigger { get; protected set; } @@ -550,49 +538,41 @@ public class Gamepad : InputDevice, IDualMotorRumble /// /// Same as . Xbox-style alias. /// - /// Same as . public ButtonControl aButton => buttonSouth; /// /// Same as . Xbox-style alias. /// - /// Same as . public ButtonControl bButton => buttonEast; /// /// Same as Xbox-style alias. /// - /// Same as . public ButtonControl xButton => buttonWest; /// /// Same as . Xbox-style alias. /// - /// Same as . public ButtonControl yButton => buttonNorth; /// /// Same as . PS4-style alias. /// - /// Same as . public ButtonControl triangleButton => buttonNorth; /// /// Same as . PS4-style alias. /// - /// Same as . public ButtonControl squareButton => buttonWest; /// /// Same as . PS4-style alias. /// - /// Same as . public ButtonControl circleButton => buttonEast; /// /// Same as . PS4-style alias. /// - /// Same as . public ButtonControl crossButton => buttonSouth; /// @@ -637,28 +617,29 @@ public ButtonControl this[GamepadButton button] /// /// When added, a device is automatically made current (see ), so /// when connecting a gamepad, it will also become current. After that, it will only become current again - /// when input change on non-noisy controls (see ) is received. + /// when input change on non-noisy controls (see ) is received. It will also + /// be available once is queried. /// /// For local multiplayer scenarios (or whenever there are multiple gamepads that need to be usable /// in a concurrent fashion), it is not recommended to rely on this property. Instead, it is recommended /// to use or . /// - /// - /// public static Gamepad current { get; private set; } /// /// A list of gamepads currently connected to the system. /// - /// All currently connected gamepads. /// + /// Returns all currently connected gamepads. + /// /// Does not cause GC allocation. /// /// Do not hold on to the value returned by this getter but rather query it whenever /// you need it. Whenever the gamepad setup changes, the value returned by this getter /// is invalidated. + /// + /// Alternately, for querying a single gamepad, you can use for example. /// - /// public new static ReadOnlyArray all => new ReadOnlyArray(s_Gamepads, 0, s_GamepadCount); /// @@ -695,24 +676,54 @@ protected override void FinishSetup() /// /// /// This is called automatically by the system when there is input on a gamepad. + /// + /// More remarks are available in when it comes to devices with + /// controls. /// + /// + /// + /// using System; + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class MakeCurrentGamepadExample : MonoBehaviour + /// { + /// void Update() + /// { + /// /// Make the first Gamepad always the current one + /// if (Gamepad.all.Count > 0) + /// { + /// Gamepad.all[0].MakeCurrent(); + /// } + /// } + /// } + /// + /// public override void MakeCurrent() { base.MakeCurrent(); current = this; } + /// /// - /// Called when the gamepad is added to the system. + /// Called when a gamepad is added to the system. /// + /// + /// Override this method if you want to do additional processing when a gamepad becomes connected. After this method is called, the gamepad is automatically added to the list of gamepads. + /// protected override void OnAdded() { ArrayHelpers.AppendWithCapacity(ref s_Gamepads, ref s_GamepadCount, this); } + /// /// /// Called when the gamepad is removed from the system. /// + /// + /// Override this method if you want to do additional processing when a gamepad becomes disconnected. After this method is called, the gamepad is automatically removed from the list of gamepads. + /// protected override void OnRemoved() { if (current == this) @@ -730,34 +741,61 @@ protected override void OnRemoved() } /// - /// Pause rumble effects on the gamepad. Resume with . + /// Pause rumble effects on the gamepad. /// + /// + /// It will pause rumble effects and save the current motor speeds. + /// Resume from those speeds with . + /// Some devices such as and + /// can also set the LED color when this method is called. + /// /// + /// + /// + /// public virtual void PauseHaptics() { m_Rumble.PauseHaptics(this); } /// - /// Resume rumble affects on the gamepad that have been paused with . + /// Resume rumble effects on the gamepad. /// + /// + /// It will resume rumble effects from the previously set motor speeds, such as motor speeds saved when + /// calling . + /// Some devices such as and + /// can also set the LED color when this method is called. + /// /// + /// + /// + /// public virtual void ResumeHaptics() { m_Rumble.ResumeHaptics(this); } /// - /// Reset rumble effects on the gamepad. Puts the gamepad rumble motors back into their - /// default state. + /// Resets rumble effects on the gamepad by setting motor speeds to 0. /// + /// + /// Some devices such as and + /// can also set the LED color when this method is called. + /// /// + /// + /// + /// public virtual void ResetHaptics() { m_Rumble.ResetHaptics(this); } /// + /// + /// + /// public virtual void SetMotorSpeeds(float lowFrequency, float highFrequency) { m_Rumble.SetMotorSpeeds(this, lowFrequency, highFrequency); diff --git a/InputSystem/Devices/Haptics/DualMotorRumble.cs b/InputSystem/Devices/Haptics/DualMotorRumble.cs index 9a4d0ddb..c54a0f4c 100644 --- a/InputSystem/Devices/Haptics/DualMotorRumble.cs +++ b/InputSystem/Devices/Haptics/DualMotorRumble.cs @@ -39,9 +39,13 @@ internal struct DualMotorRumble || !Mathf.Approximately(highFrequencyMotorSpeed, 0f); /// - /// Reset motor speeds to zero but retain current values for - /// and . + /// Stops haptics by setting motor speeds to zero. /// + /// + /// Sets both motor speeds to zero while retaining the current values for + /// and . + /// It will only send the command if is true. + /// /// Device to send command to. /// is null. public void PauseHaptics(InputDevice device) @@ -60,6 +64,9 @@ public void PauseHaptics(InputDevice device) /// Resume haptics by setting motor speeds to the current values of /// and . /// + /// + /// It will only set motor speeds if is true + /// /// Device to send command to. /// is null. public void ResumeHaptics(InputDevice device) @@ -74,9 +81,12 @@ public void ResumeHaptics(InputDevice device) } /// - /// Reset haptics by setting both and - /// to zero. + /// Reset haptics by setting motor speeds to zero. /// + /// + /// Sets and to zero. + /// It will only set motor speeds if is true. + /// /// Device to send command to. /// is null. public void ResetHaptics(InputDevice device) diff --git a/InputSystem/Devices/InputDevice.cs b/InputSystem/Devices/InputDevice.cs index f2b469bc..49c78673 100644 --- a/InputSystem/Devices/InputDevice.cs +++ b/InputSystem/Devices/InputDevice.cs @@ -41,21 +41,31 @@ namespace UnityEngine.InputSystem /// runtime. However, it is possible to manually add devices using methods such as . /// - /// - /// - /// // Add a "synthetic" gamepad that isn't actually backed by hardware. - /// var gamepad = InputSystem.AddDevice<Gamepad>(); - /// - /// - /// /// There are subclasses representing the most common types of devices, like , /// , , and . /// /// To create your own types of devices, you can derive from InputDevice and register your device /// as a new "layout". /// + /// Devices can have usages like any other control (). However, usages of InputDevices are allowed to be changed on the fly without requiring a change to the + /// device layout (see ). + /// + /// For a more complete example of how to implement custom input devices, check out the "Custom Device" + /// sample which you can install from the Unity package manager. + /// + /// You can also find more information in the manual. + /// /// /// + /// using System.Runtime.InteropServices; + /// using UnityEditor; + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// using UnityEngine.InputSystem.Controls; + /// using UnityEngine.InputSystem.Layouts; + /// using UnityEngine.InputSystem.LowLevel; + /// using UnityEngine.InputSystem.Utilities; + /// /// // InputControlLayoutAttribute attribute is only necessary if you want /// // to override default behavior that occurs when registering your device /// // as a layout. @@ -70,6 +80,9 @@ namespace UnityEngine.InputSystem /// public ButtonControl button { get; private set; } /// public AxisControl axis { get; private set; } /// + /// // This is an example of how to add a "synthetic" gamepad that isn't actually backed by hardware. + /// Gamepad gamepad = InputSystem.AddDevice<Gamepad>(); + /// /// // Register the device. /// static MyDevice() /// { @@ -113,7 +126,7 @@ namespace UnityEngine.InputSystem /// // particular device is connected and fed into the input system. /// // The format is a simple FourCC code that "tags" state memory blocks for the /// // device to give a base level of safety checks on memory operations. - /// public FourCC format => return new FourCC('H', 'I', 'D'); + /// public FourCC format => new FourCC('H', 'I', 'D'); /// /// // InputControlAttributes on fields tell the input system to create controls /// // for the public fields found in the struct. @@ -121,26 +134,16 @@ namespace UnityEngine.InputSystem /// // Assume a 16bit field of buttons. Create one button that is tied to /// // bit #3 (zero-based). Note that buttons do not need to be stored as bits. /// // They can also be stored as floats or shorts, for example. - /// [InputControl(name = "button", layout = "Button", bit = 3)] + /// [InputControl(name = "button", layout = "Button", bit = 3)] [FieldOffset(0)] /// public ushort buttons; /// /// // Create a floating-point axis. The name, if not supplied, is taken from /// // the field. - /// [InputControl(layout = "Axis")] + /// [InputControl(layout = "Axis")] [FieldOffset(0)] /// public short axis; /// } /// /// - /// - /// Devices can have usages like any other control (). Unlike other controls, - /// however, usages of InputDevices are allowed to be changed on the fly without requiring a change to the - /// device layout (see ). - /// - /// For a more complete example of how to implement custom input devices, check out the "Custom Device" - /// sample which you can install from the Unity package manager. - /// - /// And, as always, you can also find more information in the manual. - /// /// /// /// @@ -508,10 +511,26 @@ public virtual void MakeCurrent() /// /// /// This is called after the device has already been added. + /// + /// + /// /// - /// - /// - /// + /// + /// + /// using UnityEngine.InputSystem; + /// + /// public class MyDevice : InputDevice + /// { + /// public static MyDevice current { get; private set; } + /// protected override void OnAdded() + /// { + /// // use this context to assign the current device for instance + /// base.OnAdded(); + /// current = this; + /// } + /// } + /// + /// protected virtual void OnAdded() { } @@ -521,10 +540,27 @@ protected virtual void OnAdded() /// /// /// This is called after the device has already been removed. + /// + /// + /// /// - /// - /// - /// + /// + /// + /// using UnityEngine.InputSystem; + /// + /// public class MyDevice : InputDevice + /// { + /// public static MyDevice current { get; private set; } + /// protected override void OnRemoved() + /// { + /// // use this context to unassign the current device for instance + /// base.OnRemoved(); + /// if (current == this) + /// current = null; + /// } + /// } + /// + /// protected virtual void OnRemoved() { } @@ -542,7 +578,7 @@ protected virtual void OnRemoved() /// /// /// - /// /// + /// protected virtual void OnConfigurationChanged() { } @@ -560,8 +596,8 @@ protected virtual void OnConfigurationChanged() /// the device API. This is most useful for devices implemented in the native Unity runtime /// which, through the command interface, may provide custom, device-specific functions. /// - /// This is a low-level API. It works in a similar way to - /// DeviceIoControl on Windows and ioctl + /// This is a low-level API. It works in a similar way to + /// DeviceIoControl on Windows and ioctl /// on UNIX-like systems. /// public unsafe long ExecuteCommand(ref TCommand command) diff --git a/InputSystem/Devices/Keyboard.cs b/InputSystem/Devices/Keyboard.cs index 20b8e0ec..319e3cc4 100644 --- a/InputSystem/Devices/Keyboard.cs +++ b/InputSystem/Devices/Keyboard.cs @@ -17,24 +17,32 @@ namespace UnityEngine.InputSystem.LowLevel /// /// /// Can be used to update the state of devices. - /// + /// /// /// - /// // Send input event with A key pressed on keyboard. - /// InputSystem.QueueStateEvent(Keyboard.current, - /// new KeyboardState(Key.A)); + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// using UnityEngine.InputSystem.LowLevel; + /// + /// public class Example : MonoBehaviour + /// { + /// void Start() + /// { + /// // Send input event with A key pressed on keyboard. + /// InputSystem.QueueStateEvent(Keyboard.current, + /// new KeyboardState(Key.A)); + /// } + /// } /// /// - /// /// // NOTE: This layout has to match the KeyboardInputState layout used in native! [StructLayout(LayoutKind.Sequential)] public unsafe struct KeyboardState : IInputStateTypeInfo { /// - /// Memory format tag for KeybboardState. + /// Memory format tag for KeyboardState. Returns "KEYS". /// - /// Returns "KEYS". /// public static FourCC Format => new FourCC('K', 'E', 'Y', 'S'); @@ -220,17 +228,25 @@ namespace UnityEngine.InputSystem /// /// To find the text character (if any) generated by a key according to the currently active keyboard /// layout, use the property of . - /// + /// /// /// - /// // Look up key by key code. - /// var aKey = Keyboard.current[Key.A]; + /// using UnityEngine; + /// using UnityEngine.InputSystem; /// - /// // Find out which text is produced by the key. - /// Debug.Log($"The '{aKey.keyCode}' key produces '{aKey.displayName}' as text input"); + /// public class Example : MonoBehaviour + /// { + /// void LookUpTextInputByKey() + /// { + /// // Look up key by key code. + /// var aKey = Keyboard.current[Key.A]; + /// + /// // Find out which text is produced by the key. + /// Debug.Log($"The '{aKey.keyCode}' key produces '{aKey.displayName}' as text input"); + /// } + /// } /// /// - /// // NOTE: Has to match up with 'KeyboardInputState::KeyCode' in native. // NOTE: In the keyboard code, we depend on the order of the keys in the various keyboard blocks. public enum Key @@ -876,6 +892,32 @@ public enum Key /// keyboard or not. It is thus not possible to find out this way whether the underlying /// keyboard has certain keys or not. /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class InputExample : MonoBehaviour + /// { + /// private string inputText = ""; + /// void Start() + /// { + /// Keyboard.current.onTextInput += OnTextInput; + /// } + /// void Update() + /// { + /// // Check whether the A key on the current keyboard is pressed. + /// if (Keyboard.current.aKey.wasPressedThisFrame) + /// Debug.Log("A Key pressed"); + /// } + /// private void OnTextInput(char ch) + /// { + /// inputText += ch; + /// } + /// } + /// + /// + /// [InputControlLayout(stateType = typeof(KeyboardState), isGenericTypeOfDevice = true)] public class Keyboard : InputDevice, ITextInputReceiver { @@ -883,16 +925,18 @@ public class Keyboard : InputDevice, ITextInputReceiver /// Total number of key controls on a keyboard, i.e. the number of controls /// in . /// - /// Total number of key controls. public const int KeyCount = (int)Key.OEM5; /// /// Event that is fired for every single character entered on the keyboard. + /// See . /// - /// Triggered whenever the keyboard receives text input. - /// /// /// + /// using System; + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// /// // Let's say we want to do a typing game. We could define a component /// // something along those lines to match the typed input. /// public class MatchTextByTyping : MonoBehaviour @@ -932,7 +976,7 @@ public class Keyboard : InputDevice, ITextInputReceiver /// { /// ++m_Position; /// if (m_Position == m_Text.Length) - /// onTextTypeCorrectly?.Invoke(); + /// onTextTypedCorrectly?.Invoke(); /// } /// else /// { @@ -945,7 +989,6 @@ public class Keyboard : InputDevice, ITextInputReceiver /// } /// /// - /// public event Action onTextInput { add @@ -959,11 +1002,13 @@ public event Action onTextInput } /// - /// An event that is fired to get IME composition strings. Fired once for every change containing the entire string to date. - /// When using an IME, this event can be used to display the composition string while it is being edited. When a composition - /// string is submitted, one or many events will fire with the submitted characters. + /// When a user is entering text using IME composition, this event occurs each time the IME composition string changes, and provides the new composition string as a value. + /// When using an IME, this event can be used to display the composition string while it is being edited. /// /// + /// The composition string is held by the struct. + /// When a composition string is submitted, one or many events will fire with the submitted characters. + /// /// Some languages use complex input methods which involve opening windows to insert characters. /// Typically, this is not desirable while playing a game, as games may just interpret key strokes as game input, not as text. /// @@ -975,11 +1020,20 @@ public event Action onTextInput /// /// To subscribe to the onIMECompositionChange event, use the following sample code: /// - /// var compositionString = ""; - /// Keyboard.current.onIMECompositionChange += composition => + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class KeyboardUtils : MonoBehaviour /// { - /// compositionString = composition.ToString(); - /// }; + /// private string compositionString = ""; + /// + /// void Start(){ + /// Keyboard.current.onIMECompositionChange += composition => + /// { + /// compositionString = composition.ToString(); + /// }; + /// } + /// } /// /// public event Action onIMECompositionChange @@ -995,7 +1049,7 @@ public event Action onIMECompositionChange } /// - /// Activates/deactivates IME composition while typing. This decides whether or not to use the OS supplied IME system. + /// Activates/deactivates IME composition while typing. This decides whether to use the OS supplied IME system or not. /// /// /// @@ -1006,6 +1060,28 @@ public event Action onIMECompositionChange /// See , , /// for more IME settings and data. /// + /// + /// The IME composition enabled state. to enable the IME, to disable it. + /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class KeyboardUtils : MonoBehaviour + /// { + /// private string compositionString = ""; + /// + /// void Start(){ + /// Keyboard.current.SetIMEEnabled(true); + /// Keyboard.current.onIMECompositionChange += composition => + /// { + /// compositionString = composition.ToString(); + /// }; + /// } + /// } + /// + /// public void SetIMEEnabled(bool enabled) { var command = EnableIMECompositionCommand.Create(enabled); @@ -1021,6 +1097,29 @@ public void SetIMEEnabled(bool enabled) /// /// See for turning IME on/off /// + /// + /// A Vector2 of the IME cursor position to set. + /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class KeyboardUtils : MonoBehaviour + /// { + /// private Vector2 cursorPosition; + /// + /// void Update () + /// { + /// // Set the IME cursor position to the mouse position + /// var x = Mouse.current.position.x.ReadValue(); + /// var y = Mouse.current.position.y.ReadValue(); + /// cursorPosition = new Vector2(x, y); + /// Keyboard.current.SetIMECursorPosition(cursorPosition); + /// } + /// } + /// + /// public void SetIMECursorPosition(Vector2 position) { SetIMECursorPositionCommand command = SetIMECursorPositionCommand.Create(position); @@ -1051,471 +1150,473 @@ public string keyboardLayout /// /// A synthetic button control that is considered pressed if any key on the keyboard is pressed. /// - /// Control representing the synthetic "anyKey". + /// representing the synthetic "anyKey". public AnyKeyControl anyKey { get; protected set; } /// - /// The space bar key. + /// The space bar key at the bottom of the keyboard. /// - /// Control representing the space bar key. + /// representing the space bar key. public KeyControl spaceKey => this[Key.Space]; /// /// The enter/return key in the main key block. /// - /// Control representing the enter key. /// + /// representing the enter key. /// This key is distinct from the enter key on the numpad which is . /// public KeyControl enterKey => this[Key.Enter]; /// - /// The tab key. + /// The tab key, which is generally located on the left side above the . /// - /// Control representing the tab key. + /// representing the tab key. public KeyControl tabKey => this[Key.Tab]; /// /// The ` key. The leftmost key in the row of digits. Directly above . /// - /// Control representing the backtick/quote key. + /// representing the backtick/quote key. public KeyControl backquoteKey => this[Key.Backquote]; /// /// The ' key. The key immediately to the left of . /// - /// Control representing the quote key. + /// representing the quote key. public KeyControl quoteKey => this[Key.Quote]; /// /// The ';' key. The key immediately to the left of . /// - /// Control representing the semicolon key. + /// representing the semicolon key. public KeyControl semicolonKey => this[Key.Semicolon]; /// /// The ',' key. Third key to the left of . /// - /// Control representing the comma key. + /// representing the comma key. public KeyControl commaKey => this[Key.Comma]; /// /// The '.' key. Second key to the left of . /// - /// Control representing the period key. + /// representing the period key. public KeyControl periodKey => this[Key.Period]; /// /// The '/' key. The key immediately to the left of . /// - /// Control representing the forward slash key. + /// representing the forward slash key. public KeyControl slashKey => this[Key.Slash]; /// /// The '\' key. The key immediately to the right of and /// next to or above . /// - /// Control representing the backslash key. + /// representing the backslash key. public KeyControl backslashKey => this[Key.Backslash]; /// /// The '[' key. The key immediately to the left of . /// - /// Control representing the left bracket key. + /// representing the left bracket key. public KeyControl leftBracketKey => this[Key.LeftBracket]; /// /// The ']' key. The key in-between to the left and /// to the right. /// - /// Control representing the right bracket key. + /// representing the right bracket key. public KeyControl rightBracketKey => this[Key.RightBracket]; /// /// The '-' key. The second key to the left of . /// - /// Control representing the minus key. + /// representing the minus key. public KeyControl minusKey => this[Key.Minus]; /// /// The '=' key in the main key block. The key in-between to the left /// and to the right. /// - /// Control representing the equals key. + /// representing the equals key. public KeyControl equalsKey => this[Key.Equals]; /// /// The 'a' key. The key immediately to the right of . /// - /// Control representing the a key. + /// representing the a key. public KeyControl aKey => this[Key.A]; /// /// The 'b' key. The key in-between the to the left and the /// to the right in the bottom-most row of alphabetic characters. /// - /// Control representing the b key. + /// representing the b key. public KeyControl bKey => this[Key.B]; /// /// The 'c' key. The key in-between the to the left and the /// to the right in the bottom-most row of alphabetic characters. /// - /// Control representing the c key. + /// representing the c key. public KeyControl cKey => this[Key.C]; /// /// The 'd' key. The key in-between the to the left and the /// to the right in the middle row of alphabetic characters. /// - /// Control representing the d key. + /// representing the d key. public KeyControl dKey => this[Key.D]; /// /// The 'e' key. The key in-between the to the left and the /// to the right in the topmost row of alphabetic characters. /// - /// Control representing the e key. + /// representing the e key. public KeyControl eKey => this[Key.E]; /// /// The 'f' key. The key in-between the to the left and the /// to the right in the middle row of alphabetic characters. /// - /// Control representing the f key. + /// representing the f key. public KeyControl fKey => this[Key.F]; /// /// The 'g' key. The key in-between the to the left and the /// to the right in the middle row of alphabetic characters. /// - /// Control representing the g key. + /// representing the g key. public KeyControl gKey => this[Key.G]; /// /// The 'h' key. The key in-between the to the left and the /// to the right in the middle row of alphabetic characters. /// - /// Control representing the h key. + /// representing the h key. public KeyControl hKey => this[Key.H]; /// /// The 'i' key. The key in-between the to the left and the /// to the right in the top row of alphabetic characters. /// + /// representing the i key. public KeyControl iKey => this[Key.I]; /// /// The 'j' key. The key in-between the to the left and the /// to the right in the middle row of alphabetic characters. /// - /// Control representing the j key. + /// representing the j key. public KeyControl jKey => this[Key.J]; /// /// The 'k' key. The key in-between the to the left and the /// to the right in the middle row of alphabetic characters. /// - /// Control representing the k key. + /// representing the k key. public KeyControl kKey => this[Key.K]; /// /// The 'l' key. The key in-between the to the left and the /// to the right in the middle row of alphabetic characters. /// - /// Control representing the l key. + /// representing the l key. public KeyControl lKey => this[Key.L]; /// /// The 'm' key. The key in-between the to the left and the /// to the right in the bottom row of alphabetic characters. /// - /// Control representing the m key. + /// representing the m key. public KeyControl mKey => this[Key.M]; /// /// The 'n' key. The key in-between the to the left and the to /// the right in the bottom row of alphabetic characters. /// - /// Control representing the n key. + /// representing the n key. public KeyControl nKey => this[Key.N]; /// /// The 'o' key. The key in-between the to the left and the to /// the right in the top row of alphabetic characters. /// - /// Control representing the o key. + /// representing the o key. public KeyControl oKey => this[Key.O]; /// /// The 'p' key. The key in-between the to the left and the /// to the right in the top row of alphabetic characters. /// - /// Control representing the p key. + /// representing the p key. public KeyControl pKey => this[Key.P]; /// /// The 'q' key. The key in-between the to the left and the /// to the right in the top row of alphabetic characters. /// - /// Control representing the q key. + /// representing the q key. public KeyControl qKey => this[Key.Q]; /// /// The 'r' key. The key in-between the to the left and the /// to the right in the top row of alphabetic characters. /// - /// Control representing the r key. + /// representing the r key. public KeyControl rKey => this[Key.R]; /// /// The 's' key. The key in-between the to the left and the /// to the right in the middle row of alphabetic characters. /// - /// Control representing the s key. + /// representing the s key. public KeyControl sKey => this[Key.S]; /// /// The 't' key. The key in-between the to the left and the /// to the right in the top row of alphabetic characters. /// - /// Control representing the t key. + /// representing the t key. public KeyControl tKey => this[Key.T]; /// /// The 'u' key. The key in-between the to the left and the /// to the right in the top row of alphabetic characters. /// - /// Control representing the u key. + /// representing the u key. public KeyControl uKey => this[Key.U]; /// /// The 'v' key. The key in-between the to the left and the /// to the right in the bottom row of alphabetic characters. /// - /// Control representing the v key. + /// representing the v key. public KeyControl vKey => this[Key.V]; /// /// The 'w' key. The key in-between the to the left and the /// to the right in the top row of alphabetic characters. /// - /// Control representing the w key. + /// representing the w key. public KeyControl wKey => this[Key.W]; /// /// The 'x' key. The key in-between the to the left and the /// to the right in the bottom row of alphabetic characters. /// - /// Control representing the x key. + /// representing the x key. public KeyControl xKey => this[Key.X]; /// /// The 'y' key. The key in-between the to the left and the /// to the right in the top row of alphabetic characters. /// - /// Control representing the y key. + /// representing the y key. public KeyControl yKey => this[Key.Y]; /// /// The 'z' key. The key in-between the to the left and the /// to the right in the bottom row of alphabetic characters. /// - /// Control representing the z key. + /// representing the z key. public KeyControl zKey => this[Key.Z]; /// /// The '1' key. The key in-between the to the left and the /// to the right in the row of digit characters. /// - /// Control representing the 1 key. + /// representing the 1 key. public KeyControl digit1Key => this[Key.Digit1]; /// /// The '2' key. The key in-between the to the left and the /// to the right in the row of digit characters. /// - /// Control representing the 2 key. + /// representing the 2 key. public KeyControl digit2Key => this[Key.Digit2]; /// /// The '3' key. The key in-between the to the left and the /// to the right in the row of digit characters. /// - /// Control representing the 3 key. + /// representing the 3 key. public KeyControl digit3Key => this[Key.Digit3]; /// /// The '4' key. The key in-between the to the left and the /// to the right in the row of digit characters. /// - /// Control representing the 4 key. + /// representing the 4 key. public KeyControl digit4Key => this[Key.Digit4]; /// /// The '5' key. The key in-between the to the left and the /// to the right in the row of digit characters. /// - /// Control representing the 5 key. + /// representing the 5 key. public KeyControl digit5Key => this[Key.Digit5]; /// /// The '6' key. The key in-between the to the left and the /// to the right in the row of digit characters. /// - /// Control representing the 6 key. + /// representing the 6 key. public KeyControl digit6Key => this[Key.Digit6]; /// /// The '7' key. The key in-between the to the left and the /// to the right in the row of digit characters. /// - /// Control representing the 7 key. + /// representing the 7 key. public KeyControl digit7Key => this[Key.Digit7]; /// /// The '8' key. The key in-between the to the left and the /// to the right in the row of digit characters. /// - /// Control representing the 8 key. + /// representing the 8 key. public KeyControl digit8Key => this[Key.Digit8]; /// /// The '9' key. The key in-between the to the left and the /// to the right in the row of digit characters. /// - /// Control representing the 9 key. + /// representing the 9 key. public KeyControl digit9Key => this[Key.Digit9]; /// /// The '0' key. The key in-between the to the left and the /// to the right in the row of digit characters. /// - /// Control representing the 0 key. + /// representing the 0 key. public KeyControl digit0Key => this[Key.Digit0]; /// /// The shift key on the left side of the keyboard. /// - /// Control representing the left shift key. + /// representing the left shift key. public KeyControl leftShiftKey => this[Key.LeftShift]; /// /// The shift key on the right side of the keyboard. /// - /// Control representing the right shift key. + /// representing the right shift key. public KeyControl rightShiftKey => this[Key.RightShift]; /// /// The alt/option key on the left side of the keyboard. /// - /// Control representing the left alt/option key. + /// representing the left alt/option key. public KeyControl leftAltKey => this[Key.LeftAlt]; /// /// The alt/option key on the right side of the keyboard. /// - /// Control representing the right alt/option key. + /// representing the right alt/option key. public KeyControl rightAltKey => this[Key.RightAlt]; /// /// The control/ctrl key on the left side of the keyboard. /// - /// Control representing the left control key. + /// representing the left control key. public KeyControl leftCtrlKey => this[Key.LeftCtrl]; /// /// The control/ctrl key on the right side of the keyboard. /// - /// This key is usually not present on Mac laptops. - /// Control representing the right control key. + /// + /// This key is usually not present on Mac laptops. + /// representing the right control key. + /// public KeyControl rightCtrlKey => this[Key.RightCtrl]; /// /// The system "meta" key (Windows key on PC, Apple/command key on Mac) on the left /// side of the keyboard. /// - /// Control representing the left system meta key. + /// representing the left system meta key. public KeyControl leftMetaKey => this[Key.LeftMeta]; /// /// The system "meta" key (Windows key on PC, Apple/command key on Mac) on the right /// side of the keyboard. /// - /// Control representing the right system meta key. + /// representing the right system meta key. public KeyControl rightMetaKey => this[Key.RightMeta]; /// /// Same as . Windows system key on left side of keyboard. /// - /// Control representing the left Windows system key. + /// representing the left Windows system key. public KeyControl leftWindowsKey => this[Key.LeftWindows]; /// /// Same as . Windows system key on right side of keyboard. /// - /// Control representing the right Windows system key. + /// representing the right Windows system key. public KeyControl rightWindowsKey => this[Key.RightWindows]; /// /// Same as . Apple/command system key on left side of keyboard. /// - /// Control representing the left Apple/command system key. + /// representing the left Apple/command system key. public KeyControl leftAppleKey => this[Key.LeftApple]; /// /// Same as . Apple/command system key on right side of keyboard. /// - /// Control representing the right Apple/command system key. + /// representing the right Apple/command system key. public KeyControl rightAppleKey => this[Key.RightApple]; /// /// Same as . Apple/command system key on left side of keyboard. /// - /// Control representing the left Apple/command system key. + /// representing the left Apple/command system key. public KeyControl leftCommandKey => this[Key.LeftCommand]; /// /// Same as . Apple/command system key on right side of keyboard. /// - /// Control representing the right Apple/command system key. + /// representing the right Apple/command system key. public KeyControl rightCommandKey => this[Key.RightCommand]; /// - /// The context menu key. This key is generally only found on PC keyboards. If present, - /// the key is found in-between the to the left and the - /// to the right. It's intention is to bring up the context - /// menu according to the current selection. + /// The context menu key. This key is generally only found on PC keyboards. + /// If present, it is located in-between the and the + /// . It brings up the context menu according to the current selection. /// - /// Control representing the context menu key. + /// representing the context menu key. public KeyControl contextMenuKey => this[Key.ContextMenu]; /// /// The escape key, i.e. the key generally in the top left corner of the keyboard. /// Usually to the left of . /// - /// Control representing the escape key. + /// representing the escape key. public KeyControl escapeKey => this[Key.Escape]; /// /// The left arrow key. Usually in a block by itself and generally to the left /// of . /// - /// Control representing the left arrow key. + /// representing the left arrow key. public KeyControl leftArrowKey => this[Key.LeftArrow]; /// /// The right arrow key. Usually in a block by itself and generally to the right /// of /// - /// Control representing the right arrow key. + /// representing the right arrow key. public KeyControl rightArrowKey => this[Key.RightArrow]; /// /// The up arrow key. Usually in a block by itself and generally on top of the /// . /// - /// Control representing the up arrow key. + /// representing the up arrow key. public KeyControl upArrowKey => this[Key.UpArrow]; /// @@ -1523,15 +1624,16 @@ public string keyboardLayout /// and in-between to the /// left and to the right. /// - /// Control representing the down arrow key. + /// representing the down arrow key. public KeyControl downArrowKey => this[Key.DownArrow]; /// /// The backspace key (usually labeled "delete" on Mac). The rightmost key /// in the top digit row with to the left. /// - /// Control representing the backspace key. /// + /// representing the backspace key. + /// /// On the Mac, this key may be labeled "delete" which however is a /// key different from . /// @@ -1541,43 +1643,44 @@ public string keyboardLayout /// The page down key. Usually in a separate block with /// to the left and above it. /// - /// Control representing the page down key. + /// representing the page down key. public KeyControl pageDownKey => this[Key.PageDown]; /// /// The page up key. Usually in a separate block with /// to the left and below it. /// - /// Control representing the page up key. + /// representing the page up key. public KeyControl pageUpKey => this[Key.PageUp]; /// /// The 'home' key. Usually in a separate block with /// to the right and to the left. /// - /// Control representing the insert key. + /// representing the insert key. public KeyControl homeKey => this[Key.Home]; /// /// The 'end' key. Usually in a separate block with /// to the left and to the right. /// - /// Control representing the end key. + /// representing the end key. public KeyControl endKey => this[Key.End]; /// /// The 'insert' key. Usually in a separate block with /// to its right and sitting below it. /// - /// Control representing the insert key. + /// representing the insert key. public KeyControl insertKey => this[Key.Insert]; /// /// The 'delete' key. Usually in a separate block with /// to its right and sitting above it. /// - /// Control representing the delete key. /// + /// representing the delete key. + /// /// On the Mac, the is also labeled "delete". /// However, this is not this key. /// @@ -1587,7 +1690,7 @@ public string keyboardLayout /// The Caps Lock key. The key below and above /// . /// - /// Control representing the caps lock key. + /// representing the caps lock key. public KeyControl capsLockKey => this[Key.CapsLock]; /// @@ -1595,7 +1698,7 @@ public string keyboardLayout /// to the left and the to the right. May also /// be labeled "F14". /// - /// Control representing the scroll lock key. + /// representing the scroll lock key. public KeyControl scrollLockKey => this[Key.ScrollLock]; /// @@ -1603,7 +1706,7 @@ public string keyboardLayout /// numpad and which usually toggles the numpad between generating /// digits and triggering functions like "insert" etc. instead. /// - /// Control representing the num lock key. + /// representing the num lock key. public KeyControl numLockKey => this[Key.NumLock]; /// @@ -1611,29 +1714,30 @@ public string keyboardLayout /// to the left and to the right. May also /// be labeled "F13". /// - /// Control representing the print screen key. + /// representing the print screen key. public KeyControl printScreenKey => this[Key.PrintScreen]; /// /// The pause/break key. The key sitting to the left of . /// May also be labeled "F15". /// - /// Control representing the pause/break key. + /// representing the pause/break key. public KeyControl pauseKey => this[Key.Pause]; /// /// The enter key on the numpad. The key sitting in the bottom right corner /// of the numpad. /// - /// Control representing the numpad enter key. + /// representing the numpad enter key. public KeyControl numpadEnterKey => this[Key.NumpadEnter]; /// /// The divide ('/') key on the numpad. The key in-between /// to the left and to the right. /// - /// Control representing the numpad divide key. /// + /// representing the numpad divide key. + /// /// PC keyboards usually have a 17-key numpad layout that differs from the 18-key layout /// we use for reference. The 18-key layout is usually found on Mac keyboards. The numpad /// divide key usually is the on PC keyboards. @@ -1645,8 +1749,9 @@ public string keyboardLayout /// with to the left and /// below it. /// - /// Control representing the numpad multiply key. /// + /// representing the numpad multiply key. + /// /// PC keyboards usually have a 17-key numpad layout that differs from the 18-key layout /// we use for reference. The 18-key layout is usually found on Mac keyboards. The numpad /// multiply key usually is the on PC keyboards. @@ -1657,8 +1762,9 @@ public string keyboardLayout /// The minus ('-') key on the numpad. The key on the right side of the numpad with /// above it and below it. /// - /// Control representing the numpad minus key. /// + /// representing the numpad minus key. + /// /// PC keyboards usually have a 17-key numpad layout that differs from the 18-key layout /// we use for reference. The 18-key layout is usually found on Mac keyboards. The numpad /// minus key is usually not present on PC keyboards. Instead, the 17-key layout @@ -1670,8 +1776,9 @@ public string keyboardLayout /// The plus ('+') key on the numpad. The key on the right side of the numpad with /// above it and below it. /// - /// Control representing the numpad plus key. /// + /// representing the numpad plus key. + /// /// PC keyboards usually have a 17-key numpad layout that differs from the 18-key layout /// we use for reference. The 18-key layout is usually found on Mac keyboards. /// @@ -1687,8 +1794,9 @@ public string keyboardLayout /// The period ('.') key on the numpad. The key in-between the /// to the right and the to the left. /// - /// Control representing the numpad period key. /// + /// representing the numpad period key. + /// /// This key is the same in 17-key and 18-key numpad layouts. /// public KeyControl numpadPeriodKey => this[Key.NumpadPeriod]; @@ -1697,8 +1805,9 @@ public string keyboardLayout /// The equals ('=') key on the numpad. The key in-between to the left /// and to the right in the top row of the numpad. /// - /// Control representing the numpad equals key. /// + /// representing the numpad equals key. + /// /// PC keyboards usually have a 17-key numpad layout that differs from the 18-key layout /// we use for reference. The 18-key layout is usually found on Mac keyboards. /// @@ -1711,63 +1820,63 @@ public string keyboardLayout /// The 0 key on the numpad. The key in the bottom left corner of the numpad. Usually /// and elongated key. /// - /// Control representing the numpad 0 key. + /// representing the numpad 0 key. public KeyControl numpad0Key => this[Key.Numpad0]; /// /// The 1 key on the numpad. The key on the left side of the numpad with /// below it and above it. /// - /// Control representing the numpad 1 key. + /// representing the numpad 1 key. public KeyControl numpad1Key => this[Key.Numpad1]; /// /// The 2 key on the numpad. The key with the to its left and /// the to its right. /// - /// Control representing the numpad 2 key. + /// representing the numpad 2 key. public KeyControl numpad2Key => this[Key.Numpad2]; /// /// The 3 key on the numpad. The key with the to its left and /// the to its right. /// - /// Control representing the numpad 3 key. + /// representing the numpad 3 key. public KeyControl numpad3Key => this[Key.Numpad3]; /// /// The 4 key on the numpad. The key on the left side of the numpad with the /// below it and the above it. /// - /// Control representing the numpad 4 key. + /// representing the numpad 4 key. public KeyControl numpad4Key => this[Key.Numpad4]; /// /// The 5 key on the numpad. The key in-between the to the left and the /// to the right. /// - /// Control representing the numpad 5 key. + /// representing the numpad 5 key. public KeyControl numpad5Key => this[Key.Numpad5]; /// /// The 6 key on the numpad. The key in-between the to the let and /// the to the right. /// - /// Control representing the numpad 6 key. + /// representing the numpad 6 key. public KeyControl numpad6Key => this[Key.Numpad6]; /// /// The 7 key on the numpad. The key on the left side of the numpad with /// below it and above it. /// - /// Control representing the numpad 7 key. + /// representing the numpad 7 key. public KeyControl numpad7Key => this[Key.Numpad7]; /// /// The 8 key on the numpad. The key in-between the to the left and the /// to the right. /// - /// Control representing the numpad 8 key. + /// representing the numpad 8 key. public KeyControl numpad8Key => this[Key.Numpad8]; /// @@ -1775,98 +1884,99 @@ public string keyboardLayout /// the to the right (or, on 17-key PC keyboard numpads, the elongated /// plus key). /// - /// Control representing the numpad 9 key. + /// representing the numpad 9 key. public KeyControl numpad9Key => this[Key.Numpad9]; /// /// The F1 key. The key in-between to the left and /// to the right in the topmost row of keys. /// - /// Control representing the F1 key. + /// representing the F1 key. public KeyControl f1Key => this[Key.F1]; /// /// The F2 key. The key in-between to the left and /// to the right in the topmost row of keys. /// - /// Control representing the F2 key. + /// representing the F2 key. public KeyControl f2Key => this[Key.F2]; /// /// The F3 key. The key in-between to the left and /// to the right in the topmost row of keys. /// - /// Control representing the F3 key. + /// representing the F3 key. public KeyControl f3Key => this[Key.F3]; /// /// The F4 key. The key in-between to the left and /// to the right in the topmost row of keys. /// - /// Control representing the F4 key. + /// representing the F4 key. public KeyControl f4Key => this[Key.F4]; /// /// The F5 key. The key in-between to the left and /// to the right in the topmost row of keys. /// - /// Control representing the F5 key. + /// representing the F5 key. public KeyControl f5Key => this[Key.F5]; /// /// The F6 key. The key in-between to the left and /// to the right in the topmost row of keys. /// - /// Control representing the F6 key. + /// representing the F6 key. public KeyControl f6Key => this[Key.F6]; /// /// The F7 key. The key in-between to the left and /// to the right in the topmost row of keys. /// - /// Control representing the F7 key. + /// representing the F7 key. public KeyControl f7Key => this[Key.F7]; /// /// The F8 key. The key in-between to the left and /// to the right in the topmost row of keys. /// - /// Control representing the F8 key. + /// representing the F8 key. public KeyControl f8Key => this[Key.F8]; /// /// The F9 key. The key in-between to the left and /// to the right in the topmost row of keys. /// - /// Control representing the F9 key. + /// representing the F9 key. public KeyControl f9Key => this[Key.F9]; /// /// The F10 key. The key in-between to the left and /// to the right in the topmost row of keys. /// - /// Control representing the F10 key. + /// representing the F10 key. public KeyControl f10Key => this[Key.F10]; /// /// The F11 key. The key in-between to the left and /// to the right in the topmost row of keys. /// - /// Control representing the F11 key. + /// representing the F11 key. public KeyControl f11Key => this[Key.F11]; /// /// The F12 key. The key in-between to the left and /// to the right in the topmost row of keys. /// - /// Control representing the F12 key. + /// representing the F12 key. public KeyControl f12Key => this[Key.F12]; /// /// First additional key on the keyboard. /// - /// Control representing . /// + /// representing . + /// /// Keyboards may have additional keys that are not part of the standardized 104-key keyboard layout /// (105 in the case of an 18-key numpad). For example, many non-English keyboard layouts have an additional /// key in-between and . @@ -1880,8 +1990,9 @@ public string keyboardLayout /// /// Second additional key on the keyboard. /// - /// Control representing . /// + /// representing . + /// /// Keyboards may have additional keys that are not part of the standardized 104-key keyboard layout /// (105 in the case of an 18-key numpad). For example, many non-English keyboard layouts have an additional /// key in-between and . @@ -1895,8 +2006,9 @@ public string keyboardLayout /// /// Third additional key on the keyboard. /// - /// Control representing . /// + /// representing . + /// /// Keyboards may have additional keys that are not part of the standardized 104-key keyboard layout /// (105 in the case of an 18-key numpad). For example, many non-English keyboard layouts have an additional /// key in-between and . @@ -1910,8 +2022,9 @@ public string keyboardLayout /// /// Fourth additional key on the keyboard. /// - /// Control representing . /// + /// representing . + /// /// Keyboards may have additional keys that are not part of the standardized 104-key keyboard layout /// (105 in the case of an 18-key numpad). For example, many non-English keyboard layouts have an additional /// key in-between and . @@ -1925,8 +2038,9 @@ public string keyboardLayout /// /// Fifth additional key on the keyboard. /// - /// Control representing . /// + /// representing . + /// /// Keyboards may have additional keys that are not part of the standardized 104-key keyboard layout /// (105 in the case of an 18-key numpad). For example, many non-English keyboard layouts have an additional /// key in-between and . @@ -1940,8 +2054,9 @@ public string keyboardLayout /// /// An artificial combination of and into one control. /// - /// Control representing a combined left and right shift key. /// + /// representing a combined left and right shift key. + /// /// This is a button which is considered pressed whenever the left and/or /// right shift key is pressed. /// @@ -1950,8 +2065,9 @@ public string keyboardLayout /// /// An artificial combination of and into one control. /// - /// Control representing a combined left and right ctrl key. /// + /// representing a combined left and right ctrl key. + /// /// This is a button which is considered pressed whenever the left and/or /// right ctrl key is pressed. /// @@ -1960,8 +2076,9 @@ public string keyboardLayout /// /// An artificial combination of and into one control. /// - /// Control representing a combined left and right alt key. /// + /// representing a combined left and right alt key. + /// /// This is a button which is considered pressed whenever the left and/or /// right alt key is pressed. /// @@ -1971,9 +2088,8 @@ public string keyboardLayout /// True when IME composition is enabled. Requires to be called to enable IME, and the user to enable it at the OS level. /// /// - /// /// Some languages use complex input methods which involve opening windows to insert characters. - /// Typically, this is not desirable while playing a game, as games may just interpret key strokes as game input, not as text. + /// Typically, this is not desirable while playing a game, as games may just interpret keystrokes as game input, not as text. /// /// See for turning IME on/off /// @@ -1983,7 +2099,7 @@ public string keyboardLayout /// Look up a key control by its key code. /// /// Key code of key control to return. - /// The given is not valid. + /// The given is not valid. /// /// This is equivalent to allKeys[(int)key - 1]. /// @@ -2008,22 +2124,35 @@ public KeyControl this[Key key] /// public static Keyboard current { get; private set; } - /// - /// Make the keyboard the current keyboard (i.e. ). - /// + /// /// /// A keyboard will automatically be made current when receiving input or when /// added to the input system. + /// The currently active keyboard is tracked in . /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class InputExample : MonoBehaviour + /// { + /// void Start() + /// { + /// // Add a keyboard and make it the current keyboard. + /// var keyboard = InputSystem.AddDevice("Keyboard"); + /// keyboard.MakeCurrent(); + /// } + /// } + /// + /// public override void MakeCurrent() { base.MakeCurrent(); current = this; } - /// - /// Called when the keyboard is removed from the system. - /// + /// protected override void OnRemoved() { base.OnRemoved(); @@ -2035,6 +2164,27 @@ protected override void OnRemoved() /// Called after the keyboard has been constructed but before it is added to /// the system. /// + /// + /// This method can be overridden to perform control- or device-specific setup work. The most + /// common use case is for looking up child controls and storing them in local getters. + /// + /// + /// + /// using UnityEngine.InputSystem; + /// using UnityEngine.InputSystem.Controls; + /// + /// public class MyKeyboard : Keyboard + /// { + /// public ButtonControl button { get; private set; } + /// + /// protected override void FinishSetup() + /// { + /// // Cache controls in getters. + /// button = (ButtonControl)GetChildControl("button"); + /// } + /// } + /// + /// protected override void FinishSetup() { var keyStrings = new[] @@ -2182,11 +2332,26 @@ protected override void RefreshConfiguration() /// /// Called when text input on the keyboard is received. /// - /// Character that has been entered. + /// A type value that represents the character that has been entered. /// - /// The system will call this automatically whenever a is - /// received that targets the keyboard device. + /// The system will call this method automatically whenever a is + /// received that targets the keyboard device. Subscribe to this event by using . /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class OnTextInputExample : MonoBehaviour + /// { + /// // Simulate text input event on the current keyboard. + /// private void FakeInput() + /// { + /// Keyboard.current.OnTextInput('a'); + /// } + /// } + /// + /// public void OnTextInput(char character) { for (var i = 0; i < m_TextInputListeners.length; ++i) @@ -2204,11 +2369,22 @@ public void OnTextInput(char character) /// In most cases, this means that the key inputs the given text when pressed. However, this does not have to be the /// case. Keys do not necessarily lead to character input. /// + /// /// - /// // Find key that prints 'q' character (if any). - /// Keyboard.current.FindKeyOnCurrentKeyboardLayout("q"); + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class KeyboardUtils : MonoBehaviour + /// { + /// private void FindKey() + /// { + /// // Retrieve the q key on the current keyboard layout. + /// Keyboard.current.FindKeyOnCurrentKeyboardLayout("q"); + /// } + /// } + /// /// - /// /// public KeyControl FindKeyOnCurrentKeyboardLayout(string displayName) { @@ -2219,6 +2395,33 @@ public KeyControl FindKeyOnCurrentKeyboardLayout(string displayName) return null; } + /// + /// This is called to set the IME composition strings. Fired once for every change containing the entire string. + /// + /// The for the IME composition. + /// + /// To call back to the changed composition string, subscribe to the event. + /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// using UnityEngine.InputSystem.LowLevel; + /// + /// public class KeyboardUtils : MonoBehaviour + /// { + /// private string compositionString = ""; + /// void ChangeIMEComposition() + /// { + /// // Manually creating an input event to change the IME composition + /// var inputEvent = IMECompositionEvent.Create(Keyboard.current.deviceId, "CompositionTestCharacters! ɝ", Time.time); + /// Keyboard.current.OnIMECompositionChanged(inputEvent.compositionString); + /// } + /// } + /// + /// + /// + /// public void OnIMECompositionChanged(IMECompositionString compositionString) { if (m_ImeCompositionListeners.length > 0) diff --git a/InputSystem/Devices/Mouse.cs b/InputSystem/Devices/Mouse.cs index 552b3cb7..1740b9e3 100644 --- a/InputSystem/Devices/Mouse.cs +++ b/InputSystem/Devices/Mouse.cs @@ -26,6 +26,10 @@ public struct MouseState : IInputStateTypeInfo /// Screen-space position of the mouse in pixels. /// /// Position of mouse on screen. + /// + /// On Windows, delta originates from RAWINPUT API. + /// Note: This value might not update every frame, particularly if your project is running at a high frame rates. This value might also update at a different time than the . If you need a delta value that correlates with position, you should compute it based on the previous position value. + /// /// [InputControl(usage = "Point", dontReset = true)] // Mouse should stay put when we reset devices. [FieldOffset(0)] @@ -35,6 +39,10 @@ public struct MouseState : IInputStateTypeInfo /// Screen-space motion delta of the mouse in pixels. /// /// Mouse movement. + /// + /// On Windows, delta originates from RAWINPUT API. + /// Note: This value might not update every frame, particularly if your project is running at a high frame rates. This value might also update at a different time than the . If you need a delta value that correlates with position, you should compute it based on the previous position value. + /// /// [InputControl(usage = "Secondary2DMotion", layout = "Delta")] [FieldOffset(8)] @@ -163,18 +171,37 @@ namespace UnityEngine.InputSystem /// An input device representing a mouse. /// /// - /// Adds a scroll wheel and a typical 3-button setup with a left, middle, and right - /// button. + /// Adds a scroll wheel and a typical 5-button setup with a left, middle, right, + /// forward and backward button. /// /// To control cursor display and behavior, use . /// + /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class ExampleScript : MonoBehaviour + /// { + /// void Update() + /// { + /// // If there is a current mouse and the left button was pressed + /// if (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame) + /// { + /// // handle left mouse button being pressed + /// } + /// } + /// } + /// + /// + /// [InputControlLayout(stateType = typeof(MouseState), isGenericTypeOfDevice = true)] public class Mouse : Pointer, IInputStateCallbackReceiver { /// - /// The horizontal and vertical scroll wheels. + /// Control representing horizontal and vertical scroll wheels of a Mouse device. /// - /// Control representing the mouse scroll wheels. /// /// The x component corresponds to the horizontal scroll wheel, the /// y component to the vertical scroll wheel. Most mice do not have @@ -183,58 +210,57 @@ public class Mouse : Pointer, IInputStateCallbackReceiver public DeltaControl scroll { get; protected set; } /// - /// The left mouse button. + /// Control representing left button of a Mouse device. /// - /// Control representing the left mouse button. public ButtonControl leftButton { get; protected set; } /// - /// The middle mouse button. + /// Control representing middle button of a Mouse device. /// - /// Control representing the middle mouse button. public ButtonControl middleButton { get; protected set; } /// - /// The right mouse button. + /// Control representing right button of a Mouse device. /// - /// Control representing the right mouse button. public ButtonControl rightButton { get; protected set; } /// - /// The first side button, often labeled/used as "back". + /// Control representing the first side button, often labeled/used as "back", of a Mouse device. /// - /// Control representing the back button on the mouse. /// /// On Windows, this corresponds to RI_MOUSE_BUTTON_4. /// public ButtonControl backButton { get; protected set; } /// - /// The second side button, often labeled/used as "forward". + /// Control representing the second side button, often labeled/used as "forward", of a Mouse device. /// - /// Control representing the forward button on the mouse. /// /// On Windows, this corresponds to RI_MOUSE_BUTTON_5. /// public ButtonControl forwardButton { get; protected set; } /// - /// Number of times any of the mouse buttons has been clicked in succession within + /// Control representing the number of times any of the mouse buttons has been clicked in succession within /// the system-defined click time threshold. /// - /// Control representing the mouse click count. public IntegerControl clickCount { get; protected set; } /// /// The mouse that was added or updated last or null if there is no mouse /// connected to the system. /// - /// + /// + /// To set a mouse device as current, use . + /// public new static Mouse current { get; private set; } /// /// Called when the mouse becomes the current mouse. /// + /// + /// This is called automatically by the system when there is input on a connected mouse. + /// public override void MakeCurrent() { base.MakeCurrent(); @@ -266,7 +292,9 @@ protected override void OnRemoved() ////REVIEW: how should we handle this being called from EditorWindow's? (where the editor window space processor will turn coordinates automatically into editor window space) /// - /// Move the operating system's mouse cursor. + /// Move the operating system's mouse cursor by performing a device command in a similar way to + /// DeviceIoControl on Windows and ioctl + /// on UNIX-like systems. /// /// New position in player window space. /// @@ -305,7 +333,9 @@ protected override void FinishSetup() /// /// Implements for the mouse. /// - /// + /// Pointer to an . Makes it easier to + /// work with InputEvents and hides the unsafe operations necessary to work with them. + /// protected new unsafe void OnStateEvent(InputEventPtr eventPtr) { scroll.AccumulateValueInEvent(currentStatePtr, eventPtr); diff --git a/InputSystem/Devices/Pen.cs b/InputSystem/Devices/Pen.cs index 96b0257c..9c0f8e5a 100644 --- a/InputSystem/Devices/Pen.cs +++ b/InputSystem/Devices/Pen.cs @@ -98,8 +98,10 @@ public struct PenState : IInputStateTypeInfo [FieldOffset(32)] public ushort buttons; - // Not currently used, but still needed in this struct for padding, - // as il2cpp does not implement FieldOffset. + /// + /// The index of the display that was touched. + /// + [InputControl(name = "displayIndex", displayName = "Display Index", layout = "Integer")] [FieldOffset(34)] ushort displayIndex; @@ -379,6 +381,7 @@ protected override void FinishSetup() inRange = GetChildControl("inRange"); tilt = GetChildControl("tilt"); twist = GetChildControl("twist"); + displayIndex = GetChildControl("displayIndex"); base.FinishSetup(); } } diff --git a/InputSystem/Devices/Precompiled/FastKeyboard.cs b/InputSystem/Devices/Precompiled/FastKeyboard.cs index 00255de7..8f7b8885 100644 --- a/InputSystem/Devices/Precompiled/FastKeyboard.cs +++ b/InputSystem/Devices/Precompiled/FastKeyboard.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was auto-generated by com.unity.inputsystem:InputLayoutCodeGenerator -// version 1.11.2 +// version 1.12.0 // from "Keyboard" layout // // Changes to this file may cause incorrect behavior and will be lost if diff --git a/InputSystem/Devices/Precompiled/FastMouse.cs b/InputSystem/Devices/Precompiled/FastMouse.cs index 7026b23e..98fa0654 100644 --- a/InputSystem/Devices/Precompiled/FastMouse.cs +++ b/InputSystem/Devices/Precompiled/FastMouse.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was auto-generated by com.unity.inputsystem:InputLayoutCodeGenerator -// version 1.11.2 +// version 1.12.0 // from "Mouse" layout // // Changes to this file may cause incorrect behavior and will be lost if diff --git a/InputSystem/Devices/Precompiled/FastTouchscreen.cs b/InputSystem/Devices/Precompiled/FastTouchscreen.cs index 40a3fd31..c899aeda 100644 --- a/InputSystem/Devices/Precompiled/FastTouchscreen.cs +++ b/InputSystem/Devices/Precompiled/FastTouchscreen.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was auto-generated by com.unity.inputsystem:InputLayoutCodeGenerator -// version 1.11.2 +// version 1.12.0 // from "Touchscreen" layout // // Changes to this file may cause incorrect behavior and will be lost if diff --git a/InputSystem/Editor/AssetEditor/InputActionEditorWindow.cs b/InputSystem/Editor/AssetEditor/InputActionEditorWindow.cs index f3fcc1ab..2aa6a7d9 100644 --- a/InputSystem/Editor/AssetEditor/InputActionEditorWindow.cs +++ b/InputSystem/Editor/AssetEditor/InputActionEditorWindow.cs @@ -45,7 +45,7 @@ static InputActionEditorWindow() public static bool OnOpenAsset(int instanceId, int line) { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) + if (!InputSystem.settings.useIMGUIEditorForAssets) return false; #endif var path = AssetDatabase.GetAssetPath(instanceId); diff --git a/InputSystem/Editor/AssetEditor/PackageResources/personal/actionTreeBackgroundWithoutBorder.png b/InputSystem/Editor/AssetEditor/PackageResources/personal/actionTreeBackgroundWithoutBorder.png new file mode 100644 index 00000000..550ac3f7 Binary files /dev/null and b/InputSystem/Editor/AssetEditor/PackageResources/personal/actionTreeBackgroundWithoutBorder.png differ diff --git a/InputSystem/Editor/AssetEditor/PackageResources/personal/actionTreeBackgroundWithoutBorder.png.meta b/InputSystem/Editor/AssetEditor/PackageResources/personal/actionTreeBackgroundWithoutBorder.png.meta new file mode 100644 index 00000000..76d5c65b --- /dev/null +++ b/InputSystem/Editor/AssetEditor/PackageResources/personal/actionTreeBackgroundWithoutBorder.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: cf632ed80bf1f46c980de6b1b8b903ef +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/InputSystem/Editor/AssetEditor/PackageResources/pro/actionTreeBackgroundWithoutBorder.png.meta b/InputSystem/Editor/AssetEditor/PackageResources/pro/actionTreeBackgroundWithoutBorder.png.meta new file mode 100644 index 00000000..85672370 --- /dev/null +++ b/InputSystem/Editor/AssetEditor/PackageResources/pro/actionTreeBackgroundWithoutBorder.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 943deaaee9c9347e0a455aa208526c42 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/InputSystem/Editor/AssetEditor/ParameterListView.cs b/InputSystem/Editor/AssetEditor/ParameterListView.cs index d7bf20c4..4e1df4bb 100644 --- a/InputSystem/Editor/AssetEditor/ParameterListView.cs +++ b/InputSystem/Editor/AssetEditor/ParameterListView.cs @@ -280,6 +280,7 @@ void OnEditEnd() { var intValue = parameter.value.value.ToInt32(); var field = new DropdownField(label.text, parameter.enumNames.Select(x => x.text).ToList(), intValue); + field.tooltip = label.tooltip; field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, field.index, closedIndex)); field.RegisterCallback(_ => OnEditEnd()); root.Add(field); @@ -287,7 +288,7 @@ void OnEditEnd() else if (parameter.value.type == TypeCode.Int64 || parameter.value.type == TypeCode.UInt64) { var longValue = parameter.value.value.ToInt64(); - var field = new LongField(label.text) { value = longValue }; + var field = new LongField(label.text) { value = longValue, tooltip = label.tooltip }; field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, evt.newValue, closedIndex)); field.RegisterCallback(_ => OnEditEnd()); root.Add(field); @@ -295,7 +296,7 @@ void OnEditEnd() else if (parameter.value.type.IsInt()) { var intValue = parameter.value.value.ToInt32(); - var field = new IntegerField(label.text) { value = intValue }; + var field = new IntegerField(label.text) { value = intValue, tooltip = label.tooltip }; field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, evt.newValue, closedIndex)); field.RegisterCallback(_ => OnEditEnd()); root.Add(field); @@ -303,7 +304,7 @@ void OnEditEnd() else if (parameter.value.type == TypeCode.Single) { var floatValue = parameter.value.value.ToSingle(); - var field = new FloatField(label.text) { value = floatValue }; + var field = new FloatField(label.text) { value = floatValue, tooltip = label.tooltip }; field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, evt.newValue, closedIndex)); field.RegisterCallback(_ => OnEditEnd()); root.Add(field); @@ -311,7 +312,7 @@ void OnEditEnd() else if (parameter.value.type == TypeCode.Double) { var floatValue = parameter.value.value.ToDouble(); - var field = new DoubleField(label.text) { value = floatValue }; + var field = new DoubleField(label.text) { value = floatValue, tooltip = label.tooltip }; field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, evt.newValue, closedIndex)); field.RegisterCallback(_ => OnEditEnd()); root.Add(field); @@ -319,7 +320,7 @@ void OnEditEnd() else if (parameter.value.type == TypeCode.Boolean) { var boolValue = parameter.value.value.ToBoolean(); - var field = new Toggle(label.text) { value = boolValue }; + var field = new Toggle(label.text) { value = boolValue, tooltip = label.tooltip }; field.RegisterValueChangedCallback(evt => OnValueChanged(ref parameter, evt.newValue, closedIndex)); field.RegisterValueChangedCallback(_ => OnEditEnd()); root.Add(field); @@ -352,7 +353,7 @@ public void OnGUI() #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS // handled by OnDrawVisualElements with UI Toolkit - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return; + if (!InputSystem.settings.useIMGUIEditorForAssets) return; #endif // Otherwise, fall back to our default logic. if (m_Parameters == null) diff --git a/InputSystem/Editor/AssetImporter/InputActionCodeGenerator.cs b/InputSystem/Editor/AssetImporter/InputActionCodeGenerator.cs index dab46023..a7bdf3f8 100644 --- a/InputSystem/Editor/AssetImporter/InputActionCodeGenerator.cs +++ b/InputSystem/Editor/AssetImporter/InputActionCodeGenerator.cs @@ -1,5 +1,6 @@ #if UNITY_EDITOR using System; +using System.Collections; using System.IO; using System.Linq; using System.Text; @@ -41,6 +42,54 @@ public static class InputActionCodeGenerator { private const int kSpacesPerIndentLevel = 4; + private const string kClassExample = @"using namespace UnityEngine; +using UnityEngine.InputSystem; + +// Example of using an InputActionMap named ""Player"" from a UnityEngine.MonoBehaviour implementing callback interface. +public class Example : MonoBehaviour, MyActions.IPlayerActions +{ + private MyActions_Actions m_Actions; // Source code representation of asset. + private MyActions_Actions.PlayerActions m_Player; // Source code representation of action map. + + void Awake() + { + m_Actions = new MyActions_Actions(); // Create asset object. + m_Player = m_Actions.Player; // Extract action map object. + m_Player.AddCallbacks(this); // Register callback interface IPlayerActions. + } + + void OnDestroy() + { + m_Actions.Dispose(); // Destroy asset object. + } + + void OnEnable() + { + m_Player.Enable(); // Enable all actions within map. + } + + void OnDisable() + { + m_Player.Disable(); // Disable all actions within map. + } + + #region Interface implementation of MyActions.IPlayerActions + + // Invoked when ""Move"" action is either started, performed or canceled. + public void OnMove(InputAction.CallbackContext context) + { + Debug.Log($""OnMove: {context.ReadValue()}""); + } + + // Invoked when ""Attack"" action is either started, performed or canceled. + public void OnAttack(InputAction.CallbackContext context) + { + Debug.Log($""OnAttack: {context.ReadValue()}""); + } + + #endregion +}"; + public struct Options { public string className { get; set; } @@ -94,12 +143,22 @@ public static string GenerateWrapperCode(InputActionAsset asset, Options options } // Begin class. + writer.DocSummary($"Provides programmatic access to , " + + ", and " + + " instances defined " + + $"in asset \"{options.sourceAssetPath}\"."); + writer.DocRemarks("This class is source generated and any manual edits will be discarded if the associated asset is reimported or modified."); + writer.DocExample(kClassExample); + writer.WriteLine($"public partial class @{options.className}: IInputActionCollection2, IDisposable"); writer.BeginBlock(); + writer.DocSummary("Provides access to the underlying asset instance."); writer.WriteLine($"public InputActionAsset asset {{ get; }}"); + writer.WriteLine(); // Default constructor. + writer.DocSummary("Constructs a new instance."); writer.WriteLine($"public @{options.className}()"); writer.BeginBlock(); writer.WriteLine($"asset = InputActionAsset.FromJson(@\"{asset.ToJson().Replace("\"", "\"\"")}\");"); @@ -131,12 +190,15 @@ public static string GenerateWrapperCode(InputActionAsset asset, Options options writer.EndBlock(); writer.WriteLine(); + writer.DocSummary("Destroys this asset and all associated instances."); writer.WriteLine("public void Dispose()"); writer.BeginBlock(); writer.WriteLine("UnityEngine.Object.Destroy(asset);"); writer.EndBlock(); writer.WriteLine(); + var classNamePrefix = typeof(InputActionAsset).Namespace + "." + nameof(InputActionAsset) + "."; + writer.DocInherit(classNamePrefix + nameof(InputActionAsset.bindingMask)); writer.WriteLine("public InputBinding? bindingMask"); writer.BeginBlock(); writer.WriteLine("get => asset.bindingMask;"); @@ -144,6 +206,7 @@ public static string GenerateWrapperCode(InputActionAsset asset, Options options writer.EndBlock(); writer.WriteLine(); + writer.DocInherit(classNamePrefix + nameof(InputActionAsset.devices)); writer.WriteLine("public ReadOnlyArray? devices"); writer.BeginBlock(); writer.WriteLine("get => asset.devices;"); @@ -151,54 +214,64 @@ public static string GenerateWrapperCode(InputActionAsset asset, Options options writer.EndBlock(); writer.WriteLine(); + writer.DocInherit(classNamePrefix + nameof(InputActionAsset.controlSchemes)); writer.WriteLine("public ReadOnlyArray controlSchemes => asset.controlSchemes;"); writer.WriteLine(); + writer.DocInherit(classNamePrefix + nameof(InputActionAsset.Contains) + "(InputAction)"); writer.WriteLine("public bool Contains(InputAction action)"); writer.BeginBlock(); writer.WriteLine("return asset.Contains(action);"); writer.EndBlock(); writer.WriteLine(); + writer.DocInherit(classNamePrefix + nameof(InputActionAsset.GetEnumerator) + "()"); writer.WriteLine("public IEnumerator GetEnumerator()"); writer.BeginBlock(); writer.WriteLine("return asset.GetEnumerator();"); writer.EndBlock(); writer.WriteLine(); + writer.DocInherit(nameof(IEnumerable) + "." + nameof(IEnumerable.GetEnumerator) + "()"); writer.WriteLine("IEnumerator IEnumerable.GetEnumerator()"); writer.BeginBlock(); writer.WriteLine("return GetEnumerator();"); writer.EndBlock(); writer.WriteLine(); + writer.DocInherit(classNamePrefix + nameof(InputActionAsset.Enable) + "()"); writer.WriteLine("public void Enable()"); writer.BeginBlock(); writer.WriteLine("asset.Enable();"); writer.EndBlock(); writer.WriteLine(); + writer.DocInherit(classNamePrefix + nameof(InputActionAsset.Disable) + "()"); writer.WriteLine("public void Disable()"); writer.BeginBlock(); writer.WriteLine("asset.Disable();"); writer.EndBlock(); writer.WriteLine(); + writer.DocInherit(classNamePrefix + nameof(InputActionAsset.bindings)); writer.WriteLine("public IEnumerable bindings => asset.bindings;"); writer.WriteLine(); + writer.DocInherit(classNamePrefix + nameof(InputActionAsset.FindAction) + "(string, bool)"); writer.WriteLine("public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false)"); writer.BeginBlock(); writer.WriteLine("return asset.FindAction(actionNameOrId, throwIfNotFound);"); writer.EndBlock(); writer.WriteLine(); + writer.DocInherit(classNamePrefix + nameof(InputActionAsset.FindBinding) + "(InputBinding, out InputAction)"); writer.WriteLine("public int FindBinding(InputBinding bindingMask, out InputAction action)"); writer.BeginBlock(); writer.WriteLine("return asset.FindBinding(bindingMask, out action);"); writer.EndBlock(); // Action map accessors. + var inputActionMapClassPrefix = typeof(InputActionMap).Namespace + "." + nameof(InputActionMap) + "."; foreach (var map in maps) { writer.WriteLine(); @@ -219,34 +292,48 @@ public static string GenerateWrapperCode(InputActionAsset asset, Options options } // Struct wrapping access to action set. + writer.DocSummary($"Provides access to input actions defined in input action map \"{map.name}\"."); writer.WriteLine($"public struct {mapTypeName}"); writer.BeginBlock(); - // Constructor. writer.WriteLine($"private @{options.className} m_Wrapper;"); + writer.WriteLine(); + + // Constructor. + writer.DocSummary("Construct a new instance of the input action map wrapper class."); writer.WriteLine($"public {mapTypeName}(@{options.className} wrapper) {{ m_Wrapper = wrapper; }}"); // Getter for each action. foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); + writer.DocSummary($"Provides access to the underlying input action \"{mapName}/{actionName}\"."); writer.WriteLine( $"public InputAction @{actionName} => m_Wrapper.m_{mapName}_{actionName};"); } // Action map getter. + writer.DocSummary("Provides access to the underlying input action map instance."); writer.WriteLine($"public InputActionMap Get() {{ return m_Wrapper.m_{mapName}; }}"); // Enable/disable methods. + writer.DocInherit(inputActionMapClassPrefix + nameof(InputActionMap.Enable) + "()"); writer.WriteLine("public void Enable() { Get().Enable(); }"); + writer.DocInherit(inputActionMapClassPrefix + nameof(InputActionMap.Disable) + "()"); writer.WriteLine("public void Disable() { Get().Disable(); }"); + writer.DocInherit(inputActionMapClassPrefix + nameof(InputActionMap.enabled)); writer.WriteLine("public bool enabled => Get().enabled;"); // Implicit conversion operator. + writer.DocSummary($"Implicitly converts an to an instance."); writer.WriteLine( $"public static implicit operator InputActionMap({mapTypeName} set) {{ return set.Get(); }}"); // AddCallbacks method. + writer.DocSummary("Adds , and callbacks provided via on all input actions contained in this map."); + writer.DocParam("instance", "Callback instance."); + writer.DocRemarks("If is null or have already been added this method does nothing."); + writer.DocSeeAlso(mapTypeName); writer.WriteLine($"public void AddCallbacks(I{mapTypeName} instance)"); writer.BeginBlock(); @@ -268,6 +355,9 @@ public static string GenerateWrapperCode(InputActionAsset asset, Options options writer.WriteLine(); // UnregisterCallbacks method. + writer.DocSummary("Removes , and callbacks provided via on all input actions contained in this map."); + writer.DocRemarks("Calling this method when have not previously been registered has no side-effects."); + writer.DocSeeAlso(mapTypeName); writer.WriteLine($"private void UnregisterCallbacks(I{mapTypeName} instance)"); writer.BeginBlock(); foreach (var action in map.actions) @@ -283,6 +373,8 @@ public static string GenerateWrapperCode(InputActionAsset asset, Options options writer.WriteLine(); // RemoveCallbacks method. + writer.DocSummary($"Unregisters and unregisters all input action callbacks via ."); + writer.DocSeeAlso($"{mapTypeName}.UnregisterCallbacks(I{mapTypeName})"); writer.WriteLine($"public void RemoveCallbacks(I{mapTypeName} instance)"); writer.BeginBlock(); writer.WriteLine($"if (m_Wrapper.m_{mapTypeName}CallbackInterfaces.Remove(instance))"); @@ -291,9 +383,13 @@ public static string GenerateWrapperCode(InputActionAsset asset, Options options writer.WriteLine(); // SetCallbacks method. + writer.DocSummary($"Replaces all existing callback instances and previously registered input action callbacks associated with them with callbacks provided via ."); + writer.DocRemarks($"If is null, calling this method will only unregister all existing callbacks but not register any new callbacks."); + writer.DocSeeAlso($"{mapTypeName}.AddCallbacks(I{mapTypeName})"); + writer.DocSeeAlso($"{mapTypeName}.RemoveCallbacks(I{mapTypeName})"); + writer.DocSeeAlso($"{mapTypeName}.UnregisterCallbacks(I{mapTypeName})"); writer.WriteLine($"public void SetCallbacks(I{mapTypeName} instance)"); writer.BeginBlock(); - ////REVIEW: this would benefit from having a single callback on InputActions rather than three different endpoints writer.WriteLine($"foreach (var item in m_Wrapper.m_{mapTypeName}CallbackInterfaces)"); @@ -306,6 +402,7 @@ public static string GenerateWrapperCode(InputActionAsset asset, Options options writer.EndBlock(); // Getter for instance of struct. + writer.DocSummary($"Provides a new instance referencing this action map."); writer.WriteLine($"public {mapTypeName} @{mapName} => new {mapTypeName}(this);"); } @@ -315,6 +412,8 @@ public static string GenerateWrapperCode(InputActionAsset asset, Options options var identifier = CSharpCodeHelpers.MakeIdentifier(scheme.name); writer.WriteLine($"private int m_{identifier}SchemeIndex = -1;"); + writer.DocSummary("Provides access to the input control scheme."); + writer.DocSeeAlso(typeof(InputControlScheme).Namespace + "." + nameof(InputControlScheme)); writer.WriteLine($"public InputControlScheme {identifier}Scheme"); writer.BeginBlock(); writer.WriteLine("get"); @@ -326,15 +425,23 @@ public static string GenerateWrapperCode(InputActionAsset asset, Options options } // Generate interfaces. + var inputActionClassReference = typeof(InputAction).Namespace + "." + nameof(InputAction) + "."; foreach (var map in maps) { var typeName = CSharpCodeHelpers.MakeTypeName(map.name); + writer.DocSummary($"Interface to implement callback methods for all input action callbacks associated with input actions defined by \"{map.name}\" which allows adding and removing callbacks."); + writer.DocSeeAlso($"{typeName}Actions.AddCallbacks(I{typeName}Actions)"); + writer.DocSeeAlso($"{typeName}Actions.RemoveCallbacks(I{typeName}Actions)"); writer.WriteLine($"public interface I{typeName}Actions"); writer.BeginBlock(); foreach (var action in map.actions) { var methodName = CSharpCodeHelpers.MakeTypeName(action.name); + writer.DocSummary($"Method invoked when associated input action \"{action.name}\" is either , or ."); + writer.DocSeeAlso(string.Concat(inputActionClassReference, "started")); + writer.DocSeeAlso(string.Concat(inputActionClassReference, "performed")); + writer.DocSeeAlso(string.Concat(inputActionClassReference, "canceled")); writer.WriteLine($"void On{methodName}(InputAction.CallbackContext context);"); } @@ -399,6 +506,68 @@ public void WriteIndent() buffer.Append(' '); } } + + public void DocSummary(string text) + { + DocElement("summary", text); + } + + public void DocParam(string paramName, string text) + { + WriteLine($"/// {text}"); + } + + public void DocRemarks(string text) + { + DocElement("remarks", text); + } + + public void DocInherit(string cref) + { + DocReference("inheritdoc", cref); + } + + public void DocSeeAlso(string cref) + { + DocReference("seealso", cref: cref); + } + + public void DocExample(string code) + { + DocComment(""); + DocComment(""); + + foreach (var line in code.Split('\n')) + DocComment(line.Replace("<", "<").Replace(">", ">")); + + DocComment(""); + DocComment(""); + } + + private void DocComment(string text) + { + if (string.IsNullOrEmpty(text)) + WriteLine("///"); + else + WriteLine(string.Concat("/// ", text)); + } + + private void DocElement(string tag, string text) + { + DocComment($"<{tag}>"); + DocComment(text); + DocComment($""); + } + + private void DocReference(string tag, string cref) + { + DocInlineElement(tag, "cref", cref); + } + + private void DocInlineElement(string tag, string property, string value) + { + DocComment($"<{tag} {property}=\"{value}\" />"); + } } // Updates the given file with wrapper code generated for the given action sets. diff --git a/InputSystem/Editor/AssetImporter/InputActionImporter.cs b/InputSystem/Editor/AssetImporter/InputActionImporter.cs index 6aa8af8a..6a625feb 100644 --- a/InputSystem/Editor/AssetImporter/InputActionImporter.cs +++ b/InputSystem/Editor/AssetImporter/InputActionImporter.cs @@ -256,11 +256,11 @@ private static void GenerateWrapperCode(AssetImportContext ctx, InputActionAsset if (InputActionCodeGenerator.GenerateWrapperCode(wrapperFilePath, asset, options)) { - // When we generate the wrapper code cs file during asset import, we cannot call ImportAsset on that directly because - // script assets have to be imported before all other assets, and are not allowed to be added to the import queue during - // asset import. So instead we register a callback to trigger a delayed asset refresh which should then pick up the - // changed/added script, and trigger a new import. - EditorApplication.delayCall += AssetDatabase.Refresh; + // This isn't ideal and may have side effects, but we cannot avoid compiling again. + // Previously we attempted to run a EditorApplication.delayCall += AssetDatabase.Refresh + // but this would lead to "error: Error building Player because scripts are compiling" in CI. + // Previous comment here warned against not being able to reimport here directly, but it seems it's ok. + AssetDatabase.ImportAsset(wrapperFilePath); } } diff --git a/InputSystem/Editor/AssetImporter/InputActionImporterEditor.cs b/InputSystem/Editor/AssetImporter/InputActionImporterEditor.cs index f13391c2..74244994 100644 --- a/InputSystem/Editor/AssetImporter/InputActionImporterEditor.cs +++ b/InputSystem/Editor/AssetImporter/InputActionImporterEditor.cs @@ -150,7 +150,7 @@ private static void OpenEditor(InputActionAsset asset) } // Redirect to UI-Toolkit window editor if not configured to use IMGUI explicitly - if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) + if (!InputSystem.settings.useIMGUIEditorForAssets) InputActionsEditorWindow.OpenEditor(asset); else InputActionEditorWindow.OpenEditor(asset); diff --git a/InputSystem/Editor/BuildPipeline/LinkFileGenerator.cs b/InputSystem/Editor/BuildPipeline/LinkFileGenerator.cs index 66eaeb87..eb5afba9 100644 --- a/InputSystem/Editor/BuildPipeline/LinkFileGenerator.cs +++ b/InputSystem/Editor/BuildPipeline/LinkFileGenerator.cs @@ -77,9 +77,12 @@ public string GenerateAdditionalLinkXmlFile(BuildReport report, UnityLinkerBuild sb.AppendLine(""); - var filePathName = Path.Combine(Application.dataPath, "..", "Temp", "InputSystemLink.xml"); - File.WriteAllText(filePathName, sb.ToString()); - return filePathName; + var linkXmlDirectory = Path.Combine(Application.dataPath, "..", "Library", "InputSystem"); + var linkXmlFile = Path.Combine(linkXmlDirectory, $"{data.target}Link.xml"); + + Directory.CreateDirectory(linkXmlDirectory); + File.WriteAllText(linkXmlFile, sb.ToString()); + return linkXmlFile; } static bool IsTypeUsedViaReflectionByInputSystem(Type type) diff --git a/InputSystem/Editor/InputParameterEditor.cs b/InputSystem/Editor/InputParameterEditor.cs index cbb90665..2ecbf89e 100644 --- a/InputSystem/Editor/InputParameterEditor.cs +++ b/InputSystem/Editor/InputParameterEditor.cs @@ -249,8 +249,16 @@ public void OnDrawVisualElements(VisualElement root, Action onChangedCallback) m_HelpBox = new HelpBox(m_HelpBoxText.text, HelpBoxMessageType.None); - m_DefaultToggle = new Toggle("Default") { value = m_UseDefaultValue }; + m_DefaultToggle = new Toggle("Default") + { + value = m_UseDefaultValue, + style = + { + flexDirection = FlexDirection.RowReverse + } + }; m_DefaultToggle.RegisterValueChangedCallback(evt => ToggleUseDefaultValue(evt, onChangedCallback)); + m_DefaultToggle.Q