using System; using System.Collections.Generic; using System.Reflection; using UnityEngine.InputSystem.Haptics; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.DualShock; using UnityEngine.InputSystem.HID; using UnityEngine.InputSystem.Users; using UnityEngine.InputSystem.XInput; using UnityEngine.InputSystem.Utilities; #if UNITY_EDITOR using UnityEditor; using UnityEngine.InputSystem.Editor; using UnityEditor.Networking.PlayerConnection; #else using System.Linq; using UnityEngine.Networking.PlayerConnection; #endif ////TODO: allow aliasing processors etc ////REVIEW: rename all references to "frame" to refer to "update" instead (e.g. wasPressedThisUpdate)? ////TODO: add APIs to get to the state blocks (equivalent to what you currently get with e.g. InputSystem.devices[0].currentStatePtr) ////FIXME: modal dialogs (or anything that interrupts normal Unity operation) are likely a problem for the system as is; there's a good //// chance the event queue will just get swamped; should be only the background queue though so I guess once it fills up we //// simply start losing input but it won't grow infinitely ////REVIEW: make more APIs thread-safe? ////REVIEW: it'd be great to be able to set up monitors from control paths (independently of actions; or should we just use actions?) ////REVIEW: have InputSystem.onTextInput that's fired directly from the event processing loop? //// (and allow text input events that have no associated target device? this way we don't need a keyboard to get text input) ////REVIEW: split lower-level APIs (anything mentioning events and state) off into InputSystemLowLevel API to make this API more focused? ////TODO: release native allocations when exiting namespace UnityEngine.InputSystem { /// /// This is the central hub for the input system. /// /// /// This class has the central APIs for working with the input system. You /// can manage devices available in the system (, /// , and related APIs) or extend /// the input system with custom functionality (, /// , , /// , and related APIs). /// /// To control haptics globally, you can use , , /// and . /// /// To enable and disable individual devices (such as devices), /// you can use and . /// /// The input system is initialized as part of Unity starting up. It is generally safe /// to call the APIs here from any of Unity's script callbacks. /// /// Note that, like most Unity APIs, most of the properties and methods in this API can only /// be called on the main thread. However, select APIs like can be /// called from threads. Where this is the case, it is stated in the documentation. /// [System.Diagnostics.CodeAnalysis.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.")] #if UNITY_EDITOR [InitializeOnLoad] #endif public static class InputSystem { #region Layouts /// /// Event that is signalled when the layout setup in the system changes. /// /// /// First parameter is the name of the layout that has changed and second parameter is the /// type of change that has occurred. /// /// /// /// InputSystem.onLayoutChange += /// (name, change) => /// { /// switch (change) /// { /// case InputControlLayoutChange.Added: /// Debug.Log($"New layout {name} has been added"); /// break; /// case InputControlLayoutChange.Removed: /// Debug.Log($"Layout {name} has been removed"); /// break; /// case InputControlLayoutChange.Replaced: /// Debug.Log($"Layout {name} has been updated"); /// break; /// } /// } /// /// /// /// public static event Action onLayoutChange { add { lock (s_Manager) s_Manager.onLayoutChange += value; } remove { lock (s_Manager) s_Manager.onLayoutChange -= value; } } /// /// Register a control layout based on a type. /// /// Type to derive a control layout from. Must be derived from . /// Name to use for the layout. If null or empty, the short name of the type () will be used. /// Optional device matcher. If this is supplied, the layout will automatically /// be instantiated for newly discovered devices that match the description. /// /// When the layout is instantiated, the system will reflect on all public fields and properties of the type /// which have a value type derived from or which are annotated with . /// /// The type can be annotated with for additional options /// but the attribute is not necessary for a type to be usable as a control layout. Note that if the type /// does have and has set , /// the system will not reflect on properties and fields in the type but do that on the given /// state type instead. /// /// /// /// // InputControlLayoutAttribute attribute is only necessary if you want /// // to override default behavior that occurs when registering your device /// // as a layout. /// // The most common use of InputControlLayoutAttribute is to direct the system /// // to a custom "state struct" through the `stateType` property. See below for details. /// [InputControlLayout(displayName = "My Device", stateType = typeof(MyDeviceState))] /// #if UNITY_EDITOR /// [InitializeOnLoad] /// #endif /// public class MyDevice : InputDevice /// { /// public ButtonControl button { get; private set; } /// public AxisControl axis { get; private set; } /// /// // Register the device. /// static MyDevice() /// { /// // In case you want instance of your device to automatically be created /// // when specific hardware is detected by the Unity runtime, you have to /// // add one or more "device matchers" (InputDeviceMatcher) for the layout. /// // These matchers are compared to an InputDeviceDescription received from /// // the Unity runtime when a device is connected. You can add them either /// // using InputSystem.RegisterLayoutMatcher() or by directly specifying a /// // matcher when registering the layout. /// InputSystem.RegisterLayout<MyDevice>( /// // For the sake of demonstration, let's assume your device is a HID /// // and you want to match by PID and VID. /// matches: new InputDeviceMatcher() /// .WithInterface("HID") /// .WithCapability("PID", 1234) /// .WithCapability("VID", 5678)); /// } /// /// // This is only to trigger the static class constructor to automatically run /// // in the player. /// [RuntimeInitializeOnLoadMethod] /// private static void InitializeInPlayer() {} /// /// protected override void FinishSetup() /// { /// base.FinishSetup(); /// button = GetChildControl<ButtonControl>("button"); /// axis = GetChildControl<AxisControl>("axis"); /// } /// } /// /// // A "state struct" describes the memory format used by a device. Each device can /// // receive and store memory in its custom format. InputControls are then connected /// // the individual pieces of memory and read out values from them. /// [StructLayout(LayoutKind.Explicit, Size = 32)] /// public struct MyDeviceState : IInputStateTypeInfo /// { /// // In the case of a HID (which we assume for the sake of this demonstration), /// // the format will be "HID". In practice, the format will depend on how your /// // particular device is connected and fed into the input system. /// // The format is a simple FourCC code that "tags" state memory blocks for the /// // device to give a base level of safety checks on memory operations. /// public FourCC format => return new FourCC('H', 'I', 'D'); /// /// // InputControlAttributes on fields tell the input system to create controls /// // for the public fields found in the struct. /// /// // Assume a 16bit field of buttons. Create one button that is tied to /// // bit #3 (zero-based). Note that buttons do not need to be stored as bits. /// // They can also be stored as floats or shorts, for example. /// [InputControl(name = "button", layout = "Button", bit = 3)] /// public ushort buttons; /// /// // Create a floating-point axis. The name, if not supplied, is taken from /// // the field. /// [InputControl(layout = "Axis")] /// public short axis; /// } /// /// /// /// Note that if is supplied, it will immediately be matched /// against the descriptions () of all available devices. /// If it matches any description where no layout matched before, a new device will immediately /// be created (except if suppressed by ). If it /// matches a description better (see ) than /// the currently used layout, the existing device will be a removed and a new device with /// the newly registered layout will be created. /// /// See or for examples of layouts. /// /// is null. /// public static void RegisterLayout(Type type, string name = null, InputDeviceMatcher? matches = null) { if (type == null) throw new ArgumentNullException(nameof(type)); if (string.IsNullOrEmpty(name)) name = type.Name; s_Manager.RegisterControlLayout(name, type); if (matches != null) s_Manager.RegisterControlLayoutMatcher(name, matches.Value); } /// /// Register a type as a control layout. /// /// Type to derive a control layout from. /// Name to use for the layout. If null or empty, the short name of the type will be used. /// Optional device matcher. If this is supplied, the layout will automatically /// be instantiated for newly discovered devices that match the description. /// /// This method is equivalent to calling with /// typeof(T). See that method for details of the layout registration process. /// /// public static void RegisterLayout(string name = null, InputDeviceMatcher? matches = null) where T : InputControl { RegisterLayout(typeof(T), name, matches); } /// /// Register a layout in JSON format. /// /// JSON data describing the layout. /// Optional name of the layout. If null or empty, the name is taken from the "name" /// property of the JSON data. If it is supplied, it will override the "name" property if present. If neither /// is supplied, an is thrown. /// Optional device matcher. If this is supplied, the layout will automatically /// be instantiated for newly discovered devices that match the description. /// is null or empty. /// No name has been supplied either through /// or the "name" JSON property. /// /// The JSON format makes it possible to create new device and control layouts completely /// in data. They have to ultimately be based on a layout backed by a C# type, however (e.g. /// ). /// /// Note that most errors in layouts will only be detected when instantiated (i.e. when a device or control is /// being created from a layout). The JSON data will, however, be parsed once on registration to check for a /// device description in the layout. JSON format errors will thus be detected during registration. /// /// /// /// InputSystem.RegisterLayout(@" /// { /// ""name"" : ""MyDevice"", /// ""controls"" : [ /// { /// ""name"" : ""myButton"", /// ""layout"" : ""Button"" /// } /// ] /// } /// ); /// /// /// /// public static void RegisterLayout(string json, string name = null, InputDeviceMatcher? matches = null) { s_Manager.RegisterControlLayout(json, name); if (matches != null) s_Manager.RegisterControlLayoutMatcher(name, matches.Value); } /// /// Register a layout that applies overrides to one or more other layouts. /// /// Layout in JSON format. /// Optional name of the layout. If null or empty, the name is taken from the "name" /// property of the JSON data. If it is supplied, it will override the "name" property if present. If neither /// is supplied, an is thrown. /// /// Layout overrides are layout pieces that are applied on top of existing layouts. /// This can be used to modify any layout in the system non-destructively. The process works the /// same as extending an existing layout except that instead of creating a new layout /// by merging the derived layout and the base layout, the overrides are merged /// directly into the base layout. /// /// The layout merging logic used for overrides, is the same as the one used for /// derived layouts, i.e. . /// /// Layouts used as overrides look the same as normal layouts and have the same format. /// The only difference is that they are explicitly registered as overrides. /// /// Note that unlike "normal" layouts, layout overrides have the ability to extend /// multiple base layouts. The changes from the override will simply be merged into /// each of the layouts it extends. Use the extendMultiple rather than the /// extend property in JSON to give a list of base layouts instead of a single /// one. /// /// /// /// // Override default button press points on the gamepad triggers. /// InputSystem.RegisterLayoutOverride(@" /// { /// ""name"" : ""CustomTriggerPressPoints"", /// ""extend"" : ""Gamepad"", /// ""controls"" : [ /// { ""name"" : ""leftTrigger"", ""parameters"" : ""pressPoint=0.25"" }, /// { ""name"" : ""rightTrigger"", ""parameters"" : ""pressPoint=0.25"" } /// ] /// } /// "); /// /// /// public static void RegisterLayoutOverride(string json, string name = null) { s_Manager.RegisterControlLayout(json, name, isOverride: true); } /// /// Add an additional device matcher to an existing layout. /// /// Name of the device layout that should be instantiated if /// matches an of a discovered device. /// Specification to match against instances. /// /// Each device layout can have zero or more matchers associated with it. If any one of the /// matchers matches a given (see ) /// better than any other matcher (for the same or any other layout), then the given layout /// will be used for the discovered device. /// /// Note that registering a matcher may immediately lead to devices being created or recreated. /// If matches any devices currently on the list of unsupported devices /// (see ), new s will be created /// using the layout called . Also, if /// matches the description of a device better than the matcher (if any) for the device's currently /// used layout, the device will be recreated using the given layout. /// /// is null or empty/ /// is empty (). /// /// public static void RegisterLayoutMatcher(string layoutName, InputDeviceMatcher matcher) { s_Manager.RegisterControlLayoutMatcher(layoutName, matcher); } /// /// Add an additional device matcher to the layout registered for . /// /// A device matcher. /// Type that has been registered as a layout. See . /// /// Calling this method is equivalent to calling /// with the name under which has been registered. /// /// is empty () /// -or- has not been registered as a layout. public static void RegisterLayoutMatcher(InputDeviceMatcher matcher) where TDevice : InputDevice { s_Manager.RegisterControlLayoutMatcher(typeof(TDevice), matcher); } /// /// Register a builder that delivers an instance on demand. /// /// Method to invoke to generate a layout when the layout is chosen. /// Should not cache the layout but rather return a fresh instance every time. /// Name under which to register the layout. If a layout with the same /// name is already registered, the call to this method will replace the existing layout. /// Name of the layout that the layout returned from /// will be based on. The system needs to know this in advance in order to update devices /// correctly if layout registrations in the system are changed. /// Optional matcher for an . If supplied, /// it is equivalent to calling . /// is null -or- /// is null or empty. /// /// Layout builders are most useful for procedurally building device layouts from metadata /// supplied by external systems. A good example is where the "HID" standard /// includes a way for input devices to describe their various inputs and outputs in the form /// of a . While not sufficient to build a perfectly robust /// , these descriptions are usually enough to at least make the device /// work out-of-the-box to some extent. /// /// The builder method would usually use to build the /// actual layout. /// /// /// /// InputSystem.RegisterLayoutBuilder( /// () => /// { /// var builder = new InputControlLayout.Builder() /// .WithType<MyDevice>(); /// builder.AddControl("button1").WithLayout("Button"); /// return builder.Build(); /// }, "MyCustomLayout" /// } /// /// /// /// Layout builders can be used in combination with to /// build layouts dynamically for devices as they are connected to the system. /// /// Be aware that the same builder must not build different layouts. Each /// layout registered in the system is considered to be immutable for as long as it /// is registered. So, if a layout builder is registered under the name "Custom", for /// example, then every time the builder is invoked, it must return the same identical /// . /// /// /// public static void RegisterLayoutBuilder(Func buildMethod, string name, string baseLayout = null, InputDeviceMatcher? matches = null) { if (buildMethod == null) throw new ArgumentNullException(nameof(buildMethod)); if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); s_Manager.RegisterControlLayoutBuilder(buildMethod, name, baseLayout: baseLayout); if (matches != null) s_Manager.RegisterControlLayoutMatcher(name, matches.Value); } /// /// Remove an already registered layout from the system. /// /// Name of the layout to remove. Note that layout names are case-insensitive. /// /// Note that removing a layout also removes all devices that directly or indirectly /// use the layout. /// /// This method can be used to remove both control or device layouts. /// public static void RemoveLayout(string name) { s_Manager.RemoveControlLayout(name); } /// /// Try to match a description for an input device to a layout. /// /// Description of an input device. /// Name of the layout that has been matched to the given description or null if no /// matching layout was found. /// /// This method performs the same matching process that is invoked if a device is reported /// by the Unity runtime or using . The result /// depends on the matches () registered for the device /// layout in the system. /// /// /// /// var layoutName = InputSystem.TryFindMatchingLayout( /// new InputDeviceDescription /// { /// interface = "XInput", /// product = "Xbox Wired Controller", /// manufacturer = "Microsoft" /// } /// ); /// /// /// /// /// public static string TryFindMatchingLayout(InputDeviceDescription deviceDescription) { return s_Manager.TryFindMatchingControlLayout(ref deviceDescription); } /// /// Return a list with the names of all layouts that have been registered. /// /// A list of layout names. /// /// /// public static IEnumerable ListLayouts() { return s_Manager.ListControlLayouts(); } /// /// List all the layouts that are based on the given layout. /// /// Name of a registered layout. /// is null or empty. /// The names of all registered layouts based on . /// /// The list will not include layout overrides (see ). /// /// /// /// // List all gamepad layouts in the system. /// Debug.Log(string.Join("\n", InputSystem.ListLayoutsBasedOn("Gamepad")); /// /// /// public static IEnumerable ListLayoutsBasedOn(string baseLayout) { if (string.IsNullOrEmpty(baseLayout)) throw new ArgumentNullException(nameof(baseLayout)); return s_Manager.ListControlLayouts(basedOn: baseLayout); } /// /// Load a registered layout. /// /// Name of the layout to load. Note that layout names are case-insensitive. /// is null or empty. /// The constructed layout instance or null if no layout of the given name could be found. /// /// The result of this method is what's called a "fully merged" layout, i.e. a layout with /// the information of all the base layouts as well as from all overrides merged into it. See /// for details. /// /// What this means in practice is that all inherited controls and settings will be present /// on the layout. /// /// /// // List all controls defined for gamepads. /// var gamepadLayout = InputSystem.LoadLayout("Gamepad"); /// foreach (var control in gamepadLayout.controls) /// { /// // There may be control elements that are not introducing new controls but rather /// // change settings on controls added indirectly by other layouts referenced from /// // Gamepad. These are not adding new controls so we skip them here. /// if (control.isModifyingExistingControl) /// continue; /// /// Debug.Log($"Control: {control.name} ({control.layout])"); /// } /// /// /// However, note that controls which are added from other layouts referenced by the loaded layout /// will not necessarily be visible on it (they will only if referenced by a /// where is true). /// For example, let's assume we have the following layout which adds a device with a single stick. /// /// /// /// InputSystem.RegisterLayout(@" /// { /// ""name"" : ""DeviceWithStick"", /// ""controls"" : [ /// { ""name"" : ""stick"", ""layout"" : ""Stick"" } /// ] /// } /// "); /// /// /// /// If we load this layout, the "stick" control will be visible on the layout but the /// X and Y (as well as up/down/left/right) controls added by the "Stick" layout will /// not be. /// /// public static InputControlLayout LoadLayout(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); ////FIXME: this will intern the name even if the operation fails return s_Manager.TryLoadControlLayout(new InternedString(name)); } /// /// Load the layout registered for the given type. /// /// An InputControl type. /// The layout registered for or null if no /// such layout exists. /// /// This method is equivalent to calling with the name /// of the layout under which has been registered. /// /// /// /// // Load the InputControlLayout generated from StickControl. /// var stickLayout = InputSystem.LoadLayout<StickControl>(); /// /// /// /// public static InputControlLayout LoadLayout() where TControl : InputControl { return s_Manager.TryLoadControlLayout(typeof(TControl)); } /// /// Return the name of the layout that the layout registered as /// is based on. /// /// Name of a layout as registered with a method such as . Case-insensitive. /// Name of the immediate parent layout of or null if no layout /// with the given name is registered or if it is not based on another layout or if it is a layout override. /// is null or empty. /// /// This method does not work for layout overrides (which can be based on multiple base layouts). To find /// out which layouts a specific override registered with is based on, /// load the layout with and inspect . /// This method will return null when is the name of a layout override. /// /// One advantage of this method over calling and looking at /// is that this method does not have to actually load the layout but instead only performs a simple lookup. /// /// /// /// // Prints "Pointer". /// Debug.Log(InputSystem.GetNameOfBaseLayout("Mouse")); /// /// // Also works for control layouts. Prints "Axis". /// Debug.Log(InputSystem.GetNameOfBaseLayout("Button")); /// /// /// /// public static string GetNameOfBaseLayout(string layoutName) { if (string.IsNullOrEmpty(layoutName)) throw new ArgumentNullException(nameof(layoutName)); var internedLayoutName = new InternedString(layoutName); if (InputControlLayout.s_Layouts.baseLayoutTable.TryGetValue(internedLayoutName, out var result)) return result; return null; } /// /// Check whether the first layout is based on the second. /// /// Name of a registered . /// Name of a registered . /// True if is based on . /// is null or empty -or- /// is null or empty. /// /// This is /// /// /// public static bool IsFirstLayoutBasedOnSecond(string firstLayoutName, string secondLayoutName) { if (string.IsNullOrEmpty(firstLayoutName)) throw new ArgumentNullException(nameof(firstLayoutName)); if (string.IsNullOrEmpty(secondLayoutName)) throw new ArgumentNullException(nameof(secondLayoutName)); var internedFirstName = new InternedString(firstLayoutName); var internedSecondName = new InternedString(secondLayoutName); if (internedFirstName == internedSecondName) return true; return InputControlLayout.s_Layouts.IsBasedOn(internedSecondName, internedFirstName); } #endregion #region Processors /// /// Register an with the system. /// /// Type that implements . /// Name to use for the processor. If null or empty, name will be taken from the short name /// of (if it ends in "Processor", that suffix will be clipped from the name). Names /// are case-insensitive. /// /// Processors are used by both bindings (see ) and by controls /// (see ) to post-process input values as they are being requested /// from calls such as or . /// /// /// /// // Let's say that we want to define a processor that adds some random jitter to its input. /// // We have to pick a value type to operate on if we want to derive from InputProcessor<T> /// // so we go with float here. /// // /// // Also, as we will need to place our call to RegisterProcessor somewhere, we add attributes /// // to hook into Unity's initialization. This works differently in the editor and in the player, /// // so we use both [InitializeOnLoad] and [RuntimeInitializeOnLoadMethod]. /// #if UNITY_EDITOR /// [InitializeOnLoad] /// #endif /// public class JitterProcessor : InputProcessor<float> /// { /// // Add a parameter that defines the amount of jitter we apply. /// // This will be editable in the Unity editor UI and can be set /// // programmatically in code. For example: /// // /// // myAction.AddBinding("<Gamepad>/rightTrigger", /// // processors: "jitter(amount=0.1)"); /// // /// [Tooltip("Amount of jitter to apply. Will add a random value in the range [-amount..amount] " /// + "to each input value.)] /// public float amount; /// /// // Process is called when an input value is read from a control. This is /// // where we perform our jitter. /// public override float Process(float value, InputControl control) /// { /// return float + Random.Range(-amount, amount); /// } /// /// // [InitializeOnLoad] will call the static class constructor which /// // we use to call Register. /// #if UNITY_EDITOR /// static JitterProcessor() /// { /// Register(); /// } /// #endif /// /// // [RuntimeInitializeOnLoadMethod] will make sure that Register gets called /// // in the player on startup. /// // NOTE: This will also get called when going into play mode in the editor. In that /// // case we get two calls to Register instead of one. We don't bother with that /// // here. Calling RegisterProcessor twice here doesn't do any harm. /// [RuntimeInitializeOnLoadMethod] /// static void Register() /// { /// // We don't supply a name here. The input system will take "JitterProcessor" /// // and automatically snip off the "Processor" suffix thus leaving us with /// // a name of "Jitter" (all this is case-insensitive). /// InputSystem.RegisterProcessor<JitterProcessor>(); /// } /// } /// /// // It doesn't really make sense in our case as the default parameter editor is just /// // fine (it will pick up the tooltip we defined above) but let's say we want to replace /// // the default float edit field we get on the "amount" parameter with a slider. We can /// // do so by defining a custom parameter editor. /// // /// // NOTE: We don't need to have a registration call here. The input system will automatically /// // find our parameter editor based on the JitterProcessor type parameter we give to /// // InputParameterEditor<T>. /// #if UNITY_EDITOR /// public class JitterProcessorEditor : InputParameterEditor<JitterProcessor> /// { /// public override void OnGUI() /// { /// target.amount = EditorGUILayout.Slider(m_AmountLabel, target.amount, 0, 0.25f); /// } /// /// private GUIContent m_AmountLabel = new GUIContent("Amount", /// "Amount of jitter to apply. Will add a random value in the range [-amount..amount] " /// + "to each input value.); /// } /// #endif /// /// /// /// Note that it is allowed to register the same processor type multiple types with /// different names. When doing so, the first registration is considered as the "proper" /// name for the processor and all subsequent registrations will be considered aliases. /// /// See the manual for more details. /// /// /// /// /// /// public static void RegisterProcessor(Type type, string name = null) { if (type == null) throw new ArgumentNullException(nameof(type)); if (string.IsNullOrEmpty(name)) { name = type.Name; if (name.EndsWith("Processor")) name = name.Substring(0, name.Length - "Processor".Length); } s_Manager.processors.AddTypeRegistration(name, type); } /// /// Register an with the system. /// /// Type that implements . /// Name to use for the processor. If null or empty, name will be taken from the short name /// of (if it ends in "Processor", that suffix will be clipped from the name). Names /// are case-insensitive. /// /// Processors are used by both bindings (see ) and by controls /// (see ) to post-process input values as they are being requested /// from calls such as or . /// /// /// /// // Let's say that we want to define a processor that adds some random jitter to its input. /// // We have to pick a value type to operate on if we want to derive from InputProcessor<T> /// // so we go with float here. /// // /// // Also, as we will need to place our call to RegisterProcessor somewhere, we add attributes /// // to hook into Unity's initialization. This works differently in the editor and in the player, /// // so we use both [InitializeOnLoad] and [RuntimeInitializeOnLoadMethod]. /// #if UNITY_EDITOR /// [InitializeOnLoad] /// #endif /// public class JitterProcessor : InputProcessor<float> /// { /// // Add a parameter that defines the amount of jitter we apply. /// // This will be editable in the Unity editor UI and can be set /// // programmatically in code. For example: /// // /// // myAction.AddBinding("<Gamepad>/rightTrigger", /// // processors: "jitter(amount=0.1)"); /// // /// [Tooltip("Amount of jitter to apply. Will add a random value in the range [-amount..amount] " /// + "to each input value.)] /// public float amount; /// /// // Process is called when an input value is read from a control. This is /// // where we perform our jitter. /// public override float Process(float value, InputControl control) /// { /// return float + Random.Range(-amount, amount); /// } /// /// // [InitializeOnLoad] will call the static class constructor which /// // we use to call Register. /// #if UNITY_EDITOR /// static JitterProcessor() /// { /// Register(); /// } /// #endif /// /// // [RuntimeInitializeOnLoadMethod] will make sure that Register gets called /// // in the player on startup. /// // NOTE: This will also get called when going into play mode in the editor. In that /// // case we get two calls to Register instead of one. We don't bother with that /// // here. Calling RegisterProcessor twice here doesn't do any harm. /// [RuntimeInitializeOnLoadMethod] /// static void Register() /// { /// // We don't supply a name here. The input system will take "JitterProcessor" /// // and automatically snip off the "Processor" suffix thus leaving us with /// // a name of "Jitter" (all this is case-insensitive). /// InputSystem.RegisterProcessor<JitterProcessor>(); /// } /// } /// /// // It doesn't really make sense in our case as the default parameter editor is just /// // fine (it will pick up the tooltip we defined above) but let's say we want to replace /// // the default float edit field we get on the "amount" parameter with a slider. We can /// // do so by defining a custom parameter editor. /// // /// // NOTE: We don't need to have a registration call here. The input system will automatically /// // find our parameter editor based on the JitterProcessor type parameter we give to /// // InputParameterEditor<T>. /// #if UNITY_EDITOR /// public class JitterProcessorEditor : InputParameterEditor<JitterProcessor> /// { /// public override void OnGUI() /// { /// target.amount = EditorGUILayout.Slider(m_AmountLabel, target.amount, 0, 0.25f); /// } /// /// private GUIContent m_AmountLabel = new GUIContent("Amount", /// "Amount of jitter to apply. Will add a random value in the range [-amount..amount] " /// + "to each input value.); /// } /// #endif /// /// /// /// Note that it is allowed to register the same processor type multiple types with /// different names. When doing so, the first registration is considered as the "proper" /// name for the processor and all subsequent registrations will be considered aliases. /// /// See the manual for more details. /// /// /// /// /// /// public static void RegisterProcessor(string name = null) { RegisterProcessor(typeof(T), name); } /// /// Return the processor type registered under the given name. If no such processor /// has been registered, return null. /// /// Name of processor. Case-insensitive. /// is null or empty. /// The given processor type or null if not found. /// public static Type TryGetProcessor(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); return s_Manager.processors.LookupTypeRegistration(name); } /// /// List the names of all processors have been registered. /// /// List of registered processors. /// /// Note that the result will include both "proper" names and aliases registered /// for processors. If, for example, a given type JitterProcessor has been registered /// under both "Jitter" and "Randomize", it will appear in the list with both those names. /// /// /// public static IEnumerable ListProcessors() { return s_Manager.processors.names; } #endregion #region Devices /// /// The list of currently connected devices. /// /// Currently connected devices. /// /// Note that accessing this property does not allocate. It gives read-only access /// directly to the system's internal array of devices. /// /// The value returned by this property should not be held on to. When the device /// setup in the system changes, any value previously returned by this property /// may become invalid. Query the property directly whenever you need it. /// /// /// public static ReadOnlyArray devices => s_Manager.devices; /// /// Devices that have been disconnected but are retained by the input system in case /// they are plugged back in. /// /// Devices that have been retained by the input system in case they are plugged /// back in. /// /// During gameplay it is undesirable to have the system allocate and release managed memory /// as devices are unplugged and plugged back in as it would ultimately lead to GC spikes /// during gameplay. To avoid that, input devices that have been reported by the /// runtime and are removed through events are retained /// by the system and then reused if the device is plugged back in. /// /// Note that the devices moved to disconnected status will still see a /// notification and a notification when plugged back in. /// /// To determine if a newly discovered device is one we have seen before, the system uses a /// simple approach of comparing device descriptions. /// Note that there can be errors and a device may be incorrectly classified as /// when in fact it is a different device from before. The problem is that based on information /// made available by platforms, it can be inherently difficult to determine whether a device is /// indeed the very same one. /// /// For example, it is often not possible to determine with 100% certainty whether an identical looking device /// to one we've previously seen on a different USB port is indeed the very same device. OSs will usually /// reattach a USB device to its previous instance if it is plugged into the same USB port but create a /// new instance of the same device is plugged into a different port. /// /// For devices that do relay their serials the matching /// is reliable. /// /// The list can be purged by calling . Doing so, will release /// all reference we hold to the devices or any controls inside of them and allow the devices to be /// reclaimed by the garbage collector. /// /// Note that if you call explicitly, the given device is not retained /// by the input system and will not appear on this list. /// /// Also note that devices on this list will be lost when domain reloads happen in the editor (i.e. on /// script recompilation and when entering play mode). /// /// public static ReadOnlyArray disconnectedDevices => new ReadOnlyArray(s_Manager.m_DisconnectedDevices, 0, s_Manager.m_DisconnectedDevicesCount); /// /// Event that is signalled when the device setup in the system changes. /// /// Callback when device setup ni system changes. /// /// This can be used to detect when devices are added or removed as well as /// detecting when existing devices change their configuration. /// /// /// /// InputSystem.onDeviceChange += /// (device, change) => /// { /// switch (change) /// { /// case InputDeviceChange.Added: /// Debug.Log("Device added: " + device); /// break; /// case InputDeviceChange.Removed: /// Debug.Log("Device removed: " + device); /// break; /// case InputDeviceChange.ConfigurationChanged: /// Debug.Log("Device configuration changed: " + device); /// break; /// } /// }; /// /// /// /// Delegate reference is null. /// /// /// public static event Action onDeviceChange { add { if (value == null) throw new ArgumentNullException(nameof(value)); lock (s_Manager) s_Manager.onDeviceChange += value; } remove { if (value == null) throw new ArgumentNullException(nameof(value)); lock (s_Manager) s_Manager.onDeviceChange -= value; } } ////REVIEW: this one isn't really well-designed and the means of intercepting communication //// with the backend should be revisited >1.0 /// /// Event that is signalled when an is sent to /// an . /// /// Event that gets signalled on s. /// /// This can be used to intercept commands and optionally handle them without them reaching /// the . /// /// The first delegate in the list that returns a result other than null is considered /// to have handled the command. If a command is handled by a delegate in the list, it will /// not be sent on to the runtime. /// /// Delegate reference is null. /// /// public static event InputDeviceCommandDelegate onDeviceCommand { add { if (value == null) throw new ArgumentNullException(nameof(value)); lock (s_Manager) s_Manager.onDeviceCommand += value; } remove { if (value == null) throw new ArgumentNullException(nameof(value)); lock (s_Manager) s_Manager.onDeviceCommand -= value; } } /// /// Event that is signalled when the system is trying to match a layout to /// a device it has discovered. /// /// /// This event allows customizing the layout discovery process and to generate /// layouts on the fly, if need be. When a device is reported from the Unity /// runtime or through , it is /// reported in the form of an . The system /// will take that description and run it through all the s /// that have been registered for layouts (). /// Based on that, it will come up with either no matching layout or with a single /// layout that has the highest matching score according to (or, in case multiple layouts have /// the same score, the first one to achieve that score -- which is quasi-non-deterministic). /// /// It will then take this layout name (which, again, may be empty) and invoke this /// event here passing it not only the layout name but also information such as the /// for the device. Each of the callbacks hooked /// into the event will be run in turn. The first one to return a string /// that is not null and not empty will cause a switch from the layout the /// system has chosen to the layout that has been returned by the callback. The remaining /// layouts after that will then be invoked with that newly selected name but will not /// be able to change the name anymore. /// /// If none of the callbacks returns a string that is not null or empty, /// the system will stick with the layout that it had initially selected. /// /// Once all callbacks have been run, the system will either have a final layout /// name or not. If it does, a device is created using that layout. If it does not, /// no device is created. /// /// One thing this allows is to generate callbacks on the fly. Let's say that if /// an input device is reported with the "Custom" interface, we want to generate /// a layout for it on the fly. For details about how to build layouts dynamically /// from code, see and . /// /// /// /// InputSystem.onFindLayoutForDevice += /// (deviceId, description, matchedLayout, runtime) => /// { /// // If the system does have a matching layout, we do nothing. /// // This could be the case, for example, if we already generated /// // a layout for the device or if someone explicitly registered /// // a layout. /// if (!string.IsNullOrEmpty(matchedLayout)) /// return null; // Tell system we did nothing. /// /// // See if the reported device uses the "Custom" interface. We /// // are only interested in those. /// if (description.interfaceName != "Custom") /// return null; // Tell system we did nothing. /// /// // So now we know that we want to build a layout on the fly /// // for this device. What we do is to register what's called a /// // layout builder. These can use C# code to build an InputControlLayout /// // on the fly. /// /// // First we need to come up with a sufficiently unique name for the layout /// // under which we register the builder. This will usually involve some /// // information from the InputDeviceDescription we have been supplied with. /// // Let's say we can sufficiently tell devices on our interface apart by /// // product name alone. So we just do this: /// var layoutName = "Custom" + description.product; /// /// // We also need an InputDeviceMatcher that in the future will automatically /// // select our newly registered layout whenever a new device of the same type /// // is connected. We can get one simply like so: /// var matcher = InputDeviceMatcher.FromDescription(description); /// /// // With these pieces in place, we can register our builder which /// // mainly consists of a delegate that will get invoked when an instance /// // of InputControlLayout is needed for the layout. /// InputSystem.RegisterLayoutBuilder( /// () => /// { /// // Here is where we do the actual building. In practice, /// // this would probably look at the 'capabilities' property /// // of the InputDeviceDescription we got and create a tailor-made /// // layout. But what you put in the layout here really depends on /// // the specific use case you have. /// // /// // We just add some preset things here which should still sufficiently /// // serve as a demonstration. /// // /// // Note that we can base our layout here on whatever other layout /// // in the system. We could extend Gamepad, for example. If we don't /// // choose a base layout, the system automatically implies InputDevice. /// /// var builder = new InputControlLayout.Builder() /// .WithDisplayName(description.product); /// /// // Add controls. /// builder.AddControl("stick") /// .WithLayout("Stick"); /// /// return builder.Build(); /// }, /// layoutName, /// matches: matcher); /// /// // So, we want the system to use our layout for the device that has just /// // been connected. We return it from this callback to do that. /// return layoutName; /// }; /// /// /// /// Note that it may appear like one could simply use /// like below instead of going through onFindLayoutForDevice. /// /// /// /// InputSystem.RegisterLayoutBuilder( /// () => /// { /// // Layout building code from above... /// }, /// "CustomLayout", /// matches: new InputDeviceMatcher().WithInterface("Custom")); /// /// /// /// However, the difference here is that all devices using the "Custom" interface will /// end up with the same single layout -- which has to be identical. By hooking into /// onFindLayoutForDevice, it is possible to register a new layout for every new /// type of device that is discovered and thus build a multitude of different layouts. /// /// It is best to register for this callback during startup. One way to do it is to /// use InitializeOnLoadAttribute and RuntimeInitializeOnLoadMethod. /// /// /// public static event InputDeviceFindControlLayoutDelegate onFindLayoutForDevice { add { lock (s_Manager) s_Manager.onFindControlLayoutForDevice += value; } remove { lock (s_Manager) s_Manager.onFindControlLayoutForDevice -= value; } } ////REVIEW: should this be disambiguated more to separate it more from sensor sampling frequency? ////REVIEW: this should probably be exposed as an input setting /// /// Frequency at which devices that need polling are being queried in the background. /// /// Polled device sampling frequency in Hertz. /// /// Input data is gathered from platform APIs either as events or polled periodically. /// /// In the former case, where we get input as events, the platform is responsible for monitoring /// input devices and sending their state changes which the Unity runtime receives /// and queues as s. This form of input collection usually happens on a /// system-specific thread (which may be Unity's main thread) as part of how the Unity player /// loop operates. In most cases, this means that this form of input will invariably get picked up /// once per frame. /// /// In the latter case, where input has to be explicitly polled from the system, the Unity runtime /// will periodically sample the state of input devices and send it off as input events. Wherever /// possible, this happens in the background at a fixed frequency on a dedicated thread. The /// pollingFrequency property controls the rate at which this sampling happens. /// /// The unit is Hertz. A value of 120, for example, means that devices are sampled 120 times /// per second. /// /// The default polling frequency is 60 Hz. /// /// For devices that are polled, the frequency setting will directly translate to changes in the /// patterns. At 60 Hz, for example, timestamps for a specific, /// polled device will be spaced at roughly 1/60th of a second apart. /// /// Note that it depends on the platform which devices are polled (if any). On Win32, for example, /// only XInput gamepads are polled. /// /// Also note that the polling frequency applies to all devices that are polled. It is not possible /// to set polling frequency on a per-device basis. /// public static float pollingFrequency { get => s_Manager.pollingFrequency; set => s_Manager.pollingFrequency = value; } /// /// Add a new device by instantiating the given device layout. /// /// Name of the layout to instantiate. Must be a device layout. Note that /// layout names are case-insensitive. /// Name to assign to the device. If null, the layout's display name ( is used instead. Note that device names are made /// unique automatically by the system by appending numbers to them (e.g. "gamepad", "gamepad1", /// "gamepad2", etc.). /// Semicolon-separated list of layout variants to use for the device. /// is null or empty. /// The newly created input device. /// /// The device will be added to the list and a notification on /// will be triggered. /// /// Note that adding a device to the system will allocate and also create garbage on the GC heap. /// /// /// /// // This is one way to instantiate the "Gamepad" layout. /// InputSystem.AddDevice("Gamepad"); /// /// // In this case, because the "Gamepad" layout is based on the Gamepad /// // class, we can also do this instead: /// InputSystem.AddDevice<Gamepad>(); /// /// /// /// /// /// /// /// /// public static InputDevice AddDevice(string layout, string name = null, string variants = null) { if (string.IsNullOrEmpty(layout)) throw new ArgumentNullException(nameof(layout)); return s_Manager.AddDevice(layout, name, new InternedString(variants)); } /// /// Add a new device by instantiating the layout registered for type . /// /// Name to assign to the device. If null, the layout's display name ( is used instead. Note that device names are made /// unique automatically by the system by appending numbers to them (e.g. "gamepad", "gamepad1", /// "gamepad2", etc.). /// Type of device to add. /// The newly added device. /// Instantiating the layout for /// did not produce a device of type . /// /// The device will be added to the list and a notification on /// will be triggered. /// /// Note that adding a device to the system will allocate and also create garbage on the GC heap. /// /// /// /// // Add a gamepad. /// InputSystem.AddDevice<Gamepad>(); /// /// /// /// /// /// /// public static TDevice AddDevice(string name = null) where TDevice : InputDevice { var device = s_Manager.AddDevice(typeof(TDevice), name); if (!(device is TDevice deviceOfType)) { // Consider the entire operation as failed, so remove the device we just added. if (device != null) RemoveDevice(device); throw new InvalidOperationException( $"Layout registered for type '{typeof(TDevice).Name}' did not produce a device of that type; layout probably has been overridden"); } return deviceOfType; } /// /// Tell the input system that a new device has become available. /// /// Description of the input device. /// The newly created device that has been added to . /// The given is empty -or- /// no layout can be found that matches the given device . /// /// This method is different from methods such as /// or in that it employs the usual matching process the /// same way that it happens when the Unity runtime reports an input device. /// /// In particular, the same procedure described in the documentation for /// is employed where all registered s are matched against the /// supplied device description and the most suitable match determines the layout to use. This in /// turn is run through to determine the final layout to use. /// /// If no suitable layout can be found, the method throws ArgumentException. /// /// /// InputSystem.AddDevice( /// new InputDeviceDescription /// { /// interfaceName = "Custom", /// product = "Product" /// }); /// /// /// public static InputDevice AddDevice(InputDeviceDescription description) { if (description.empty) throw new ArgumentException("Description must not be empty", nameof(description)); return s_Manager.AddDevice(description); } /// /// Add the given device back to the system. /// /// An input device. If the device is currently already added to /// the system (i.e. is in ), the method will do nothing. /// /// /// This can be used when a device has been manually removed with . /// /// The device will be added to the list and a notification on /// will be triggered. /// /// It may be tempting to do the following but this will not work: /// /// /// /// // This will *NOT* work. /// var device = new Gamepad(); /// InputSystem.AddDevice(device); /// /// /// /// s, like s in general, cannot /// simply be instantiated with new but must be created by the input system /// instead. /// /// /// /// public static void AddDevice(InputDevice device) { if (device == null) throw new ArgumentNullException(nameof(device)); s_Manager.AddDevice(device); } /// /// Remove a device from the system such that it no longer receives input and is no longer part of the /// set of devices in . /// /// Device to remove. If the device has already been removed (i.e. if /// is false), the method does nothing. /// /// Actions that are bound to controls on the device will automatically unbind when the device /// is removed. /// /// When a device is removed, will be triggered with . /// The device will be removed from as well as from any device-specific getters such as /// . /// /// is null. /// public static void RemoveDevice(InputDevice device) { s_Manager.RemoveDevice(device); } /// /// Purge all disconnected devices from . /// /// /// This will release all references held on to for these devices or any of their controls and will /// allow the devices to be reclaimed by the garbage collector. /// /// public static void FlushDisconnectedDevices() { s_Manager.FlushDisconnectedDevices(); } public static InputDevice GetDevice(string nameOrLayout) { return s_Manager.TryGetDevice(nameOrLayout); } public static TDevice GetDevice() where TDevice : InputDevice { TDevice result = null; var lastUpdateTime = -1.0; foreach (var device in devices) { var deviceOfType = device as TDevice; if (deviceOfType == null) continue; if (result == null || deviceOfType.m_LastUpdateTimeInternal > lastUpdateTime) { result = deviceOfType; lastUpdateTime = result.m_LastUpdateTimeInternal; } } return result; } ////REVIEW: this API seems inconsistent with GetDevice(string); both have very different meaning yet very similar signatures /// /// Return the device of the given type that has the /// given usage assigned. Returns null if no such device currently exists. /// /// Usage of the device, e.g. "LeftHand". /// Type of device to look for. /// The device with the given type and usage or null. /// /// Devices usages are most commonly employed to "tag" devices for a specific role. /// A common scenario, for example, is to distinguish which hand a specific /// is associated with. However, arbitrary usages can be assigned to devices. /// /// /// // Get the left hand XRController. /// var leftHand = InputSystem.GetDevice<XRController>(CommonUsages.leftHand); /// /// // Mark gamepad #2 as being for player 1. /// InputSystem.SetDeviceUsage(Gamepad.all[1], "Player1"); /// // And later look it up. /// var player1Gamepad = InputSystem.GetDevice<Gamepad>(new InternedString("Player1")); /// /// /// /// /// public static TDevice GetDevice(InternedString usage) where TDevice : InputDevice { TDevice result = null; var lastUpdateTime = -1.0; foreach (var device in devices) { var deviceOfType = device as TDevice; if (deviceOfType == null) continue; if (!deviceOfType.usages.Contains(usage)) continue; if (result == null || deviceOfType.m_LastUpdateTimeInternal > lastUpdateTime) { result = deviceOfType; lastUpdateTime = result.m_LastUpdateTimeInternal; } } return result; } public static TDevice GetDevice(string usage) where TDevice : InputDevice { return GetDevice(new InternedString(usage)); } /// /// Look up a device by its unique ID. /// /// Unique ID of device. Such as given by . /// The device for the given ID or null if no device with the given ID exists (or no longer exists). /// /// Device IDs are not reused in a given session of the application (or Unity editor). /// /// /// /// public static InputDevice GetDeviceById(int deviceId) { return s_Manager.TryGetDeviceById(deviceId); } /// /// Return the list of devices that have been reported by the runtime /// but could not be matched to any known layout. /// /// A list of descriptions of devices that could not be recognized. /// /// If new layouts are added to the system or if additional matches /// are added to existing layouts, devices in this list may appear or disappear. /// /// /// public static List GetUnsupportedDevices() { var list = new List(); GetUnsupportedDevices(list); return list; } public static int GetUnsupportedDevices(List descriptions) { return s_Manager.GetUnsupportedDevices(descriptions); } /// /// (Re-)enable the given device. /// /// Device to enable. If already enabled, the method will do nothing. /// /// This can be used after a device has been disabled with or /// with devices that start out in disabled state (usually the case for all /// devices). /// /// When enabled, a device will receive input when available. /// /// /// /// // Enable the gyroscope, if present. /// if (Gyroscope.current != null) /// InputSystem.EnableDevice(Gyroscope.current); /// /// /// /// /// public static void EnableDevice(InputDevice device) { s_Manager.EnableOrDisableDevice(device, true); } /// /// Disable the given device, i.e. "mute" it. /// /// Device to disable. If already disabled, the method will do nothing. /// /// A disabled device will not receive input and will remain in its default state. It will remain /// present in the system but without actually feeding input into it. /// /// Disabling devices is most useful for devices on battery-powered platforms /// where having a sensor enabled will increase energy consumption. Sensors will usually start /// out in disabled state and can be enabled, when needed, with and /// disabled again wth this method. /// /// However, disabling a device can be useful in other situations, too. For example, when simulating /// input (say, mouse input) locally from a remote source, it can be desirable to turn off the respective /// local device. /// /// To remove a device altogether, use instead. This will not only silence /// input but remove the instance from the system altogether. /// /// /// public static void DisableDevice(InputDevice device) { s_Manager.EnableOrDisableDevice(device, false); } public static bool TrySyncDevice(InputDevice device) { if (device == null) throw new ArgumentNullException(nameof(device)); var syncCommand = RequestSyncCommand.Create(); var result = device.ExecuteCommand(ref syncCommand); return result >= 0; } public static bool TryResetDevice(InputDevice device) { if (device == null) throw new ArgumentNullException(nameof(device)); return device.RequestReset(); } ////REVIEW: should there be a global pause state? what about haptics that are issued *while* paused? /// /// Pause haptic effect playback on all devices. /// /// /// Calls on all input devices /// that implement the interface. /// /// /// /// /// /// // When going into the menu from gameplay, pause haptics. /// gameplayControls.backAction.onPerformed += /// ctx => /// { /// gameplayControls.Disable(); /// menuControls.Enable(); /// InputSystem.PauseHaptics(); /// }; /// /// public static void PauseHaptics() { var devicesList = devices; var devicesCount = devicesList.Count; for (var i = 0; i < devicesCount; ++i) { var device = devicesList[i]; if (device is IHaptics haptics) haptics.PauseHaptics(); } } /// /// Resume haptic effect playback on all devices. /// /// /// Calls on all input devices /// that implement the interface. /// /// public static void ResumeHaptics() { var devicesList = devices; var devicesCount = devicesList.Count; for (var i = 0; i < devicesCount; ++i) { var device = devicesList[i]; if (device is IHaptics haptics) haptics.ResumeHaptics(); } } /// /// Stop haptic effect playback on all devices. /// /// /// Will reset haptics effects on all devices to their default state. /// /// Calls on all input devices /// that implement the interface. /// public static void ResetHaptics() { var devicesList = devices; var devicesCount = devicesList.Count; for (var i = 0; i < devicesCount; ++i) { var device = devicesList[i]; if (device is IHaptics haptics) haptics.ResetHaptics(); } } #endregion #region Controls /// /// Set the usage tag of the given device to . /// /// Device to set the usage on. /// New usage for the device. /// /// Usages allow to "tag" a specific device such that the tag can then be used in lookups /// and bindings. A common use is for identifying the handedness of an /// but the usages can be arbitrary strings. /// /// This method either sets the usages of the device to a single string (meaning it will /// clear whatever, if any usages, the device has when the method is called) or, /// if is null or empty, resets the usages of the device /// to be empty. To add to a device's set of usages, call . /// To remove usages from a device, call . /// /// The set of usages a device has can be queried with (a device /// is an and thus, like controls, has an associated set of usages). /// /// /// /// // Tag a gamepad to be associated with player #1. /// InputSystem.SetDeviceUsage(myGamepad, "Player1"); /// /// // Create an action that binds to player #1's gamepad specifically. /// var action = new InputAction(binding: "<Gamepad>{Player1}/buttonSouth"); /// /// // Move the tag from one gamepad to another. /// InputSystem.SetDeviceUsage(myGamepad, null); // Clears usages on 'myGamepad'. /// InputSystem.SetDeviceUsage(otherGamepad, "Player1"); /// /// /// /// is null. /// /// /// /// /// public static void SetDeviceUsage(InputDevice device, string usage) { SetDeviceUsage(device, new InternedString(usage)); } /// /// Set the usage tag of the given device to . /// /// Device to set the usage on. /// New usage for the device. /// /// Usages allow to "tag" a specific device such that the tag can then be used in lookups /// and bindings. A common use is for identifying the handedness of an /// but the usages can be arbitrary strings. /// /// This method either sets the usages of the device to a single string (meaning it will /// clear whatever, if any usages, the device has when the method is called) or, /// if is null or empty, resets the usages of the device /// to be empty. To add to a device's set of usages, call . /// To remove usages from a device, call . /// /// The set of usages a device has can be queried with (a device /// is an and thus, like controls, has an associated set of usages). /// /// If the set of usages on the device changes as a result of calling this method, /// will be triggered with . /// /// /// /// // Tag a gamepad to be associated with player #1. /// InputSystem.SetDeviceUsage(myGamepad, new InternedString("Player1")); /// /// // Create an action that binds to player #1's gamepad specifically. /// var action = new InputAction(binding: "<Gamepad>{Player1}/buttonSouth"); /// /// // Move the tag from one gamepad to another. /// InputSystem.SetDeviceUsage(myGamepad, null); // Clears usages on 'myGamepad'. /// InputSystem.SetDeviceUsage(otherGamepad, new InternedString("Player1")); /// /// /// /// is null. /// /// /// /// /// public static void SetDeviceUsage(InputDevice device, InternedString usage) { s_Manager.SetDeviceUsage(device, usage); } /// /// Add a usage tag to the given device. /// /// Device to add the usage to. /// New usage to add to the device. /// /// Usages allow to "tag" a specific device such that the tag can then be used in lookups /// and bindings. A common use is for identifying the handedness of an /// but the usages can be arbitrary strings. /// /// This method adds a new usage to the device's set of usages. If the device already has /// the given usage, the method does nothing. To instead set the device's usages to a single /// one, use . To remove usages from a device, /// call . /// /// The set of usages a device has can be queried with (a device /// is an and thus, like controls, has an associated set of usages). /// /// If the set of usages on the device changes as a result of calling this method, /// will be triggered with . /// /// is null. /// is null or empty. /// /// /// /// /// public static void AddDeviceUsage(InputDevice device, string usage) { s_Manager.AddDeviceUsage(device, new InternedString(usage)); } /// /// Add a usage tag to the given device. /// /// Device to add the usage to. /// New usage to add to the device. /// /// Usages allow to "tag" a specific device such that the tag can then be used in lookups /// and bindings. A common use is for identifying the handedness of an /// but the usages can be arbitrary strings. /// /// This method adds a new usage to the device's set of usages. If the device already has /// the given usage, the method does nothing. To instead set the device's usages to a single /// one, use . To remove usages from a device, /// call . /// /// The set of usages a device has can be queried with (a device /// is an and thus, like controls, has an associated set of usages). /// /// If the set of usages on the device changes as a result of calling this method, /// will be triggered with . /// /// is null. /// is empty. /// /// /// /// /// public static void AddDeviceUsage(InputDevice device, InternedString usage) { s_Manager.AddDeviceUsage(device, usage); } /// /// Remove a usage tag from the given device. /// /// Device to remove the usage from. /// Usage to remove from the device. /// /// This method removes an existing usage from the given device. If the device does not /// have the given usage tag, the method does nothing. Use /// or to add usages to a device. /// /// The set of usages a device has can be queried with (a device /// is an and thus, like controls, has an associated set of usages). /// /// If the set of usages on the device changes as a result of calling this method, /// will be triggered with . /// /// is null. /// is null or empty. /// /// /// /// /// public static void RemoveDeviceUsage(InputDevice device, string usage) { s_Manager.RemoveDeviceUsage(device, new InternedString(usage)); } /// /// Remove a usage tag from the given device. /// /// Device to remove the usage from. /// Usage to remove from the device. /// /// This method removes an existing usage from the given device. If the device does not /// have the given usage tag, the method does nothing. Use /// or to add usages to a device. /// /// The set of usages a device has can be queried with (a device /// is an and thus, like controls, has an associated set of usages). /// /// If the set of usages on the device changes as a result of calling this method, /// will be triggered with . /// /// is null. /// is empty. /// /// /// /// /// public static void RemoveDeviceUsage(InputDevice device, InternedString usage) { s_Manager.RemoveDeviceUsage(device, usage); } /// /// Find the first control that matches the given control path. /// /// Path of a control, e.g. "<Gamepad>/buttonSouth". See /// for details. /// The first control that matches the given path or null if no control matches. /// is null or empty. /// /// If multiple controls match the given path, which result is considered the first is indeterminate. /// /// /// /// // Add gamepad. /// InputSystem.AddDevice<Gamepad>(); /// /// // Look up various controls on it. /// var aButton = InputSystem.FindControl("<Gamepad>/buttonSouth"); /// var leftStickX = InputSystem.FindControl("*/leftStick/x"); /// var bButton = InputSystem.FindControl"*/{back}"); /// /// // This one returns the gamepad itself as devices are also controls. /// var gamepad = InputSystem.FindControl("<Gamepad>"); /// /// /// /// /// public static InputControl FindControl(string path) { if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); var devices = s_Manager.devices; var numDevices = devices.Count; for (var i = 0; i < numDevices; ++i) { var device = devices[i]; var control = InputControlPath.TryFindControl(device, path); if (control != null) return control; } return null; } /// /// Find all controls that match the given control path. /// /// /// /// /// /// // Find all gamepads (literally: that use the "Gamepad" layout). /// InputSystem.FindControls("<Gamepad>"); /// /// // Find all sticks on all gamepads. /// InputSystem.FindControls("<Gamepad>/*stick"); /// /// // Same but filter stick by type rather than by name. /// InputSystem.FindControls<StickControl>("<Gamepad>/*"); /// /// /// /// public static InputControlList FindControls(string path) { return FindControls(path); } public static InputControlList FindControls(string path) where TControl : InputControl { var list = new InputControlList(); FindControls(path, ref list); return list; } public static int FindControls(string path, ref InputControlList controls) where TControl : InputControl { return s_Manager.GetControls(path, ref controls); } #endregion #region Events /// /// Called during for each event that is processed. /// /// /// Every time the input system updates (see /// or for details about when and how this happens), /// it flushes all events from the internal event buffer that are due in the current /// update ( for details about when events /// may be postponed to a subsequent frame). /// /// As the input system reads events from the buffer one by one, it will trigger this /// callback for each event which originates from a recognized device, before then proceeding /// to process the event. However, if any of the callbacks sets /// to true, the event will be skipped and ignored. /// /// Note that the input system does NOT sort events by timestamps (). /// Instead, they are consumed in the order they are produced. This means that they /// will also surface on this callback in that order. /// /// /// /// // Treat left+right mouse button as middle mouse button. /// // (Note: This example is more for demonstrative purposes; it isn't necessarily a good use case) /// InputSystem.onEvent += /// (eventPtr, device) => /// { /// // Only deal with state events. /// if (!eventPtr.IsA<StateEvent>()) /// return; /// /// if (!(device is Mouse mouse)) /// return; /// /// mouse.leftButton.ReadValueFromEvent(eventPtr, out var lmbDown); /// mouse.rightButton.ReadValueFromEvent(eventPtr, out var rmbDown); /// /// if (lmbDown > 0 && rmbDown > 0) /// mouse.middleButton.WriteValueIntoEvent(1f, eventPtr); /// }; /// /// /// /// If you are looking for a way to capture events, may be of /// interest and an alternative to directly hooking into this event. /// /// If you are looking to monitor changes to specific input controls, state change monitors /// (see /// are usually a more efficient and convenient way to set this up. /// /// Delegate reference is null. /// /// /// /// public static event Action onEvent { add { if (value == null) throw new ArgumentNullException(nameof(value)); lock (s_Manager) s_Manager.onEvent += value; } remove { if (value == null) throw new ArgumentNullException(nameof(value)); lock (s_Manager) s_Manager.onEvent -= value; } } ////TODO: need to handle events being queued *during* event processing /// /// Add an event to the internal event queue. /// /// Event to add to the internal event buffer. /// is not /// valid (see ). /// /// The event will be copied in full to the internal event buffer meaning that /// you can release memory for the event after it has been queued. The internal event /// buffer is flushed on the next input system update (see ). /// Note that if timeslicing is in effect (see ), /// then the event may not get processed until its timestamp /// is within the update window of the input system. /// /// As part of queuing, the event will receive its own unique ID (see ). /// Note that this ID will be written into the memory buffer referenced by /// meaning that after calling QueueEvent, you will see the event ID with which the event /// was queued. /// /// /// /// // Queue an input event on the first gamepad. /// var gamepad = Gamepad.all[0]; /// using (StateEvent.From(gamepad, out var eventPtr)) /// { /// gamepad.leftStick.WriteValueIntoEvent(new Vector2(0.123, 0.234), eventPtr); /// InputSystem.QueueEvent(eventPtr); /// } /// /// /// /// public static void QueueEvent(InputEventPtr eventPtr) { if (!eventPtr.valid) throw new ArgumentException("Received a null event pointer", nameof(eventPtr)); s_Manager.QueueEvent(eventPtr); } public static void QueueEvent(ref TEvent inputEvent) where TEvent : struct, IInputEventTypeInfo { s_Manager.QueueEvent(ref inputEvent); } ////REVIEW: consider moving these out into extension methods in UnityEngine.InputSystem.LowLevel ////TODO: find a more elegant solution for this // Mono will ungracefully poop exceptions if we try to use LayoutKind.Explicit in generic // structs. So we can't just stuff a generic TState into a StateEvent and enforce // proper layout. Thus the jumping through lots of ugly hoops here. private unsafe struct StateEventBuffer { public StateEvent stateEvent; public const int kMaxSize = 512; public fixed byte data[kMaxSize - 1]; // StateEvent already adds one. } /// /// Queue a to update the input state of the given device. /// /// Device whose input state to update /// /// Timestamp for the event. If not supplied, the current time is used. Note /// that if the given time is in the future and timeslicing is active (, /// the event will only get processed once the actual time has caught up with the given time. /// Type of input state, such as . Must match the expected /// type of state of . /// /// The given state must match exactly what is expected by the given device. If unsure, an alternative /// is to grab the state as an event directly from the device using which can then /// be queued using . /// /// /// /// // Allocates temporary, unmanaged memory for the event. /// // using statement automatically disposes the memory once we have queued the event. /// using (StateEvent.From(Mouse.current, out var eventPtr)) /// { /// // Use controls on mouse to write values into event. /// Mouse.current.position.WriteValueIntoEvent(new Vector(123, 234), eventPtr); /// /// // Queue event. /// InputSystem.QueueEvent(eventPtr); /// } /// /// /// /// The event will only be queued and not processed right away. This means that the state of /// will not change immediately as a result of calling this method. Instead, /// the event will be processed as part of the next input update. /// /// Note that this method updates the complete input state of the device including all of its /// controls. To update just part of the state of a device, you can use /// (however, note that there are some restrictions; see documentation). /// /// /// InputSystem.QueueStateEvent(Mouse.current, new MouseState { position = new Vector(123, 234) }); /// /// /// /// is null. /// has not been added to the system /// () and thus cannot receive events. /// public static unsafe void QueueStateEvent(InputDevice device, TState state, double time = -1) where TState : struct, IInputStateTypeInfo { if (device == null) throw new ArgumentNullException(nameof(device)); // Make sure device is actually in the system. if (device.m_DeviceIndex == InputDevice.kInvalidDeviceIndex) throw new InvalidOperationException( $"Cannot queue state event for device '{device}' because device has not been added to system"); ////REVIEW: does it make more sense to go off the 'stateBlock' on the device and let that determine size? var stateSize = (uint)UnsafeUtility.SizeOf(); if (stateSize > StateEventBuffer.kMaxSize) throw new ArgumentException( $"Size of '{typeof(TState).Name}' exceeds maximum supported state size of {StateEventBuffer.kMaxSize}", nameof(state)); var eventSize = UnsafeUtility.SizeOf() + stateSize - StateEvent.kStateDataSizeToSubtract; if (time < 0) time = InputRuntime.s_Instance.currentTime; else time += InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; StateEventBuffer eventBuffer; eventBuffer.stateEvent = new StateEvent { baseEvent = new InputEvent(StateEvent.Type, (int)eventSize, device.deviceId, time), stateFormat = state.format }; var ptr = eventBuffer.stateEvent.stateData; UnsafeUtility.MemCpy(ptr, UnsafeUtility.AddressOf(ref state), stateSize); s_Manager.QueueEvent(ref eventBuffer.stateEvent); } private unsafe struct DeltaStateEventBuffer { public DeltaStateEvent stateEvent; public const int kMaxSize = 512; public fixed byte data[kMaxSize - 1]; // DeltaStateEvent already adds one. } /// /// Queue a to update part of the input state of the given device. /// /// Control on a device to update state of. /// New state for the control. Type of state must match the state of the control. /// /// /// is null. /// /// public static unsafe void QueueDeltaStateEvent(InputControl control, TDelta delta, double time = -1) where TDelta : struct { if (control == null) throw new ArgumentNullException(nameof(control)); if (control.stateBlock.bitOffset != 0) throw new InvalidOperationException( $"Cannot send delta state events against bitfield controls: {control}"); // Make sure device is actually in the system. var device = control.device; if (device.m_DeviceIndex == InputDevice.kInvalidDeviceIndex) throw new InvalidOperationException( $"Cannot queue state event for control '{control}' on device '{device}' because device has not been added to system"); if (time < 0) time = InputRuntime.s_Instance.currentTime; else time += InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; var deltaSize = (uint)UnsafeUtility.SizeOf(); if (deltaSize > DeltaStateEventBuffer.kMaxSize) throw new ArgumentException( $"Size of state delta '{typeof(TDelta).Name}' exceeds maximum supported state size of {DeltaStateEventBuffer.kMaxSize}", nameof(delta)); ////TODO: recognize a matching C# representation of a state format and convert to what we expect for trivial cases if (deltaSize != control.stateBlock.alignedSizeInBytes) throw new ArgumentException( $"Size {deltaSize} of delta state of type {typeof(TDelta).Name} provided for control '{control}' does not match size {control.stateBlock.alignedSizeInBytes} of control", nameof(delta)); var eventSize = UnsafeUtility.SizeOf() + deltaSize - 1; DeltaStateEventBuffer eventBuffer; eventBuffer.stateEvent = new DeltaStateEvent { baseEvent = new InputEvent(DeltaStateEvent.Type, (int)eventSize, device.deviceId, time), stateFormat = device.stateBlock.format, stateOffset = control.m_StateBlock.byteOffset - device.m_StateBlock.byteOffset }; var ptr = eventBuffer.stateEvent.stateData; UnsafeUtility.MemCpy(ptr, UnsafeUtility.AddressOf(ref delta), deltaSize); s_Manager.QueueEvent(ref eventBuffer.stateEvent); } /// /// Queue a that signals that the configuration of the given device has changed /// and that cached configuration will thus have to be refreshed. /// /// Device whose configuration has changed. /// Timestamp for the event. If not supplied, the current time will be used. /// /// All state of an input device that is not input or output state is considered its "configuration". /// /// A simple example is keyboard layouts. A will typically have an associated /// keyboard layout that dictates the function of each key and which can be changed by the user at the /// system level. In the input system, the current keyboard layout can be queried via . /// When the layout changes at the system level, the input backend sends a configuration change event /// to signal that the configuration of the keyboard has changed and that cached data may be outdated. /// In response, will flush out cached information such as the name of the keyboard /// layout and display names () of individual keys which causes them /// to be fetched again from the backend the next time they are accessed. /// /// is null. /// has not been added /// (; ) and thus cannot /// receive events. public static void QueueConfigChangeEvent(InputDevice device, double time = -1) { if (device == null) throw new ArgumentNullException(nameof(device)); if (device.deviceId == InputDevice.InvalidDeviceId) throw new InvalidOperationException("Device has not been added"); if (time < 0) time = InputRuntime.s_Instance.currentTime; else time += InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; var inputEvent = DeviceConfigurationEvent.Create(device.deviceId, time); s_Manager.QueueEvent(ref inputEvent); } /// /// Queue a on the given device. /// /// Device to queue the event on. /// Text character to input through the event. /// Optional event time stamp. If not supplied, the current time will be used. /// /// Text input is sent to devices character by character. This allows sending strings of arbitrary /// length without necessary incurring GC overhead. /// /// For the event to have any effect on , the device must /// implement . It will see /// being called when the event is processed. /// /// is null. /// is a device that has not been /// added to the system. /// public static void QueueTextEvent(InputDevice device, char character, double time = -1) { if (device == null) throw new ArgumentNullException(nameof(device)); if (device.deviceId == InputDevice.InvalidDeviceId) throw new InvalidOperationException("Device has not been added"); if (time < 0) time = InputRuntime.s_Instance.currentTime; else time += InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; var inputEvent = TextEvent.Create(device.deviceId, character, time); s_Manager.QueueEvent(ref inputEvent); } ////TODO: rename or move this to a less obvious place /// /// Run a single update of input state. /// /// /// Except in tests and when using , this method should not /// normally be called. The input system will automatically update as part of the player loop as /// determined by . Calling this method is equivalent to /// inserting extra frames, i.e. it will advance the entire state of the input system by one complete /// frame. /// /// When using , this method MUST be called for input to update in the /// player. Not calling the method as part of the player loop may result in excessive memory /// consumption and/or potential loss of input. /// /// Each update will flush out buffered input events and cause them to be processed. This in turn /// will update the state of input devices () and trigger actions () /// that monitor affected device state. /// /// /// public static void Update() { s_Manager.Update(); } internal static void Update(InputUpdateType updateType) { if (updateType != InputUpdateType.None && (s_Manager.updateMask & updateType) == 0) throw new InvalidOperationException( $"'{updateType}' updates are not enabled; InputSystem.settings.updateMode is set to '{settings.updateMode}'"); s_Manager.Update(updateType); } /// /// Event that is fired before the input system updates. /// /// /// The input system updates in sync with player loop and editor updates. Input updates /// are run right before the respective script update. For example, an input update for /// is run before MonoBehaviour.Update methods /// are executed. /// /// The update callback itself is triggered before the input system runs its own update and /// before it flushes out its event queue. This means that events queued from a callback will /// be fed right into the upcoming update. /// /// /// public static event Action onBeforeUpdate { add { lock (s_Manager) s_Manager.onBeforeUpdate += value; } remove { lock (s_Manager) s_Manager.onBeforeUpdate -= value; } } /// /// Event that is fired after the input system has completed an update and processed all pending events. /// /// /// public static event Action onAfterUpdate { add { lock (s_Manager) s_Manager.onAfterUpdate += value; } remove { lock (s_Manager) s_Manager.onAfterUpdate -= value; } } #endregion #region Settings /// /// The current configuration of the input system. /// /// Global configuration object for the input system. /// /// The input system can be configured on a per-project basis. Settings can either be created and /// installed on the fly or persisted as assets in the project. /// /// Value is null when setting the property. public static InputSettings settings { get => s_Manager.settings; set { if (value == null) throw new ArgumentNullException(nameof(value)); if (s_Manager.m_Settings == value) return; // In the editor, we keep track of the settings asset through EditorBuildSettings. #if UNITY_EDITOR if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(value))) { EditorBuildSettings.AddConfigObject(InputSettingsProvider.kEditorBuildSettingsConfigKey, value, true); } #endif s_Manager.settings = value; } } /// /// Event that is triggered if any of the properties in changes or if /// is replaced entirely with a new object. /// /// /// public static event Action onSettingsChange { add => s_Manager.onSettingsChange += value; remove => s_Manager.onSettingsChange -= value; } #endregion #region Actions /// /// Event that is signalled when the state of enabled actions in the system changes or /// when actions are triggered. /// /// /// The object received by the callback is either an , /// , or depending on whether the /// affects a single action, an entire action map, or an /// entire action asset. /// /// For and , /// the given object is an if the action is not part of an action map, /// an if the the actions are part of a map but not part of an asset, and an /// if the actions are part of an asset. In other words, the notification is /// sent for the topmost object in the hierarchy. /// /// /// /// InputSystem.onActionChange += /// (obj, change) => /// { /// if (change == InputActionChange.ActionPerformed) /// { /// var action = (InputAction)obj; /// var control = action.activeControl; /// //... /// } /// else if (change == InputActionChange.ActionMapEnabled) /// { /// var actionMap = (InputActionMap)obj; /// //... /// } /// else if (change == InputActionChange.BoundControlsChanged) /// { /// // This is one way to deal with the fact that obj may be an InputAction /// // InputActionMap, or InputActionAsset and may be part of an InputActionAsset or not. /// var action = obj as InputAction; /// var actionMap = action?.actionMap ?? obj as InputActionMap; /// var actionAsset = actionMap?.asset ?? obj as InputActionAsset; /// /// // Note that if bound controls are changed on any map in an asset, there *will* /// // be a BoundControlsChanged notification for the entire asset. /// /// //... /// } /// }; /// /// /// public static event Action onActionChange { add { if (value == null) throw new ArgumentNullException(nameof(value)); lock (s_Manager) if (!InputActionState.s_OnActionChange.Contains(value)) InputActionState.s_OnActionChange.Append(value); } remove { if (value == null) throw new ArgumentNullException(nameof(value)); lock (s_Manager) InputActionState.s_OnActionChange.Remove(value); } } /// /// Register a new type of interaction with the system. /// /// Type that implements the interaction. Must support . /// Name to register the interaction with. This is used in bindings to refer to the interaction /// (e.g. an interactions called "Tap" can be added to a binding by listing it in its /// property). If no name is supplied, the short name of is used (with "Interaction" clipped off /// the name if the type name ends in that). /// /// /// // Interaction that is performed when control resets to default state. /// public class ResetInteraction : InputInteraction /// { /// public void Process(ref InputInteractionContext context) /// { /// if (context.isWaiting && !context.controlHasDefaultValue) /// context.Started(); /// else if (context.isStarted && context.controlHasDefaultValue) /// context.Performed(); /// } /// } /// /// // Make interaction globally available on bindings. /// // "Interaction" suffix in type name will get dropped automatically. /// InputSystem.RegisterInteraction(typeof(ResetInteraction)); /// /// // Set up action with binding that has the 'reset' interaction applied to it. /// var action = new InputAction(binding: "/<Gamepad>/buttonSouth", interactions: "reset"); /// /// /// /// public static void RegisterInteraction(Type type, string name = null) { if (type == null) throw new ArgumentNullException(nameof(type)); if (string.IsNullOrEmpty(name)) { name = type.Name; if (name.EndsWith("Interaction")) name = name.Substring(0, name.Length - "Interaction".Length); } s_Manager.interactions.AddTypeRegistration(name, type); } public static void RegisterInteraction(string name = null) { RegisterInteraction(typeof(T), name); } ////REVIEW: can we move the getters and listers somewhere else? maybe `interactions` and `processors` properties and such? public static Type TryGetInteraction(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); return s_Manager.interactions.LookupTypeRegistration(name); } public static IEnumerable ListInteractions() { return s_Manager.interactions.names; } public static void RegisterBindingComposite(Type type, string name) { if (type == null) throw new ArgumentNullException(nameof(type)); if (string.IsNullOrEmpty(name)) { name = type.Name; if (name.EndsWith("Composite")) name = name.Substring(0, name.Length - "Composite".Length); } s_Manager.composites.AddTypeRegistration(name, type); } public static void RegisterBindingComposite(string name = null) { RegisterBindingComposite(typeof(T), name); } public static Type TryGetBindingComposite(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); return s_Manager.composites.LookupTypeRegistration(name); } /// /// Disable all actions (and implicitly all action sets) that are currently enabled. /// /// /// public static void DisableAllEnabledActions() { InputActionState.DisableAllActions(); } /// /// Return a list of all the actions that are currently enabled in the system. /// /// A new list instance containing all currently enabled actions. /// /// To avoid allocations, use . /// /// public static List ListEnabledActions() { var result = new List(); ListEnabledActions(result); return result; } /// /// Add all actions that are currently enabled in the system to the given list. /// /// List to add actions to. /// The number of actions added to the list. /// is null. /// /// If the capacity of the given list is large enough, this method will not allocate memory. /// public static int ListEnabledActions(List actions) { if (actions == null) throw new ArgumentNullException(nameof(actions)); return InputActionState.FindAllEnabledActions(actions); } #endregion #region Remoting /// /// The local InputRemoting instance which can mirror local input to a remote /// input system or can make input in a remote system available locally. /// /// /// In the editor, this is always initialized. In players, this will be null /// if remoting is disabled (which it is by default in release players). /// public static InputRemoting remoting => s_Remote; #endregion /// /// The current version of the input system package. /// /// Current version of the input system. public static Version version => Assembly.GetExecutingAssembly().GetName().Version; ////REVIEW: restrict metrics to editor and development builds? /// /// Get various up-to-date metrics about the input system. /// /// Up-to-date metrics on input system activity. public static InputMetrics metrics => s_Manager.metrics; internal static InputManager s_Manager; internal static InputRemoting s_Remote; #if DEVELOPMENT_BUILD || UNITY_EDITOR internal static RemoteInputPlayerConnection s_RemoteConnection; private static void SetUpRemoting() { Debug.Assert(s_Manager != null); #if UNITY_EDITOR s_Remote = new InputRemoting(s_Manager); // NOTE: We use delayCall as our initial startup will run in editor initialization before // PlayerConnection is itself ready. If we call Bind() directly here, we won't // see any errors but the callbacks we register for will not trigger. EditorApplication.delayCall += SetUpRemotingInternal; #else s_Remote = new InputRemoting(s_Manager); SetUpRemotingInternal(); #endif } private static void SetUpRemotingInternal() { if (s_RemoteConnection == null) { #if UNITY_EDITOR s_RemoteConnection = RemoteInputPlayerConnection.instance; s_RemoteConnection.Bind(EditorConnection.instance, false); #else s_RemoteConnection = ScriptableObject.CreateInstance(); s_RemoteConnection.Bind(PlayerConnection.instance, PlayerConnection.instance.isConnected); #endif } s_Remote.Subscribe(s_RemoteConnection); // Feed messages from players into editor. s_RemoteConnection.Subscribe(s_Remote); // Feed messages from editor into players. } #if !UNITY_EDITOR private static bool ShouldEnableRemoting() { ////FIXME: is there a better way to detect whether we are running tests? var isRunningTests = Application.productName == "UnityTestFramework"; if (isRunningTests) return false; // Don't remote while running tests. return true; } #endif #endif // DEVELOPMENT_BUILD || UNITY_EDITOR // The rest here is internal stuff to manage singletons, survive domain reloads, // and to support the reset ability for tests. static InputSystem() { #if UNITY_EDITOR InitializeInEditor(); #else InitializeInPlayer(); #endif } ////FIXME: Unity is not calling this method if it's inside an #if block that is not //// visible to the editor; that shouldn't be the case [RuntimeInitializeOnLoadMethod(loadType: RuntimeInitializeLoadType.BeforeSceneLoad)] private static void RunInitializeInPlayer() { // We're using this method just to make sure the class constructor is called // so we don't need any code in here. When the engine calls this method, the // class constructor will be run if it hasn't been run already. // IL2CPP has a bug that causes the class constructor to not be run when // the RuntimeInitializeOnLoadMethod is invoked. So we need an explicit check // here until that is fixed (case 1014293). #if !UNITY_EDITOR if (s_Manager == null) InitializeInPlayer(); #endif } #if UNITY_EDITOR internal static InputSystemObject s_SystemObject; internal static void InitializeInEditor(IInputRuntime runtime = null) { Profiling.Profiler.BeginSample("InputSystem.InitializeInEditor"); Reset(runtime: runtime); var existingSystemObjects = Resources.FindObjectsOfTypeAll(); if (existingSystemObjects != null && existingSystemObjects.Length > 0) { ////FIXME: does not preserve action map state // We're coming back out of a domain reload. We're restoring part of the // InputManager state here but we're still waiting from layout registrations // that happen during domain initialization. s_SystemObject = existingSystemObjects[0]; s_Manager.RestoreStateWithoutDevices(s_SystemObject.systemState.managerState); InputDebuggerWindow.ReviveAfterDomainReload(); // Restore remoting state. s_RemoteConnection = s_SystemObject.systemState.remoteConnection; SetUpRemoting(); s_Remote.RestoreState(s_SystemObject.systemState.remotingState, s_Manager); // Get manager to restore devices on first input update. By that time we // should have all (possibly updated) layout information in place. s_Manager.m_SavedDeviceStates = s_SystemObject.systemState.managerState.devices; s_Manager.m_SavedAvailableDevices = s_SystemObject.systemState.managerState.availableDevices; // Restore editor settings. InputEditorUserSettings.s_Settings = s_SystemObject.systemState.userSettings; // Get rid of saved state. s_SystemObject.systemState = new State(); } else { s_SystemObject = ScriptableObject.CreateInstance(); s_SystemObject.hideFlags = HideFlags.HideAndDontSave; // See if we have a remembered settings object. if (EditorBuildSettings.TryGetConfigObject(InputSettingsProvider.kEditorBuildSettingsConfigKey, out InputSettings settingsAsset)) { if (s_Manager.m_Settings.hideFlags == HideFlags.HideAndDontSave) ScriptableObject.DestroyImmediate(s_Manager.m_Settings); s_Manager.m_Settings = settingsAsset; s_Manager.ApplySettings(); } InputEditorUserSettings.Load(); SetUpRemoting(); } Debug.Assert(settings != null); #if UNITY_EDITOR Debug.Assert(EditorUtility.InstanceIDToObject(settings.GetInstanceID()) != null, "InputSettings has lost its native object"); #endif // If native backends for new input system aren't enabled, ask user whether we should // enable them (requires restart). We only ask once per session and don't ask when // running in batch mode. if (!s_SystemObject.newInputBackendsCheckedAsEnabled && !EditorPlayerSettingHelpers.newSystemBackendsEnabled && !s_Manager.m_Runtime.isInBatchMode) { 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. " + "This means that no input from native devices will come through." + "\n\nDo you want to enable the backends? Doing so will *RESTART* the editor and will *DISABLE* the old UnityEngine.Input APIs."; if (EditorUtility.DisplayDialog("Warning", dialogText, "Yes", "No")) { EditorPlayerSettingHelpers.newSystemBackendsEnabled = true; EditorPlayerSettingHelpers.oldSystemBackendsEnabled = false; EditorHelpers.RestartEditorAndRecompileScripts(); } } s_SystemObject.newInputBackendsCheckedAsEnabled = true; RunInitialUpdate(); Profiling.Profiler.EndSample(); } internal static void OnPlayModeChange(PlayModeStateChange change) { ////REVIEW: should we pause haptics when play mode is paused and stop haptics when play mode is exited? switch (change) { case PlayModeStateChange.ExitingEditMode: s_SystemObject.settings = JsonUtility.ToJson(settings); s_SystemObject.exitEditModeTime = InputRuntime.s_Instance.currentTime; s_SystemObject.enterPlayModeTime = 0; break; case PlayModeStateChange.EnteredPlayMode: s_SystemObject.enterPlayModeTime = InputRuntime.s_Instance.currentTime; break; ////TODO: also nuke all callbacks installed on InputActions and InputActionMaps ////REVIEW: is there any other cleanup work we want to before? should we automatically nuke //// InputDevices that have been created with AddDevice<> during play mode? case PlayModeStateChange.EnteredEditMode: // Nuke all InputActionMapStates. Releases their unmanaged memory. InputActionState.DestroyAllActionMapStates(); // Restore settings. if (!string.IsNullOrEmpty(s_SystemObject.settings)) { JsonUtility.FromJsonOverwrite(s_SystemObject.settings, settings); s_SystemObject.settings = null; settings.OnChange(); } break; } } private static void OnProjectChange() { ////TODO: use dirty count to find whether settings have actually changed // May have added, removed, moved, or renamed settings asset. Force a refresh // of the UI. InputSettingsProvider.ForceReload(); // Also, if the asset holding our current settings got deleted, switch back to a // temporary settings object. // NOTE: We access m_Settings directly here to make sure we're not running into asserts // from the settings getter checking it has a valid object. if (EditorUtility.InstanceIDToObject(s_Manager.m_Settings.GetInstanceID()) == null) { var newSettings = ScriptableObject.CreateInstance(); newSettings.hideFlags = HideFlags.HideAndDontSave; settings = newSettings; } } #else private static void InitializeInPlayer(IInputRuntime runtime = null, InputSettings settings = null) { if (settings == null) settings = Resources.FindObjectsOfTypeAll().FirstOrDefault() ?? ScriptableObject.CreateInstance(); // No domain reloads in the player so we don't need to look for existing // instances. s_Manager = new InputManager(); s_Manager.Initialize(runtime ?? NativeInputRuntime.instance, settings); #if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION PerformDefaultPluginInitialization(); #endif // Automatically enable remoting in development players. #if DEVELOPMENT_BUILD if (ShouldEnableRemoting()) SetUpRemoting(); #endif RunInitialUpdate(); } #endif // UNITY_EDITOR private static void RunInitialUpdate() { // Request an initial Update so that user methods such as Start and Awake // can access the input devices. // // NOTE: We use InputUpdateType.None here to run a "null" update. InputManager.OnBeforeUpdate() // and InputManager.OnUpdate() will both early out when comparing this to their update // mask but will still restore devices. This means we're not actually processing input, // but we will force the runtime to push its devices. Update(InputUpdateType.None); } #if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION private static void PerformDefaultPluginInitialization() { UISupport.Initialize(); #if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WSA || UNITY_IOS XInputSupport.Initialize(); #endif #if UNITY_EDITOR || UNITY_STANDALONE || UNITY_PS4 || UNITY_WSA || UNITY_IOS DualShockSupport.Initialize(); #endif #if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WSA HIDSupport.Initialize(); #endif #if UNITY_EDITOR || UNITY_ANDROID Android.AndroidSupport.Initialize(); #endif #if UNITY_EDITOR || UNITY_IOS || UNITY_TVOS iOS.iOSSupport.Initialize(); #endif #if UNITY_EDITOR || UNITY_WEBGL WebGL.WebGLSupport.Initialize(); #endif #if UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_WSA Switch.SwitchSupportHID.Initialize(); #endif #if (UNITY_EDITOR || UNITY_STANDALONE || UNITY_ANDROID || UNITY_IOS || UNITY_WSA) && UNITY_INPUT_SYSTEM_ENABLE_XR XR.XRSupport.Initialize(); #endif #if UNITY_EDITOR || UNITY_STANDALONE_LINUX Linux.LinuxSupport.Initialize(); #endif #if UNITY_EDITOR || UNITY_ANDROID || UNITY_IOS || UNITY_TVOS || UNITY_WSA OnScreen.OnScreenSupport.Initialize(); #endif #if (UNITY_EDITOR || UNITY_STANDALONE) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT Steam.SteamSupport.Initialize(); #endif } #endif // UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION // For testing, we want the ability to push/pop system state even in the player. // However, we don't want it in release players. #if DEVELOPMENT_BUILD || UNITY_EDITOR /// /// Return the input system to its default state. /// private static void Reset(bool enableRemoting = false, IInputRuntime runtime = null) { Profiling.Profiler.BeginSample("InputSystem.Reset"); // Some devices keep globals. Get rid of them by pretending the devices // are removed. if (s_Manager != null) { foreach (var device in s_Manager.devices) device.NotifyRemoved(); s_Manager.UninstallGlobals(); } // Create temporary settings. In the tests, this is all we need. But outside of tests,d // this should get replaced with an actual InputSettings asset. var settings = ScriptableObject.CreateInstance(); settings.hideFlags = HideFlags.HideAndDontSave; #if UNITY_EDITOR s_Manager = new InputManager(); s_Manager.Initialize(runtime ?? NativeInputRuntime.instance, settings); s_Manager.m_Runtime.onPlayModeChanged = OnPlayModeChange; s_Manager.m_Runtime.onProjectChange = OnProjectChange; InputEditorUserSettings.s_Settings = new InputEditorUserSettings.SerializedState(); if (enableRemoting) SetUpRemoting(); #if !UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION PerformDefaultPluginInitialization(); #endif #else InitializeInPlayer(runtime, settings); #endif InputUser.ResetGlobals(); Profiling.Profiler.EndSample(); } /// /// Destroy the current setup of the input system. /// /// /// NOTE: This also de-allocates data we're keeping in unmanaged memory! /// private static void Destroy() { // NOTE: Does not destroy InputSystemObject. We want to destroy input system // state repeatedly during tests but we want to not create InputSystemObject // over and over. InputActionState.ResetGlobals(); s_Manager.Destroy(); if (s_RemoteConnection != null) Object.DestroyImmediate(s_RemoteConnection); #if UNITY_EDITOR EditorInputControlLayoutCache.Clear(); InputDeviceDebuggerWindow.s_OnToolbarGUIActions.Clear(); InputEditorUserSettings.s_Settings = new InputEditorUserSettings.SerializedState(); #endif s_Manager = null; s_RemoteConnection = null; s_Remote = null; } /// /// Snapshot of the state used by the input system. /// /// /// Can be taken across domain reloads. /// [Serializable] internal struct State { [NonSerialized] public InputManager manager; [NonSerialized] public InputRemoting remote; [SerializeField] public RemoteInputPlayerConnection remoteConnection; [SerializeField] public InputManager.SerializedState managerState; [SerializeField] public InputRemoting.SerializedState remotingState; #if UNITY_EDITOR [SerializeField] public InputEditorUserSettings.SerializedState userSettings; [SerializeField] public string systemObject; #endif ////REVIEW: preserve InputUser state? (if even possible) } private static Stack s_SavedStateStack; internal static State GetSavedState() { return s_SavedStateStack.Peek(); } /// /// Push the current state of the input system onto a stack and /// reset the system to its default state. /// /// /// The save stack is not able to survive domain reloads. It is intended solely /// for use in tests. /// internal static void SaveAndReset(bool enableRemoting = false, IInputRuntime runtime = null) { if (s_SavedStateStack == null) s_SavedStateStack = new Stack(); ////FIXME: does not preserve global state in InputActionState ////TODO: preserve InputUser state s_SavedStateStack.Push(new State { manager = s_Manager, remote = s_Remote, remoteConnection = s_RemoteConnection, managerState = s_Manager.SaveState(), remotingState = s_Remote?.SaveState() ?? new InputRemoting.SerializedState(), #if UNITY_EDITOR userSettings = InputEditorUserSettings.s_Settings, systemObject = JsonUtility.ToJson(s_SystemObject), #endif }); Reset(enableRemoting, runtime ?? InputRuntime.s_Instance); // Keep current runtime. } ////FIXME: this method doesn't restore things like InputDeviceDebuggerWindow.onToolbarGUI /// /// Restore the state of the system from the last state pushed with . /// internal static void Restore() { Debug.Assert(s_SavedStateStack != null && s_SavedStateStack.Count > 0); // Nuke what we have. Destroy(); // Load back previous state. var state = s_SavedStateStack.Pop(); s_Manager = state.manager; s_Remote = state.remote; s_RemoteConnection = state.remoteConnection; InputUpdate.Restore(state.managerState.updateState); s_Manager.InstallRuntime(s_Manager.m_Runtime); s_Manager.InstallGlobals(); s_Manager.ApplySettings(); #if UNITY_EDITOR InputEditorUserSettings.s_Settings = state.userSettings; JsonUtility.FromJsonOverwrite(state.systemObject, s_SystemObject); #endif // Get devices that keep global lists (like Gamepad) to re-initialize them // by pretending the devices have been added. foreach (var device in devices) device.NotifyAdded(); } #endif } }