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<MyDevice>(
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<ButtonControl>("button");
198 /// axis = GetChildControl<AxisControl>("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<MyDevice>();
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<MyDevice>();
495 ///
496 /// // Register a precompiled version of the layout.
497 /// InputSystem.RegisterPrecompiledLayout<PrecompiledMyDevice>(PrecompiledMyDevice.metadata);
498 ///
499 /// // This implicitly uses the precompiled version.
500 /// InputSystem.AddDevice<MyDevice>();
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<StickControl>();
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<T>
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<float>
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("<Gamepad>/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<JitterProcessor>();
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<T>.
851 /// #if UNITY_EDITOR
852 /// public class JitterProcessorEditor : InputParameterEditor<JitterProcessor>
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<T>
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<float>
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("<Gamepad>/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<JitterProcessor>();
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<T>.
979 /// #if UNITY_EDITOR
980 /// public class JitterProcessorEditor : InputParameterEditor<JitterProcessor>
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<Gamepad>();
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<Gamepad>();
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<TDevice>()"/>
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<XRController>(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<Gamepad>(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: "<Gamepad>{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: "<Gamepad>{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>"<Gamepad>/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<Gamepad>();
2209 ///
2210 /// // Look up various controls on it.
2211 /// var aButton = InputSystem.FindControl("<Gamepad>/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("<Gamepad>");
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("<Gamepad>");
2250 ///
2251 /// // Find all sticks on all gamepads.
2252 /// InputSystem.FindControls("<Gamepad>/*stick");
2253 ///
2254 /// // Same but filter stick by type rather than by name.
2255 /// InputSystem.FindControls<StickControl>("<Gamepad>/*");
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<StateEvent>())
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 && 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 && !context.controlHasDefaultValue)
3225 /// context.Started();
3226 /// else if (context.isStarted && 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: "/<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}