A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using System.Diagnostics.CodeAnalysis; 4using System.Runtime.CompilerServices; 5using UnityEngine.InputSystem.Haptics; 6using Unity.Collections.LowLevel.Unsafe; 7using UnityEngine.InputSystem.Controls; 8using UnityEngine.InputSystem.Layouts; 9using UnityEngine.InputSystem.LowLevel; 10using UnityEngine.InputSystem.DualShock; 11using UnityEngine.InputSystem.EnhancedTouch; 12using UnityEngine.InputSystem.HID; 13using UnityEngine.InputSystem.Users; 14using UnityEngine.InputSystem.XInput; 15using UnityEngine.InputSystem.Utilities; 16using Unity.Profiling; 17 18#if UNITY_EDITOR 19using UnityEditor; 20using UnityEngine.InputSystem.Editor; 21using UnityEditor.Networking.PlayerConnection; 22#else 23using System.Linq; 24using UnityEngine.Networking.PlayerConnection; 25#endif 26 27#if UNITY_EDITOR 28using CustomBindingPathValidator = System.Func<string, System.Action>; 29#endif 30 31////TODO: allow aliasing processors etc 32 33////REVIEW: rename all references to "frame" to refer to "update" instead (e.g. wasPressedThisUpdate)? 34 35////TODO: add APIs to get to the state blocks (equivalent to what you currently get with e.g. InputSystem.devices[0].currentStatePtr) 36 37////FIXME: modal dialogs (or anything that interrupts normal Unity operation) are likely a problem for the system as is; there's a good 38//// chance the event queue will just get swamped; should be only the background queue though so I guess once it fills up we 39//// simply start losing input but it won't grow infinitely 40 41////REVIEW: make more APIs thread-safe? 42 43////REVIEW: it'd be great to be able to set up monitors from control paths (independently of actions; or should we just use actions?) 44 45////REVIEW: have InputSystem.onTextInput that's fired directly from the event processing loop? 46//// (and allow text input events that have no associated target device? this way we don't need a keyboard to get text input) 47 48////REVIEW: split lower-level APIs (anything mentioning events and state) off into InputSystemLowLevel API to make this API more focused? 49 50////TODO: release native allocations when exiting 51 52namespace UnityEngine.InputSystem 53{ 54 /// <summary> 55 /// This is the central hub for the input system. 56 /// </summary> 57 /// <remarks> 58 /// This class has the central APIs for working with the input system. You 59 /// can manage devices available in the system (<see cref="AddDevice{TDevice}"/>, 60 /// <see cref="devices"/>, <see cref="onDeviceChange"/> and related APIs) or extend 61 /// the input system with custom functionality (<see cref="RegisterLayout{TLayout}"/>, 62 /// <see cref="RegisterInteraction{T}"/>, <see cref="RegisterProcessor{T}"/>, 63 /// <see cref="RegisterBindingComposite{T}"/>, and related APIs). 64 /// 65 /// To control haptics globally, you can use <see cref="PauseHaptics"/>, <see cref="ResumeHaptics"/>, 66 /// and <see cref="ResetHaptics"/>. 67 /// 68 /// To enable and disable individual devices (such as <see cref="Sensor"/> devices), 69 /// you can use <see cref="EnableDevice"/> and <see cref="DisableDevice"/>. 70 /// 71 /// The input system is initialized as part of Unity starting up. It is generally safe 72 /// to call the APIs here from any of Unity's script callbacks. 73 /// 74 /// Note that, like most Unity APIs, most of the properties and methods in this API can only 75 /// be called on the main thread. However, select APIs like <see cref="QueueEvent"/> can be 76 /// called from threads. Where this is the case, it is stated in the documentation. 77 /// </remarks> 78 79 [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "Options for namespaces are limited due to the legacy input class. Agreed on this as the least bad solution.")] 80#if UNITY_EDITOR 81 [InitializeOnLoad] 82#endif 83 84 public static partial class InputSystem 85 { 86#if UNITY_EDITOR 87 static readonly ProfilerMarker k_InputInitializeInEditorMarker = new ProfilerMarker("InputSystem.InitializeInEditor"); 88#endif 89 static readonly ProfilerMarker k_InputResetMarker = new ProfilerMarker("InputSystem.Reset"); 90 91 #region Layouts 92 93 /// <summary> 94 /// Event that is signalled when the layout setup in the system changes. 95 /// </summary> 96 /// <remarks> 97 /// First parameter is the name of the layout that has changed and second parameter is the 98 /// type of change that has occurred. 99 /// 100 /// <example> 101 /// <code> 102 /// InputSystem.onLayoutChange += 103 /// (name, change) => 104 /// { 105 /// switch (change) 106 /// { 107 /// case InputControlLayoutChange.Added: 108 /// Debug.Log($"New layout {name} has been added"); 109 /// break; 110 /// case InputControlLayoutChange.Removed: 111 /// Debug.Log($"Layout {name} has been removed"); 112 /// break; 113 /// case InputControlLayoutChange.Replaced: 114 /// Debug.Log($"Layout {name} has been updated"); 115 /// break; 116 /// } 117 /// } 118 /// </code> 119 /// </example> 120 /// </remarks> 121 /// <seealso cref="InputControlLayout"/> 122 public static event Action<string, InputControlLayoutChange> onLayoutChange 123 { 124 add 125 { 126 lock (s_Manager) 127 s_Manager.onLayoutChange += value; 128 } 129 remove 130 { 131 lock (s_Manager) 132 s_Manager.onLayoutChange -= value; 133 } 134 } 135 136 /// <summary> 137 /// Register a control layout based on a type. 138 /// </summary> 139 /// <param name="type">Type to derive a control layout from. Must be derived from <see cref="InputControl"/>.</param> 140 /// <param name="name">Name to use for the layout. If null or empty, the short name of the type (<c>Type.Name</c>) will be used.</param> 141 /// <param name="matches">Optional device matcher. If this is supplied, the layout will automatically 142 /// be instantiated for newly discovered devices that match the description.</param> 143 /// <exception cref="ArgumentNullException"><paramref name="type"/> is <c>null</c>.</exception> 144 /// <remarks> 145 /// When the layout is instantiated, the system will reflect on all public fields and properties of the type 146 /// which have a value type derived from <see cref="InputControl"/> or which are annotated with <see cref="InputControlAttribute"/>. 147 /// 148 /// The type can be annotated with <see cref="InputControlLayoutAttribute"/> for additional options 149 /// but the attribute is not necessary for a type to be usable as a control layout. Note that if the type 150 /// does have <see cref="InputControlLayoutAttribute"/> and has set <see cref="InputControlLayoutAttribute.stateType"/>, 151 /// the system will <em>not</em> reflect on properties and fields in the type but do that on the given 152 /// state type instead. 153 /// 154 /// <example> 155 /// <code> 156 /// // InputControlLayoutAttribute attribute is only necessary if you want 157 /// // to override default behavior that occurs when registering your device 158 /// // as a layout. 159 /// // The most common use of InputControlLayoutAttribute is to direct the system 160 /// // to a custom "state struct" through the `stateType` property. See below for details. 161 /// [InputControlLayout(displayName = "My Device", stateType = typeof(MyDeviceState))] 162 /// #if UNITY_EDITOR 163 /// [InitializeOnLoad] 164 /// #endif 165 /// public class MyDevice : InputDevice 166 /// { 167 /// public ButtonControl button { get; private set; } 168 /// public AxisControl axis { get; private set; } 169 /// 170 /// // Register the device. 171 /// static MyDevice() 172 /// { 173 /// // In case you want instance of your device to automatically be created 174 /// // when specific hardware is detected by the Unity runtime, you have to 175 /// // add one or more "device matchers" (InputDeviceMatcher) for the layout. 176 /// // These matchers are compared to an InputDeviceDescription received from 177 /// // the Unity runtime when a device is connected. You can add them either 178 /// // using InputSystem.RegisterLayoutMatcher() or by directly specifying a 179 /// // matcher when registering the layout. 180 /// InputSystem.RegisterLayout&lt;MyDevice&gt;( 181 /// // For the sake of demonstration, let's assume your device is a HID 182 /// // and you want to match by PID and VID. 183 /// matches: new InputDeviceMatcher() 184 /// .WithInterface("HID") 185 /// .WithCapability("PID", 1234) 186 /// .WithCapability("VID", 5678)); 187 /// } 188 /// 189 /// // This is only to trigger the static class constructor to automatically run 190 /// // in the player. 191 /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] 192 /// private static void InitializeInPlayer() {} 193 /// 194 /// protected override void FinishSetup() 195 /// { 196 /// base.FinishSetup(); 197 /// button = GetChildControl&lt;ButtonControl&gt;("button"); 198 /// axis = GetChildControl&lt;AxisControl&gt;("axis"); 199 /// } 200 /// } 201 /// 202 /// // A "state struct" describes the memory format used by a device. Each device can 203 /// // receive and store memory in its custom format. InputControls are then connected 204 /// // the individual pieces of memory and read out values from them. 205 /// [StructLayout(LayoutKind.Explicit, Size = 32)] 206 /// public struct MyDeviceState : IInputStateTypeInfo 207 /// { 208 /// // In the case of a HID (which we assume for the sake of this demonstration), 209 /// // the format will be "HID". In practice, the format will depend on how your 210 /// // particular device is connected and fed into the input system. 211 /// // The format is a simple FourCC code that "tags" state memory blocks for the 212 /// // device to give a base level of safety checks on memory operations. 213 /// public FourCC format => return new FourCC('H', 'I', 'D'); 214 /// 215 /// // InputControlAttributes on fields tell the input system to create controls 216 /// // for the public fields found in the struct. 217 /// 218 /// // Assume a 16bit field of buttons. Create one button that is tied to 219 /// // bit #3 (zero-based). Note that buttons do not need to be stored as bits. 220 /// // They can also be stored as floats or shorts, for example. 221 /// [InputControl(name = "button", layout = "Button", bit = 3)] 222 /// public ushort buttons; 223 /// 224 /// // Create a floating-point axis. The name, if not supplied, is taken from 225 /// // the field. 226 /// [InputControl(layout = "Axis")] 227 /// public short axis; 228 /// } 229 /// </code> 230 /// </example> 231 /// 232 /// Note that if <paramref name="matches"/> is supplied, it will immediately be matched 233 /// against the descriptions (<see cref="InputDeviceDescription"/>) of all available devices. 234 /// If it matches any description where no layout matched before, a new device will immediately 235 /// be created (except if suppressed by <see cref="InputSettings.supportedDevices"/>). If it 236 /// matches a description better (see <see cref="InputDeviceMatcher.MatchPercentage"/>) than 237 /// the currently used layout, the existing device will be a removed and a new device with 238 /// the newly registered layout will be created. 239 /// 240 /// See <see cref="Controls.StickControl"/> or <see cref="Gamepad"/> for examples of layouts. 241 /// </remarks> 242 /// <seealso cref="InputControlLayout"/> 243 public static void RegisterLayout(Type type, string name = null, InputDeviceMatcher? matches = null) 244 { 245 if (type == null) 246 throw new ArgumentNullException(nameof(type)); 247 248 if (string.IsNullOrEmpty(name)) 249 name = type.Name; 250 251 s_Manager.RegisterControlLayout(name, type); 252 253 if (matches != null) 254 s_Manager.RegisterControlLayoutMatcher(name, matches.Value); 255 } 256 257 /// <summary> 258 /// Register a type as a control layout. 259 /// </summary> 260 /// <typeparam name="T">Type to derive a control layout from.</typeparam> 261 /// <param name="name">Name to use for the layout. If null or empty, the short name of the type will be used.</param> 262 /// <param name="matches">Optional device matcher. If this is supplied, the layout will automatically 263 /// be instantiated for newly discovered devices that match the description.</param> 264 /// <remarks> 265 /// This method is equivalent to calling <see cref="RegisterLayout(Type,string,InputDeviceMatcher?)"/> with 266 /// <c>typeof(T)</c>. See that method for details of the layout registration process. 267 /// </remarks> 268 /// <seealso cref="RegisterLayout(Type,string,InputDeviceMatcher?)"/> 269 public static void RegisterLayout<T>(string name = null, InputDeviceMatcher? matches = null) 270 where T : InputControl 271 { 272 RegisterLayout(typeof(T), name, matches); 273 } 274 275 /// <summary> 276 /// Register a layout in JSON format. 277 /// </summary> 278 /// <param name="json">JSON data describing the layout.</param> 279 /// <param name="name">Optional name of the layout. If null or empty, the name is taken from the "name" 280 /// property of the JSON data. If it is supplied, it will override the "name" property if present. If neither 281 /// is supplied, an <see cref="ArgumentException"/> is thrown.</param> 282 /// <param name="matches">Optional device matcher. If this is supplied, the layout will automatically 283 /// be instantiated for newly discovered devices that match the description.</param> 284 /// <exception cref="ArgumentNullException"><paramref name="json"/> is null or empty.</exception> 285 /// <exception cref="ArgumentException">No name has been supplied either through <paramref name="name"/> 286 /// or the "name" JSON property.</exception> 287 /// <remarks> 288 /// The JSON format makes it possible to create new device and control layouts completely 289 /// in data. They have to ultimately be based on a layout backed by a C# type, however (e.g. 290 /// <see cref="Gamepad"/>). 291 /// 292 /// Note that most errors in layouts will only be detected when instantiated (i.e. when a device or control is 293 /// being created from a layout). The JSON data will, however, be parsed once on registration to check for a 294 /// device description in the layout. JSON format errors will thus be detected during registration. 295 /// 296 /// <example> 297 /// <code> 298 /// InputSystem.RegisterLayout(@" 299 /// { 300 /// ""name"" : ""MyDevice"", 301 /// ""controls"" : [ 302 /// { 303 /// ""name"" : ""myButton"", 304 /// ""layout"" : ""Button"" 305 /// } 306 /// ] 307 /// } 308 /// ); 309 /// </code> 310 /// </example> 311 /// </remarks> 312 /// <seealso cref="RemoveLayout"/> 313 public static void RegisterLayout(string json, string name = null, InputDeviceMatcher? matches = null) 314 { 315 s_Manager.RegisterControlLayout(json, name); 316 317 if (matches != null) 318 s_Manager.RegisterControlLayoutMatcher(name, matches.Value); 319 } 320 321 /// <summary> 322 /// Register a layout that applies overrides to one or more other layouts. 323 /// </summary> 324 /// <param name="json">Layout in JSON format.</param> 325 /// <param name="name">Optional name of the layout. If null or empty, the name is taken from the "name" 326 /// property of the JSON data. If it is supplied, it will override the "name" property if present. If neither 327 /// is supplied, an <see cref="ArgumentException"/> is thrown.</param> 328 /// <remarks> 329 /// Layout overrides are layout pieces that are applied on top of existing layouts. 330 /// This can be used to modify any layout in the system non-destructively. The process works the 331 /// same as extending an existing layout except that instead of creating a new layout 332 /// by merging the derived layout and the base layout, the overrides are merged 333 /// directly into the base layout. 334 /// 335 /// The layout merging logic used for overrides, is the same as the one used for 336 /// derived layouts, i.e. <see cref="InputControlLayout.MergeLayout"/>. 337 /// 338 /// Layouts used as overrides look the same as normal layouts and have the same format. 339 /// The only difference is that they are explicitly registered as overrides. 340 /// 341 /// Note that unlike "normal" layouts, layout overrides have the ability to extend 342 /// multiple base layouts. The changes from the override will simply be merged into 343 /// each of the layouts it extends. Use the <c>extendMultiple</c> rather than the 344 /// <c>extend</c> property in JSON to give a list of base layouts instead of a single 345 /// one. 346 /// 347 /// <example> 348 /// <code> 349 /// // Override default button press points on the gamepad triggers. 350 /// InputSystem.RegisterLayoutOverride(@" 351 /// { 352 /// ""name"" : ""CustomTriggerPressPoints"", 353 /// ""extend"" : ""Gamepad"", 354 /// ""controls"" : [ 355 /// { ""name"" : ""leftTrigger"", ""parameters"" : ""pressPoint=0.25"" }, 356 /// { ""name"" : ""rightTrigger"", ""parameters"" : ""pressPoint=0.25"" } 357 /// ] 358 /// } 359 /// "); 360 /// </code> 361 /// </example> 362 /// </remarks> 363 public static void RegisterLayoutOverride(string json, string name = null) 364 { 365 s_Manager.RegisterControlLayout(json, name, isOverride: true); 366 } 367 368 /// <summary> 369 /// Add an additional device matcher to an existing layout. 370 /// </summary> 371 /// <param name="layoutName">Name of the device layout that should be instantiated if <paramref name="matcher"/> 372 /// matches an <see cref="InputDeviceDescription"/> of a discovered device.</param> 373 /// <param name="matcher">Specification to match against <see cref="InputDeviceDescription"/> instances.</param> 374 /// <remarks> 375 /// Each device layout can have zero or more matchers associated with it. If any one of the 376 /// matchers matches a given <see cref="InputDeviceDescription"/> (see <see cref="InputDeviceMatcher.MatchPercentage"/>) 377 /// better than any other matcher (for the same or any other layout), then the given layout 378 /// will be used for the discovered device. 379 /// 380 /// Note that registering a matcher may immediately lead to devices being created or recreated. 381 /// If <paramref name="matcher"/> matches any devices currently on the list of unsupported devices 382 /// (see <see cref="GetUnsupportedDevices()"/>), new <see cref="InputDevice"/>s will be created 383 /// using the layout called <paramref name="layoutName"/>. Also, if <paramref name="matcher"/> 384 /// matches the description of a device better than the matcher (if any) for the device's currently 385 /// used layout, the device will be recreated using the given layout. 386 /// </remarks> 387 /// <exception cref="ArgumentNullException"><paramref name="layoutName"/> is <c>null</c> or empty/</exception> 388 /// <exception cref="ArgumentException"><paramref name="matcher"/> is empty (<see cref="InputDeviceMatcher.empty"/>).</exception> 389 /// <seealso cref="RegisterLayout(Type,string,InputDeviceMatcher?)"/> 390 /// <seealso cref="TryFindMatchingLayout"/> 391 public static void RegisterLayoutMatcher(string layoutName, InputDeviceMatcher matcher) 392 { 393 s_Manager.RegisterControlLayoutMatcher(layoutName, matcher); 394 } 395 396 /// <summary> 397 /// Add an additional device matcher to the layout registered for <typeparamref name="TDevice"/>. 398 /// </summary> 399 /// <param name="matcher">A device matcher.</param> 400 /// <typeparam name="TDevice">Type that has been registered as a layout. See <see cref="RegisterLayout{T}"/>.</typeparam> 401 /// <remarks> 402 /// Calling this method is equivalent to calling <see cref="RegisterLayoutMatcher(string,InputDeviceMatcher)"/> 403 /// with the name under which <typeparamref name="TDevice"/> has been registered. 404 /// </remarks> 405 /// <exception cref="ArgumentException"><paramref name="matcher"/> is empty (<see cref="InputDeviceMatcher.empty"/>) 406 /// -or- <typeparamref name="TDevice"/> has not been registered as a layout.</exception> 407 public static void RegisterLayoutMatcher<TDevice>(InputDeviceMatcher matcher) 408 where TDevice : InputDevice 409 { 410 s_Manager.RegisterControlLayoutMatcher(typeof(TDevice), matcher); 411 } 412 413 /// <summary> 414 /// Register a builder that delivers an <see cref="InputControlLayout"/> instance on demand. 415 /// </summary> 416 /// <param name="buildMethod">Method to invoke to generate a layout when the layout is chosen. 417 /// Should not cache the layout but rather return a fresh instance every time.</param> 418 /// <param name="name">Name under which to register the layout. If a layout with the same 419 /// name is already registered, the call to this method will replace the existing layout.</param> 420 /// <param name="baseLayout">Name of the layout that the layout returned from <paramref name="buildMethod"/> 421 /// will be based on. The system needs to know this in advance in order to update devices 422 /// correctly if layout registrations in the system are changed.</param> 423 /// <param name="matches">Optional matcher for an <see cref="InputDeviceDescription"/>. If supplied, 424 /// it is equivalent to calling <see cref="RegisterLayoutMatcher"/>.</param> 425 /// <exception cref="ArgumentNullException"><paramref name="buildMethod"/> is <c>null</c> -or- 426 /// <paramref name="name"/> is <c>null</c> or empty.</exception> 427 /// <remarks> 428 /// Layout builders are most useful for procedurally building device layouts from metadata 429 /// supplied by external systems. A good example is <see cref="HID"/> where the "HID" standard 430 /// includes a way for input devices to describe their various inputs and outputs in the form 431 /// of a <see cref="UnityEngine.InputSystem.HID.HID.HIDDeviceDescriptor"/>. While not sufficient to build a perfectly robust 432 /// <see cref="InputDevice"/>, these descriptions are usually enough to at least make the device 433 /// work out-of-the-box to some extent. 434 /// 435 /// The builder method would usually use <see cref="InputControlLayout.Builder"/> to build the 436 /// actual layout. 437 /// 438 /// <example> 439 /// <code> 440 /// InputSystem.RegisterLayoutBuilder( 441 /// () => 442 /// { 443 /// var builder = new InputControlLayout.Builder() 444 /// .WithType&lt;MyDevice&gt;(); 445 /// builder.AddControl("button1").WithLayout("Button"); 446 /// return builder.Build(); 447 /// }, "MyCustomLayout" 448 /// } 449 /// </code> 450 /// </example> 451 /// 452 /// Layout builders can be used in combination with <see cref="onFindLayoutForDevice"/> to 453 /// build layouts dynamically for devices as they are connected to the system. 454 /// 455 /// Be aware that the same builder <em>must</em> not build different layouts. Each 456 /// layout registered in the system is considered to be immutable for as long as it 457 /// is registered. So, if a layout builder is registered under the name "Custom", for 458 /// example, then every time the builder is invoked, it must return the same identical 459 /// <see cref="InputControlLayout"/>. 460 /// </remarks> 461 /// <seealso cref="InputControlLayout.Builder"/> 462 /// <seealso cref="InputSystem.onFindLayoutForDevice"/> 463 public static void RegisterLayoutBuilder(Func<InputControlLayout> buildMethod, string name, 464 string baseLayout = null, InputDeviceMatcher? matches = null) 465 { 466 if (buildMethod == null) 467 throw new ArgumentNullException(nameof(buildMethod)); 468 if (string.IsNullOrEmpty(name)) 469 throw new ArgumentNullException(nameof(name)); 470 471 s_Manager.RegisterControlLayoutBuilder(buildMethod, name, baseLayout: baseLayout); 472 if (matches != null) 473 s_Manager.RegisterControlLayoutMatcher(name, matches.Value); 474 } 475 476 /// <summary> 477 /// Register a "baked" version of a device layout. 478 /// </summary> 479 /// <typeparam name="TDevice">C# class that represents the precompiled version of the device layout that the 480 /// class is derived from.</typeparam> 481 /// <param name="metadata">Metadata automatically generated for the precompiled layout.</param> 482 /// <remarks> 483 /// This method is used to register device implementations for which their layout has been "baked" into 484 /// a C# class. To generate such a class, right-click a device layout in the input debugger and select 485 /// "Generate Precompiled Layout". This generates a C# file containing a class that represents the precompiled 486 /// version of the device layout. The class can be registered using this method. 487 /// 488 /// Note that registering a precompiled layout will not implicitly register the "normal" version of the layout. 489 /// In other words, <see cref="RegisterLayout{TDevice}"/> must be called before calling this method. 490 /// 491 /// <example> 492 /// <code> 493 /// // Register the non-precompiled, normal version of the layout. 494 /// InputSystem.RegisterLayout&lt;MyDevice&gt;(); 495 /// 496 /// // Register a precompiled version of the layout. 497 /// InputSystem.RegisterPrecompiledLayout&lt;PrecompiledMyDevice&gt;(PrecompiledMyDevice.metadata); 498 /// 499 /// // This implicitly uses the precompiled version. 500 /// InputSystem.AddDevice&lt;MyDevice&gt;(); 501 /// </code> 502 /// </example> 503 /// 504 /// The main advantage of precompiled layouts is that instantiating them is many times faster than the default 505 /// device creation path. By default, when creating an <see cref="InputDevice"/>, the system will have to load 506 /// the <see cref="InputControlLayout"/> for the device as well as any layouts used directly or indirectly by 507 /// that layout. This in itself is a slow process that generates GC heap garbage and uses .NET reflection (which 508 /// itself may add additional permanent data to the GC heap). In addition, interpreting the layouts to construct 509 /// an <see cref="InputDevice"/> and populate it with <see cref="InputControl"/> children is not a fast process. 510 /// 511 /// A precompiled layout, however, has all necessary construction steps "baked" into the generated code. It will 512 /// not use reflection and will generally generate little to no GC heap garbage. 513 /// 514 /// A precompiled layout derives from the C# device class whose layout is "baked". If, for example, you generate 515 /// a precompiled version for <see cref="Keyboard"/>, the resulting class will be derived from <see cref="Keyboard"/>. 516 /// When registering the precompiled layout. If someone afterwards creates a <see cref="Keyboard"/>, the precompiled 517 /// version will implicitly be instantiated and thus skips the default device creation path that will construct 518 /// a <see cref="Keyboard"/> device from an <see cref="InputControlLayout"/> (it will thus not require the 519 /// <see cref="Keyboard"/> layout or any other layout it depends on to be loaded). 520 /// 521 /// Note that when layout overrides (see <see cref="RegisterLayoutOverride"/>) or new versions of 522 /// existing layouts are registered (e.g. if you replace the built-in "Button" layout by registering 523 /// a new layout with that name), precompiled layouts affected by the change will automatically be 524 /// <em>removed</em>. This causes the system to fall back to the default device creation path which can 525 /// take runtime layout changes into account. 526 /// </remarks> 527 public static void RegisterPrecompiledLayout<TDevice>(string metadata) 528 where TDevice : InputDevice, new() 529 { 530 s_Manager.RegisterPrecompiledLayout<TDevice>(metadata); 531 } 532 533 /// <summary> 534 /// Remove an already registered layout from the system. 535 /// </summary> 536 /// <param name="name">Name of the layout to remove. Note that layout names are case-insensitive.</param> 537 /// <remarks> 538 /// Note that removing a layout also removes all devices that directly or indirectly 539 /// use the layout. 540 /// 541 /// This method can be used to remove both control or device layouts. 542 /// </remarks> 543 public static void RemoveLayout(string name) 544 { 545 s_Manager.RemoveControlLayout(name); 546 } 547 548 /// <summary> 549 /// Try to match a description for an input device to a layout. 550 /// </summary> 551 /// <param name="deviceDescription">Description of an input device.</param> 552 /// <returns>Name of the layout that has been matched to the given description or null if no 553 /// matching layout was found.</returns> 554 /// <remarks> 555 /// This method performs the same matching process that is invoked if a device is reported 556 /// by the Unity runtime or using <see cref="AddDevice(InputDeviceDescription)"/>. The result 557 /// depends on the matches (<see cref="InputDeviceMatcher"/>) registered for the device 558 /// layout in the system. 559 /// 560 /// <example> 561 /// <code> 562 /// var layoutName = InputSystem.TryFindMatchingLayout( 563 /// new InputDeviceDescription 564 /// { 565 /// interface = "XInput", 566 /// product = "Xbox Wired Controller", 567 /// manufacturer = "Microsoft" 568 /// } 569 /// ); 570 /// </code> 571 /// </example> 572 /// </remarks> 573 /// <seealso cref="RegisterLayoutMatcher{TDevice}"/> 574 /// <seealso cref="RegisterLayoutMatcher(string,InputDeviceMatcher)"/> 575 public static string TryFindMatchingLayout(InputDeviceDescription deviceDescription) 576 { 577 return s_Manager.TryFindMatchingControlLayout(ref deviceDescription); 578 } 579 580 /// <summary> 581 /// Return a list with the names of all layouts that have been registered. 582 /// </summary> 583 /// <returns>A list of layout names.</returns> 584 /// <seealso cref="LoadLayout"/> 585 /// <seealso cref="ListLayoutsBasedOn"/> 586 /// <seealso cref="RegisterLayout(System.Type,string,Nullable{InputDeviceMatcher})"/> 587 public static IEnumerable<string> ListLayouts() 588 { 589 return s_Manager.ListControlLayouts(); 590 } 591 592 /// <summary> 593 /// List all the layouts that are based on the given layout. 594 /// </summary> 595 /// <param name="baseLayout">Name of a registered layout.</param> 596 /// <exception cref="ArgumentNullException"><paramref name="baseLayout"/> is <c>null</c> or empty.</exception> 597 /// <returns>The names of all registered layouts based on <paramref name="baseLayout"/>.</returns> 598 /// <remarks> 599 /// The list will not include layout overrides (see <see cref="RegisterLayoutOverride"/>). 600 /// 601 /// <example> 602 /// <code> 603 /// // List all gamepad layouts in the system. 604 /// Debug.Log(string.Join("\n", InputSystem.ListLayoutsBasedOn("Gamepad")); 605 /// </code> 606 /// </example> 607 /// </remarks> 608 public static IEnumerable<string> ListLayoutsBasedOn(string baseLayout) 609 { 610 if (string.IsNullOrEmpty(baseLayout)) 611 throw new ArgumentNullException(nameof(baseLayout)); 612 return s_Manager.ListControlLayouts(basedOn: baseLayout); 613 } 614 615 ////TODO: allow loading an *unmerged* layout 616 /// <summary> 617 /// Load a registered layout. 618 /// </summary> 619 /// <param name="name">Name of the layout to load. Note that layout names are case-insensitive.</param> 620 /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c> or empty.</exception> 621 /// <returns>The constructed layout instance or <c>null</c> if no layout of the given name could be found.</returns> 622 /// <remarks> 623 /// The result of this method is what's called a "fully merged" layout, i.e. a layout with 624 /// the information of all the base layouts as well as from all overrides merged into it. See 625 /// <see cref="InputControlLayout.MergeLayout"/> for details. 626 /// 627 /// What this means in practice is that all inherited controls and settings will be present 628 /// on the layout. 629 /// 630 /// <example> 631 /// // List all controls defined for gamepads. 632 /// var gamepadLayout = InputSystem.LoadLayout("Gamepad"); 633 /// foreach (var control in gamepadLayout.controls) 634 /// { 635 /// // There may be control elements that are not introducing new controls but rather 636 /// // change settings on controls added indirectly by other layouts referenced from 637 /// // Gamepad. These are not adding new controls so we skip them here. 638 /// if (control.isModifyingExistingControl) 639 /// continue; 640 /// 641 /// Debug.Log($"Control: {control.name} ({control.layout])"); 642 /// } 643 /// </example> 644 /// 645 /// However, note that controls which are added from other layouts referenced by the loaded layout 646 /// will not necessarily be visible on it (they will only if referenced by a <see cref="InputControlLayout.ControlItem"/> 647 /// where <see cref="InputControlLayout.ControlItem.isModifyingExistingControl"/> is <c>true</c>). 648 /// For example, let's assume we have the following layout which adds a device with a single stick. 649 /// 650 /// <example> 651 /// <code> 652 /// InputSystem.RegisterLayout(@" 653 /// { 654 /// ""name"" : ""DeviceWithStick"", 655 /// ""controls"" : [ 656 /// { ""name"" : ""stick"", ""layout"" : ""Stick"" } 657 /// ] 658 /// } 659 /// "); 660 /// </code> 661 /// </example> 662 /// 663 /// If we load this layout, the <c>"stick"</c> control will be visible on the layout but the 664 /// X and Y (as well as up/down/left/right) controls added by the <c>"Stick"</c> layout will 665 /// not be. 666 /// </remarks> 667 /// <seealso cref="RegisterLayout(Type,string,Nullable{InputDeviceMatcher})"/> 668 public static InputControlLayout LoadLayout(string name) 669 { 670 if (string.IsNullOrEmpty(name)) 671 throw new ArgumentNullException(nameof(name)); 672 ////FIXME: this will intern the name even if the operation fails 673 return s_Manager.TryLoadControlLayout(new InternedString(name)); 674 } 675 676 /// <summary> 677 /// Load the layout registered for the given type. 678 /// </summary> 679 /// <typeparam name="TControl">An InputControl type.</typeparam> 680 /// <returns>The layout registered for <typeparamref name="TControl"/> or <c>null</c> if no 681 /// such layout exists.</returns> 682 /// <remarks> 683 /// This method is equivalent to calling <see cref="LoadLayout(string)"/> with the name 684 /// of the layout under which <typeparamref name="TControl"/> has been registered. 685 /// 686 /// <example> 687 /// <code> 688 /// // Load the InputControlLayout generated from StickControl. 689 /// var stickLayout = InputSystem.LoadLayout&lt;StickControl&gt;(); 690 /// </code> 691 /// </example> 692 /// </remarks> 693 /// <seealso cref="LoadLayout(string)"/> 694 public static InputControlLayout LoadLayout<TControl>() 695 where TControl : InputControl 696 { 697 return s_Manager.TryLoadControlLayout(typeof(TControl)); 698 } 699 700 /// <summary> 701 /// Return the name of the layout that the layout registered as <paramref name="layoutName"/> 702 /// is based on. 703 /// </summary> 704 /// <param name="layoutName">Name of a layout as registered with a method such as <see 705 /// cref="RegisterLayout{T}(string,InputDeviceMatcher?)"/>. Case-insensitive.</param> 706 /// <returns>Name of the immediate parent layout of <paramref name="layoutName"/> or <c>null</c> if no layout 707 /// with the given name is registered or if it is not based on another layout or if it is a layout override.</returns> 708 /// <exception cref="ArgumentNullException"><paramref name="layoutName"/> is <c>null</c> or empty.</exception> 709 /// <remarks> 710 /// This method does not work for layout overrides (which can be based on multiple base layouts). To find 711 /// out which layouts a specific override registered with <see cref="RegisterLayoutOverride"/> is based on, 712 /// load the layout with <see cref="LoadLayout"/> and inspect <see cref="InputControlLayout.baseLayouts"/>. 713 /// This method will return <c>null</c> when <paramref name="layoutName"/> is the name of a layout override. 714 /// 715 /// One advantage of this method over calling <see cref="LoadLayout"/> and looking at <see cref="InputControlLayout.baseLayouts"/> 716 /// is that this method does not have to actually load the layout but instead only performs a simple lookup. 717 /// 718 /// <example> 719 /// <code> 720 /// // Prints "Pointer". 721 /// Debug.Log(InputSystem.GetNameOfBaseLayout("Mouse")); 722 /// 723 /// // Also works for control layouts. Prints "Axis". 724 /// Debug.Log(InputSystem.GetNameOfBaseLayout("Button")); 725 /// </code> 726 /// </example> 727 /// </remarks> 728 /// <seealso cref="InputControlLayout.baseLayouts"/> 729 public static string GetNameOfBaseLayout(string layoutName) 730 { 731 if (string.IsNullOrEmpty(layoutName)) 732 throw new ArgumentNullException(nameof(layoutName)); 733 734 var internedLayoutName = new InternedString(layoutName); 735 if (InputControlLayout.s_Layouts.baseLayoutTable.TryGetValue(internedLayoutName, out var result)) 736 return result; 737 738 return null; 739 } 740 741 /// <summary> 742 /// Check whether the first layout is based on the second. 743 /// </summary> 744 /// <param name="firstLayoutName">Name of a registered <see cref="InputControlLayout"/>.</param> 745 /// <param name="secondLayoutName">Name of a registered <see cref="InputControlLayout"/>.</param> 746 /// <returns>True if <paramref name="firstLayoutName"/> is based on <paramref name="secondLayoutName"/>.</returns> 747 /// <exception cref="ArgumentNullException"><paramref name="firstLayoutName"/> is <c>null</c> or empty -or- 748 /// <paramref name="secondLayoutName"/> is <c>null</c> or empty.</exception> 749 /// <remarks> 750 /// This is 751 /// <example> 752 /// </example> 753 /// </remarks> 754 public static bool IsFirstLayoutBasedOnSecond(string firstLayoutName, string secondLayoutName) 755 { 756 if (string.IsNullOrEmpty(firstLayoutName)) 757 throw new ArgumentNullException(nameof(firstLayoutName)); 758 if (string.IsNullOrEmpty(secondLayoutName)) 759 throw new ArgumentNullException(nameof(secondLayoutName)); 760 761 var internedFirstName = new InternedString(firstLayoutName); 762 var internedSecondName = new InternedString(secondLayoutName); 763 764 if (internedFirstName == internedSecondName) 765 return true; 766 767 return InputControlLayout.s_Layouts.IsBasedOn(internedSecondName, internedFirstName); 768 } 769 770 #endregion 771 772 #region Processors 773 774 /// <summary> 775 /// Register an <see cref="InputProcessor{TValue}"/> with the system. 776 /// </summary> 777 /// <param name="type">Type that implements <see cref="InputProcessor"/>.</param> 778 /// <param name="name">Name to use for the processor. If <c>null</c> or empty, name will be taken from the short name 779 /// of <paramref name="type"/> (if it ends in "Processor", that suffix will be clipped from the name). Names 780 /// are case-insensitive.</param> 781 /// <remarks> 782 /// Processors are used by both bindings (see <see cref="InputBinding"/>) and by controls 783 /// (see <see cref="InputControl"/>) to post-process input values as they are being requested 784 /// from calls such as <see cref="InputAction.ReadValue{TValue}"/> or <see 785 /// cref="InputControl{T}.ReadValue"/>. 786 /// 787 /// <example> 788 /// <code> 789 /// // Let's say that we want to define a processor that adds some random jitter to its input. 790 /// // We have to pick a value type to operate on if we want to derive from InputProcessor&lt;T&gt; 791 /// // so we go with float here. 792 /// // 793 /// // Also, as we will need to place our call to RegisterProcessor somewhere, we add attributes 794 /// // to hook into Unity's initialization. This works differently in the editor and in the player, 795 /// // so we use both [InitializeOnLoad] and [RuntimeInitializeOnLoadMethod]. 796 /// #if UNITY_EDITOR 797 /// [InitializeOnLoad] 798 /// #endif 799 /// public class JitterProcessor : InputProcessor&lt;float&gt; 800 /// { 801 /// // Add a parameter that defines the amount of jitter we apply. 802 /// // This will be editable in the Unity editor UI and can be set 803 /// // programmatically in code. For example: 804 /// // 805 /// // myAction.AddBinding("&lt;Gamepad&gt;/rightTrigger", 806 /// // processors: "jitter(amount=0.1)"); 807 /// // 808 /// [Tooltip("Amount of jitter to apply. Will add a random value in the range [-amount..amount] " 809 /// + "to each input value.)] 810 /// public float amount; 811 /// 812 /// // Process is called when an input value is read from a control. This is 813 /// // where we perform our jitter. 814 /// public override float Process(float value, InputControl control) 815 /// { 816 /// return float + Random.Range(-amount, amount); 817 /// } 818 /// 819 /// // [InitializeOnLoad] will call the static class constructor which 820 /// // we use to call Register. 821 /// #if UNITY_EDITOR 822 /// static JitterProcessor() 823 /// { 824 /// Register(); 825 /// } 826 /// #endif 827 /// 828 /// // [RuntimeInitializeOnLoadMethod] will make sure that Register gets called 829 /// // in the player on startup. 830 /// // NOTE: This will also get called when going into play mode in the editor. In that 831 /// // case we get two calls to Register instead of one. We don't bother with that 832 /// // here. Calling RegisterProcessor twice here doesn't do any harm. 833 /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] 834 /// static void Register() 835 /// { 836 /// // We don't supply a name here. The input system will take "JitterProcessor" 837 /// // and automatically snip off the "Processor" suffix thus leaving us with 838 /// // a name of "Jitter" (all this is case-insensitive). 839 /// InputSystem.RegisterProcessor&lt;JitterProcessor&gt;(); 840 /// } 841 /// } 842 /// 843 /// // It doesn't really make sense in our case as the default parameter editor is just 844 /// // fine (it will pick up the tooltip we defined above) but let's say we want to replace 845 /// // the default float edit field we get on the "amount" parameter with a slider. We can 846 /// // do so by defining a custom parameter editor. 847 /// // 848 /// // NOTE: We don't need to have a registration call here. The input system will automatically 849 /// // find our parameter editor based on the JitterProcessor type parameter we give to 850 /// // InputParameterEditor&lt;T&gt;. 851 /// #if UNITY_EDITOR 852 /// public class JitterProcessorEditor : InputParameterEditor&lt;JitterProcessor&gt; 853 /// { 854 /// public override void OnGUI() 855 /// { 856 /// target.amount = EditorGUILayout.Slider(m_AmountLabel, target.amount, 0, 0.25f); 857 /// } 858 /// 859 /// private GUIContent m_AmountLabel = new GUIContent("Amount", 860 /// "Amount of jitter to apply. Will add a random value in the range [-amount..amount] " 861 /// + "to each input value.); 862 /// } 863 /// #endif 864 /// </code> 865 /// </example> 866 /// 867 /// Note that it is allowed to register the same processor type multiple types with 868 /// different names. When doing so, the first registration is considered as the "proper" 869 /// name for the processor and all subsequent registrations will be considered aliases. 870 /// 871 /// See the <a href="../manual/Processors.html">manual</a> for more details. 872 /// </remarks> 873 /// <seealso cref="InputProcessor{T}"/> 874 /// <seealso cref="InputBinding.processors"/> 875 /// <seealso cref="InputAction.processors"/> 876 /// <seealso cref="InputControlLayout.ControlItem.processors"/> 877 /// <seealso cref="UnityEngine.InputSystem.Editor.InputParameterEditor{TObject}"/> 878 public static void RegisterProcessor(Type type, string name = null) 879 { 880 if (type == null) 881 throw new ArgumentNullException(nameof(type)); 882 883 // Default name to name of type without Processor suffix. 884 if (string.IsNullOrEmpty(name)) 885 { 886 name = type.Name; 887 if (name.EndsWith("Processor")) 888 name = name.Substring(0, name.Length - "Processor".Length); 889 } 890 891 // Flush out any precompiled layout depending on the processor. 892 var precompiledLayouts = s_Manager.m_Layouts.precompiledLayouts; 893 foreach (var key in new List<InternedString>(precompiledLayouts.Keys)) // Need to keep key list stable while iterating; ToList() for some reason not available with .NET Standard 2.0 on Mono. 894 { 895 if (StringHelpers.CharacterSeparatedListsHaveAtLeastOneCommonElement(precompiledLayouts[key].metadata, name, ';')) 896 s_Manager.m_Layouts.precompiledLayouts.Remove(key); 897 } 898 899 s_Manager.processors.AddTypeRegistration(name, type); 900 } 901 902 /// <summary> 903 /// Register an <see cref="InputProcessor{TValue}"/> with the system. 904 /// </summary> 905 /// <typeparam name="T">Type that implements <see cref="InputProcessor"/>.</typeparam> 906 /// <param name="name">Name to use for the processor. If <c>null</c> or empty, name will be taken from the short name 907 /// of <typeparamref name="T"/> (if it ends in "Processor", that suffix will be clipped from the name). Names 908 /// are case-insensitive.</param> 909 /// <remarks> 910 /// Processors are used by both bindings (see <see cref="InputBinding"/>) and by controls 911 /// (see <see cref="InputControl"/>) to post-process input values as they are being requested 912 /// from calls such as <see cref="InputAction.ReadValue{TValue}"/> or <see 913 /// cref="InputControl{T}.ReadValue"/>. 914 /// 915 /// <example> 916 /// <code> 917 /// // Let's say that we want to define a processor that adds some random jitter to its input. 918 /// // We have to pick a value type to operate on if we want to derive from InputProcessor&lt;T&gt; 919 /// // so we go with float here. 920 /// // 921 /// // Also, as we will need to place our call to RegisterProcessor somewhere, we add attributes 922 /// // to hook into Unity's initialization. This works differently in the editor and in the player, 923 /// // so we use both [InitializeOnLoad] and [RuntimeInitializeOnLoadMethod]. 924 /// #if UNITY_EDITOR 925 /// [InitializeOnLoad] 926 /// #endif 927 /// public class JitterProcessor : InputProcessor&lt;float&gt; 928 /// { 929 /// // Add a parameter that defines the amount of jitter we apply. 930 /// // This will be editable in the Unity editor UI and can be set 931 /// // programmatically in code. For example: 932 /// // 933 /// // myAction.AddBinding("&lt;Gamepad&gt;/rightTrigger", 934 /// // processors: "jitter(amount=0.1)"); 935 /// // 936 /// [Tooltip("Amount of jitter to apply. Will add a random value in the range [-amount..amount] " 937 /// + "to each input value.)] 938 /// public float amount; 939 /// 940 /// // Process is called when an input value is read from a control. This is 941 /// // where we perform our jitter. 942 /// public override float Process(float value, InputControl control) 943 /// { 944 /// return float + Random.Range(-amount, amount); 945 /// } 946 /// 947 /// // [InitializeOnLoad] will call the static class constructor which 948 /// // we use to call Register. 949 /// #if UNITY_EDITOR 950 /// static JitterProcessor() 951 /// { 952 /// Register(); 953 /// } 954 /// #endif 955 /// 956 /// // [RuntimeInitializeOnLoadMethod] will make sure that Register gets called 957 /// // in the player on startup. 958 /// // NOTE: This will also get called when going into play mode in the editor. In that 959 /// // case we get two calls to Register instead of one. We don't bother with that 960 /// // here. Calling RegisterProcessor twice here doesn't do any harm. 961 /// [RuntimeInitializeOnLoadMethod] 962 /// static void Register() 963 /// { 964 /// // We don't supply a name here. The input system will take "JitterProcessor" 965 /// // and automatically snip off the "Processor" suffix thus leaving us with 966 /// // a name of "Jitter" (all this is case-insensitive). 967 /// InputSystem.RegisterProcessor&lt;JitterProcessor&gt;(); 968 /// } 969 /// } 970 /// 971 /// // It doesn't really make sense in our case as the default parameter editor is just 972 /// // fine (it will pick up the tooltip we defined above) but let's say we want to replace 973 /// // the default float edit field we get on the "amount" parameter with a slider. We can 974 /// // do so by defining a custom parameter editor. 975 /// // 976 /// // NOTE: We don't need to have a registration call here. The input system will automatically 977 /// // find our parameter editor based on the JitterProcessor type parameter we give to 978 /// // InputParameterEditor&lt;T&gt;. 979 /// #if UNITY_EDITOR 980 /// public class JitterProcessorEditor : InputParameterEditor&lt;JitterProcessor&gt; 981 /// { 982 /// public override void OnGUI() 983 /// { 984 /// target.amount = EditorGUILayout.Slider(m_AmountLabel, target.amount, 0, 0.25f); 985 /// } 986 /// 987 /// private GUIContent m_AmountLabel = new GUIContent("Amount", 988 /// "Amount of jitter to apply. Will add a random value in the range [-amount..amount] " 989 /// + "to each input value.); 990 /// } 991 /// #endif 992 /// </code> 993 /// </example> 994 /// 995 /// Note that it is allowed to register the same processor type multiple types with 996 /// different names. When doing so, the first registration is considered as the "proper" 997 /// name for the processor and all subsequent registrations will be considered aliases. 998 /// 999 /// See the <a href="../manual/Processors.html">manual</a> for more details. 1000 /// </remarks> 1001 /// <seealso cref="InputProcessor{T}"/> 1002 /// <seealso cref="InputBinding.processors"/> 1003 /// <seealso cref="InputAction.processors"/> 1004 /// <seealso cref="InputControlLayout.ControlItem.processors"/> 1005 /// <seealso cref="UnityEngine.InputSystem.Editor.InputParameterEditor{TObject}"/> 1006 public static void RegisterProcessor<T>(string name = null) 1007 { 1008 RegisterProcessor(typeof(T), name); 1009 } 1010 1011 /// <summary> 1012 /// Return the processor type registered under the given name. If no such processor 1013 /// has been registered, return <c>null</c>. 1014 /// </summary> 1015 /// <param name="name">Name of processor. Case-insensitive.</param> 1016 /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c> or empty.</exception> 1017 /// <returns>The given processor type or <c>null</c> if not found.</returns> 1018 /// <seealso cref="RegisterProcessor{T}"/> 1019 public static Type TryGetProcessor(string name) 1020 { 1021 if (string.IsNullOrEmpty(name)) 1022 throw new ArgumentNullException(nameof(name)); 1023 return s_Manager.processors.LookupTypeRegistration(name); 1024 } 1025 1026 /// <summary> 1027 /// List the names of all processors have been registered. 1028 /// </summary> 1029 /// <returns>List of registered processors.</returns> 1030 /// <remarks> 1031 /// Note that the result will include both "proper" names and aliases registered 1032 /// for processors. If, for example, a given type <c>JitterProcessor</c> has been registered 1033 /// under both "Jitter" and "Randomize", it will appear in the list with both those names. 1034 /// </remarks> 1035 /// <seealso cref="TryGetProcessor"/> 1036 /// <seealso cref="RegisterProcessor{T}"/> 1037 public static IEnumerable<string> ListProcessors() 1038 { 1039 return s_Manager.processors.names; 1040 } 1041 1042 #endregion 1043 1044 #region Devices 1045 1046 /// <summary> 1047 /// The list of currently connected devices. 1048 /// </summary> 1049 /// <value>Currently connected devices.</value> 1050 /// <remarks> 1051 /// Note that accessing this property does not allocate. It gives read-only access 1052 /// directly to the system's internal array of devices. 1053 /// 1054 /// The value returned by this property should not be held on to. When the device 1055 /// setup in the system changes, any value previously returned by this property 1056 /// may become invalid. Query the property directly whenever you need it. 1057 /// </remarks> 1058 /// <seealso cref="AddDevice{TDevice}"/> 1059 /// <seealso cref="RemoveDevice"/> 1060 public static ReadOnlyArray<InputDevice> devices => s_Manager.devices; 1061 1062 /// <summary> 1063 /// Devices that have been disconnected but are retained by the input system in case 1064 /// they are plugged back in. 1065 /// </summary> 1066 /// <value>Devices that have been retained by the input system in case they are plugged 1067 /// back in.</value> 1068 /// <remarks> 1069 /// During gameplay it is undesirable to have the system allocate and release managed memory 1070 /// as devices are unplugged and plugged back in as it would ultimately lead to GC spikes 1071 /// during gameplay. To avoid that, input devices that have been reported by the <see cref="IInputRuntime"> 1072 /// runtime</see> and are removed through <see cref="DeviceRemoveEvent">events</see> are retained 1073 /// by the system and then reused if the device is plugged back in. 1074 /// 1075 /// Note that the devices moved to disconnected status will still see a <see cref="InputDeviceChange.Removed"/> 1076 /// notification and a <see cref="InputDeviceChange.Added"/> notification when plugged back in. 1077 /// 1078 /// To determine if a newly discovered device is one we have seen before, the system uses a 1079 /// simple approach of comparing <see cref="InputDeviceDescription">device descriptions</see>. 1080 /// Note that there can be errors and a device may be incorrectly classified as <see cref="InputDeviceChange.Reconnected"/> 1081 /// when in fact it is a different device from before. The problem is that based on information 1082 /// made available by platforms, it can be inherently difficult to determine whether a device is 1083 /// indeed the very same one. 1084 /// 1085 /// For example, it is often not possible to determine with 100% certainty whether an identical looking device 1086 /// to one we've previously seen on a different USB port is indeed the very same device. OSs will usually 1087 /// reattach a USB device to its previous instance if it is plugged into the same USB port but create a 1088 /// new instance of the same device is plugged into a different port. 1089 /// 1090 /// For devices that do relay their <see cref="InputDeviceDescription.serial">serials</see> the matching 1091 /// is reliable. 1092 /// 1093 /// The list can be purged by calling <see cref="FlushDisconnectedDevices"/>. Doing so, will release 1094 /// all reference we hold to the devices or any controls inside of them and allow the devices to be 1095 /// reclaimed by the garbage collector. 1096 /// 1097 /// Note that if you call <see cref="RemoveDevice"/> explicitly, the given device is not retained 1098 /// by the input system and will not appear on this list. 1099 /// 1100 /// Also note that devices on this list will be lost when domain reloads happen in the editor (i.e. on 1101 /// script recompilation and when entering play mode). 1102 /// </remarks> 1103 /// <seealso cref="FlushDisconnectedDevices"/> 1104 public static ReadOnlyArray<InputDevice> disconnectedDevices => 1105 new ReadOnlyArray<InputDevice>(s_Manager.m_DisconnectedDevices, 0, 1106 s_Manager.m_DisconnectedDevicesCount); 1107 1108 /// <summary> 1109 /// Event that is signalled when the device setup in the system changes. 1110 /// </summary> 1111 /// <value>Callback when device setup ni system changes.</value> 1112 /// <remarks> 1113 /// This can be used to detect when devices are added or removed as well as 1114 /// detecting when existing devices change their configuration. 1115 /// 1116 /// <example> 1117 /// <code> 1118 /// InputSystem.onDeviceChange += 1119 /// (device, change) => 1120 /// { 1121 /// switch (change) 1122 /// { 1123 /// case InputDeviceChange.Added: 1124 /// Debug.Log("Device added: " + device); 1125 /// break; 1126 /// case InputDeviceChange.Removed: 1127 /// Debug.Log("Device removed: " + device); 1128 /// break; 1129 /// case InputDeviceChange.ConfigurationChanged: 1130 /// Debug.Log("Device configuration changed: " + device); 1131 /// break; 1132 /// } 1133 /// }; 1134 /// </code> 1135 /// </example> 1136 /// </remarks> 1137 /// <exception cref="ArgumentNullException">Delegate reference is <c>null</c>.</exception> 1138 /// <seealso cref="devices"/> 1139 /// <seealso cref="AddDevice{TDevice}"/> 1140 /// <seealso cref="RemoveDevice"/> 1141 public static event Action<InputDevice, InputDeviceChange> onDeviceChange 1142 { 1143 add 1144 { 1145 if (value == null) 1146 throw new ArgumentNullException(nameof(value)); 1147 lock (s_Manager) 1148 s_Manager.onDeviceChange += value; 1149 } 1150 remove 1151 { 1152 if (value == null) 1153 throw new ArgumentNullException(nameof(value)); 1154 lock (s_Manager) 1155 s_Manager.onDeviceChange -= value; 1156 } 1157 } 1158 1159 ////REVIEW: this one isn't really well-designed and the means of intercepting communication 1160 //// with the backend should be revisited >1.0 1161 /// <summary> 1162 /// Event that is signalled when an <see cref="InputDeviceCommand"/> is sent to 1163 /// an <see cref="InputDevice"/>. 1164 /// </summary> 1165 /// <value>Event that gets signalled on <see cref="InputDeviceCommand"/>s.</value> 1166 /// <remarks> 1167 /// This can be used to intercept commands and optionally handle them without them reaching 1168 /// the <see cref="IInputRuntime"/>. 1169 /// 1170 /// The first delegate in the list that returns a result other than <c>null</c> is considered 1171 /// to have handled the command. If a command is handled by a delegate in the list, it will 1172 /// not be sent on to the runtime. 1173 /// </remarks> 1174 /// <exception cref="ArgumentNullException">Delegate reference is <c>null</c>.</exception> 1175 /// <seealso cref="InputDevice.ExecuteCommand{TCommand}"/> 1176 /// <seealso cref="IInputRuntime.DeviceCommand"/> 1177 public static event InputDeviceCommandDelegate onDeviceCommand 1178 { 1179 add 1180 { 1181 if (value == null) 1182 throw new ArgumentNullException(nameof(value)); 1183 lock (s_Manager) 1184 s_Manager.onDeviceCommand += value; 1185 } 1186 remove 1187 { 1188 if (value == null) 1189 throw new ArgumentNullException(nameof(value)); 1190 lock (s_Manager) 1191 s_Manager.onDeviceCommand -= value; 1192 } 1193 } 1194 1195 /// <summary> 1196 /// Event that is signalled when the system is trying to match a layout to 1197 /// a device it has discovered. 1198 /// </summary> 1199 /// <remarks> 1200 /// This event allows customizing the layout discovery process and to generate 1201 /// layouts on the fly, if need be. When a device is reported from the Unity 1202 /// runtime or through <see cref="AddDevice(InputDeviceDescription)"/>, it is 1203 /// reported in the form of an <see cref="InputDeviceDescription"/>. The system 1204 /// will take that description and run it through all the <see cref="InputDeviceMatcher"/>s 1205 /// that have been registered for layouts (<see cref="RegisterLayoutMatcher{TDevice}"/>). 1206 /// Based on that, it will come up with either no matching layout or with a single 1207 /// layout that has the highest matching score according to <see 1208 /// cref="InputDeviceMatcher.MatchPercentage"/> (or, in case multiple layouts have 1209 /// the same score, the first one to achieve that score -- which is quasi-non-deterministic). 1210 /// 1211 /// It will then take this layout name (which, again, may be empty) and invoke this 1212 /// event here passing it not only the layout name but also information such as the 1213 /// <see cref="InputDeviceDescription"/> for the device. Each of the callbacks hooked 1214 /// into the event will be run in turn. The <em>first</em> one to return a string 1215 /// that is not <c>null</c> and not empty will cause a switch from the layout the 1216 /// system has chosen to the layout that has been returned by the callback. The remaining 1217 /// layouts after that will then be invoked with that newly selected name but will not 1218 /// be able to change the name anymore. 1219 /// 1220 /// If none of the callbacks returns a string that is not <c>null</c> or empty, 1221 /// the system will stick with the layout that it had initially selected. 1222 /// 1223 /// Once all callbacks have been run, the system will either have a final layout 1224 /// name or not. If it does, a device is created using that layout. If it does not, 1225 /// no device is created. 1226 /// 1227 /// One thing this allows is to generate callbacks on the fly. Let's say that if 1228 /// an input device is reported with the "Custom" interface, we want to generate 1229 /// a layout for it on the fly. For details about how to build layouts dynamically 1230 /// from code, see <see cref="InputControlLayout.Builder"/> and <see cref="RegisterLayoutBuilder"/>. 1231 /// 1232 /// <example> 1233 /// <code> 1234 /// InputSystem.onFindLayoutForDevice += 1235 /// (deviceId, description, matchedLayout, runtime) => 1236 /// { 1237 /// // If the system does have a matching layout, we do nothing. 1238 /// // This could be the case, for example, if we already generated 1239 /// // a layout for the device or if someone explicitly registered 1240 /// // a layout. 1241 /// if (!string.IsNullOrEmpty(matchedLayout)) 1242 /// return null; // Tell system we did nothing. 1243 /// 1244 /// // See if the reported device uses the "Custom" interface. We 1245 /// // are only interested in those. 1246 /// if (description.interfaceName != "Custom") 1247 /// return null; // Tell system we did nothing. 1248 /// 1249 /// // So now we know that we want to build a layout on the fly 1250 /// // for this device. What we do is to register what's called a 1251 /// // layout builder. These can use C# code to build an InputControlLayout 1252 /// // on the fly. 1253 /// 1254 /// // First we need to come up with a sufficiently unique name for the layout 1255 /// // under which we register the builder. This will usually involve some 1256 /// // information from the InputDeviceDescription we have been supplied with. 1257 /// // Let's say we can sufficiently tell devices on our interface apart by 1258 /// // product name alone. So we just do this: 1259 /// var layoutName = "Custom" + description.product; 1260 /// 1261 /// // We also need an InputDeviceMatcher that in the future will automatically 1262 /// // select our newly registered layout whenever a new device of the same type 1263 /// // is connected. We can get one simply like so: 1264 /// var matcher = InputDeviceMatcher.FromDescription(description); 1265 /// 1266 /// // With these pieces in place, we can register our builder which 1267 /// // mainly consists of a delegate that will get invoked when an instance 1268 /// // of InputControlLayout is needed for the layout. 1269 /// InputSystem.RegisterLayoutBuilder( 1270 /// () => 1271 /// { 1272 /// // Here is where we do the actual building. In practice, 1273 /// // this would probably look at the 'capabilities' property 1274 /// // of the InputDeviceDescription we got and create a tailor-made 1275 /// // layout. But what you put in the layout here really depends on 1276 /// // the specific use case you have. 1277 /// // 1278 /// // We just add some preset things here which should still sufficiently 1279 /// // serve as a demonstration. 1280 /// // 1281 /// // Note that we can base our layout here on whatever other layout 1282 /// // in the system. We could extend Gamepad, for example. If we don't 1283 /// // choose a base layout, the system automatically implies InputDevice. 1284 /// 1285 /// var builder = new InputControlLayout.Builder() 1286 /// .WithDisplayName(description.product); 1287 /// 1288 /// // Add controls. 1289 /// builder.AddControl("stick") 1290 /// .WithLayout("Stick"); 1291 /// 1292 /// return builder.Build(); 1293 /// }, 1294 /// layoutName, 1295 /// matches: matcher); 1296 /// 1297 /// // So, we want the system to use our layout for the device that has just 1298 /// // been connected. We return it from this callback to do that. 1299 /// return layoutName; 1300 /// }; 1301 /// </code> 1302 /// </example> 1303 /// 1304 /// Note that it may appear like one could simply use <see cref="RegisterLayoutBuilder"/> 1305 /// like below instead of going through <c>onFindLayoutForDevice</c>. 1306 /// 1307 /// <example> 1308 /// <code> 1309 /// InputSystem.RegisterLayoutBuilder( 1310 /// () => 1311 /// { 1312 /// // Layout building code from above... 1313 /// }, 1314 /// "CustomLayout", 1315 /// matches: new InputDeviceMatcher().WithInterface("Custom")); 1316 /// </code> 1317 /// </example> 1318 /// 1319 /// However, the difference here is that all devices using the "Custom" interface will 1320 /// end up with the same single layout -- which has to be identical. By hooking into 1321 /// <c>onFindLayoutForDevice</c>, it is possible to register a new layout for every new 1322 /// type of device that is discovered and thus build a multitude of different layouts. 1323 /// 1324 /// It is best to register for this callback during startup. One way to do it is to 1325 /// use <c>InitializeOnLoadAttribute</c> and <c>RuntimeInitializeOnLoadMethod</c>. 1326 /// </remarks> 1327 /// <seealso cref="RegisterLayoutBuilder"/> 1328 /// <seealso cref="InputControlLayout"/> 1329 public static event InputDeviceFindControlLayoutDelegate onFindLayoutForDevice 1330 { 1331 add 1332 { 1333 lock (s_Manager) 1334 s_Manager.onFindControlLayoutForDevice += value; 1335 } 1336 remove 1337 { 1338 lock (s_Manager) 1339 s_Manager.onFindControlLayoutForDevice -= value; 1340 } 1341 } 1342 1343 ////REVIEW: should this be disambiguated more to separate it more from sensor sampling frequency? 1344 ////REVIEW: this should probably be exposed as an input setting 1345 /// <summary> 1346 /// Frequency at which devices that need polling are being queried in the background. 1347 /// </summary> 1348 /// <value>Polled device sampling frequency in Hertz.</value> 1349 /// <remarks> 1350 /// Input data is gathered from platform APIs either as events or polled periodically. 1351 /// 1352 /// In the former case, where we get input as events, the platform is responsible for monitoring 1353 /// input devices and sending their state changes which the Unity runtime receives 1354 /// and queues as <see cref="InputEvent"/>s. This form of input collection usually happens on a 1355 /// system-specific thread (which may be Unity's main thread) as part of how the Unity player 1356 /// loop operates. In most cases, this means that this form of input will invariably get picked up 1357 /// once per frame. 1358 /// 1359 /// In the latter case, where input has to be explicitly polled from the system, the Unity runtime 1360 /// will periodically sample the state of input devices and send it off as input events. Wherever 1361 /// possible, this happens in the background at a fixed frequency on a dedicated thread. The 1362 /// <c>pollingFrequency</c> property controls the rate at which this sampling happens. 1363 /// 1364 /// The unit is Hertz. A value of 120, for example, means that devices are sampled 120 times 1365 /// per second. 1366 /// 1367 /// The default polling frequency is 60 Hz. 1368 /// 1369 /// For devices that are polled, the frequency setting will directly translate to changes in the 1370 /// <see cref="InputEvent.time"/> patterns. At 60 Hz, for example, timestamps for a specific, 1371 /// polled device will be spaced at roughly 1/60th of a second apart. 1372 /// 1373 /// Note that it depends on the platform which devices are polled (if any). On Win32, for example, 1374 /// only XInput gamepads are polled. 1375 /// 1376 /// Also note that the polling frequency applies to all devices that are polled. It is not possible 1377 /// to set polling frequency on a per-device basis. 1378 /// </remarks> 1379 public static float pollingFrequency 1380 { 1381 get => s_Manager.pollingFrequency; 1382 set => s_Manager.pollingFrequency = value; 1383 } 1384 1385 /// <summary> 1386 /// Add a new device by instantiating the given device layout. 1387 /// </summary> 1388 /// <param name="layout">Name of the layout to instantiate. Must be a device layout. Note that 1389 /// layout names are case-insensitive.</param> 1390 /// <param name="name">Name to assign to the device. If null, the layout's display name (<see 1391 /// cref="InputControlLayout.displayName"/> is used instead. Note that device names are made 1392 /// unique automatically by the system by appending numbers to them (e.g. "gamepad", "gamepad1", 1393 /// "gamepad2", etc.).</param> 1394 /// <param name="variants">Semicolon-separated list of layout variants to use for the device.</param> 1395 /// <exception cref="ArgumentNullException"><paramref name="layout"/> is <c>null</c> or empty.</exception> 1396 /// <returns>The newly created input device.</returns> 1397 /// <remarks> 1398 /// The device will be added to the <see cref="devices"/> list and a notification on 1399 /// <see cref="onDeviceChange"/> will be triggered. 1400 /// 1401 /// Note that adding a device to the system will allocate and also create garbage on the GC heap. 1402 /// 1403 /// <example> 1404 /// <code> 1405 /// // This is one way to instantiate the "Gamepad" layout. 1406 /// InputSystem.AddDevice("Gamepad"); 1407 /// 1408 /// // In this case, because the "Gamepad" layout is based on the Gamepad 1409 /// // class, we can also do this instead: 1410 /// InputSystem.AddDevice&lt;Gamepad&gt;(); 1411 /// </code> 1412 /// </example> 1413 /// </remarks> 1414 /// <seealso cref="AddDevice{T}"/> 1415 /// <seealso cref="RemoveDevice"/> 1416 /// <seealso cref="onDeviceChange"/> 1417 /// <seealso cref="InputDeviceChange.Added"/> 1418 /// <seealso cref="devices"/> 1419 /// <seealso cref="RegisterLayout(Type,string,Nullable{InputDeviceMatcher})"/> 1420 public static InputDevice AddDevice(string layout, string name = null, string variants = null) 1421 { 1422 if (string.IsNullOrEmpty(layout)) 1423 throw new ArgumentNullException(nameof(layout)); 1424 return s_Manager.AddDevice(layout, name, new InternedString(variants)); 1425 } 1426 1427 /// <summary> 1428 /// Add a new device by instantiating the layout registered for type <typeparamref name="TDevice"/>. 1429 /// </summary> 1430 /// <param name="name">Name to assign to the device. If null, the layout's display name (<see 1431 /// cref="InputControlLayout.displayName"/> is used instead. Note that device names are made 1432 /// unique automatically by the system by appending numbers to them (e.g. "gamepad", "gamepad1", 1433 /// "gamepad2", etc.).</param> 1434 /// <typeparam name="TDevice">Type of device to add.</typeparam> 1435 /// <returns>The newly added device.</returns> 1436 /// <exception cref="InvalidOperationException">Instantiating the layout for <typeparamref name="TDevice"/> 1437 /// did not produce a device of type <typeparamref name="TDevice"/>.</exception> 1438 /// <remarks> 1439 /// The device will be added to the <see cref="devices"/> list and a notification on 1440 /// <see cref="onDeviceChange"/> will be triggered. 1441 /// 1442 /// Note that adding a device to the system will allocate and also create garbage on the GC heap. 1443 /// 1444 /// <example> 1445 /// <code> 1446 /// // Add a gamepad. 1447 /// InputSystem.AddDevice&lt;Gamepad&gt;(); 1448 /// </code> 1449 /// </example> 1450 /// </remarks> 1451 /// <seealso cref="RemoveDevice"/> 1452 /// <seealso cref="onDeviceChange"/> 1453 /// <seealso cref="InputDeviceChange.Added"/> 1454 /// <seealso cref="devices"/> 1455 public static TDevice AddDevice<TDevice>(string name = null) 1456 where TDevice : InputDevice 1457 { 1458 var device = s_Manager.AddDevice(typeof(TDevice), name); 1459 if (!(device is TDevice deviceOfType)) 1460 { 1461 // Consider the entire operation as failed, so remove the device we just added. 1462 if (device != null) 1463 RemoveDevice(device); 1464 throw new InvalidOperationException( 1465 $"Layout registered for type '{typeof(TDevice).Name}' did not produce a device of that type; layout probably has been overridden"); 1466 } 1467 return deviceOfType; 1468 } 1469 1470 /// <summary> 1471 /// Tell the input system that a new device has become available. 1472 /// </summary> 1473 /// <param name="description">Description of the input device.</param> 1474 /// <returns>The newly created device that has been added to <see cref="devices"/>.</returns> 1475 /// <exception cref="ArgumentException">The given <paramref name="description"/> is empty -or- 1476 /// no layout can be found that matches the given device <paramref name="description"/>.</exception> 1477 /// <remarks> 1478 /// This method is different from methods such as <see cref="AddDevice(string,string,string)"/> 1479 /// or <see cref="AddDevice{TDevice}"/> in that it employs the usual matching process the 1480 /// same way that it happens when the Unity runtime reports an input device. 1481 /// 1482 /// In particular, the same procedure described in the documentation for <see cref="onFindLayoutForDevice"/> 1483 /// is employed where all registered <see cref="InputDeviceMatcher"/>s are matched against the 1484 /// supplied device description and the most suitable match determines the layout to use. This in 1485 /// turn is run through <see cref="onFindLayoutForDevice"/> to determine the final layout to use. 1486 /// 1487 /// If no suitable layout can be found, the method throws <c>ArgumentException</c>. 1488 /// <example> 1489 /// <code> 1490 /// InputSystem.AddDevice( 1491 /// new InputDeviceDescription 1492 /// { 1493 /// interfaceName = "Custom", 1494 /// product = "Product" 1495 /// }); 1496 /// </code> 1497 /// </example> 1498 /// </remarks> 1499 public static InputDevice AddDevice(InputDeviceDescription description) 1500 { 1501 if (description.empty) 1502 throw new ArgumentException("Description must not be empty", nameof(description)); 1503 return s_Manager.AddDevice(description); 1504 } 1505 1506 /// <summary> 1507 /// Add the given device back to the system. 1508 /// </summary> 1509 /// <param name="device">An input device. If the device is currently already added to 1510 /// the system (i.e. is in <see cref="devices"/>), the method will do nothing.</param> 1511 /// <exception cref="ArgumentNullException"></exception> 1512 /// <remarks> 1513 /// This can be used when a device has been manually removed with <see cref="RemoveDevice"/>. 1514 /// 1515 /// The device will be added to the <see cref="devices"/> list and a notification on 1516 /// <see cref="onDeviceChange"/> will be triggered. 1517 /// 1518 /// It may be tempting to do the following but this will not work: 1519 /// 1520 /// <example> 1521 /// <code> 1522 /// // This will *NOT* work. 1523 /// var device = new Gamepad(); 1524 /// InputSystem.AddDevice(device); 1525 /// </code> 1526 /// </example> 1527 /// 1528 /// <see cref="InputDevice"/>s, like <see cref="InputControl"/>s in general, cannot 1529 /// simply be instantiated with <c>new</c> but must be created by the input system 1530 /// instead. 1531 /// </remarks> 1532 /// <seealso cref="RemoveDevice"/> 1533 /// <seealso cref="AddDevice{TDevice}"/> 1534 /// <seealso cref="devices"/> 1535 public static void AddDevice(InputDevice device) 1536 { 1537 if (device == null) 1538 throw new ArgumentNullException(nameof(device)); 1539 s_Manager.AddDevice(device); 1540 } 1541 1542 /// <summary> 1543 /// Remove a device from the system such that it no longer receives input and is no longer part of the 1544 /// set of devices in <see cref="devices"/>. 1545 /// </summary> 1546 /// <param name="device">Device to remove. If the device has already been removed (i.e. if <see cref="InputDevice.added"/> 1547 /// is false), the method does nothing.</param> 1548 /// <remarks> 1549 /// Actions that are bound to controls on the device will automatically unbind when the device 1550 /// is removed. 1551 /// 1552 /// When a device is removed, <see cref="onDeviceChange"/> will be triggered with <see cref="InputDeviceChange.Removed"/>. 1553 /// The device will be removed from <see cref="devices"/> as well as from any device-specific getters such as 1554 /// <see cref="Gamepad.all"/>. 1555 /// </remarks> 1556 /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception> 1557 /// <seealso cref="InputDevice.added"/> 1558 public static void RemoveDevice(InputDevice device) 1559 { 1560 s_Manager.RemoveDevice(device); 1561 } 1562 1563 /// <summary> 1564 /// Purge all disconnected devices from <see cref="disconnectedDevices"/>. 1565 /// </summary> 1566 /// <remarks> 1567 /// This will release all references held on to for these devices or any of their controls and will 1568 /// allow the devices to be reclaimed by the garbage collector. 1569 /// </remarks> 1570 /// <seealso cref="disconnectedDevices"/> 1571 public static void FlushDisconnectedDevices() 1572 { 1573 s_Manager.FlushDisconnectedDevices(); 1574 } 1575 1576 /// <summary> 1577 /// Return the device with given name or layout <param name="nameOrLayout"/>. 1578 /// Returns null if no such device currently exists. 1579 /// </summary> 1580 /// <param name="nameOrLayout">Unique device name or layout to search for.</param> 1581 /// <returns>The device matching the given search criteria or null.</returns> 1582 /// <seealso cref="GetDevice(Type)"/> 1583 /// <seealso cref="GetDevice{TDevice}"/> 1584 /// <seealso cref="AddDevice{TDevice}"/> 1585 public static InputDevice GetDevice(string nameOrLayout) 1586 { 1587 return s_Manager.TryGetDevice(nameOrLayout); 1588 } 1589 1590 ////REVIEW: this API seems inconsistent with GetDevice(string); both have very different meaning yet very similar signatures 1591 /// <summary> 1592 /// Return the most recently used device that is assignable to the given type <typeparamref name="TDevice"/>. 1593 /// Returns null if no such device currently exists. 1594 /// </summary> 1595 /// <typeparam name="TDevice">Type of device to look for.</typeparam> 1596 /// <returns>The device that is assignable to the given type or null.</returns> 1597 /// <seealso cref="GetDevice(string)"/> 1598 /// <seealso cref="GetDevice(Type)"/> 1599 public static TDevice GetDevice<TDevice>() 1600 where TDevice : InputDevice 1601 { 1602 return (TDevice)GetDevice(typeof(TDevice)); 1603 } 1604 1605 ////REVIEW: this API seems inconsistent with GetDevice(string); both have very different meaning yet very similar signatures 1606 /// <summary> 1607 /// Return the most recently used device that is assignable to the given type <param name="type"/>. 1608 /// Returns null if no such device currently exists. 1609 /// </summary> 1610 /// <param name="type">Type of the device</param> 1611 /// <returns>The device that is assignable to the given type or null.</returns> 1612 /// <seealso cref="GetDevice(string)"/> 1613 /// <seealso cref="GetDevice&lt;TDevice&gt;()"/> 1614 public static InputDevice GetDevice(Type type) 1615 { 1616 InputDevice result = null; 1617 var lastUpdateTime = -1.0; 1618 foreach (var device in devices) 1619 { 1620 if (!type.IsInstanceOfType(device)) 1621 continue; 1622 1623 if (result == null || device.m_LastUpdateTimeInternal > lastUpdateTime) 1624 { 1625 result = device; 1626 lastUpdateTime = result.m_LastUpdateTimeInternal; 1627 } 1628 } 1629 1630 return result; 1631 } 1632 1633 ////REVIEW: this API seems inconsistent with GetDevice(string); both have very different meaning yet very similar signatures 1634 /// <summary> 1635 /// Return the device of the given type <typeparamref name="TDevice"/> that has the 1636 /// given usage assigned. Returns null if no such device currently exists. 1637 /// </summary> 1638 /// <param name="usage">Usage of the device, e.g. "LeftHand".</param> 1639 /// <typeparam name="TDevice">Type of device to look for.</typeparam> 1640 /// <returns>The device with the given type and usage or null.</returns> 1641 /// <remarks> 1642 /// Devices usages are most commonly employed to "tag" devices for a specific role. 1643 /// A common scenario, for example, is to distinguish which hand a specific <see cref="XR.XRController"/> 1644 /// is associated with. However, arbitrary usages can be assigned to devices. 1645 /// <example> 1646 /// <code> 1647 /// // Get the left hand XRController. 1648 /// var leftHand = InputSystem.GetDevice&lt;XRController&gt;(CommonUsages.leftHand); 1649 /// 1650 /// // Mark gamepad #2 as being for player 1. 1651 /// InputSystem.SetDeviceUsage(Gamepad.all[1], "Player1"); 1652 /// // And later look it up. 1653 /// var player1Gamepad = InputSystem.GetDevice&lt;Gamepad&gt;(new InternedString("Player1")); 1654 /// </code> 1655 /// </example> 1656 /// </remarks> 1657 /// <seealso cref="GetDevice(string)"/> 1658 /// <seealso cref="SetDeviceUsage(InputDevice,string)"/> 1659 /// <seealso cref="InputControl.usages"/> 1660 public static TDevice GetDevice<TDevice>(InternedString usage) 1661 where TDevice : InputDevice 1662 { 1663 TDevice result = null; 1664 var lastUpdateTime = -1.0; 1665 foreach (var device in devices) 1666 { 1667 var deviceOfType = device as TDevice; 1668 if (deviceOfType == null) 1669 continue; 1670 if (!deviceOfType.usages.Contains(usage)) 1671 continue; 1672 1673 if (result == null || deviceOfType.m_LastUpdateTimeInternal > lastUpdateTime) 1674 { 1675 result = deviceOfType; 1676 lastUpdateTime = result.m_LastUpdateTimeInternal; 1677 } 1678 } 1679 1680 return result; 1681 } 1682 1683 /// <summary> 1684 /// Return the device of the given type <typeparamref name="TDevice"/> that has the 1685 /// given usage assigned. Returns null if no such device currently exists. 1686 /// </summary> 1687 /// <param name="usage">Usage of the device, e.g. "LeftHand".</param> 1688 /// <typeparam name="TDevice">Type of device to look for.</typeparam> 1689 /// <returns>The device with the given type and usage or null.</returns> 1690 /// <remarks> 1691 /// Devices usages are most commonly employed to "tag" devices for a specific role. 1692 /// A common scenario, for example, is to distinguish which hand a specific <see cref="XR.XRController"/> 1693 /// is associated with. However, arbitrary usages can be assigned to devices. 1694 /// </remarks> 1695 /// <seealso cref="GetDevice(InternedString)"/> 1696 /// <seealso cref="SetDeviceUsage(InputDevice,string)"/> 1697 /// <seealso cref="InputControl.usages"/> 1698 public static TDevice GetDevice<TDevice>(string usage) 1699 where TDevice : InputDevice 1700 { 1701 return GetDevice<TDevice>(new InternedString(usage)); 1702 } 1703 1704 /// <summary> 1705 /// Look up a device by its unique ID. 1706 /// </summary> 1707 /// <param name="deviceId">Unique ID of device. Such as given by <see cref="InputEvent.deviceId"/>.</param> 1708 /// <returns>The device for the given ID or null if no device with the given ID exists (or no longer exists).</returns> 1709 /// <remarks> 1710 /// Device IDs are not reused in a given session of the application (or Unity editor). 1711 /// </remarks> 1712 /// <seealso cref="InputEvent.deviceId"/> 1713 /// <seealso cref="InputDevice.deviceId"/> 1714 /// <seealso cref="IInputRuntime.AllocateDeviceId"/> 1715 public static InputDevice GetDeviceById(int deviceId) 1716 { 1717 return s_Manager.TryGetDeviceById(deviceId); 1718 } 1719 1720 /// <summary> 1721 /// Return the list of devices that have been reported by the <see cref="IInputRuntime">runtime</see> 1722 /// but could not be matched to any known <see cref="InputControlLayout">layout</see>. 1723 /// </summary> 1724 /// <returns>A list of descriptions of devices that could not be recognized.</returns> 1725 /// <remarks> 1726 /// If new layouts are added to the system or if additional <see cref="InputDeviceMatcher">matches</see> 1727 /// are added to existing layouts, devices in this list may appear or disappear. 1728 /// </remarks> 1729 /// <seealso cref="InputDeviceMatcher"/> 1730 /// <seealso cref="RegisterLayoutMatcher"/> 1731 public static List<InputDeviceDescription> GetUnsupportedDevices() 1732 { 1733 var list = new List<InputDeviceDescription>(); 1734 GetUnsupportedDevices(list); 1735 return list; 1736 } 1737 1738 /// <summary> 1739 /// Populate a list of devices that have been reported by the <see cref="IInputRuntime">runtime</see> 1740 /// but could not be matched to any known <see cref="InputControlLayout">layout</see>. 1741 /// </summary> 1742 /// <param name="descriptions">A list to be populated with descriptions of devices that could not be recognized.</param> 1743 /// <returns>The number of devices that could not be recognized.</returns> 1744 /// <remarks> 1745 /// If new layouts are added to the system or if additional <see cref="InputDeviceMatcher">matches</see> 1746 /// are added to existing layouts, devices in this list may appear or disappear. 1747 /// </remarks> 1748 /// <seealso cref="InputDeviceMatcher"/> 1749 /// <seealso cref="RegisterLayoutMatcher"/> 1750 public static int GetUnsupportedDevices(List<InputDeviceDescription> descriptions) 1751 { 1752 return s_Manager.GetUnsupportedDevices(descriptions); 1753 } 1754 1755 /// <summary> 1756 /// (Re-)enable the given device. 1757 /// </summary> 1758 /// <param name="device">Device to enable. If already enabled, the method will do nothing.</param> 1759 /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception> 1760 /// <remarks> 1761 /// This can be used after a device has been disabled with <see cref="DisableDevice"/> or 1762 /// with devices that start out in disabled state (usually the case for all <see cref="Sensor"/> 1763 /// devices). 1764 /// 1765 /// When enabled, a device will receive input when available. 1766 /// 1767 /// <example> 1768 /// <code> 1769 /// // Enable the gyroscope, if present. 1770 /// if (Gyroscope.current != null) 1771 /// InputSystem.EnableDevice(Gyroscope.current); 1772 /// </code> 1773 /// </example> 1774 /// </remarks> 1775 /// <seealso cref="DisableDevice"/> 1776 /// <seealso cref="InputDevice.enabled"/> 1777 public static void EnableDevice(InputDevice device) 1778 { 1779 s_Manager.EnableOrDisableDevice(device, true); 1780 } 1781 1782 /// <summary> 1783 /// Disable the given device, i.e. "mute" it. 1784 /// </summary> 1785 /// <param name="device">Device to disable. If already disabled, the method will do nothing.</param> 1786 /// <param name="keepSendingEvents">If true, no <see cref="LowLevel.DisableDeviceCommand"/> will be sent 1787 /// for the device. This means that the backend sending input events will not be notified about the device 1788 /// being disabled and will thus keep sending events. This can be useful when input is being rerouted from 1789 /// one device to another. For example, <see cref="TouchSimulation"/> uses this to disable the <see cref="Mouse"/> 1790 /// while redirecting its events to input on a <see cref="Touchscreen"/>.<br/><br/>This parameter is false by default.</param> 1791 /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception> 1792 /// <remarks> 1793 /// A disabled device will not receive input and will remain in its default state. It will remain 1794 /// present in the system but without actually feeding input into it. 1795 /// 1796 /// Disabling devices is most useful for <see cref="Sensor"/> devices on battery-powered platforms 1797 /// where having a sensor enabled will increase energy consumption. Sensors will usually start 1798 /// out in disabled state and can be enabled, when needed, with <see cref="EnableDevice"/> and 1799 /// disabled again wth this method. 1800 /// 1801 /// However, disabling a device can be useful in other situations, too. For example, when simulating 1802 /// input (say, mouse input) locally from a remote source, it can be desirable to turn off the respective 1803 /// local device. 1804 /// 1805 /// To remove a device altogether, use <see cref="RemoveDevice"/> instead. This will not only silence 1806 /// input but remove the <see cref="InputDevice"/> instance from the system altogether. 1807 /// </remarks> 1808 /// <seealso cref="EnableDevice"/> 1809 /// <seealso cref="InputDevice.enabled"/> 1810 public static void DisableDevice(InputDevice device, bool keepSendingEvents = false) 1811 { 1812 s_Manager.EnableOrDisableDevice(device, false, keepSendingEvents ? InputManager.DeviceDisableScope.InFrontendOnly : default); 1813 } 1814 1815 /// <summary> 1816 /// Issue a <see cref="RequestSyncCommand"/> on <paramref name="device"/>. This requests the device to 1817 /// send its current state as an event. If successful, the device will be updated in the next <see cref="InputSystem.Update"/>. 1818 /// </summary> 1819 /// <param name="device">An <see cref="InputDevice"/> that is currently part of <see cref="devices"/>.</param> 1820 /// <returns>True if the request succeeded, false if it fails.</returns> 1821 /// <remarks> 1822 /// It depends on the backend/platform implementation whether explicit synchronization is supported. If it is, the method 1823 /// will return true. If it is not, the method will return false and the request is ignored. 1824 /// </remarks> 1825 /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception> 1826 /// <exception cref="InvalidOperationException"><paramref name="device"/> has not been <see cref="InputDevice.added"/>.</exception> 1827 /// <seealso cref="RequestSyncCommand"/> 1828 /// <seealso cref="ResetDevice"/> 1829 public static bool TrySyncDevice(InputDevice device) 1830 { 1831 if (device == null) 1832 throw new ArgumentNullException(nameof(device)); 1833 if (!device.added) 1834 throw new InvalidOperationException($"Device '{device}' has not been added"); 1835 return device.RequestSync(); 1836 } 1837 1838 /// <summary> 1839 /// Reset the state of the given device. 1840 /// </summary> 1841 /// <param name="device">Device to reset. Must be <see cref="InputDevice.added"/> to the system.</param> 1842 /// <param name="alsoResetDontResetControls">If true, also reset controls that are marked as <see cref="InputControlAttribute.dontReset"/>. 1843 /// Leads to <see cref="InputDeviceChange.HardReset"/>.</param> 1844 /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception> 1845 /// <exception cref="InvalidOperationException"><paramref name="device"/> has not been <see cref="InputDevice.added"/>.</exception> 1846 /// <remarks> 1847 /// There are two different kinds of resets performed by the input system: a "soft" reset and a "hard" reset. 1848 /// 1849 /// A "hard" reset resets all controls on the device to their default state and also sends a <see cref="RequestResetCommand"/> 1850 /// to the backend, instructing to also reset its own internal state (if any) to the default. 1851 /// 1852 /// A "soft" reset will reset only controls that are not marked as <see cref="InputControlAttribute.noisy"/> and not marked as 1853 /// <see cref="InputControlAttribute.dontReset"/>. It will also not set a <see cref="RequestResetCommand"/> to the backend, 1854 /// i.e. the reset will be internal to the input system only (and thus can be partial in nature). 1855 /// 1856 /// By default, the method will perform a "soft" reset if <paramref name="device"/> has <see cref="InputControlAttribute.noisy"/> 1857 /// or <see cref="InputControlAttribute.dontReset"/> controls. If it does not, it will perform a "hard" reset. 1858 /// 1859 /// A "hard" reset can be forced by setting <paramref name="alsoResetDontResetControls"/> to true. 1860 /// 1861 /// <example> 1862 /// <code> 1863 /// // "Soft" reset the mouse. This will leave controls such as the mouse position intact 1864 /// // but will reset button press states. 1865 /// InputSystem.ResetDevice(Mouse.current); 1866 /// 1867 /// // "Hard" reset the mouse. This will wipe everything and reset the mouse to its default 1868 /// // state. 1869 /// InputSystem.ResetDevice(Mouse.current, alsoResetDontResetControls: true); 1870 /// </code> 1871 /// </example> 1872 /// 1873 /// Resetting a device will trigger a <see cref="InputDeviceChange.SoftReset"/> or <see cref="InputDeviceChange.HardReset"/> 1874 /// (based on the value of <paramref name="alsoResetDontResetControls"/>) notification on <see cref="onDeviceChange"/>. 1875 /// Also, all <see cref="InputAction"/>s currently in progress from controls on <paramref name="device"/> will be cancelled 1876 /// (see <see cref="InputAction.canceled"/>) in a way that guarantees for them to not get triggered. That is, a reset is 1877 /// semantically different from simply sending an event with default state. Using the latter, a button may be considered as 1878 /// going from pressed to released whereas with a device reset, the change back to unpressed state will not be considered 1879 /// a button release (and thus not trigger interactions that are waiting for a button release). 1880 /// </remarks> 1881 /// <seealso cref="TrySyncDevice"/> 1882 /// <seealso cref="InputDeviceChange.HardReset"/> 1883 /// <seealso cref="InputDeviceChange.SoftReset"/> 1884 /// <seealso cref="LowLevel.DeviceResetEvent"/> 1885 public static void ResetDevice(InputDevice device, bool alsoResetDontResetControls = false) 1886 { 1887 s_Manager.ResetDevice(device, alsoResetDontResetControls); 1888 } 1889 1890 // Not an auto-upgrade as it implies a change in behavior. 1891 [Obsolete("Use 'ResetDevice' instead.", error: false)] 1892 public static bool TryResetDevice(InputDevice device) 1893 { 1894 if (device == null) 1895 throw new ArgumentNullException(nameof(device)); 1896 return device.RequestReset(); 1897 } 1898 1899 ////REVIEW: should there be a global pause state? what about haptics that are issued *while* paused? 1900 1901 /// <summary> 1902 /// Pause haptic effect playback on all devices. 1903 /// </summary> 1904 /// <remarks> 1905 /// Calls <see cref="Haptics.IHaptics.PauseHaptics"/> on all <see cref="InputDevice">input devices</see> 1906 /// that implement the interface. 1907 /// </remarks> 1908 /// <seealso cref="ResumeHaptics"/> 1909 /// <seealso cref="ResetHaptics"/> 1910 /// <example> 1911 /// <code> 1912 /// // When going into the menu from gameplay, pause haptics. 1913 /// gameplayControls.backAction.onPerformed += 1914 /// ctx => 1915 /// { 1916 /// gameplayControls.Disable(); 1917 /// menuControls.Enable(); 1918 /// InputSystem.PauseHaptics(); 1919 /// }; 1920 /// </code> 1921 /// </example> 1922 public static void PauseHaptics() 1923 { 1924 var devicesList = devices; 1925 var devicesCount = devicesList.Count; 1926 1927 for (var i = 0; i < devicesCount; ++i) 1928 { 1929 var device = devicesList[i]; 1930 if (device is IHaptics haptics) 1931 haptics.PauseHaptics(); 1932 } 1933 } 1934 1935 /// <summary> 1936 /// Resume haptic effect playback on all devices. 1937 /// </summary> 1938 /// <remarks> 1939 /// Calls <see cref="Haptics.IHaptics.ResumeHaptics"/> on all <see cref="InputDevice">input devices</see> 1940 /// that implement the interface. 1941 /// </remarks> 1942 /// <seealso cref="PauseHaptics"/> 1943 public static void ResumeHaptics() 1944 { 1945 var devicesList = devices; 1946 var devicesCount = devicesList.Count; 1947 1948 for (var i = 0; i < devicesCount; ++i) 1949 { 1950 var device = devicesList[i]; 1951 if (device is IHaptics haptics) 1952 haptics.ResumeHaptics(); 1953 } 1954 } 1955 1956 /// <summary> 1957 /// Stop haptic effect playback on all devices. 1958 /// </summary> 1959 /// <remarks> 1960 /// Will reset haptics effects on all devices to their default state. 1961 /// 1962 /// Calls <see cref="Haptics.IHaptics.ResetHaptics"/> on all <see cref="InputDevice">input devices</see> 1963 /// that implement the interface. 1964 /// </remarks> 1965 public static void ResetHaptics() 1966 { 1967 var devicesList = devices; 1968 var devicesCount = devicesList.Count; 1969 1970 for (var i = 0; i < devicesCount; ++i) 1971 { 1972 var device = devicesList[i]; 1973 if (device is IHaptics haptics) 1974 haptics.ResetHaptics(); 1975 } 1976 } 1977 1978 #endregion 1979 1980 #region Controls 1981 1982 /// <summary> 1983 /// Set the usage tag of the given device to <paramref name="usage"/>. 1984 /// </summary> 1985 /// <param name="device">Device to set the usage on.</param> 1986 /// <param name="usage">New usage for the device.</param> 1987 /// <remarks> 1988 /// Usages allow to "tag" a specific device such that the tag can then be used in lookups 1989 /// and bindings. A common use is for identifying the handedness of an <see cref="XR.XRController"/> 1990 /// but the usages can be arbitrary strings. 1991 /// 1992 /// This method either sets the usages of the device to a single string (meaning it will 1993 /// clear whatever, if any usages, the device has when the method is called) or, 1994 /// if <paramref name="usage"/> is null or empty, resets the usages of the device 1995 /// to be empty. To add to a device's set of usages, call <see cref="AddDeviceUsage(InputDevice,string)"/>. 1996 /// To remove usages from a device, call <see cref="RemoveDeviceUsage(InputDevice,string)"/>. 1997 /// 1998 /// The set of usages a device has can be queried with <see cref="InputControl.usages"/> (a device 1999 /// is an <see cref="InputControl"/> and thus, like controls, has an associated set of usages). 2000 /// 2001 /// <example> 2002 /// <code> 2003 /// // Tag a gamepad to be associated with player #1. 2004 /// InputSystem.SetDeviceUsage(myGamepad, "Player1"); 2005 /// 2006 /// // Create an action that binds to player #1's gamepad specifically. 2007 /// var action = new InputAction(binding: "&lt;Gamepad&gt;{Player1}/buttonSouth"); 2008 /// 2009 /// // Move the tag from one gamepad to another. 2010 /// InputSystem.SetDeviceUsage(myGamepad, null); // Clears usages on 'myGamepad'. 2011 /// InputSystem.SetDeviceUsage(otherGamepad, "Player1"); 2012 /// </code> 2013 /// </example> 2014 /// </remarks> 2015 /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception> 2016 /// <seealso cref="InputControl.usages"/> 2017 /// <seealso cref="AddDeviceUsage(InputDevice,string)"/> 2018 /// <seealso cref="RemoveDeviceUsage(InputDevice,string)"/> 2019 /// <seealso cref="CommonUsages"/> 2020 /// <seealso cref="InputDeviceChange.UsageChanged"/> 2021 public static void SetDeviceUsage(InputDevice device, string usage) 2022 { 2023 SetDeviceUsage(device, new InternedString(usage)); 2024 } 2025 2026 /// <summary> 2027 /// Set the usage tag of the given device to <paramref name="usage"/>. 2028 /// </summary> 2029 /// <param name="device">Device to set the usage on.</param> 2030 /// <param name="usage">New usage for the device.</param> 2031 /// <remarks> 2032 /// Usages allow to "tag" a specific device such that the tag can then be used in lookups 2033 /// and bindings. A common use is for identifying the handedness of an <see cref="XR.XRController"/> 2034 /// but the usages can be arbitrary strings. 2035 /// 2036 /// This method either sets the usages of the device to a single string (meaning it will 2037 /// clear whatever, if any usages, the device has when the method is called) or, 2038 /// if <paramref name="usage"/> is null or empty, resets the usages of the device 2039 /// to be empty. To add to a device's set of usages, call <see cref="AddDeviceUsage(InputDevice,InternedString)"/>. 2040 /// To remove usages from a device, call <see cref="RemoveDeviceUsage(InputDevice,InternedString)"/>. 2041 /// 2042 /// The set of usages a device has can be queried with <see cref="InputControl.usages"/> (a device 2043 /// is an <see cref="InputControl"/> and thus, like controls, has an associated set of usages). 2044 /// 2045 /// If the set of usages on the device changes as a result of calling this method, <see cref="onDeviceChange"/> 2046 /// will be triggered with <see cref="InputDeviceChange.UsageChanged"/>. 2047 /// 2048 /// <example> 2049 /// <code> 2050 /// // Tag a gamepad to be associated with player #1. 2051 /// InputSystem.SetDeviceUsage(myGamepad, new InternedString("Player1")); 2052 /// 2053 /// // Create an action that binds to player #1's gamepad specifically. 2054 /// var action = new InputAction(binding: "&lt;Gamepad&gt;{Player1}/buttonSouth"); 2055 /// 2056 /// // Move the tag from one gamepad to another. 2057 /// InputSystem.SetDeviceUsage(myGamepad, null); // Clears usages on 'myGamepad'. 2058 /// InputSystem.SetDeviceUsage(otherGamepad, new InternedString("Player1")); 2059 /// </code> 2060 /// </example> 2061 /// </remarks> 2062 /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception> 2063 /// <seealso cref="InputControl.usages"/> 2064 /// <seealso cref="AddDeviceUsage(InputDevice,InternedString)"/> 2065 /// <seealso cref="RemoveDeviceUsage(InputDevice,InternedString)"/> 2066 /// <seealso cref="CommonUsages"/> 2067 /// <seealso cref="InputDeviceChange.UsageChanged"/> 2068 public static void SetDeviceUsage(InputDevice device, InternedString usage) 2069 { 2070 s_Manager.SetDeviceUsage(device, usage); 2071 } 2072 2073 /// <summary> 2074 /// Add a usage tag to the given device. 2075 /// </summary> 2076 /// <param name="device">Device to add the usage to.</param> 2077 /// <param name="usage">New usage to add to the device.</param> 2078 /// <remarks> 2079 /// Usages allow to "tag" a specific device such that the tag can then be used in lookups 2080 /// and bindings. A common use is for identifying the handedness of an <see cref="XR.XRController"/> 2081 /// but the usages can be arbitrary strings. 2082 /// 2083 /// This method adds a new usage to the device's set of usages. If the device already has 2084 /// the given usage, the method does nothing. To instead set the device's usages to a single 2085 /// one, use <see cref="SetDeviceUsage(InputDevice,string)"/>. To remove usages from a device, 2086 /// call <see cref="RemoveDeviceUsage(InputDevice,string)"/>. 2087 /// 2088 /// The set of usages a device has can be queried with <see cref="InputControl.usages"/> (a device 2089 /// is an <see cref="InputControl"/> and thus, like controls, has an associated set of usages). 2090 /// 2091 /// If the set of usages on the device changes as a result of calling this method, <see cref="onDeviceChange"/> 2092 /// will be triggered with <see cref="InputDeviceChange.UsageChanged"/>. 2093 /// </remarks> 2094 /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception> 2095 /// <exception cref="ArgumentException"><paramref name="usage"/> is null or empty.</exception> 2096 /// <seealso cref="InputControl.usages"/> 2097 /// <seealso cref="SetDeviceUsage(InputDevice,string)"/> 2098 /// <seealso cref="RemoveDeviceUsage(InputDevice,string)"/> 2099 /// <seealso cref="CommonUsages"/> 2100 /// <seealso cref="InputDeviceChange.UsageChanged"/> 2101 public static void AddDeviceUsage(InputDevice device, string usage) 2102 { 2103 s_Manager.AddDeviceUsage(device, new InternedString(usage)); 2104 } 2105 2106 /// <summary> 2107 /// Add a usage tag to the given device. 2108 /// </summary> 2109 /// <param name="device">Device to add the usage to.</param> 2110 /// <param name="usage">New usage to add to the device.</param> 2111 /// <remarks> 2112 /// Usages allow to "tag" a specific device such that the tag can then be used in lookups 2113 /// and bindings. A common use is for identifying the handedness of an <see cref="XR.XRController"/> 2114 /// but the usages can be arbitrary strings. 2115 /// 2116 /// This method adds a new usage to the device's set of usages. If the device already has 2117 /// the given usage, the method does nothing. To instead set the device's usages to a single 2118 /// one, use <see cref="SetDeviceUsage(InputDevice,InternedString)"/>. To remove usages from a device, 2119 /// call <see cref="RemoveDeviceUsage(InputDevice,InternedString)"/>. 2120 /// 2121 /// The set of usages a device has can be queried with <see cref="InputControl.usages"/> (a device 2122 /// is an <see cref="InputControl"/> and thus, like controls, has an associated set of usages). 2123 /// 2124 /// If the set of usages on the device changes as a result of calling this method, <see cref="onDeviceChange"/> 2125 /// will be triggered with <see cref="InputDeviceChange.UsageChanged"/>. 2126 /// </remarks> 2127 /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception> 2128 /// <exception cref="ArgumentException"><paramref name="usage"/> is empty.</exception> 2129 /// <seealso cref="InputControl.usages"/> 2130 /// <seealso cref="SetDeviceUsage(InputDevice,InternedString)"/> 2131 /// <seealso cref="RemoveDeviceUsage(InputDevice,InternedString)"/> 2132 /// <seealso cref="CommonUsages"/> 2133 /// <seealso cref="InputDeviceChange.UsageChanged"/> 2134 public static void AddDeviceUsage(InputDevice device, InternedString usage) 2135 { 2136 s_Manager.AddDeviceUsage(device, usage); 2137 } 2138 2139 /// <summary> 2140 /// Remove a usage tag from the given device. 2141 /// </summary> 2142 /// <param name="device">Device to remove the usage from.</param> 2143 /// <param name="usage">Usage to remove from the device.</param> 2144 /// <remarks> 2145 /// This method removes an existing usage from the given device. If the device does not 2146 /// have the given usage tag, the method does nothing. Use <see cref="SetDeviceUsage(InputDevice,string)"/> 2147 /// or <see cref="AddDeviceUsage(InputDevice,string)"/> to add usages to a device. 2148 /// 2149 /// The set of usages a device has can be queried with <see cref="InputControl.usages"/> (a device 2150 /// is an <see cref="InputControl"/> and thus, like controls, has an associated set of usages). 2151 /// 2152 /// If the set of usages on the device changes as a result of calling this method, <see cref="onDeviceChange"/> 2153 /// will be triggered with <see cref="InputDeviceChange.UsageChanged"/>. 2154 /// </remarks> 2155 /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception> 2156 /// <exception cref="ArgumentException"><paramref name="usage"/> is null or empty.</exception> 2157 /// <seealso cref="InputControl.usages"/> 2158 /// <seealso cref="SetDeviceUsage(InputDevice,string)"/> 2159 /// <seealso cref="AddDeviceUsage(InputDevice,string)"/> 2160 /// <seealso cref="CommonUsages"/> 2161 /// <seealso cref="InputDeviceChange.UsageChanged"/> 2162 public static void RemoveDeviceUsage(InputDevice device, string usage) 2163 { 2164 s_Manager.RemoveDeviceUsage(device, new InternedString(usage)); 2165 } 2166 2167 /// <summary> 2168 /// Remove a usage tag from the given device. 2169 /// </summary> 2170 /// <param name="device">Device to remove the usage from.</param> 2171 /// <param name="usage">Usage to remove from the device.</param> 2172 /// <remarks> 2173 /// This method removes an existing usage from the given device. If the device does not 2174 /// have the given usage tag, the method does nothing. Use <see cref="SetDeviceUsage(InputDevice,InternedString)"/> 2175 /// or <see cref="AddDeviceUsage(InputDevice,InternedString)"/> to add usages to a device. 2176 /// 2177 /// The set of usages a device has can be queried with <see cref="InputControl.usages"/> (a device 2178 /// is an <see cref="InputControl"/> and thus, like controls, has an associated set of usages). 2179 /// 2180 /// If the set of usages on the device changes as a result of calling this method, <see cref="onDeviceChange"/> 2181 /// will be triggered with <see cref="InputDeviceChange.UsageChanged"/>. 2182 /// </remarks> 2183 /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception> 2184 /// <exception cref="ArgumentException"><paramref name="usage"/> is empty.</exception> 2185 /// <seealso cref="InputControl.usages"/> 2186 /// <seealso cref="SetDeviceUsage(InputDevice,InternedString)"/> 2187 /// <seealso cref="AddDeviceUsage(InputDevice,InternedString)"/> 2188 /// <seealso cref="CommonUsages"/> 2189 /// <seealso cref="InputDeviceChange.UsageChanged"/> 2190 public static void RemoveDeviceUsage(InputDevice device, InternedString usage) 2191 { 2192 s_Manager.RemoveDeviceUsage(device, usage); 2193 } 2194 2195 /// <summary> 2196 /// Find the first control that matches the given control path. 2197 /// </summary> 2198 /// <param name="path">Path of a control, e.g. <c>"&lt;Gamepad&gt;/buttonSouth"</c>. See <see cref="InputControlPath"/> 2199 /// for details.</param> 2200 /// <returns>The first control that matches the given path or <c>null</c> if no control matches.</returns> 2201 /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c> or empty.</exception> 2202 /// <remarks> 2203 /// If multiple controls match the given path, which result is considered the first is indeterminate. 2204 /// 2205 /// <example> 2206 /// <code> 2207 /// // Add gamepad. 2208 /// InputSystem.AddDevice&lt;Gamepad&gt;(); 2209 /// 2210 /// // Look up various controls on it. 2211 /// var aButton = InputSystem.FindControl("&lt;Gamepad&gt;/buttonSouth"); 2212 /// var leftStickX = InputSystem.FindControl("*/leftStick/x"); 2213 /// var bButton = InputSystem.FindControl"*/{back}"); 2214 /// 2215 /// // This one returns the gamepad itself as devices are also controls. 2216 /// var gamepad = InputSystem.FindControl("&lt;Gamepad&gt;"); 2217 /// </code> 2218 /// </example> 2219 /// </remarks> 2220 /// <seealso cref="InputControlPath"/> 2221 /// <seealso cref="InputControl.path"/> 2222 public static InputControl FindControl(string path) 2223 { 2224 if (string.IsNullOrEmpty(path)) 2225 throw new ArgumentNullException(nameof(path)); 2226 2227 var devices = s_Manager.devices; 2228 var numDevices = devices.Count; 2229 2230 for (var i = 0; i < numDevices; ++i) 2231 { 2232 var device = devices[i]; 2233 var control = InputControlPath.TryFindControl(device, path); 2234 if (control != null) 2235 return control; 2236 } 2237 2238 return null; 2239 } 2240 2241 /// <summary> 2242 /// Find all controls that match the given <see cref="InputControlPath">control path</see>. 2243 /// </summary> 2244 /// <param name="path">Control path to search for</param> 2245 /// <returns>List of <see cref="InputControl"/> which matched the given search criteria</returns> 2246 /// <example> 2247 /// <code> 2248 /// // Find all gamepads (literally: that use the "Gamepad" layout). 2249 /// InputSystem.FindControls("&lt;Gamepad&gt;"); 2250 /// 2251 /// // Find all sticks on all gamepads. 2252 /// InputSystem.FindControls("&lt;Gamepad&gt;/*stick"); 2253 /// 2254 /// // Same but filter stick by type rather than by name. 2255 /// InputSystem.FindControls&lt;StickControl&gt;("&lt;Gamepad&gt;/*"); 2256 /// </code> 2257 /// </example> 2258 /// <seealso cref="FindControls{TControl}(string)"/> 2259 /// <seealso cref="FindControls{TControl}(string,ref UnityEngine.InputSystem.InputControlList{TControl})"/> 2260 public static InputControlList<InputControl> FindControls(string path) 2261 { 2262 return FindControls<InputControl>(path); 2263 } 2264 2265 /// <summary> 2266 /// Find all controls that match the given <see cref="InputControlPath">control path</see>. 2267 /// </summary> 2268 /// <param name="path">Control path to search for</param> 2269 /// <typeparam name="TControl">Type of control <see cref="InputControl"/>.</typeparam> 2270 /// <returns>Generic list of <see cref="InputControl"/> which matched the given search criteria</returns> 2271 /// <seealso cref="FindControls{InputControl}(string)"/> 2272 /// <seealso cref="FindControls{TControl}(string,ref UnityEngine.InputSystem.InputControlList{TControl})"/> 2273 public static InputControlList<TControl> FindControls<TControl>(string path) 2274 where TControl : InputControl 2275 { 2276 var list = new InputControlList<TControl>(); 2277 FindControls(path, ref list); 2278 return list; 2279 } 2280 2281 /// <summary> 2282 /// Populate a list with all controls that match the given <see cref="InputControlPath">control path</see>. 2283 /// </summary> 2284 /// <param name="path">Control path to search for</param> 2285 /// <param name="controls">Generic list of <see cref="InputControl"/> to populate with the search results</param> 2286 /// <typeparam name="TControl">Type of control <see cref="InputControl"/>.</typeparam> 2287 /// <returns>Count of controls which matched the given search criteria</returns> 2288 /// <seealso cref="FindControls{TControl}(string)"/> 2289 /// <seealso cref="FindControls{TControl}(string,ref UnityEngine.InputSystem.InputControlList{TControl})"/> 2290 public static int FindControls<TControl>(string path, ref InputControlList<TControl> controls) 2291 where TControl : InputControl 2292 { 2293 return s_Manager.GetControls(path, ref controls); 2294 } 2295 2296 #endregion 2297 2298 #region Events 2299 2300 internal static bool isProcessingEvents => s_Manager.isProcessingEvents; 2301 2302 /// <summary> 2303 /// Called during <see cref="Update"/> for each event that is processed. 2304 /// </summary> 2305 /// <remarks> 2306 /// Every time the input system updates (see <see cref="InputSettings.updateMode"/> 2307 /// or <see cref="Update"/> for details about when and how this happens), 2308 /// it flushes all events from the internal event buffer. 2309 /// 2310 /// As the Input System reads events from the buffer one by one, it will trigger this 2311 /// callback for each event which originates from a recognized device, before then proceeding 2312 /// to process the event. If any of the callbacks sets <see cref="InputEvent.handled"/> 2313 /// to true, the event will be skipped and ignored. 2314 /// 2315 /// Note that a device that is disabled (see <see cref="InputDevice.enabled"/>) may still get 2316 /// this event signalled for it. A <see cref="DisableDeviceCommand"/> will usually be sent to 2317 /// backends when a device is disabled but a backend may or may not respond to the command and 2318 /// thus may or may not keep sending events for the device. 2319 /// 2320 /// Note that the Input System does NOT sort events by timestamps (<see cref="InputEvent.time"/>). 2321 /// Instead, they are consumed in the order they are produced. This means that they 2322 /// will also surface on this callback in that order. 2323 /// 2324 /// <example> 2325 /// <code> 2326 /// // Treat left+right mouse button as middle mouse button. 2327 /// // (Note: This example is more for demonstrative purposes; it isn't necessarily a good use case) 2328 /// InputSystem.onEvent += 2329 /// (eventPtr, device) => 2330 /// { 2331 /// // Only deal with state events. 2332 /// if (!eventPtr.IsA&lt;StateEvent&gt;()) 2333 /// return; 2334 /// 2335 /// if (!(device is Mouse mouse)) 2336 /// return; 2337 /// 2338 /// mouse.leftButton.ReadValueFromEvent(eventPtr, out var lmbDown); 2339 /// mouse.rightButton.ReadValueFromEvent(eventPtr, out var rmbDown); 2340 /// 2341 /// if (lmbDown > 0 &amp;&amp; rmbDown > 0) 2342 /// mouse.middleButton.WriteValueIntoEvent(1f, eventPtr); 2343 /// }; 2344 /// </code> 2345 /// </example> 2346 /// 2347 /// The property returns an <see cref="InputEventListener"/> struct that, beyond adding and removing 2348 /// callbacks, can be used to flexibly listen in on the event stream. 2349 /// 2350 /// <example> 2351 /// <code> 2352 /// // Listen for mouse events. 2353 /// InputSystem.onEvent 2354 /// .ForDevice(Mouse.current) 2355 /// .Call(e => Debug.Log("Mouse event")); 2356 /// </code> 2357 /// </example> 2358 /// 2359 /// If you are looking for a way to capture events, <see cref="InputEventTrace"/> may be of 2360 /// interest and an alternative to directly hooking into this event. 2361 /// 2362 /// If you are looking to monitor changes to specific input controls, state change monitors 2363 /// (see <see cref="InputState.AddChangeMonitor(InputControl,IInputStateChangeMonitor,long,uint)"/> 2364 /// are usually a more efficient and convenient way to set this up. 2365 /// </remarks> 2366 /// <exception cref="ArgumentNullException">Delegate reference is <c>null</c>.</exception> 2367 /// <seealso cref="QueueEvent(InputEventPtr)"/> 2368 /// <seealso cref="InputEvent"/> 2369 /// <seealso cref="Update"/> 2370 /// <seealso cref="InputSettings.updateMode"/> 2371 public static InputEventListener onEvent 2372 { 2373 // The listener syntax is an artificial struct. Setting it has no effect. 2374 // Its only purpose is to give us access to both the += and -= syntax of C# events 2375 // and at the same time provide a springboard into IObservable. 2376 get => default; 2377 // ReSharper disable once ValueParameterNotUsed 2378 set {} 2379 } 2380 2381 /// <summary> 2382 /// Listen through <see cref="onEvent"/> for a button to be pressed. 2383 /// </summary> 2384 /// <remarks> 2385 /// The listener will get triggered whenever a <see cref="ButtonControl"/> on any device in the list of <see cref="devices"/> 2386 /// goes from not being pressed to being pressed. 2387 /// 2388 /// <example> 2389 /// <code> 2390 /// // Response to the first button press. Calls our delegate 2391 /// // and then immediately stops listening. 2392 /// InputSystem.onAnyButtonPress 2393 /// .CallOnce(ctrl => Debug.Log($"Button {ctrl} was pressed")); 2394 /// </code> 2395 /// </example> 2396 /// 2397 /// Note that the listener will get triggered from the first button that was found in a pressed state in a 2398 /// given <see cref="InputEvent"/>. If multiple buttons are pressed in an event, the listener will not 2399 /// get triggered multiple times. To get all button presses in an event, use <see cref="InputControlExtensions.GetAllButtonPresses"/> 2400 /// and instead listen directly through <see cref="onEvent"/>. 2401 /// 2402 /// <example> 2403 /// <code> 2404 /// InputSystem.onEvent 2405 /// .Where(e => e.HasButtonPress()) 2406 /// .CallOnce(eventPtr => 2407 /// { 2408 /// foreach (var button in l.eventPtr.GetAllButtonPresses()) 2409 /// Debug.Log($"Button {button} was pressed"); 2410 /// }); 2411 /// </code> 2412 /// </example> 2413 /// 2414 /// There is a certain overhead to listening for button presses so it is best to have listeners 2415 /// installed only while the information is actually needed. 2416 /// 2417 /// <example> 2418 /// <code> 2419 /// // Script that will spawn a new player when a button on a device is pressed. 2420 /// public class JoinPlayerOnPress : MonoBehaviour 2421 /// { 2422 /// // We instantiate this GameObject to create a new player object. 2423 /// // Expected to have a PlayerInput component in its hierarchy. 2424 /// public GameObject playerPrefab; 2425 /// 2426 /// // We want to remove the event listener we install through InputSystem.onAnyButtonPress 2427 /// // after we're done so remember it here. 2428 /// private IDisposable m_EventListener; 2429 /// 2430 /// // When enabled, we install our button press listener. 2431 /// void OnEnable() 2432 /// { 2433 /// // Start listening. 2434 /// m_EventListener = 2435 /// InputSystem.onAnyButtonPress 2436 /// .Call(OnButtonPressed) 2437 /// } 2438 /// 2439 /// // When disabled, we remove our button press listener. 2440 /// void OnDisable() 2441 /// { 2442 /// m_EventListener.Dispose(); 2443 /// } 2444 /// 2445 /// void OnButtonPressed(InputControl button) 2446 /// { 2447 /// var device = button.device; 2448 /// 2449 /// // Ignore presses on devices that are already used by a player. 2450 /// if (PlayerInput.FindFirstPairedToDevice(device) != null) 2451 /// return; 2452 /// 2453 /// // Create a new player. 2454 /// var player = PlayerInput.Instantiate(playerPrefab, pairWithDevice: device); 2455 /// 2456 /// // If the player did not end up with a valid input setup, 2457 /// // unjoin the player. 2458 /// if (player.hasMissingRequiredDevices) 2459 /// Destroy(player); 2460 /// 2461 /// // If we only want to join a single player, could uninstall our listener here 2462 /// // or use CallOnce() instead of Call() when we set it up. 2463 /// } 2464 /// } 2465 /// </code> 2466 /// </example> 2467 /// </remarks> 2468 /// <seealso cref="ButtonControl.isPressed"/> 2469 /// <seealso cref="onEvent"/> 2470 public static IObservable<InputControl> onAnyButtonPress => 2471 onEvent 2472 .Select(e => e.GetFirstButtonPressOrNull()).Where(c => c != null); 2473 2474 /// <summary> 2475 /// Add an event to the internal event queue. 2476 /// </summary> 2477 /// <param name="eventPtr">Event to add to the internal event buffer.</param> 2478 /// <exception cref="ArgumentException"><paramref name="eventPtr"/> is not 2479 /// valid (see <see cref="InputEventPtr.valid"/>).</exception> 2480 /// <exception cref="InvalidOperationException">The method was called from 2481 /// within event processing more than 1000 times. To avoid deadlocking, this 2482 /// results in an exception being thrown.</exception> 2483 /// <remarks> 2484 /// The event will be copied in full to the internal event buffer meaning that 2485 /// you can release memory for the event after it has been queued. The internal event 2486 /// buffer is flushed on the next input system update (see <see cref="Update"/>). 2487 /// Note that if input is process in <c>FixedUpdate()</c> (see <see cref="InputSettings.updateMode"/>), 2488 /// then the event may not get processed until its <see cref="InputEvent.time"/> timestamp 2489 /// is within the update window of the input system. 2490 /// 2491 /// As part of queuing, the event will receive its own unique ID (see <see cref="InputEvent.eventId"/>). 2492 /// Note that this ID will be written into the memory buffer referenced by <paramref cref="eventPtr"/> 2493 /// meaning that after calling <c>QueueEvent</c>, you will see the event ID with which the event 2494 /// was queued. 2495 /// 2496 /// Events that are queued during event processing will get processed in the same update. 2497 /// This happens, for example, when queuing input from within <see cref="onEvent"/> or from 2498 /// action callbacks such as <see cref="InputAction.performed"/>. 2499 /// 2500 /// The total size of <see cref="InputEvent"/>s processed in a single update is limited by 2501 /// <see cref="InputSettings.maxEventBytesPerUpdate"/>. This also prevents deadlocks when 2502 /// each processing of an event leads to one or more additional events getting queued. 2503 /// 2504 /// <example> 2505 /// <code> 2506 /// // Queue an input event on the first gamepad. 2507 /// var gamepad = Gamepad.all[0]; 2508 /// using (StateEvent.From(gamepad, out var eventPtr)) 2509 /// { 2510 /// gamepad.leftStick.WriteValueIntoEvent(new Vector2(0.123f, 0.234f), eventPtr); 2511 /// InputSystem.QueueEvent(eventPtr); 2512 /// } 2513 /// </code> 2514 /// </example> 2515 /// </remarks> 2516 /// <seealso cref="Update"/> 2517 /// <seealso cref="onEvent"/> 2518 /// <seealso cref="onBeforeUpdate"/> 2519 /// <seealso cref="InputEvent"/> 2520 public static void QueueEvent(InputEventPtr eventPtr) 2521 { 2522 if (!eventPtr.valid) 2523 throw new ArgumentException("Received a null event pointer", nameof(eventPtr)); 2524 2525 s_Manager.QueueEvent(eventPtr); 2526 } 2527 2528 /// <summary> 2529 /// Add an event to the internal event queue. 2530 /// </summary> 2531 /// <typeparam name="TEvent">Type of event to look enqueue.</typeparam> 2532 /// <param name="inputEvent">Event to add to the internal event buffer.</param> 2533 /// <remarks> 2534 /// The event will be copied in full to the internal event buffer. The internal event 2535 /// buffer is flushed on the next input system update (see <see cref="Update"/>). 2536 /// Note that if input is process in <c>FixedUpdate()</c> (see <see cref="InputSettings.updateMode"/>), 2537 /// then the event may not get processed until its <see cref="InputEvent.time"/> timestamp 2538 /// is within the update window of the input system. 2539 /// 2540 /// As part of queuing, the event will receive its own unique ID (see <see cref="InputEvent.eventId"/>). 2541 /// Note that this ID will be written into <paramref name="inputEvent"/> 2542 /// meaning that after calling this method, you will see the event ID with which the event 2543 /// was queued. 2544 /// 2545 /// <example> 2546 /// <code> 2547 /// // Queue a disconnect event on the first gamepad. 2548 /// var inputEvent = DeviceRemoveEvent(Gamepad.all[0].deviceId); 2549 /// InputSystem.QueueEvent(inputEvent); 2550 /// </code> 2551 /// </example> 2552 /// </remarks> 2553 /// <seealso cref="Update"/> 2554 /// <seealso cref="onEvent"/> 2555 /// <seealso cref="onBeforeUpdate"/> 2556 public static void QueueEvent<TEvent>(ref TEvent inputEvent) 2557 where TEvent : struct, IInputEventTypeInfo 2558 { 2559 s_Manager.QueueEvent(ref inputEvent); 2560 } 2561 2562 ////REVIEW: consider moving these out into extension methods in UnityEngine.InputSystem.LowLevel 2563 2564 ////TODO: find a more elegant solution for this 2565 // Mono will ungracefully poop exceptions if we try to use LayoutKind.Explicit in generic 2566 // structs. So we can't just stuff a generic TState into a StateEvent<TState> and enforce 2567 // proper layout. Thus the jumping through lots of ugly hoops here. 2568 private unsafe struct StateEventBuffer 2569 { 2570 public StateEvent stateEvent; 2571 public const int kMaxSize = 512; 2572 public fixed byte data[kMaxSize - 1]; // StateEvent already adds one. 2573 } 2574 /// <summary> 2575 /// Queue a <see cref="StateEvent"/> to update the input state of the given device. 2576 /// </summary> 2577 /// <param name="device">Device whose input state to update</param> 2578 /// <param name="state"></param> 2579 /// <param name="time">Timestamp for the event. If not supplied, the current time is used. Note 2580 /// that if the given time is in the future and events processed in 2581 /// <a href="https://docs.unity3d.com/ScriptReference/MonoBehaviour.FixedUpdate.html">FixedUpdate</a> (see <see cref="InputSettings.updateMode"/>), 2582 /// the event will only get processed once the actual time has caught up with the given time.</param> 2583 /// <typeparam name="TState">Type of input state, such as <see cref="MouseState"/>. Must match the expected 2584 /// type of state of <paramref name="device"/>.</typeparam> 2585 /// <remarks> 2586 /// The given state must match exactly what is expected by the given device. If unsure, an alternative 2587 /// is to grab the state as an event directly from the device using <see 2588 /// cref="StateEvent.From(InputDevice,out InputEventPtr,Unity.Collections.Allocator)"/> which can then 2589 /// be queued using <see cref="QueueEvent(InputEventPtr)"/>. 2590 /// 2591 /// <example> 2592 /// <code> 2593 /// // Allocates temporary, unmanaged memory for the event. 2594 /// // using statement automatically disposes the memory once we have queued the event. 2595 /// using (StateEvent.From(Mouse.current, out var eventPtr)) 2596 /// { 2597 /// // Use controls on mouse to write values into event. 2598 /// Mouse.current.position.WriteValueIntoEvent(new Vector(123, 234), eventPtr); 2599 /// 2600 /// // Queue event. 2601 /// InputSystem.QueueEvent(eventPtr); 2602 /// } 2603 /// </code> 2604 /// </example> 2605 /// 2606 /// The event will only be queued and not processed right away. This means that the state of 2607 /// <paramref name="device"/> will not change immediately as a result of calling this method. Instead, 2608 /// the event will be processed as part of the next input update. 2609 /// 2610 /// Note that this method updates the complete input state of the device including all of its 2611 /// controls. To update just part of the state of a device, you can use <see cref="QueueDeltaStateEvent{TDelta}"/> 2612 /// (however, note that there are some restrictions; see documentation). 2613 /// <example> 2614 /// <code> 2615 /// InputSystem.QueueStateEvent(Mouse.current, new MouseState { position = new Vector(123, 234) }); 2616 /// </code> 2617 /// </example> 2618 /// </remarks> 2619 /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception> 2620 /// <exception cref="InvalidOperationException"><paramref name="device"/> has not been added to the system 2621 /// (<see cref="AddDevice(InputDevice)"/>) and thus cannot receive events.</exception> 2622 /// <exception cref="ArgumentException"></exception> 2623 public static unsafe void QueueStateEvent<TState>(InputDevice device, TState state, double time = -1) 2624 where TState : struct, IInputStateTypeInfo 2625 { 2626 if (device == null) 2627 throw new ArgumentNullException(nameof(device)); 2628 2629 // Make sure device is actually in the system. 2630 if (device.m_DeviceIndex == InputDevice.kInvalidDeviceIndex) 2631 throw new InvalidOperationException( 2632 $"Cannot queue state event for device '{device}' because device has not been added to system"); 2633 2634 ////REVIEW: does it make more sense to go off the 'stateBlock' on the device and let that determine size? 2635 2636 var stateSize = (uint)UnsafeUtility.SizeOf<TState>(); 2637 if (stateSize > StateEventBuffer.kMaxSize) 2638 throw new ArgumentException( 2639 $"Size of '{typeof(TState).Name}' exceeds maximum supported state size of {StateEventBuffer.kMaxSize}", 2640 nameof(state)); 2641 var eventSize = UnsafeUtility.SizeOf<StateEvent>() + stateSize - StateEvent.kStateDataSizeToSubtract; 2642 2643 if (time < 0) 2644 time = InputRuntime.s_Instance.currentTime; 2645 else 2646 time += InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; 2647 2648 StateEventBuffer eventBuffer; 2649 eventBuffer.stateEvent = 2650 new StateEvent 2651 { 2652 baseEvent = new InputEvent(StateEvent.Type, (int)eventSize, device.deviceId, time), 2653 stateFormat = state.format 2654 }; 2655 2656 var ptr = eventBuffer.stateEvent.stateData; 2657 UnsafeUtility.MemCpy(ptr, UnsafeUtility.AddressOf(ref state), stateSize); 2658 2659 s_Manager.QueueEvent(ref eventBuffer.stateEvent); 2660 } 2661 2662 private unsafe struct DeltaStateEventBuffer 2663 { 2664 public DeltaStateEvent stateEvent; 2665 public const int kMaxSize = 512; 2666 public fixed byte data[kMaxSize - 1]; // DeltaStateEvent already adds one. 2667 } 2668 2669 /// <summary> 2670 /// Queue a <see cref="DeltaStateEvent"/> to update part of the input state of the given device. 2671 /// </summary> 2672 /// <param name="control">Control on a device to update state of.</param> 2673 /// <param name="delta">New state for the control. Type of state must match the state of the control.</param> 2674 /// <param name="time"></param> 2675 /// <typeparam name="TDelta"></typeparam> 2676 /// <exception cref="ArgumentNullException"><paramref name="control"/> is null.</exception> 2677 /// <exception cref="InvalidOperationException"></exception> 2678 /// <exception cref="ArgumentException"></exception> 2679 public static unsafe void QueueDeltaStateEvent<TDelta>(InputControl control, TDelta delta, double time = -1) 2680 where TDelta : struct 2681 { 2682 if (control == null) 2683 throw new ArgumentNullException(nameof(control)); 2684 2685 if (control.stateBlock.bitOffset != 0) 2686 throw new InvalidOperationException( 2687 $"Cannot send delta state events against bitfield controls: {control}"); 2688 2689 // Make sure device is actually in the system. 2690 var device = control.device; 2691 if (device.m_DeviceIndex == InputDevice.kInvalidDeviceIndex) 2692 throw new InvalidOperationException( 2693 $"Cannot queue state event for control '{control}' on device '{device}' because device has not been added to system"); 2694 2695 if (time < 0) 2696 time = InputRuntime.s_Instance.currentTime; 2697 else 2698 time += InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; 2699 2700 var deltaSize = (uint)UnsafeUtility.SizeOf<TDelta>(); 2701 if (deltaSize > DeltaStateEventBuffer.kMaxSize) 2702 throw new ArgumentException( 2703 $"Size of state delta '{typeof(TDelta).Name}' exceeds maximum supported state size of {DeltaStateEventBuffer.kMaxSize}", 2704 nameof(delta)); 2705 2706 ////TODO: recognize a matching C# representation of a state format and convert to what we expect for trivial cases 2707 if (deltaSize != control.stateBlock.alignedSizeInBytes) 2708 throw new ArgumentException( 2709 $"Size {deltaSize} of delta state of type {typeof(TDelta).Name} provided for control '{control}' does not match size {control.stateBlock.alignedSizeInBytes} of control", 2710 nameof(delta)); 2711 2712 var eventSize = UnsafeUtility.SizeOf<DeltaStateEvent>() + deltaSize - 1; 2713 2714 DeltaStateEventBuffer eventBuffer; 2715 eventBuffer.stateEvent = 2716 new DeltaStateEvent 2717 { 2718 baseEvent = new InputEvent(DeltaStateEvent.Type, (int)eventSize, device.deviceId, time), 2719 stateFormat = device.stateBlock.format, 2720 stateOffset = control.m_StateBlock.byteOffset - device.m_StateBlock.byteOffset 2721 }; 2722 2723 var ptr = eventBuffer.stateEvent.stateData; 2724 UnsafeUtility.MemCpy(ptr, UnsafeUtility.AddressOf(ref delta), deltaSize); 2725 2726 s_Manager.QueueEvent(ref eventBuffer.stateEvent); 2727 } 2728 2729 /// <summary> 2730 /// Queue a <see cref="DeviceConfigurationEvent"/> that signals that the configuration of the given device has changed 2731 /// and that cached configuration will thus have to be refreshed. 2732 /// </summary> 2733 /// <param name="device">Device whose configuration has changed.</param> 2734 /// <param name="time">Timestamp for the event. If not supplied, the current time will be used.</param> 2735 /// <remarks> 2736 /// All state of an input device that is not input or output state is considered its "configuration". 2737 /// 2738 /// A simple example is keyboard layouts. A <see cref="Keyboard"/> will typically have an associated 2739 /// keyboard layout that dictates the function of each key and which can be changed by the user at the 2740 /// system level. In the input system, the current keyboard layout can be queried via <see cref="Keyboard.keyboardLayout"/>. 2741 /// When the layout changes at the system level, the input backend sends a configuration change event 2742 /// to signal that the configuration of the keyboard has changed and that cached data may be outdated. 2743 /// In response, <see cref="Keyboard"/> will flush out cached information such as the name of the keyboard 2744 /// layout and display names (<see cref="InputControl.displayName"/>) of individual keys which causes them 2745 /// to be fetched again from the backend the next time they are accessed. 2746 /// </remarks> 2747 /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception> 2748 /// <exception cref="InvalidOperationException"><paramref name="device"/> has not been added 2749 /// (<see cref="InputDevice.added"/>; <see cref="AddDevice(InputDevice)"/>) and thus cannot 2750 /// receive events.</exception> 2751 public static void QueueConfigChangeEvent(InputDevice device, double time = -1) 2752 { 2753 if (device == null) 2754 throw new ArgumentNullException(nameof(device)); 2755 if (device.deviceId == InputDevice.InvalidDeviceId) 2756 throw new InvalidOperationException("Device has not been added"); 2757 2758 if (time < 0) 2759 time = InputRuntime.s_Instance.currentTime; 2760 else 2761 time += InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; 2762 2763 var inputEvent = DeviceConfigurationEvent.Create(device.deviceId, time); 2764 s_Manager.QueueEvent(ref inputEvent); 2765 } 2766 2767 /// <summary> 2768 /// Queue a <see cref="TextEvent"/> on the given device. 2769 /// </summary> 2770 /// <param name="device">Device to queue the event on.</param> 2771 /// <param name="character">Text character to input through the event.</param> 2772 /// <param name="time">Optional event time stamp. If not supplied, the current time will be used.</param> 2773 /// <remarks> 2774 /// Text input is sent to devices character by character. This allows sending strings of arbitrary 2775 /// length without necessary incurring GC overhead. 2776 /// 2777 /// For the event to have any effect on <paramref name="device"/>, the device must 2778 /// implement <see cref="ITextInputReceiver"/>. It will see <see cref="ITextInputReceiver.OnTextInput"/> 2779 /// being called when the event is processed. 2780 /// </remarks> 2781 /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception> 2782 /// <exception cref="InvalidOperationException"><paramref name="device"/> is a device that has not been 2783 /// added to the system.</exception> 2784 /// <seealso cref="Keyboard.onTextInput"/> 2785 public static void QueueTextEvent(InputDevice device, char character, double time = -1) 2786 { 2787 if (device == null) 2788 throw new ArgumentNullException(nameof(device)); 2789 if (device.deviceId == InputDevice.InvalidDeviceId) 2790 throw new InvalidOperationException("Device has not been added"); 2791 2792 if (time < 0) 2793 time = InputRuntime.s_Instance.currentTime; 2794 else 2795 time += InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; 2796 2797 var inputEvent = TextEvent.Create(device.deviceId, character, time); 2798 s_Manager.QueueEvent(ref inputEvent); 2799 } 2800 2801 /// <summary> 2802 /// Run a single update of input state. 2803 /// </summary> 2804 /// <remarks> 2805 /// Except in tests and when using <see cref="InputSettings.UpdateMode.ProcessEventsManually"/>, this method should not 2806 /// normally be called. The input system will automatically update as part of the player loop as 2807 /// determined by <see cref="InputSettings.updateMode"/>. Calling this method is equivalent to 2808 /// inserting extra frames, i.e. it will advance the entire state of the input system by one complete 2809 /// frame. 2810 /// 2811 /// When using <see cref="InputUpdateType.Manual"/>, this method MUST be called for input to update in the 2812 /// player. Not calling the method as part of the player loop may result in excessive memory 2813 /// consumption and/or potential loss of input. 2814 /// 2815 /// Each update will flush out buffered input events and cause them to be processed. This in turn 2816 /// will update the state of input devices (<see cref="InputDevice"/>) and trigger actions (<see cref="InputAction"/>) 2817 /// that monitor affected device state. 2818 /// </remarks> 2819 /// <seealso cref="InputUpdateType"/> 2820 /// <seealso cref="InputSettings.updateMode"/> 2821 public static void Update() 2822 { 2823 s_Manager.Update(); 2824 } 2825 2826 internal static void Update(InputUpdateType updateType) 2827 { 2828 if (updateType != InputUpdateType.None && (s_Manager.updateMask & updateType) == 0) 2829 throw new InvalidOperationException( 2830 $"'{updateType}' updates are not enabled; InputSystem.settings.updateMode is set to '{settings.updateMode}'"); 2831 s_Manager.Update(updateType); 2832 } 2833 2834 /// <summary> 2835 /// Event that is fired before the input system updates. 2836 /// </summary> 2837 /// <remarks> 2838 /// The input system updates in sync with player loop and editor updates. Input updates 2839 /// are run right before the respective script update. For example, an input update for 2840 /// <see cref="InputUpdateType.Dynamic"/> is run before <c>MonoBehaviour.Update</c> methods 2841 /// are executed. 2842 /// 2843 /// The update callback itself is triggered before the input system runs its own update and 2844 /// before it flushes out its event queue. This means that events queued from a callback will 2845 /// be fed right into the upcoming update. 2846 /// </remarks> 2847 /// <seealso cref="onAfterUpdate"/> 2848 /// <seealso cref="Update"/> 2849 public static event Action onBeforeUpdate 2850 { 2851 add 2852 { 2853 lock (s_Manager) 2854 s_Manager.onBeforeUpdate += value; 2855 } 2856 remove 2857 { 2858 lock (s_Manager) 2859 s_Manager.onBeforeUpdate -= value; 2860 } 2861 } 2862 2863 /// <summary> 2864 /// Event that is fired after the input system has completed an update and processed all pending events. 2865 /// </summary> 2866 /// <seealso cref="onBeforeUpdate"/> 2867 /// <seealso cref="Update"/> 2868 public static event Action onAfterUpdate 2869 { 2870 add 2871 { 2872 lock (s_Manager) 2873 s_Manager.onAfterUpdate += value; 2874 } 2875 remove 2876 { 2877 lock (s_Manager) 2878 s_Manager.onAfterUpdate -= value; 2879 } 2880 } 2881 2882 #endregion 2883 2884 #region Settings 2885 2886 /// <summary> 2887 /// The current configuration of the input system. 2888 /// </summary> 2889 /// <value>Global configuration object for the input system.</value> 2890 /// <remarks> 2891 /// The input system can be configured on a per-project basis. Settings can either be created and 2892 /// installed on the fly or persisted as assets in the project. 2893 /// </remarks> 2894 /// <exception cref="ArgumentNullException">Value is null when setting the property.</exception> 2895 public static InputSettings settings 2896 { 2897 get => s_Manager.settings; 2898 set 2899 { 2900 if (value == null) 2901 throw new ArgumentNullException(nameof(value)); 2902 2903 if (s_Manager.m_Settings == value) 2904 return; 2905 2906 // In the editor, we keep track of the settings asset through EditorBuildSettings. 2907 #if UNITY_EDITOR 2908 if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(value))) 2909 { 2910 EditorBuildSettings.AddConfigObject(InputSettingsProvider.kEditorBuildSettingsConfigKey, 2911 value, true); 2912 } 2913 #endif 2914 2915 s_Manager.settings = value; 2916 } 2917 } 2918 2919 /// <summary> 2920 /// Event that is triggered if any of the properties in <see cref="settings"/> changes or if 2921 /// <see cref="settings"/> is replaced entirely with a new <see cref="InputSettings"/> object. 2922 /// </summary> 2923 /// <seealso cref="settings"/> 2924 /// <seealso cref="InputSettings"/> 2925 public static event Action onSettingsChange 2926 { 2927 add => s_Manager.onSettingsChange += value; 2928 remove => s_Manager.onSettingsChange -= value; 2929 } 2930 2931#if UNITY_EDITOR 2932 /// <summary> 2933 /// Callback that can be used to display a warning and draw additional custom Editor UI for bindings. 2934 /// </summary> 2935 /// <seealso cref="InputBinding"/> 2936 /// <remarks> 2937 /// This allows Users to control the behavior of the <see cref="InputActionAsset"/> Editor. 2938 /// Specifically this controls whether a warning icon will appear next to a particular 2939 /// <see cref="InputBinding"/> in the list and also draw custom UI content for it once 2940 /// it is selected. 2941 /// By default no callbacks exist and therefore no warnings or custom content will be shown. 2942 /// A User interested in customizing this behavior is expected to provide a callback function here. 2943 /// This callback function will receive the binding path to be inspected. 2944 /// The callback is then expected to either return null to indicate no warning is to be displayed 2945 /// for this binding path or a <see cref="System.Action"/> which contains the custom rendering function 2946 /// to be shown in the Binding properties panel when a InputBinding has been selected. 2947 /// Returning any <see cref="System.Action"/> will also display a small warning icon next to the 2948 /// particular <see cref="InputBinding"/> in the list, regardless of the contents of that function. 2949 /// </remarks> 2950 /// 2951 /// <example> 2952 /// <code> 2953 /// InputSystem.customBindingPathValidators += (string bindingPath) => { 2954 /// // Mark <Gamepad> bindings with a warning 2955 /// if (!bindingPath.StartsWith("<Gamepad>")) 2956 /// return null; 2957 /// 2958 /// // Draw the warning information in the Binding Properties panel 2959 /// return () => 2960 /// { 2961 /// GUILayout.BeginVertical("GroupBox"); 2962 /// GUILayout.BeginHorizontal(); 2963 /// GUILayout.Box(EditorGUIUtility.FindTexture("console.warnicon.sml")); 2964 /// GUILayout.Label( 2965 /// "This binding is inactive because it refers to a disabled OpenXR interaction profile.", 2966 /// EditorStyles.wordWrappedLabel); 2967 /// GUILayout.EndHorizontal(); 2968 /// 2969 /// GUILayout.Button("Manage Interaction Profiles"); 2970 /// GUILayout.EndVertical(); 2971 /// }; 2972 /// }; 2973 /// </code> 2974 /// </example> 2975 public static event CustomBindingPathValidator customBindingPathValidators 2976 { 2977 add => s_Manager.customBindingPathValidators += value; 2978 remove => s_Manager.customBindingPathValidators -= value; 2979 } 2980 2981 /// <summary> 2982 /// Invokes any custom UI rendering code for this Binding Path in the editor. 2983 /// </summary> 2984 /// <seealso cref="customBindingPathValidators"/> 2985 /// <remarks> 2986 /// This is called internally by the <see cref="InputActionAsset"/> Editor while displaying 2987 /// the properties for a <see cref="InputBinding"/>. 2988 /// This is not intended to be called directly. 2989 /// Please use <see cref="customBindingPathValidators"/> instead. 2990 /// </remarks> 2991 internal static void OnDrawCustomWarningForBindingPath(string bindingPath) 2992 { 2993 s_Manager.OnDrawCustomWarningForBindingPath(bindingPath); 2994 } 2995 2996 /// <summary> 2997 /// Determines if any warning icon is to be displayed for this Binding Path in the editor. 2998 /// </summary> 2999 /// <seealso cref="customBindingPathValidators"/> 3000 /// <remarks> 3001 /// This is called internally by the <see cref="InputActionAsset"/> Editor while displaying 3002 /// the list of each <see cref="InputBinding"/>. 3003 /// This is not intended to be called directly. 3004 /// Please use <see cref="customBindingPathValidators"/> instead. 3005 /// </remarks> 3006 internal static bool ShouldDrawWarningIconForBinding(string bindingPath) 3007 { 3008 return s_Manager.ShouldDrawWarningIconForBinding(bindingPath); 3009 } 3010 3011#endif 3012 3013 #endregion 3014 3015 #region Actions 3016 3017#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 3018 // EnteredEditMode Occurs during the next update of the Editor application if it is in edit mode and was previously in play mode. 3019 // ExitingEditMode Occurs when exiting edit mode, before the Editor is in play mode. 3020 // EnteredPlayMode Occurs during the next update of the Editor application if it is in play mode and was previously in edit mode. 3021 // ExitingPlayMode Occurs when exiting play mode, before the Editor is in edit mode. 3022 // 3023 // Using the EnteredEditMode / EnteredPlayMode states to transition the actions' enabled 3024 // state ensures that the they are active in all of these MonoBehavior methods: 3025 // 3026 // Awake() / Start() / OnEnable() / OnDisable() / OnDestroy() 3027 // 3028 private static void EnableActions() 3029 { 3030#if UNITY_EDITOR 3031 // Abort if not in play-mode in editor 3032 if (!EditorApplication.isPlayingOrWillChangePlaymode) 3033 return; 3034#endif // UNITY_EDITOR 3035 if (actions == null) 3036 return; 3037 3038 actions.Enable(); 3039 } 3040 3041 private static void DisableActions(bool triggerSetupChanged = false) 3042 { 3043 // Make sure project wide input actions are disabled 3044 var projectWideActions = actions; 3045 if (projectWideActions == null) 3046 return; 3047 3048 projectWideActions.Disable(); 3049 3050 if (triggerSetupChanged) 3051 projectWideActions.OnSetupChanged(); 3052 } 3053 3054 /// <summary> 3055 /// An input action asset (see <see cref="InputActionAsset"/>) which is always available if 3056 /// assigned in Input System Package settings in Edit, Project Settings, Input System Package in editor. 3057 /// </summary> 3058 /// <remarks> 3059 /// Project-wide actions may only be assigned in Edit Mode and any attempt to change this property 3060 /// in Play Mode will result in an <c>System.Exception</c> being thrown. 3061 /// A default set of actions and action maps are installed and enabled by default on every project 3062 /// that enables Project-wide Input Actions by assigning a project-wide asset in Project Settings. 3063 /// These actions and their bindings may be modified in the Project Settings. 3064 /// 3065 /// All actions in the associated <c>InputActionAsset</c> will be automatically enabled when entering 3066 /// Play Mode and automatically disabled when exiting Play Mode. 3067 /// The asset associated with this property will be included in a Player build as a preloaded asset. 3068 /// 3069 /// Note that attempting to assign a non-persisted <c>InputActionAsset</c> to this property will result in 3070 /// <c>ArgumentException</c> being thrown. 3071 /// </remarks> 3072 /// <seealso cref="InputActionAsset"/> 3073 /// <seealso cref="InputActionMap"/> 3074 /// <seealso cref="InputAction"/> 3075 /// <example> 3076 /// <code> 3077 /// public class MyScript : MonoBehaviour 3078 /// { 3079 /// InputAction move; 3080 /// InputAction jump; 3081 /// 3082 /// void Start() 3083 /// { 3084 /// // Get InputAction references from Project-wide input actions. 3085 /// if (InputSystem.actions) 3086 /// { 3087 /// move = InputSystem.actions.FindAction("Player/Move"); 3088 /// jump = InputSystem.actions.FindAction("Player/Jump"); 3089 /// } 3090 /// } 3091 /// } 3092 /// </code> 3093 /// </example> 3094 public static InputActionAsset actions 3095 { 3096 get => s_Manager?.actions; 3097 set 3098 { 3099 // Prevent this property from being assigned in play-mode. 3100 if (Application.isPlaying) 3101 throw new Exception($"Attempted to set property InputSystem.actions during Play-mode which is not supported. Assigning this property is only allowed in Edit-mode."); 3102 3103 // Note that we use reference equality to determine if object changed or not. 3104 // This allows us to change the associated value even if changed or destroyed. 3105 var current = s_Manager.actions; 3106 if (ReferenceEquals(current, value)) 3107 return; 3108 3109 var valueIsNotNull = value != null; 3110#if UNITY_EDITOR 3111 // Do not allow assigning non-persistent assets (pure in-memory objects) 3112 if (valueIsNotNull && !EditorUtility.IsPersistent(value)) 3113 throw new ArgumentException($"Assigning a non-persistent {nameof(InputActionAsset)} to this property is not allowed. The assigned asset need to be persisted on disc inside the /Assets folder."); 3114 3115 // Track reference to enable including it in built Players, note that it will discard any non-persisted 3116 // object reference 3117 ProjectWideActionsBuildProvider.actionsToIncludeInPlayerBuild = value; 3118#endif // UNITY_EDITOR 3119 3120 // Update underlying value 3121 s_Manager.actions = value; 3122 3123 // Note that we do not enable/disable any actions until play-mode 3124 } 3125 } 3126 3127 /// <summary> 3128 /// Event that is triggered if the instance assigned to property <see cref="actions"/> changes. 3129 /// </summary> 3130 /// <remarks> 3131 /// Note that any event handlers registered to this event will only receive callbacks in Edit mode 3132 /// since assigning <c>InputSystem.actions</c> is not possible in Play mode. 3133 /// </remarks> 3134 /// <seealso cref="actions"/> 3135 /// <seealso cref="InputActionAsset"/> 3136 public static event Action onActionsChange 3137 { 3138 add => s_Manager.onActionsChange += value; 3139 remove => s_Manager.onActionsChange -= value; 3140 } 3141 3142#endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 3143 3144 /// <summary> 3145 /// Event that is signalled when the state of enabled actions in the system changes or 3146 /// when actions are triggered. 3147 /// </summary> 3148 /// <remarks> 3149 /// The object received by the callback is either an <see cref="InputAction"/>, 3150 /// <see cref="InputActionMap"/>, or <see cref="InputActionAsset"/> depending on whether the 3151 /// <see cref="InputActionChange"/> affects a single action, an entire action map, or an 3152 /// entire action asset. 3153 /// 3154 /// For <see cref="InputActionChange.BoundControlsAboutToChange"/> and <see cref="InputActionChange.BoundControlsChanged"/>, 3155 /// the given object is an <see cref="InputAction"/> if the action is not part of an action map, 3156 /// an <see cref="InputActionMap"/> if the actions are part of a map but not part of an asset, and an 3157 /// <see cref="InputActionAsset"/> if the actions are part of an asset. In other words, the notification is 3158 /// sent for the topmost object in the hierarchy. 3159 /// </remarks> 3160 /// <example> 3161 /// <code> 3162 /// InputSystem.onActionChange += 3163 /// (obj, change) => 3164 /// { 3165 /// if (change == InputActionChange.ActionPerformed) 3166 /// { 3167 /// var action = (InputAction)obj; 3168 /// var control = action.activeControl; 3169 /// //... 3170 /// } 3171 /// else if (change == InputActionChange.ActionMapEnabled) 3172 /// { 3173 /// var actionMap = (InputActionMap)obj; 3174 /// //... 3175 /// } 3176 /// else if (change == InputActionChange.BoundControlsChanged) 3177 /// { 3178 /// // This is one way to deal with the fact that obj may be an InputAction 3179 /// // InputActionMap, or InputActionAsset and may be part of an InputActionAsset or not. 3180 /// var action = obj as InputAction; 3181 /// var actionMap = action?.actionMap ?? obj as InputActionMap; 3182 /// var actionAsset = actionMap?.asset ?? obj as InputActionAsset; 3183 /// 3184 /// // Note that if bound controls are changed on any map in an asset, there *will* 3185 /// // be a BoundControlsChanged notification for the entire asset. 3186 /// 3187 /// //... 3188 /// } 3189 /// }; 3190 /// </code> 3191 /// </example> 3192 /// <seealso cref="InputAction.controls"/> 3193 public static event Action<object, InputActionChange> onActionChange 3194 { 3195 add 3196 { 3197 if (value == null) 3198 throw new ArgumentNullException(nameof(value)); 3199 InputActionState.s_GlobalState.onActionChange.AddCallback(value); 3200 } 3201 remove 3202 { 3203 if (value == null) 3204 throw new ArgumentNullException(nameof(value)); 3205 InputActionState.s_GlobalState.onActionChange.RemoveCallback(value); 3206 } 3207 } 3208 3209 /// <summary> 3210 /// Register a new type of interaction with the system. 3211 /// </summary> 3212 /// <param name="type">Type that implements the interaction. Must support <see cref="InputInteraction"/>.</param> 3213 /// <param name="name">Name to register the interaction with. This is used in bindings to refer to the interaction 3214 /// (e.g. an interactions called "Tap" can be added to a binding by listing it in its <see cref="InputBinding.interactions"/> 3215 /// property). If no name is supplied, the short name of <paramref name="type"/> is used (with "Interaction" clipped off 3216 /// the name if the type name ends in that).</param> 3217 /// <example> 3218 /// <code> 3219 /// // Interaction that is performed when control resets to default state. 3220 /// public class ResetInteraction : InputInteraction 3221 /// { 3222 /// public void Process(ref InputInteractionContext context) 3223 /// { 3224 /// if (context.isWaiting &amp;&amp; !context.controlHasDefaultValue) 3225 /// context.Started(); 3226 /// else if (context.isStarted &amp;&amp; context.controlHasDefaultValue) 3227 /// context.Performed(); 3228 /// } 3229 /// } 3230 /// 3231 /// // Make interaction globally available on bindings. 3232 /// // "Interaction" suffix in type name will get dropped automatically. 3233 /// InputSystem.RegisterInteraction(typeof(ResetInteraction)); 3234 /// 3235 /// // Set up action with binding that has the 'reset' interaction applied to it. 3236 /// var action = new InputAction(binding: "/&lt;Gamepad>/buttonSouth", interactions: "reset"); 3237 /// </code> 3238 /// </example> 3239 /// <seealso cref="IInputInteraction"/> 3240 /// <seealso cref="RegisterInteraction{T}"/> 3241 /// <seealso cref="TryGetInteraction"/> 3242 /// <seealso cref="ListInteractions"/> 3243 public static void RegisterInteraction(Type type, string name = null) 3244 { 3245 if (type == null) 3246 throw new ArgumentNullException(nameof(type)); 3247 3248 if (string.IsNullOrEmpty(name)) 3249 { 3250 name = type.Name; 3251 if (name.EndsWith("Interaction")) 3252 name = name.Substring(0, name.Length - "Interaction".Length); 3253 } 3254 3255 s_Manager.interactions.AddTypeRegistration(name, type); 3256 } 3257 3258 /// <summary> 3259 /// Register a new type of interaction with the system. 3260 /// </summary> 3261 /// <typeparam name="T">Type that implements the interaction. Must support <see cref="InputInteraction"/>.</typeparam> 3262 /// <param name="name">Name to register the interaction with. This is used in bindings to refer to the interaction 3263 /// (e.g. an interactions called "Tap" can be added to a binding by listing it in its <see cref="InputBinding.interactions"/> 3264 /// property). If no name is supplied, the short name of <typeparamref name="T"/> is used (with "Interaction" clipped off 3265 /// the name if the type name ends in that).</param> 3266 /// <seealso cref="IInputInteraction"/> 3267 /// <seealso cref="RegisterInteraction(Type, string)"/> 3268 /// <seealso cref="TryGetInteraction"/> 3269 /// <seealso cref="ListInteractions"/> 3270 public static void RegisterInteraction<T>(string name = null) 3271 { 3272 RegisterInteraction(typeof(T), name); 3273 } 3274 3275 ////REVIEW: can we move the getters and listers somewhere else? maybe `interactions` and `processors` properties and such? 3276 3277 /// <summary> 3278 /// Search for a registered interaction type with the given name. 3279 /// </summary> 3280 /// <param name="name">Name of the registered interaction to search for.</param> 3281 /// <returns>The type of the interaction, if one was previously registered with the give name, otherwise null.</returns> 3282 /// <seealso cref="IInputInteraction"/> 3283 /// <seealso cref="RegisterInteraction"/> 3284 /// <seealso cref="ListInteractions"/> 3285 public static Type TryGetInteraction(string name) 3286 { 3287 if (string.IsNullOrEmpty(name)) 3288 throw new ArgumentNullException(nameof(name)); 3289 return s_Manager.interactions.LookupTypeRegistration(name); 3290 } 3291 3292 /// <summary> 3293 /// Gets the names of of all currently registered interactions. 3294 /// </summary> 3295 /// <returns>A list of currently registered interaction names.</returns> 3296 /// <seealso cref="IInputInteraction"/> 3297 /// <seealso cref="RegisterInteraction"/> 3298 /// <seealso cref="TryGetInteraction"/> 3299 public static IEnumerable<string> ListInteractions() 3300 { 3301 return s_Manager.interactions.names; 3302 } 3303 3304 /// <summary> 3305 /// Register a new type of binding composite with the system. 3306 /// </summary> 3307 /// <param name="type">Type that implements the binding composite. Must support <see cref="InputBindingComposite"/>.</param> 3308 /// <param name="name">Name to register the binding composite with. This is used in bindings to refer to the composite.</param> 3309 /// <seealso cref="InputBindingComposite"/> 3310 /// <seealso cref="RegisterBindingComposite{T}"/> 3311 /// <seealso cref="TryGetBindingComposite"/> 3312 public static void RegisterBindingComposite(Type type, string name) 3313 { 3314 if (type == null) 3315 throw new ArgumentNullException(nameof(type)); 3316 3317 if (string.IsNullOrEmpty(name)) 3318 { 3319 name = type.Name; 3320 if (name.EndsWith("Composite")) 3321 name = name.Substring(0, name.Length - "Composite".Length); 3322 } 3323 3324 s_Manager.composites.AddTypeRegistration(name, type); 3325 } 3326 3327 /// <summary> 3328 /// Register a new type of binding composite with the system. 3329 /// </summary> 3330 /// <typeparam name="T">Type that implements the binding composite. Must support <see cref="InputBindingComposite"/>.</typeparam> 3331 /// <param name="name">Name to register the binding composite with. This is used in bindings to refer to the composite.</param> 3332 /// <seealso cref="InputBindingComposite"/> 3333 /// <seealso cref="RegisterBindingComposite(Type, string)"/> 3334 /// <seealso cref="TryGetBindingComposite"/> 3335 public static void RegisterBindingComposite<T>(string name = null) 3336 { 3337 RegisterBindingComposite(typeof(T), name); 3338 } 3339 3340 /// <summary> 3341 /// Search for a registered binding composite type with the given name. 3342 /// </summary> 3343 /// <param name="name">Name of the registered binding composite to search for.</param> 3344 /// <returns>The type of the binding composite, if one was previously registered with the give name, otherwise null.</returns> 3345 /// <seealso cref="InputBindingComposite"/> 3346 /// <seealso cref="RegisterBindingComposite"/> 3347 public static Type TryGetBindingComposite(string name) 3348 { 3349 if (string.IsNullOrEmpty(name)) 3350 throw new ArgumentNullException(nameof(name)); 3351 return s_Manager.composites.LookupTypeRegistration(name); 3352 } 3353 3354 /// <summary> 3355 /// Disable all actions (and implicitly all action sets) that are currently enabled. 3356 /// </summary> 3357 /// <seealso cref="ListEnabledActions()"/> 3358 /// <seealso cref="InputAction.Disable"/> 3359 public static void DisableAllEnabledActions() 3360 { 3361 InputActionState.DisableAllActions(); 3362 } 3363 3364 /// <summary> 3365 /// Return a list of all the actions that are currently enabled in the system. 3366 /// </summary> 3367 /// <returns>A new list instance containing all currently enabled actions.</returns> 3368 /// <remarks> 3369 /// To avoid allocations, use <see cref="ListEnabledActions(List{UnityEngine.InputSystem.InputAction})"/>. 3370 /// </remarks> 3371 /// <seealso cref="InputAction.enabled"/> 3372 public static List<InputAction> ListEnabledActions() 3373 { 3374 var result = new List<InputAction>(); 3375 ListEnabledActions(result); 3376 return result; 3377 } 3378 3379 /// <summary> 3380 /// Add all actions that are currently enabled in the system to the given list. 3381 /// </summary> 3382 /// <param name="actions">List to add actions to.</param> 3383 /// <returns>The number of actions added to the list.</returns> 3384 /// <exception cref="ArgumentNullException"><paramref name="actions"/> is null.</exception> 3385 /// <remarks> 3386 /// If the capacity of the given list is large enough, this method will not allocate memory. 3387 /// </remarks> 3388 public static int ListEnabledActions(List<InputAction> actions) 3389 { 3390 if (actions == null) 3391 throw new ArgumentNullException(nameof(actions)); 3392 return InputActionState.FindAllEnabledActions(actions); 3393 } 3394 3395 #endregion 3396 3397 #region Remoting 3398 3399 /// <summary> 3400 /// The local InputRemoting instance which can mirror local input to a remote 3401 /// input system or can make input in a remote system available locally. 3402 /// </summary> 3403 /// <remarks> 3404 /// In the editor, this is always initialized. In players, this will be null 3405 /// if remoting is disabled (which it is by default in release players). 3406 /// </remarks> 3407 public static InputRemoting remoting => s_Remote; 3408 3409 #endregion 3410 3411 3412 /// <summary> 3413 /// The current version of the input system package. 3414 /// </summary> 3415 /// <value>Current version of the input system.</value> 3416 public static Version version => new Version(kAssemblyVersion); 3417 3418 /// <summary> 3419 /// Property for internal use that allows setting the player to run in the background. 3420 /// </summary> 3421 /// <remarks> 3422 /// Some platforms don't care about <see cref="Application.runInBackground"/> and for those we need to 3423 /// enable it manually through this propriety. 3424 /// </remarks> 3425 /// <param name="value">The boolean value to set to <see cref="NativeInputRuntime.runInBackground"/></param> 3426 public static bool runInBackground 3427 { 3428 get => s_Manager.m_Runtime.runInBackground; 3429 set => s_Manager.m_Runtime.runInBackground = value; 3430 } 3431 3432#if UNITY_INPUT_SYSTEM_PLATFORM_SCROLL_DELTA 3433 internal static float scrollWheelDeltaPerTick => InputRuntime.s_Instance.scrollWheelDeltaPerTick; 3434#else 3435 internal const float scrollWheelDeltaPerTick = 1.0f; 3436#endif 3437 3438 ////REVIEW: restrict metrics to editor and development builds? 3439 /// <summary> 3440 /// Get various up-to-date metrics about the input system. 3441 /// </summary> 3442 /// <value>Up-to-date metrics on input system activity.</value> 3443 public static InputMetrics metrics => s_Manager.metrics; 3444 3445 internal static InputManager s_Manager; 3446 internal static InputRemoting s_Remote; 3447 3448#if DEVELOPMENT_BUILD || UNITY_EDITOR 3449 internal static RemoteInputPlayerConnection s_RemoteConnection; 3450 3451 private static void SetUpRemoting() 3452 { 3453 Debug.Assert(s_Manager != null); 3454 3455 #if UNITY_EDITOR 3456 s_Remote = new InputRemoting(s_Manager); 3457 // NOTE: We use delayCall as our initial startup will run in editor initialization before 3458 // PlayerConnection is itself ready. If we call Bind() directly here, we won't 3459 // see any errors but the callbacks we register for will not trigger. 3460 EditorApplication.delayCall += SetUpRemotingInternal; 3461 #else 3462 s_Remote = new InputRemoting(s_Manager); 3463 SetUpRemotingInternal(); 3464 #endif 3465 } 3466 3467 private static void SetUpRemotingInternal() 3468 { 3469 if (s_RemoteConnection == null) 3470 { 3471 #if UNITY_EDITOR 3472 s_RemoteConnection = RemoteInputPlayerConnection.instance; 3473 s_RemoteConnection.Bind(EditorConnection.instance, false); 3474 #else 3475 s_RemoteConnection = ScriptableObject.CreateInstance<RemoteInputPlayerConnection>(); 3476 s_RemoteConnection.Bind(PlayerConnection.instance, PlayerConnection.instance.isConnected); 3477 #endif 3478 } 3479 3480 s_Remote.Subscribe(s_RemoteConnection); // Feed messages from players into editor. 3481 s_RemoteConnection.Subscribe(s_Remote); // Feed messages from editor into players. 3482 } 3483 3484 #if !UNITY_EDITOR 3485 private static bool ShouldEnableRemoting() 3486 { 3487#if UNITY_INCLUDE_TESTS 3488 var isRunningTests = true; 3489#else 3490 var isRunningTests = false; 3491#endif 3492 if (isRunningTests) 3493 return false; // Don't remote while running tests. 3494 return true; 3495 } 3496 3497 #endif 3498#endif // DEVELOPMENT_BUILD || UNITY_EDITOR 3499 3500 // The rest here is internal stuff to manage singletons, survive domain reloads, 3501 // and to support the reset ability for tests. 3502 static InputSystem() 3503 { 3504 #if UNITY_EDITOR 3505 InitializeInEditor(); 3506 #else 3507 InitializeInPlayer(); 3508 #endif 3509 } 3510 3511 ////FIXME: Unity is not calling this method if it's inside an #if block that is not 3512 //// visible to the editor; that shouldn't be the case 3513 [RuntimeInitializeOnLoadMethod(loadType: RuntimeInitializeLoadType.SubsystemRegistration)] 3514 private static void RunInitializeInPlayer() 3515 { 3516 // We're using this method just to make sure the class constructor is called 3517 // so we don't need any code in here. When the engine calls this method, the 3518 // class constructor will be run if it hasn't been run already. 3519 3520 // IL2CPP has a bug that causes the class constructor to not be run when 3521 // the RuntimeInitializeOnLoadMethod is invoked. So we need an explicit check 3522 // here until that is fixed (case 1014293). 3523 #if !UNITY_EDITOR 3524 if (s_Manager == null) 3525 InitializeInPlayer(); 3526 #endif 3527 } 3528 3529 // Initialization is triggered by accessing InputSystem. Some parts (like InputActions) 3530 // do not rely on InputSystem and thus can be accessed without tapping InputSystem. 3531 // This method will explicitly make sure we trigger initialization. 3532 internal static void EnsureInitialized() 3533 { 3534 } 3535 3536#if UNITY_EDITOR 3537 internal static InputSystemObject s_SystemObject; 3538 3539 internal static void InitializeInEditor(IInputRuntime runtime = null) 3540 { 3541 k_InputInitializeInEditorMarker.Begin(); 3542 3543 Reset(runtime: runtime); 3544 3545 var existingSystemObjects = Resources.FindObjectsOfTypeAll<InputSystemObject>(); 3546 if (existingSystemObjects != null && existingSystemObjects.Length > 0) 3547 { 3548 ////FIXME: does not preserve action map state 3549 3550 // We're coming back out of a domain reload. We're restoring part of the 3551 // InputManager state here but we're still waiting from layout registrations 3552 // that happen during domain initialization. 3553 3554 s_SystemObject = existingSystemObjects[0]; 3555 s_Manager.RestoreStateWithoutDevices(s_SystemObject.systemState.managerState); 3556 InputDebuggerWindow.ReviveAfterDomainReload(); 3557 3558 // Restore remoting state. 3559 s_RemoteConnection = s_SystemObject.systemState.remoteConnection; 3560 SetUpRemoting(); 3561 s_Remote.RestoreState(s_SystemObject.systemState.remotingState, s_Manager); 3562 3563 // Get manager to restore devices on first input update. By that time we 3564 // should have all (possibly updated) layout information in place. 3565 s_Manager.m_SavedDeviceStates = s_SystemObject.systemState.managerState.devices; 3566 s_Manager.m_SavedAvailableDevices = s_SystemObject.systemState.managerState.availableDevices; 3567 3568 // Restore editor settings. 3569 InputEditorUserSettings.s_Settings = s_SystemObject.systemState.userSettings; 3570 3571 // Get rid of saved state. 3572 s_SystemObject.systemState = new State(); 3573 } 3574 else 3575 { 3576 s_SystemObject = ScriptableObject.CreateInstance<InputSystemObject>(); 3577 s_SystemObject.hideFlags = HideFlags.HideAndDontSave; 3578 3579 // See if we have a remembered settings object. 3580 if (EditorBuildSettings.TryGetConfigObject(InputSettingsProvider.kEditorBuildSettingsConfigKey, 3581 out InputSettings settingsAsset)) 3582 { 3583 if (s_Manager.m_Settings.hideFlags == HideFlags.HideAndDontSave) 3584 ScriptableObject.DestroyImmediate(s_Manager.m_Settings); 3585 s_Manager.m_Settings = settingsAsset; 3586 s_Manager.ApplySettings(); 3587 } 3588 3589 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 3590 // See if we have a saved actions object 3591 var savedActions = ProjectWideActionsBuildProvider.actionsToIncludeInPlayerBuild; 3592 if (savedActions != null) 3593 s_Manager.actions = savedActions; 3594 #endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 3595 3596 InputEditorUserSettings.Load(); 3597 3598 SetUpRemoting(); 3599 } 3600 3601 Debug.Assert(settings != null); 3602 #if UNITY_EDITOR 3603 Debug.Assert(EditorUtility.InstanceIDToObject(settings.GetInstanceID()) != null, 3604 "InputSettings has lost its native object"); 3605 #endif 3606 3607 // If native backends for new input system aren't enabled, ask user whether we should 3608 // enable them (requires restart). We only ask once per session and don't ask when 3609 // running in batch mode. 3610 if (!s_SystemObject.newInputBackendsCheckedAsEnabled && 3611 !EditorPlayerSettingHelpers.newSystemBackendsEnabled && 3612 !s_Manager.m_Runtime.isInBatchMode) 3613 { 3614 const string dialogText = "This project is using the new input system package but the native platform backends for the new input system are not enabled in the player settings. " + 3615 "This means that no input from native devices will come through." + 3616 "\n\nDo you want to enable the backends? Doing so will *RESTART* the editor."; 3617 3618 if (EditorUtility.DisplayDialog("Warning", dialogText, "Yes", "No")) 3619 { 3620 EditorPlayerSettingHelpers.newSystemBackendsEnabled = true; 3621 EditorHelpers.RestartEditorAndRecompileScripts(); 3622 } 3623 } 3624 s_SystemObject.newInputBackendsCheckedAsEnabled = true; 3625 3626#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 3627 // Make sure project wide input actions are enabled. 3628 // Note that this will always fail if entering play-mode within editor since not yet in play-mode. 3629 EnableActions(); 3630#endif 3631 3632 RunInitialUpdate(); 3633 3634 k_InputInitializeInEditorMarker.End(); 3635 } 3636 3637 internal static void OnPlayModeChange(PlayModeStateChange change) 3638 { 3639 ////REVIEW: should we pause haptics when play mode is paused and stop haptics when play mode is exited? 3640 3641 switch (change) 3642 { 3643 case PlayModeStateChange.ExitingEditMode: 3644 s_SystemObject.settings = JsonUtility.ToJson(settings); 3645 s_SystemObject.exitEditModeTime = InputRuntime.s_Instance.currentTime; 3646 s_SystemObject.enterPlayModeTime = 0; 3647 3648 // InputSystem.actions is not setup yet 3649 break; 3650 3651 case PlayModeStateChange.EnteredPlayMode: 3652 s_SystemObject.enterPlayModeTime = InputRuntime.s_Instance.currentTime; 3653 s_Manager.SyncAllDevicesAfterEnteringPlayMode(); 3654 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 3655 EnableActions(); 3656 #endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 3657 break; 3658 3659 case PlayModeStateChange.ExitingPlayMode: 3660 s_Manager.LeavePlayMode(); 3661 break; 3662 3663 ////TODO: also nuke all callbacks installed on InputActions and InputActionMaps 3664 ////REVIEW: is there any other cleanup work we want to before? should we automatically nuke 3665 //// InputDevices that have been created with AddDevice<> during play mode? 3666 case PlayModeStateChange.EnteredEditMode: 3667#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 3668 DisableActions(false); 3669#endif 3670 3671 // Nuke all InputUsers. 3672 InputUser.ResetGlobals(); 3673 3674 // Nuke all InputActionMapStates. Releases their unmanaged memory. 3675 InputActionState.DestroyAllActionMapStates(); 3676 3677 // Clear the Action reference from all InputActionReference objects 3678 InputActionReference.ResetCachedAction(); 3679 3680 // Restore settings. 3681 if (!string.IsNullOrEmpty(s_SystemObject.settings)) 3682 { 3683 JsonUtility.FromJsonOverwrite(s_SystemObject.settings, settings); 3684 s_SystemObject.settings = null; 3685 settings.OnChange(); 3686 } 3687 3688 // reload input action assets marked as dirty from disk 3689 if (s_TrackedDirtyAssets == null) 3690 return; 3691 3692 foreach (var assetGuid in s_TrackedDirtyAssets) 3693 { 3694 var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid); 3695 3696 if (string.IsNullOrEmpty(assetPath)) 3697 continue; 3698 3699 AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); 3700 } 3701 3702 s_TrackedDirtyAssets.Clear(); 3703 3704 break; 3705 } 3706 } 3707 3708 private static void OnProjectChange() 3709 { 3710 ////TODO: use dirty count to find whether settings have actually changed 3711 // May have added, removed, moved, or renamed settings asset. Force a refresh 3712 // of the UI. 3713 InputSettingsProvider.ForceReload(); 3714 3715 // Also, if the asset holding our current settings got deleted, switch back to a 3716 // temporary settings object. 3717 // NOTE: We access m_Settings directly here to make sure we're not running into asserts 3718 // from the settings getter checking it has a valid object. 3719 if (EditorUtility.InstanceIDToObject(s_Manager.m_Settings.GetInstanceID()) == null) 3720 { 3721 var newSettings = ScriptableObject.CreateInstance<InputSettings>(); 3722 newSettings.hideFlags = HideFlags.HideAndDontSave; 3723 settings = newSettings; 3724 } 3725 } 3726 3727 private static HashSet<string> s_TrackedDirtyAssets; 3728 3729 /// <summary> 3730 /// Keep track of InputActionAsset assets that you want to re-load on exiting Play mode. This is useful because 3731 /// some user actions, such as adding a new input binding at runtime, change the in-memory representation of the 3732 /// input action asset and those changes survive when exiting Play mode. If you re-open an Input 3733 /// Action Asset in the Editor that has been changed this way, you see the new bindings that have been added 3734 /// during Play mode which you might not typically want to happen. 3735 /// 3736 /// You can avoid this by force re-loading from disk any asset that has been marked as dirty. 3737 /// </summary> 3738 /// <param name="asset"></param> 3739 internal static void TrackDirtyInputActionAsset(InputActionAsset asset) 3740 { 3741 if (s_TrackedDirtyAssets == null) 3742 s_TrackedDirtyAssets = new HashSet<string>(); 3743 3744 if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out string assetGuid, out long _) == false) 3745 return; 3746 3747 s_TrackedDirtyAssets.Add(assetGuid); 3748 } 3749 3750#else 3751 private static void InitializeInPlayer(IInputRuntime runtime = null, InputSettings settings = null) 3752 { 3753 if (settings == null) 3754 settings = Resources.FindObjectsOfTypeAll<InputSettings>().FirstOrDefault() ?? ScriptableObject.CreateInstance<InputSettings>(); 3755 3756 // No domain reloads in the player so we don't need to look for existing 3757 // instances. 3758 s_Manager = new InputManager(); 3759 s_Manager.Initialize(runtime ?? NativeInputRuntime.instance, settings); 3760 3761#if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION 3762 PerformDefaultPluginInitialization(); 3763#endif 3764 3765 // Automatically enable remoting in development players. 3766#if DEVELOPMENT_BUILD 3767 if (ShouldEnableRemoting()) 3768 SetUpRemoting(); 3769#endif 3770 3771#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS // && !UNITY_INCLUDE_TESTS 3772 // This is the point where we initialise project-wide actions for the Player 3773 EnableActions(); 3774#endif 3775 } 3776 3777#endif // UNITY_EDITOR 3778 3779 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] 3780 private static void RunInitialUpdate() 3781 { 3782 // Request an initial Update so that user methods such as Start and Awake 3783 // can access the input devices. 3784 // 3785 // NOTE: We use InputUpdateType.None here to run a "null" update. InputManager.OnBeforeUpdate() 3786 // and InputManager.OnUpdate() will both early out when comparing this to their update 3787 // mask but will still restore devices. This means we're not actually processing input, 3788 // but we will force the runtime to push its devices. 3789 Update(InputUpdateType.None); 3790 } 3791 3792#if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION 3793 private static void PerformDefaultPluginInitialization() 3794 { 3795 UISupport.Initialize(); 3796 3797 #if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WSA || UNITY_ANDROID || UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS 3798 XInputSupport.Initialize(); 3799 #endif 3800 3801 #if UNITY_EDITOR || UNITY_STANDALONE || UNITY_PS4 || UNITY_PS5 || UNITY_WSA || UNITY_ANDROID || UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS 3802 DualShockSupport.Initialize(); 3803 #endif 3804 3805 #if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WSA 3806 HIDSupport.Initialize(); 3807 #endif 3808 3809 #if UNITY_EDITOR || UNITY_ANDROID 3810 Android.AndroidSupport.Initialize(); 3811 #endif 3812 3813 #if UNITY_EDITOR || UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS 3814 iOS.iOSSupport.Initialize(); 3815 #endif 3816 3817 #if UNITY_EDITOR || UNITY_STANDALONE_OSX 3818 OSX.OSXSupport.Initialize(); 3819 #endif 3820 3821 #if UNITY_EDITOR || UNITY_WEBGL 3822 WebGL.WebGLSupport.Initialize(); 3823 #endif 3824 3825 #if UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_WSA 3826 Switch.SwitchSupportHID.Initialize(); 3827 #endif 3828 3829 #if UNITY_INPUT_SYSTEM_ENABLE_XR && (ENABLE_VR || UNITY_GAMECORE) && !UNITY_FORCE_INPUTSYSTEM_XR_OFF 3830 XR.XRSupport.Initialize(); 3831 #endif 3832 3833 #if UNITY_EDITOR || UNITY_STANDALONE_LINUX 3834 Linux.LinuxSupport.Initialize(); 3835 #endif 3836 3837 #if UNITY_EDITOR || UNITY_ANDROID || UNITY_IOS || UNITY_TVOS || UNITY_WSA || UNITY_VISIONOS 3838 OnScreen.OnScreenSupport.Initialize(); 3839 #endif 3840 3841 #if (UNITY_EDITOR || UNITY_STANDALONE) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT 3842 Steam.SteamSupport.Initialize(); 3843 #endif 3844 3845 #if UNITY_EDITOR 3846 UnityRemoteSupport.Initialize(); 3847 #endif 3848 } 3849 3850#endif // UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION 3851 3852 // For testing, we want the ability to push/pop system state even in the player. 3853 // However, we don't want it in release players. 3854#if DEVELOPMENT_BUILD || UNITY_EDITOR 3855 /// <summary> 3856 /// Return the input system to its default state. 3857 /// </summary> 3858 private static void Reset(bool enableRemoting = false, IInputRuntime runtime = null) 3859 { 3860 k_InputResetMarker.Begin(); 3861 3862#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 3863 // Note that in a test setup we might enter reset with project-wide actions already enabled but the 3864 // reset itself has pushed the action system state on the state stack. To avoid action state memory 3865 // problems we disable actions here and also request asset to be marked dirty and reimported. 3866 DisableActions(triggerSetupChanged: true); 3867 if (s_Manager != null) 3868 s_Manager.actions = null; 3869#endif // UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 3870 3871 // Some devices keep globals. Get rid of them by pretending the devices 3872 // are removed. 3873 if (s_Manager != null) 3874 { 3875 foreach (var device in s_Manager.devices) 3876 device.NotifyRemoved(); 3877 3878 s_Manager.UninstallGlobals(); 3879 } 3880 3881 // Create temporary settings. In the tests, this is all we need. But outside of tests,d 3882 // this should get replaced with an actual InputSettings asset. 3883 var settings = ScriptableObject.CreateInstance<InputSettings>(); 3884 settings.hideFlags = HideFlags.HideAndDontSave; 3885 3886 #if UNITY_EDITOR 3887 s_Manager = new InputManager(); 3888 s_Manager.Initialize( 3889 runtime: runtime ?? NativeInputRuntime.instance, 3890 settings: settings); 3891 3892 s_Manager.m_Runtime.onPlayModeChanged = OnPlayModeChange; 3893 s_Manager.m_Runtime.onProjectChange = OnProjectChange; 3894 3895 InputEditorUserSettings.s_Settings = new InputEditorUserSettings.SerializedState(); 3896 3897 if (enableRemoting) 3898 SetUpRemoting(); 3899 3900 #if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION 3901 PerformDefaultPluginInitialization(); 3902 #endif 3903 3904 #else 3905 InitializeInPlayer(runtime, settings); 3906 #endif 3907 3908 Mouse.s_PlatformMouseDevice = null; 3909 3910 InputEventListener.s_ObserverState = default; 3911 InputUser.ResetGlobals(); 3912 EnhancedTouchSupport.Reset(); 3913 3914 // This is the point where we initialise project-wide actions for the Editor, Editor Tests and Player Tests. 3915 // Note this is too early for editor ! actions is not setup yet. 3916 #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS 3917 EnableActions(); 3918 #endif 3919 3920 k_InputResetMarker.End(); 3921 } 3922 3923 /// <summary> 3924 /// Destroy the current setup of the input system. 3925 /// </summary> 3926 /// <remarks> 3927 /// NOTE: This also de-allocates data we're keeping in unmanaged memory! 3928 /// </remarks> 3929 private static void Destroy() 3930 { 3931 // NOTE: Does not destroy InputSystemObject. We want to destroy input system 3932 // state repeatedly during tests but we want to not create InputSystemObject 3933 // over and over. 3934 s_Manager.Destroy(); 3935 if (s_RemoteConnection != null) 3936 Object.DestroyImmediate(s_RemoteConnection); 3937 #if UNITY_EDITOR 3938 EditorInputControlLayoutCache.Clear(); 3939 InputDeviceDebuggerWindow.s_OnToolbarGUIActions.Clear(); 3940 InputEditorUserSettings.s_Settings = new InputEditorUserSettings.SerializedState(); 3941 #endif 3942 3943 s_Manager = null; 3944 s_RemoteConnection = null; 3945 s_Remote = null; 3946 } 3947 3948 /// <summary> 3949 /// Snapshot of the state used by the input system. 3950 /// </summary> 3951 /// <remarks> 3952 /// Can be taken across domain reloads. 3953 /// </remarks> 3954 [Serializable] 3955 internal struct State 3956 { 3957 [NonSerialized] public InputManager manager; 3958 [NonSerialized] public InputRemoting remote; 3959 [SerializeField] public RemoteInputPlayerConnection remoteConnection; 3960 [SerializeField] public InputManager.SerializedState managerState; 3961 [SerializeField] public InputRemoting.SerializedState remotingState; 3962 #if UNITY_EDITOR 3963 [SerializeField] public InputEditorUserSettings.SerializedState userSettings; 3964 [SerializeField] public string systemObject; 3965 #endif 3966 ////TODO: make these saved states capable of surviving domain reloads 3967 [NonSerialized] public ISavedState inputActionState; 3968 [NonSerialized] public ISavedState touchState; 3969 [NonSerialized] public ISavedState inputUserState; 3970 } 3971 3972 private static Stack<State> s_SavedStateStack; 3973 3974 internal static State GetSavedState() 3975 { 3976 return s_SavedStateStack.Peek(); 3977 } 3978 3979 /// <summary> 3980 /// Push the current state of the input system onto a stack and 3981 /// reset the system to its default state. 3982 /// </summary> 3983 /// <remarks> 3984 /// The save stack is not able to survive domain reloads. It is intended solely 3985 /// for use in tests. 3986 /// </remarks> 3987 internal static void SaveAndReset(bool enableRemoting = false, IInputRuntime runtime = null) 3988 { 3989 if (s_SavedStateStack == null) 3990 s_SavedStateStack = new Stack<State>(); 3991 3992 ////FIXME: does not preserve global state in InputActionState 3993 ////TODO: preserve InputUser state 3994 ////TODO: preserve EnhancedTouchSupport state 3995 3996 s_SavedStateStack.Push(new State 3997 { 3998 manager = s_Manager, 3999 remote = s_Remote, 4000 remoteConnection = s_RemoteConnection, 4001 managerState = s_Manager.SaveState(), 4002 remotingState = s_Remote?.SaveState() ?? new InputRemoting.SerializedState(), 4003 #if UNITY_EDITOR 4004 userSettings = InputEditorUserSettings.s_Settings, 4005 systemObject = JsonUtility.ToJson(s_SystemObject), 4006 #endif 4007 inputActionState = InputActionState.SaveAndResetState(), 4008 touchState = EnhancedTouch.Touch.SaveAndResetState(), 4009 inputUserState = InputUser.SaveAndResetState() 4010 }); 4011 4012 Reset(enableRemoting, runtime ?? InputRuntime.s_Instance); // Keep current runtime. 4013 } 4014 4015 ////FIXME: this method doesn't restore things like InputDeviceDebuggerWindow.onToolbarGUI 4016 /// <summary> 4017 /// Restore the state of the system from the last state pushed with <see cref="SaveAndReset"/>. 4018 /// </summary> 4019 internal static void Restore() 4020 { 4021 Debug.Assert(s_SavedStateStack != null && s_SavedStateStack.Count > 0); 4022 4023 // Load back previous state. 4024 var state = s_SavedStateStack.Pop(); 4025 4026 state.inputUserState.StaticDisposeCurrentState(); 4027 state.touchState.StaticDisposeCurrentState(); 4028 state.inputActionState.StaticDisposeCurrentState(); 4029 4030 // Nuke what we have. 4031 Destroy(); 4032 4033 state.inputUserState.RestoreSavedState(); 4034 state.touchState.RestoreSavedState(); 4035 state.inputActionState.RestoreSavedState(); 4036 4037 s_Manager = state.manager; 4038 s_Remote = state.remote; 4039 s_RemoteConnection = state.remoteConnection; 4040 4041 InputUpdate.Restore(state.managerState.updateState); 4042 4043 s_Manager.InstallRuntime(s_Manager.m_Runtime); 4044 s_Manager.InstallGlobals(); 4045 s_Manager.ApplySettings(); 4046 4047 #if UNITY_EDITOR 4048 InputEditorUserSettings.s_Settings = state.userSettings; 4049 JsonUtility.FromJsonOverwrite(state.systemObject, s_SystemObject); 4050 #endif 4051 4052 // Get devices that keep global lists (like Gamepad) to re-initialize them 4053 // by pretending the devices have been added. 4054 foreach (var device in devices) 4055 { 4056 device.NotifyAdded(); 4057 device.MakeCurrent(); 4058 } 4059 } 4060 4061#endif 4062 } 4063}