Maze / Library /PackageCache /com.unity.inputsystem@1.6.1 /InputSystem /Actions /InputActionAsset.cs
| using System; | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using UnityEngine.InputSystem.Utilities; | |
| ////TODO: make the FindAction logic available on any IEnumerable<InputAction> and IInputActionCollection via extension methods | |
| ////TODO: control schemes, like actions and maps, should have stable IDs so that they can be renamed | |
| ////REVIEW: have some way of expressing 'contracts' on action maps? I.e. something like | |
| //// "I expect a 'look' and a 'move' action in here" | |
| ////REVIEW: rename this from "InputActionAsset" to something else that emphasizes the asset aspect less | |
| //// and instead emphasizes the map collection aspect more? | |
| namespace UnityEngine.InputSystem | |
| { | |
| /// <summary> | |
| /// An asset that contains action maps and control schemes. | |
| /// </summary> | |
| /// <remarks> | |
| /// InputActionAssets can be created in code but are usually stored in JSON format on | |
| /// disk with the ".inputactions" extension. Unity imports them with a custom | |
| /// importer. | |
| /// | |
| /// To create an InputActionAsset in code, use the <c>Singleton</c> API and populate the | |
| /// asset with the methods found in <see cref="InputActionSetupExtensions"/>. Alternatively, | |
| /// you can use <see cref="FromJson"/> to load an InputActionAsset directly from a string in JSON format. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// // Create and configure an asset in code. | |
| /// var asset1 = ScriptableObject.CreateInstance<InputActionAsset>(); | |
| /// var actionMap1 = asset1.AddActionMap("map1"); | |
| /// action1Map.AddAction("action1", binding: "<Keyboard>/space"); | |
| /// </code> | |
| /// </example> | |
| /// | |
| /// If you use the API to modify an InputActionAsset while in Play mode, | |
| /// it does not survive the transition back to Edit Mode. Unity tracks and reloads modified assets | |
| /// from disk when exiting Play mode. This is done so that you can realistically test the input | |
| /// related functionality of your application i.e. control rebinding etc, without inadvertently changing | |
| /// the input asset. | |
| /// | |
| /// Each asset can contain arbitrary many action maps that you can enable and disable individually | |
| /// (see <see cref="InputActionMap.Enable"/> and <see cref="InputActionMap.Disable"/>) or in bulk | |
| /// (see <see cref="Enable"/> and <see cref="Disable"/>). The name of each action map must be unique. | |
| /// The list of action maps can be queried from <see cref="actionMaps"/>. | |
| /// | |
| /// InputActionAssets can only define <see cref="InputControlScheme"/>s. They can be added to | |
| /// an asset with <see cref="InputActionSetupExtensions.AddControlScheme(InputActionAsset,string)"/> | |
| /// and can be queried from <see cref="controlSchemes"/>. | |
| /// | |
| /// Be aware that input action assets do not separate between static (configuration) data and dynamic | |
| /// (instance) data. For audio, for example, <c>AudioClip</c> represents the static, | |
| /// shared data portion of audio playback whereas <c>AudioSource"</c> represents the | |
| /// dynamic, per-instance audio playback portion (referencing the clip through <c>AudioSource.clip</c>). | |
| /// | |
| /// For input, such a split is less beneficial as the same input is generally not exercised | |
| /// multiple times in parallel. Keeping both static and dynamic data together simplifies | |
| /// using the system. | |
| /// | |
| /// However, there are scenarios where you indeed want to take the same input action and | |
| /// exercise it multiple times in parallel. A prominent example of such a use case is | |
| /// local multiplayer where each player gets the same set of actions but is controlling | |
| /// them with a different device (or devices) each. This is easily achieved by simply | |
| /// using <c>UnityEngine.Object.Instantiate</c> to instantiate the input action | |
| /// asset multiple times. <see cref="PlayerInput"/> will automatically do so in its | |
| /// internals. | |
| /// | |
| /// Note also that all action maps in an asset share binding state. This means that if | |
| /// one map in an asset has to resolve its bindings, all maps in the asset have to. | |
| /// </remarks> | |
| public class InputActionAsset : ScriptableObject, IInputActionCollection2 | |
| { | |
| /// <summary> | |
| /// File extension (without the dot) for InputActionAssets in JSON format. | |
| /// </summary> | |
| /// <value>File extension for InputActionAsset source files.</value> | |
| /// <remarks> | |
| /// Files with this extension will automatically be imported by Unity as | |
| /// InputActionAssets. | |
| /// </remarks> | |
| public const string Extension = "inputactions"; | |
| /// <summary> | |
| /// True if any action in the asset is currently enabled. | |
| /// </summary> | |
| /// <seealso cref="InputAction.enabled"/> | |
| /// <seealso cref="InputActionMap.enabled"/> | |
| /// <seealso cref="InputAction.Enable"/> | |
| /// <seealso cref="InputActionMap.Enable"/> | |
| /// <seealso cref="Enable"/> | |
| public bool enabled | |
| { | |
| get | |
| { | |
| foreach (var actionMap in actionMaps) | |
| if (actionMap.enabled) | |
| return true; | |
| return false; | |
| } | |
| } | |
| /// <summary> | |
| /// List of action maps defined in the asset. | |
| /// </summary> | |
| /// <value>Action maps contained in the asset.</value> | |
| /// <seealso cref="InputActionSetupExtensions.AddActionMap(InputActionAsset,string)"/> | |
| /// <seealso cref="InputActionSetupExtensions.RemoveActionMap(InputActionAsset,InputActionMap)"/> | |
| /// <seealso cref="FindActionMap(string,bool)"/> | |
| public ReadOnlyArray<InputActionMap> actionMaps => new ReadOnlyArray<InputActionMap>(m_ActionMaps); | |
| /// <summary> | |
| /// List of control schemes defined in the asset. | |
| /// </summary> | |
| /// <value>Control schemes defined for the asset.</value> | |
| /// <seealso cref="InputActionSetupExtensions.AddControlScheme(InputActionAsset,string)"/> | |
| /// <seealso cref="InputActionSetupExtensions.RemoveControlScheme"/> | |
| public ReadOnlyArray<InputControlScheme> controlSchemes => new ReadOnlyArray<InputControlScheme>(m_ControlSchemes); | |
| /// <summary> | |
| /// Iterate over all bindings in the asset. | |
| /// </summary> | |
| /// <remarks> | |
| /// This iterates over all action maps in <see cref="actionMaps"/> and, within each | |
| /// map, over the set of <see cref="InputActionMap.bindings"/>. | |
| /// </remarks> | |
| /// <seealso cref="InputActionMap.bindings"/> | |
| public IEnumerable<InputBinding> bindings | |
| { | |
| get | |
| { | |
| var numActionMaps = m_ActionMaps.LengthSafe(); | |
| if (numActionMaps == 0) | |
| yield break; | |
| for (var i = 0; i < numActionMaps; ++i) | |
| { | |
| var actionMap = m_ActionMaps[i]; | |
| var bindings = actionMap.m_Bindings; | |
| var numBindings = bindings.LengthSafe(); | |
| for (var n = 0; n < numBindings; ++n) | |
| yield return bindings[n]; | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Binding mask to apply to all action maps and actions in the asset. | |
| /// </summary> | |
| /// <value>Optional mask that determines which bindings in the asset to enable.</value> | |
| /// <remarks> | |
| /// Binding masks can be applied at three different levels: for an entire asset through | |
| /// this property, for a specific map through <see cref="InputActionMap.bindingMask"/>, | |
| /// and for single actions through <see cref="InputAction.bindingMask"/>. By default, | |
| /// none of the masks will be set (i.e. they will be <c>null</c>). | |
| /// | |
| /// When an action is enabled, all the binding masks that apply to it are taken into | |
| /// account. Specifically, this means that any given binding on the action will be | |
| /// enabled only if it matches the mask applied to the asset, the mask applied | |
| /// to the map that contains the action, and the mask applied to the action itself. | |
| /// All the masks are individually optional. | |
| /// | |
| /// Masks are matched against bindings using <see cref="InputBinding.Matches"/>. | |
| /// | |
| /// Note that if you modify the masks applicable to an action while it is | |
| /// enabled, the action's <see cref="InputAction.controls"/> will get updated immediately to | |
| /// respect the mask. To avoid repeated binding resolution, it is most efficient | |
| /// to apply binding masks before enabling actions. | |
| /// | |
| /// Binding masks are non-destructive. All the bindings on the action are left | |
| /// in place. Setting a mask will not affect the value of the <see cref="InputAction.bindings"/> | |
| /// and <see cref="InputActionMap.bindings"/> properties. | |
| /// </remarks> | |
| /// <seealso cref="InputBinding.MaskByGroup"/> | |
| /// <seealso cref="InputAction.bindingMask"/> | |
| /// <seealso cref="InputActionMap.bindingMask"/> | |
| public InputBinding? bindingMask | |
| { | |
| get => m_BindingMask; | |
| set | |
| { | |
| if (m_BindingMask == value) | |
| return; | |
| m_BindingMask = value; | |
| ReResolveIfNecessary(fullResolve: true); | |
| } | |
| } | |
| /// <summary> | |
| /// Set of devices that bindings in the asset can bind to. | |
| /// </summary> | |
| /// <value>Optional set of devices to use by bindings in the asset.</value> | |
| /// <remarks> | |
| /// By default (with this property being <c>null</c>), bindings will bind to any of the | |
| /// controls available through <see cref="InputSystem.devices"/>, i.e. controls from all | |
| /// devices in the system will be used. | |
| /// | |
| /// By setting this property, binding resolution can instead be restricted to just specific | |
| /// devices. This restriction can either be applied to an entire asset using this property | |
| /// or to specific action maps by using <see cref="InputActionMap.devices"/>. Note that if | |
| /// both this property and <see cref="InputActionMap.devices"/> is set for a specific action | |
| /// map, the list of devices on the action map will take precedence and the list on the | |
| /// asset will be ignored for bindings in that action map. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// // Create an asset with a single action map and a single action with a | |
| /// // gamepad binding. | |
| /// var asset = ScriptableObject.CreateInstance<InputActionAsset>(); | |
| /// var actionMap = new InputActionMap(); | |
| /// var fireAction = actionMap.AddAction("Fire", binding: "<Gamepad>/buttonSouth"); | |
| /// asset.AddActionMap(actionMap); | |
| /// | |
| /// // Let's assume we have two gamepads connected. If we enable the | |
| /// // action map now, the 'Fire' action will bind to both. | |
| /// actionMap.Enable(); | |
| /// | |
| /// // This will print two controls. | |
| /// Debug.Log(string.Join("\n", fireAction.controls)); | |
| /// | |
| /// // To restrict the setup to just the first gamepad, we can assign | |
| /// // to the 'devices' property (in this case, we could do so on either | |
| /// // the action map or on the asset; we choose the latter here). | |
| /// asset.devices = new InputDevice[] { Gamepad.all[0] }; | |
| /// | |
| /// // Now this will print only one control. | |
| /// Debug.Log(string.Join("\n", fireAction.controls)); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <seealso cref="InputActionMap.devices"/> | |
| public ReadOnlyArray<InputDevice>? devices | |
| { | |
| get => m_Devices.Get(); | |
| set | |
| { | |
| if (m_Devices.Set(value)) | |
| ReResolveIfNecessary(fullResolve: false); | |
| } | |
| } | |
| /// <summary> | |
| /// Look up an action by name or ID. | |
| /// </summary> | |
| /// <param name="actionNameOrId">Name of the action as either a "map/action" combination (e.g. "gameplay/fire") or | |
| /// a simple name. In the former case, the name is split at the '/' slash and the first part is used to find | |
| /// a map with that name and the second part is used to find an action with that name inside the map. In the | |
| /// latter case, all maps are searched in order and the first action that has the given name in any of the maps | |
| /// is returned. Note that name comparisons are case-insensitive. | |
| /// | |
| /// Alternatively, the given string can be a GUID as given by <see cref="InputAction.id"/>.</param> | |
| /// <returns>The action with the corresponding name or null if no matching action could be found.</returns> | |
| /// <remarks> | |
| /// This method is equivalent to <see cref="FindAction(string,bool)"/> except that it throws | |
| /// <see cref="KeyNotFoundException"/> if no action with the given name or ID | |
| /// could be found. | |
| /// </remarks> | |
| /// <exception cref="KeyNotFoundException">No action was found matching <paramref name="actionNameOrId"/>.</exception> | |
| /// <exception cref="ArgumentNullException"><paramref name="actionNameOrId"/> is <c>null</c> or empty.</exception> | |
| /// <seealso cref="FindAction(string,bool)"/> | |
| public InputAction this[string actionNameOrId] | |
| { | |
| get | |
| { | |
| var action = FindAction(actionNameOrId); | |
| if (action == null) | |
| throw new KeyNotFoundException($"Cannot find action '{actionNameOrId}' in '{this}'"); | |
| return action; | |
| } | |
| } | |
| /// <summary> | |
| /// Return a JSON representation of the asset. | |
| /// </summary> | |
| /// <returns>A string in JSON format that represents the static/configuration data present | |
| /// in the asset.</returns> | |
| /// <remarks> | |
| /// This will not save dynamic execution state such as callbacks installed on | |
| /// <see cref="InputAction">actions</see> or enabled/disabled states of individual | |
| /// maps and actions. | |
| /// | |
| /// Use <see cref="LoadFromJson"/> to deserialize the JSON data back into an InputActionAsset. | |
| /// | |
| /// Be aware that the format used by this method is <em>different</em> than what you | |
| /// get if you call <c>JsonUtility.ToJson</c> on an InputActionAsset instance. In other | |
| /// words, the JSON format is not identical to the Unity serialized object representation | |
| /// of the asset. | |
| /// </remarks> | |
| /// <seealso cref="FromJson"/> | |
| public string ToJson() | |
| { | |
| var fileJson = new WriteFileJson | |
| { | |
| name = name, | |
| maps = InputActionMap.WriteFileJson.FromMaps(m_ActionMaps).maps, | |
| controlSchemes = InputControlScheme.SchemeJson.ToJson(m_ControlSchemes), | |
| }; | |
| return JsonUtility.ToJson(fileJson, true); | |
| } | |
| /// <summary> | |
| /// Replace the contents of the asset with the data in the given JSON string. | |
| /// </summary> | |
| /// <param name="json">JSON contents of an <c>.inputactions</c> asset.</param> | |
| /// <remarks> | |
| /// <c>.inputactions</c> assets are stored in JSON format. This method allows reading | |
| /// the JSON source text of such an asset into an existing <c>InputActionMap</c> instance. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// var asset = ScriptableObject.CreateInstance<InputActionAsset>(); | |
| /// asset.LoadFromJson(@" | |
| /// { | |
| /// ""maps"" : [ | |
| /// { | |
| /// ""name"" : ""gameplay"", | |
| /// ""actions"" : [ | |
| /// { ""name"" : ""fire"", ""type"" : ""button"" }, | |
| /// { ""name"" : ""look"", ""type"" : ""value"" }, | |
| /// { ""name"" : ""move"", ""type"" : ""value"" } | |
| /// ], | |
| /// ""bindings"" : [ | |
| /// { ""path"" : ""<Gamepad>/buttonSouth"", ""action"" : ""fire"", ""groups"" : ""Gamepad"" }, | |
| /// { ""path"" : ""<Gamepad>/leftTrigger"", ""action"" : ""fire"", ""groups"" : ""Gamepad"" }, | |
| /// { ""path"" : ""<Gamepad>/leftStick"", ""action"" : ""move"", ""groups"" : ""Gamepad"" }, | |
| /// { ""path"" : ""<Gamepad>/rightStick"", ""action"" : ""look"", ""groups"" : ""Gamepad"" }, | |
| /// { ""path"" : ""dpad"", ""action"" : ""move"", ""groups"" : ""Gamepad"", ""isComposite"" : true }, | |
| /// { ""path"" : ""<Keyboard>/a"", ""name"" : ""left"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true }, | |
| /// { ""path"" : ""<Keyboard>/d"", ""name"" : ""right"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true }, | |
| /// { ""path"" : ""<Keyboard>/w"", ""name"" : ""up"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true }, | |
| /// { ""path"" : ""<Keyboard>/s"", ""name"" : ""down"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true }, | |
| /// { ""path"" : ""<Mouse>/delta"", ""action"" : ""look"", ""groups"" : ""Keyboard&Mouse"" }, | |
| /// { ""path"" : ""<Mouse>/leftButton"", ""action"" : ""fire"", ""groups"" : ""Keyboard&Mouse"" } | |
| /// ] | |
| /// }, | |
| /// { | |
| /// ""name"" : ""ui"", | |
| /// ""actions"" : [ | |
| /// { ""name"" : ""navigate"" } | |
| /// ], | |
| /// ""bindings"" : [ | |
| /// { ""path"" : ""<Gamepad>/dpad"", ""action"" : ""navigate"", ""groups"" : ""Gamepad"" } | |
| /// ] | |
| /// } | |
| /// ], | |
| /// ""controlSchemes"" : [ | |
| /// { | |
| /// ""name"" : ""Gamepad"", | |
| /// ""bindingGroup"" : ""Gamepad"", | |
| /// ""devices"" : [ | |
| /// { ""devicePath"" : ""<Gamepad>"" } | |
| /// ] | |
| /// }, | |
| /// { | |
| /// ""name"" : ""Keyboard&Mouse"", | |
| /// ""bindingGroup"" : ""Keyboard&Mouse"", | |
| /// ""devices"" : [ | |
| /// { ""devicePath"" : ""<Keyboard>"" }, | |
| /// { ""devicePath"" : ""<Mouse>"" } | |
| /// ] | |
| /// } | |
| /// ] | |
| /// }"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <exception cref="ArgumentNullException"><paramref name="json"/> is <c>null</c> or empty.</exception> | |
| /// <seealso cref="FromJson"/> | |
| /// <seealso cref="ToJson"/> | |
| public void LoadFromJson(string json) | |
| { | |
| if (string.IsNullOrEmpty(json)) | |
| throw new ArgumentNullException(nameof(json)); | |
| var parsedJson = JsonUtility.FromJson<ReadFileJson>(json); | |
| parsedJson.ToAsset(this); | |
| } | |
| /// <summary> | |
| /// Replace the contents of the asset with the data in the given JSON string. | |
| /// </summary> | |
| /// <param name="json">JSON contents of an <c>.inputactions</c> asset.</param> | |
| /// <returns>The InputActionAsset instance created from the given JSON string.</returns> | |
| /// <remarks> | |
| /// <c>.inputactions</c> assets are stored in JSON format. This method allows turning | |
| /// the JSON source text of such an asset into a new <c>InputActionMap</c> instance. | |
| /// | |
| /// Be aware that the format used by this method is <em>different</em> than what you | |
| /// get if you call <c>JsonUtility.ToJson</c> on an InputActionAsset instance. In other | |
| /// words, the JSON format is not identical to the Unity serialized object representation | |
| /// of the asset. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// var asset = InputActionAsset.FromJson(@" | |
| /// { | |
| /// ""maps"" : [ | |
| /// { | |
| /// ""name"" : ""gameplay"", | |
| /// ""actions"" : [ | |
| /// { ""name"" : ""fire"", ""type"" : ""button"" }, | |
| /// { ""name"" : ""look"", ""type"" : ""value"" }, | |
| /// { ""name"" : ""move"", ""type"" : ""value"" } | |
| /// ], | |
| /// ""bindings"" : [ | |
| /// { ""path"" : ""<Gamepad>/buttonSouth"", ""action"" : ""fire"", ""groups"" : ""Gamepad"" }, | |
| /// { ""path"" : ""<Gamepad>/leftTrigger"", ""action"" : ""fire"", ""groups"" : ""Gamepad"" }, | |
| /// { ""path"" : ""<Gamepad>/leftStick"", ""action"" : ""move"", ""groups"" : ""Gamepad"" }, | |
| /// { ""path"" : ""<Gamepad>/rightStick"", ""action"" : ""look"", ""groups"" : ""Gamepad"" }, | |
| /// { ""path"" : ""dpad"", ""action"" : ""move"", ""groups"" : ""Gamepad"", ""isComposite"" : true }, | |
| /// { ""path"" : ""<Keyboard>/a"", ""name"" : ""left"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true }, | |
| /// { ""path"" : ""<Keyboard>/d"", ""name"" : ""right"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true }, | |
| /// { ""path"" : ""<Keyboard>/w"", ""name"" : ""up"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true }, | |
| /// { ""path"" : ""<Keyboard>/s"", ""name"" : ""down"", ""action"" : ""move"", ""groups"" : ""Keyboard&Mouse"", ""isPartOfComposite"" : true }, | |
| /// { ""path"" : ""<Mouse>/delta"", ""action"" : ""look"", ""groups"" : ""Keyboard&Mouse"" }, | |
| /// { ""path"" : ""<Mouse>/leftButton"", ""action"" : ""fire"", ""groups"" : ""Keyboard&Mouse"" } | |
| /// ] | |
| /// }, | |
| /// { | |
| /// ""name"" : ""ui"", | |
| /// ""actions"" : [ | |
| /// { ""name"" : ""navigate"" } | |
| /// ], | |
| /// ""bindings"" : [ | |
| /// { ""path"" : ""<Gamepad>/dpad"", ""action"" : ""navigate"", ""groups"" : ""Gamepad"" } | |
| /// ] | |
| /// } | |
| /// ], | |
| /// ""controlSchemes"" : [ | |
| /// { | |
| /// ""name"" : ""Gamepad"", | |
| /// ""bindingGroup"" : ""Gamepad"", | |
| /// ""devices"" : [ | |
| /// { ""devicePath"" : ""<Gamepad>"" } | |
| /// ] | |
| /// }, | |
| /// { | |
| /// ""name"" : ""Keyboard&Mouse"", | |
| /// ""bindingGroup"" : ""Keyboard&Mouse"", | |
| /// ""devices"" : [ | |
| /// { ""devicePath"" : ""<Keyboard>"" }, | |
| /// { ""devicePath"" : ""<Mouse>"" } | |
| /// ] | |
| /// } | |
| /// ] | |
| /// }"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <exception cref="ArgumentNullException"><paramref name="json"/> is <c>null</c> or empty.</exception> | |
| /// <seealso cref="LoadFromJson"/> | |
| /// <seealso cref="ToJson"/> | |
| public static InputActionAsset FromJson(string json) | |
| { | |
| if (string.IsNullOrEmpty(json)) | |
| throw new ArgumentNullException(nameof(json)); | |
| var asset = CreateInstance<InputActionAsset>(); | |
| asset.LoadFromJson(json); | |
| return asset; | |
| } | |
| /// <summary> | |
| /// Find an <see cref="InputAction"/> by its name in one of the <see cref="InputActionMap"/>s | |
| /// in the asset. | |
| /// </summary> | |
| /// <param name="actionNameOrId">Name of the action as either a "map/action" combination (e.g. "gameplay/fire") or | |
| /// a simple name. In the former case, the name is split at the '/' slash and the first part is used to find | |
| /// a map with that name and the second part is used to find an action with that name inside the map. In the | |
| /// latter case, all maps are searched in order and the first action that has the given name in any of the maps | |
| /// is returned. Note that name comparisons are case-insensitive. | |
| /// | |
| /// Alternatively, the given string can be a GUID as given by <see cref="InputAction.id"/>.</param> | |
| /// <param name="throwIfNotFound">If <c>true</c>, instead of returning <c>null</c> when the action | |
| /// cannot be found, throw <c>ArgumentException</c>.</param> | |
| /// <returns>The action with the corresponding name or <c>null</c> if no matching action could be found.</returns> | |
| /// <remarks> | |
| /// Note that no lookup structures are used internally to speed the operation up. Instead, the search is done | |
| /// linearly. For repeated access of an action, it is thus generally best to look up actions once ahead of | |
| /// time and cache the result. | |
| /// | |
| /// If multiple actions have the same name and <paramref name="actionNameOrId"/> is not an ID and not an | |
| /// action name qualified by a map name (that is, in the form of <c>"mapName/actionName"</c>), the action that | |
| /// is returned will be from the first map in <see cref="actionMaps"/> that has an action with the given name. | |
| /// An exception is if, of the multiple actions with the same name, some are enabled and some are disabled. In | |
| /// this case, the first action that is enabled is returned. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// var asset = ScriptableObject.CreateInstance<InputActionAsset>(); | |
| /// | |
| /// var map1 = new InputActionMap("map1"); | |
| /// var map2 = new InputActionMap("map2"); | |
| /// | |
| /// asset.AddActionMap(map1); | |
| /// asset.AddActionMap(map2); | |
| /// | |
| /// var action1 = map1.AddAction("action1"); | |
| /// var action2 = map1.AddAction("action2"); | |
| /// var action3 = map2.AddAction("action3"); | |
| /// | |
| /// // Search all maps in the asset for any action that has the given name. | |
| /// asset.FindAction("action1") // Returns action1. | |
| /// asset.FindAction("action2") // Returns action2 | |
| /// asset.FindAction("action3") // Returns action3. | |
| /// | |
| /// // Search for a specific action in a specific map. | |
| /// asset.FindAction("map1/action1") // Returns action1. | |
| /// asset.FindAction("map2/action2") // Returns action2. | |
| /// asset.FindAction("map3/action3") // Returns action3. | |
| /// | |
| /// // Search by unique action ID. | |
| /// asset.FindAction(action1.id.ToString()) // Returns action1. | |
| /// asset.FindAction(action2.id.ToString()) // Returns action2. | |
| /// asset.FindAction(action3.id.ToString()) // Returns action3. | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <exception cref="ArgumentNullException"><paramref name="actionNameOrId"/> is <c>null</c>.</exception> | |
| /// <exception cref="ArgumentException">Thrown if <paramref name="throwIfNotFound"/> is true and the | |
| /// action could not be found. -Or- If <paramref name="actionNameOrId"/> contains a slash but is missing | |
| /// either the action or the map name.</exception> | |
| public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false) | |
| { | |
| if (actionNameOrId == null) | |
| throw new ArgumentNullException(nameof(actionNameOrId)); | |
| if (m_ActionMaps != null) | |
| { | |
| // Check if we have a "map/action" path. | |
| var indexOfSlash = actionNameOrId.IndexOf('/'); | |
| if (indexOfSlash == -1) | |
| { | |
| // No slash so it's just a simple action name. Return either first enabled action or, if | |
| // none are enabled, first action with the given name. | |
| InputAction firstActionFound = null; | |
| for (var i = 0; i < m_ActionMaps.Length; ++i) | |
| { | |
| var action = m_ActionMaps[i].FindAction(actionNameOrId); | |
| if (action != null) | |
| { | |
| if (action.enabled || action.m_Id == actionNameOrId) // Match by ID is always exact. | |
| return action; | |
| if (firstActionFound == null) | |
| firstActionFound = action; | |
| } | |
| } | |
| if (firstActionFound != null) | |
| return firstActionFound; | |
| } | |
| else | |
| { | |
| // Have a path. First search for the map, then for the action. | |
| var mapName = new Substring(actionNameOrId, 0, indexOfSlash); | |
| var actionName = new Substring(actionNameOrId, indexOfSlash + 1); | |
| if (mapName.isEmpty || actionName.isEmpty) | |
| throw new ArgumentException("Malformed action path: " + actionNameOrId, nameof(actionNameOrId)); | |
| for (var i = 0; i < m_ActionMaps.Length; ++i) | |
| { | |
| var map = m_ActionMaps[i]; | |
| if (Substring.Compare(map.name, mapName, StringComparison.InvariantCultureIgnoreCase) != 0) | |
| continue; | |
| var actions = map.m_Actions; | |
| for (var n = 0; n < actions.Length; ++n) | |
| { | |
| var action = actions[n]; | |
| if (Substring.Compare(action.name, actionName, | |
| StringComparison.InvariantCultureIgnoreCase) == 0) | |
| return action; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| if (throwIfNotFound) | |
| throw new ArgumentException($"No action '{actionNameOrId}' in '{this}'"); | |
| return null; | |
| } | |
| /// <inheritdoc/> | |
| public int FindBinding(InputBinding mask, out InputAction action) | |
| { | |
| var numMaps = m_ActionMaps.LengthSafe(); | |
| for (var i = 0; i < numMaps; ++i) | |
| { | |
| var actionMap = m_ActionMaps[i]; | |
| var bindingIndex = actionMap.FindBinding(mask, out action); | |
| if (bindingIndex >= 0) | |
| return bindingIndex; | |
| } | |
| action = null; | |
| return -1; | |
| } | |
| /// <summary> | |
| /// Find an <see cref="InputActionMap"/> in the asset by its name or ID. | |
| /// </summary> | |
| /// <param name="nameOrId">Name or ID (see <see cref="InputActionMap.id"/>) of the action map | |
| /// to look for. Matching is case-insensitive.</param> | |
| /// <param name="throwIfNotFound">If true, instead of returning <c>null</c>, throw <c>ArgumentException</c>.</param> | |
| /// <returns>The <see cref="InputActionMap"/> with a name or ID matching <paramref name="nameOrId"/> or | |
| /// <c>null</c> if no matching map could be found.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="nameOrId"/> is <c>null</c>.</exception> | |
| /// <exception cref="ArgumentException">If <paramref name="throwIfNotFound"/> is <c>true</c>, thrown if | |
| /// the action map cannot be found.</exception> | |
| /// <seealso cref="actionMaps"/> | |
| /// <seealso cref="FindActionMap(System.Guid)"/> | |
| public InputActionMap FindActionMap(string nameOrId, bool throwIfNotFound = false) | |
| { | |
| if (nameOrId == null) | |
| throw new ArgumentNullException(nameof(nameOrId)); | |
| if (m_ActionMaps == null) | |
| return null; | |
| // If the name contains a hyphen, it may be a GUID. | |
| if (nameOrId.Contains('-') && Guid.TryParse(nameOrId, out var id)) | |
| { | |
| for (var i = 0; i < m_ActionMaps.Length; ++i) | |
| { | |
| var map = m_ActionMaps[i]; | |
| if (map.idDontGenerate == id) | |
| return map; | |
| } | |
| } | |
| // Default lookup is by name (case-insensitive). | |
| for (var i = 0; i < m_ActionMaps.Length; ++i) | |
| { | |
| var map = m_ActionMaps[i]; | |
| if (string.Compare(nameOrId, map.name, StringComparison.InvariantCultureIgnoreCase) == 0) | |
| return map; | |
| } | |
| if (throwIfNotFound) | |
| throw new ArgumentException($"Cannot find action map '{nameOrId}' in '{this}'"); | |
| return null; | |
| } | |
| /// <summary> | |
| /// Find an <see cref="InputActionMap"/> in the asset by its ID. | |
| /// </summary> | |
| /// <param name="id">ID (see <see cref="InputActionMap.id"/>) of the action map | |
| /// to look for.</param> | |
| /// <returns>The <see cref="InputActionMap"/> with ID matching <paramref name="id"/> or | |
| /// <c>null</c> if no map in the asset has the given ID.</returns> | |
| /// <seealso cref="actionMaps"/> | |
| /// <seealso cref="FindActionMap"/> | |
| public InputActionMap FindActionMap(Guid id) | |
| { | |
| if (m_ActionMaps == null) | |
| return null; | |
| for (var i = 0; i < m_ActionMaps.Length; ++i) | |
| { | |
| var map = m_ActionMaps[i]; | |
| if (map.idDontGenerate == id) | |
| return map; | |
| } | |
| return null; | |
| } | |
| /// <summary> | |
| /// Find an action by its ID (see <see cref="InputAction.id"/>). | |
| /// </summary> | |
| /// <param name="guid">ID of the action to look for.</param> | |
| /// <returns>The action in the asset with the given ID or null if no action | |
| /// in the asset has the given ID.</returns> | |
| public InputAction FindAction(Guid guid) | |
| { | |
| if (m_ActionMaps == null) | |
| return null; | |
| for (var i = 0; i < m_ActionMaps.Length; ++i) | |
| { | |
| var map = m_ActionMaps[i]; | |
| var action = map.FindAction(guid); | |
| if (action != null) | |
| return action; | |
| } | |
| return null; | |
| } | |
| /// <summary> | |
| /// Find the control scheme with the given name and return its index | |
| /// in <see cref="controlSchemes"/>. | |
| /// </summary> | |
| /// <param name="name">Name of the control scheme. Matching is case-insensitive.</param> | |
| /// <returns>The index of the given control scheme or -1 if no control scheme | |
| /// with the given name could be found.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c> | |
| /// or empty.</exception> | |
| public int FindControlSchemeIndex(string name) | |
| { | |
| if (string.IsNullOrEmpty(name)) | |
| throw new ArgumentNullException(nameof(name)); | |
| if (m_ControlSchemes == null) | |
| return -1; | |
| for (var i = 0; i < m_ControlSchemes.Length; ++i) | |
| if (string.Compare(name, m_ControlSchemes[i].name, StringComparison.InvariantCultureIgnoreCase) == 0) | |
| return i; | |
| return -1; | |
| } | |
| /// <summary> | |
| /// Find the control scheme with the given name and return it. | |
| /// </summary> | |
| /// <param name="name">Name of the control scheme. Matching is case-insensitive.</param> | |
| /// <returns>The control scheme with the given name or null if no scheme | |
| /// with the given name could be found in the asset.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c> | |
| /// or empty.</exception> | |
| public InputControlScheme? FindControlScheme(string name) | |
| { | |
| if (string.IsNullOrEmpty(name)) | |
| throw new ArgumentNullException(nameof(name)); | |
| var index = FindControlSchemeIndex(name); | |
| if (index == -1) | |
| return null; | |
| return m_ControlSchemes[index]; | |
| } | |
| /// <summary> | |
| /// Return true if the asset contains bindings (in any of its action maps) that are usable | |
| /// with the given <paramref name="device"/>. | |
| /// </summary> | |
| /// <param name="device">An arbitrary input device.</param> | |
| /// <returns></returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Find out if the actions of the given PlayerInput can be used with | |
| /// // a gamepad. | |
| /// if (playerInput.actions.IsUsableWithDevice(Gamepad.all[0])) | |
| /// /* ... */; | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <seealso cref="InputActionMap.IsUsableWithDevice"/> | |
| /// <seealso cref="InputControlScheme.SupportsDevice"/> | |
| public bool IsUsableWithDevice(InputDevice device) | |
| { | |
| if (device == null) | |
| throw new ArgumentNullException(nameof(device)); | |
| // If we have control schemes, we let those dictate our search. | |
| var numControlSchemes = m_ControlSchemes.LengthSafe(); | |
| if (numControlSchemes > 0) | |
| { | |
| for (var i = 0; i < numControlSchemes; ++i) | |
| { | |
| if (m_ControlSchemes[i].SupportsDevice(device)) | |
| return true; | |
| } | |
| } | |
| else | |
| { | |
| // Otherwise, we'll go search bindings. Slow. | |
| var actionMapCount = m_ActionMaps.LengthSafe(); | |
| for (var i = 0; i < actionMapCount; ++i) | |
| if (m_ActionMaps[i].IsUsableWithDevice(device)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// <summary> | |
| /// Enable all action maps in the asset. | |
| /// </summary> | |
| /// <remarks> | |
| /// This method is equivalent to calling <see cref="InputActionMap.Enable"/> on | |
| /// all maps in <see cref="actionMaps"/>. | |
| /// </remarks> | |
| public void Enable() | |
| { | |
| foreach (var map in actionMaps) | |
| map.Enable(); | |
| } | |
| /// <summary> | |
| /// Disable all action maps in the asset. | |
| /// </summary> | |
| /// <remarks> | |
| /// This method is equivalent to calling <see cref="InputActionMap.Disable"/> on | |
| /// all maps in <see cref="actionMaps"/>. | |
| /// </remarks> | |
| public void Disable() | |
| { | |
| foreach (var map in actionMaps) | |
| map.Disable(); | |
| } | |
| /// <summary> | |
| /// Return <c>true</c> if the given action is part of the asset. | |
| /// </summary> | |
| /// <param name="action">An action. Can be null.</param> | |
| /// <returns>True if the given action is part of the asset, false otherwise.</returns> | |
| public bool Contains(InputAction action) | |
| { | |
| var map = action?.actionMap; | |
| if (map == null) | |
| return false; | |
| return map.asset == this; | |
| } | |
| /// <summary> | |
| /// Enumerate all actions in the asset. | |
| /// </summary> | |
| /// <returns>An enumerator going over the actions in the asset.</returns> | |
| /// <remarks> | |
| /// Actions will be enumerated one action map in <see cref="actionMaps"/> | |
| /// after the other. The actions from each map will be yielded in turn. | |
| /// | |
| /// This method will allocate GC heap memory. | |
| /// </remarks> | |
| public IEnumerator<InputAction> GetEnumerator() | |
| { | |
| if (m_ActionMaps == null) | |
| yield break; | |
| for (var i = 0; i < m_ActionMaps.Length; ++i) | |
| { | |
| var actions = m_ActionMaps[i].actions; | |
| var actionCount = actions.Count; | |
| for (var n = 0; n < actionCount; ++n) | |
| yield return actions[n]; | |
| } | |
| } | |
| /// <summary> | |
| /// Enumerate all actions in the asset. | |
| /// </summary> | |
| /// <returns>An enumerator going over the actions in the asset.</returns> | |
| /// <seealso cref="GetEnumerator"/> | |
| IEnumerator IEnumerable.GetEnumerator() | |
| { | |
| return GetEnumerator(); | |
| } | |
| internal void MarkAsDirty() | |
| { | |
| InputSystem.TrackDirtyInputActionAsset(this); | |
| } | |
| internal void OnWantToChangeSetup() | |
| { | |
| if (m_ActionMaps.LengthSafe() > 0) | |
| m_ActionMaps[0].OnWantToChangeSetup(); | |
| } | |
| internal void OnSetupChanged() | |
| { | |
| MarkAsDirty(); | |
| if (m_ActionMaps.LengthSafe() > 0) | |
| m_ActionMaps[0].OnSetupChanged(); | |
| else | |
| m_SharedStateForAllMaps = null; | |
| } | |
| private void ReResolveIfNecessary(bool fullResolve) | |
| { | |
| if (m_SharedStateForAllMaps == null) | |
| return; | |
| Debug.Assert(m_ActionMaps != null && m_ActionMaps.Length > 0); | |
| // State is share between all action maps in the asset. Resolving bindings for the | |
| // first map will resolve them for all maps. | |
| m_ActionMaps[0].LazyResolveBindings(fullResolve); | |
| } | |
| internal void ResolveBindingsIfNecessary() | |
| { | |
| if (m_ActionMaps.LengthSafe() > 0) | |
| foreach (var map in m_ActionMaps) | |
| if (map.ResolveBindingsIfNecessary()) | |
| break; | |
| } | |
| private void OnDestroy() | |
| { | |
| Disable(); | |
| if (m_SharedStateForAllMaps != null) | |
| { | |
| m_SharedStateForAllMaps.Dispose(); // Will clean up InputActionMap state. | |
| m_SharedStateForAllMaps = null; | |
| } | |
| } | |
| ////TODO: ApplyBindingOverrides, RemoveBindingOverrides, RemoveAllBindingOverrides | |
| [] internal InputActionMap[] m_ActionMaps; | |
| [] internal InputControlScheme[] m_ControlSchemes; | |
| ////TODO: make this persistent across domain reloads | |
| /// <summary> | |
| /// Shared state for all action maps in the asset. | |
| /// </summary> | |
| [] internal InputActionState m_SharedStateForAllMaps; | |
| [] internal InputBinding? m_BindingMask; | |
| [] internal int m_ParameterOverridesCount; | |
| [] internal InputActionRebindingExtensions.ParameterOverride[] m_ParameterOverrides; | |
| [] internal InputActionMap.DeviceArray m_Devices; | |
| [] | |
| internal struct WriteFileJson | |
| { | |
| public string name; | |
| public InputActionMap.WriteMapJson[] maps; | |
| public InputControlScheme.SchemeJson[] controlSchemes; | |
| } | |
| [] | |
| internal struct ReadFileJson | |
| { | |
| public string name; | |
| public InputActionMap.ReadMapJson[] maps; | |
| public InputControlScheme.SchemeJson[] controlSchemes; | |
| public void ToAsset(InputActionAsset asset) | |
| { | |
| asset.name = name; | |
| asset.m_ActionMaps = new InputActionMap.ReadFileJson {maps = maps}.ToMaps(); | |
| asset.m_ControlSchemes = InputControlScheme.SchemeJson.ToSchemes(controlSchemes); | |
| // Link maps to their asset. | |
| if (asset.m_ActionMaps != null) | |
| foreach (var map in asset.m_ActionMaps) | |
| map.m_Asset = asset; | |
| } | |
| } | |
| } | |
| } | |