Maze / Library /PackageCache /com.unity.inputsystem@1.6.1 /InputSystem /Actions /InputActionSetupExtensions.cs
| using System; | |
| using UnityEngine.InputSystem.Layouts; | |
| using UnityEngine.InputSystem.Utilities; | |
| ////TODO: Rename all the xxxSyntax structs to xxxAccessor | |
| ////TODO: Replace all 'WithXXX' in the accessors with just 'SetXXX'; the 'WithXXX' reads too awkwardly | |
| namespace UnityEngine.InputSystem | |
| { | |
| /// <summary> | |
| /// Methods to change the setup of <see cref="InputAction"/>, <see cref="InputActionMap"/>, | |
| /// and <see cref="InputActionAsset"/> objects. | |
| /// </summary> | |
| /// <remarks> | |
| /// Unlike the methods in <see cref="InputActionRebindingExtensions"/>, the methods here are | |
| /// generally destructive, i.e. they will rearrange the data for actions. | |
| /// </remarks> | |
| public static class InputActionSetupExtensions | |
| { | |
| /// <summary> | |
| /// Create an action map with the given name and add it to the asset. | |
| /// </summary> | |
| /// <param name="asset">Asset to add the action map to</param> | |
| /// <param name="name">Name to assign to the </param> | |
| /// <returns>The newly added action map.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> or | |
| /// <exception cref="InvalidOperationException">An action map with the given <paramref name="name"/> | |
| /// already exists in <paramref name="asset"/>.</exception> | |
| /// <paramref name="name"/> is <c>null</c> or empty.</exception> | |
| public static InputActionMap AddActionMap(this InputActionAsset asset, string name) | |
| { | |
| if (asset == null) | |
| throw new ArgumentNullException(nameof(asset)); | |
| if (string.IsNullOrEmpty(name)) | |
| throw new ArgumentNullException(nameof(name)); | |
| if (asset.FindActionMap(name) != null) | |
| throw new InvalidOperationException( | |
| $"An action map called '{name}' already exists in the asset"); | |
| var map = new InputActionMap(name); | |
| map.GenerateId(); | |
| asset.AddActionMap(map); | |
| return map; | |
| } | |
| /// <summary> | |
| /// Add an action map to the asset. | |
| /// </summary> | |
| /// <param name="asset">Asset to add the map to.</param> | |
| /// <param name="map">A named action map.</param> | |
| /// <exception cref="ArgumentNullException"><paramref name="map"/> or <paramref name="asset"/> is <c>null</c>.</exception> | |
| /// <exception cref="InvalidOperationException"><paramref name="map"/> has no name or asset already contains a | |
| /// map with the same name -or- <paramref name="map"/> is currently enabled -or- <paramref name="map"/> is part of | |
| /// an <see cref="InputActionAsset"/> that has <see cref="InputActionMap"/>s that are enabled.</exception> | |
| /// <seealso cref="InputActionAsset.actionMaps"/> | |
| public static void AddActionMap(this InputActionAsset asset, InputActionMap map) | |
| { | |
| if (asset == null) | |
| throw new ArgumentNullException(nameof(asset)); | |
| if (map == null) | |
| throw new ArgumentNullException(nameof(map)); | |
| if (string.IsNullOrEmpty(map.name)) | |
| throw new InvalidOperationException("Maps added to an input action asset must be named"); | |
| if (map.asset != null) | |
| throw new InvalidOperationException( | |
| $"Cannot add map '{map}' to asset '{asset}' as it has already been added to asset '{map.asset}'"); | |
| ////REVIEW: some of the rules here seem stupid; just replace? | |
| if (asset.FindActionMap(map.name) != null) | |
| throw new InvalidOperationException( | |
| $"An action map called '{map.name}' already exists in the asset"); | |
| map.OnWantToChangeSetup(); | |
| asset.OnWantToChangeSetup(); | |
| ArrayHelpers.Append(ref asset.m_ActionMaps, map); | |
| map.m_Asset = asset; | |
| asset.OnSetupChanged(); | |
| } | |
| /// <summary> | |
| /// Remove the given action map from the asset. | |
| /// </summary> | |
| /// <param name="asset">Asset to add the action map to.</param> | |
| /// <param name="map">An action map. If the given map is not part of the asset, the method | |
| /// does nothing.</param> | |
| /// <exception cref="ArgumentNullException"><paramref name="asset"/> or <paramref name="map"/> is <c>null</c>.</exception> | |
| /// <exception cref="InvalidOperationException"><paramref name="map"/> is currently enabled (see <see | |
| /// cref="InputActionMap.enabled"/>) or is part of an <see cref="InputActionAsset"/> that has <see cref="InputActionMap"/>s | |
| /// that are currently enabled.</exception> | |
| /// <seealso cref="RemoveActionMap(InputActionAsset,string)"/> | |
| /// <seealso cref="InputActionAsset.actionMaps"/> | |
| public static void RemoveActionMap(this InputActionAsset asset, InputActionMap map) | |
| { | |
| if (asset == null) | |
| throw new ArgumentNullException(nameof(asset)); | |
| if (map == null) | |
| throw new ArgumentNullException(nameof(map)); | |
| map.OnWantToChangeSetup(); | |
| asset.OnWantToChangeSetup(); | |
| // Ignore if not part of this asset. | |
| if (map.m_Asset != asset) | |
| return; | |
| ArrayHelpers.Erase(ref asset.m_ActionMaps, map); | |
| map.m_Asset = null; | |
| asset.OnSetupChanged(); | |
| } | |
| /// <summary> | |
| /// Remove the action map with the given name or ID from the asset. | |
| /// </summary> | |
| /// <param name="asset">Asset to remove the action map from.</param> | |
| /// <param name="nameOrId">The name or ID (see <see cref="InputActionMap.id"/>) of a map in the | |
| /// asset. Note that lookup is case-insensitive. If no map with the given name or ID is found, | |
| /// the method does nothing.</param> | |
| /// <exception cref="ArgumentNullException"><paramref name="asset"/> or <paramref name="nameOrId"/> is <c>null</c>.</exception> | |
| /// <exception cref="InvalidOperationException">The map referenced by <paramref name="nameOrId"/> is currently enabled | |
| /// (see <see cref="InputActionMap.enabled"/>).</exception> | |
| /// <seealso cref="RemoveActionMap(InputActionAsset,string)"/> | |
| /// <seealso cref="InputActionAsset.actionMaps"/> | |
| public static void RemoveActionMap(this InputActionAsset asset, string nameOrId) | |
| { | |
| if (asset == null) | |
| throw new ArgumentNullException(nameof(asset)); | |
| if (nameOrId == null) | |
| throw new ArgumentNullException(nameof(nameOrId)); | |
| var map = asset.FindActionMap(nameOrId); | |
| if (map != null) | |
| asset.RemoveActionMap(map); | |
| } | |
| ////TODO: add method to add an existing InputAction to a map | |
| /// <summary> | |
| /// Add a new <see cref="InputAction"/> to the given <paramref name="map"/>. | |
| /// </summary> | |
| /// <param name="map">Action map to add the action to. The action will be appended to | |
| /// <see cref="InputActionMap.actions"/> of the map. The map must be disabled (see | |
| /// <see cref="InputActionMap.enabled"/>).</param> | |
| /// <param name="name">Name to give to the action. Must not be <c>null</c> or empty. Also, | |
| /// no other action that already exists in <paramref name="map"/> must have this name already.</param> | |
| /// <param name="type">Action type. See <see cref="InputAction.type"/>.</param> | |
| /// <param name="binding">If not <c>null</c>, a binding is automatically added to the newly created action | |
| /// with the value of this parameter being used as the binding's <see cref="InputBinding.path"/>.</param> | |
| /// <param name="interactions">If <paramref name="binding"/> is not <c>null</c>, this string is used for | |
| /// <see cref="InputBinding.interactions"/> of the binding that is automatically added for the action.</param> | |
| /// <param name="processors">If <paramref name="binding"/> is not <c>null</c>, this string is used for | |
| /// <see cref="InputBinding.processors"/> of the binding that is automatically added for the action.</param> | |
| /// <param name="groups">If <paramref name="binding"/> is not <c>null</c>, this string is used for | |
| /// <see cref="InputBinding.groups"/> of the binding that is automatically added for the action.</param> | |
| /// <param name="expectedControlLayout">Value for <see cref="InputAction.expectedControlType"/>; <c>null</c> | |
| /// by default.</param> | |
| /// <returns>The newly added input action.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="map"/> is <c>null</c>.</exception> | |
| /// <exception cref="ArgumentException"><paramref name="name"/> is <c>null</c> or empty.</exception> | |
| /// <exception cref="InvalidOperationException"><paramref name="map"/> is enabled (see <see cref="InputActionMap.enabled"/>) | |
| /// or is part of an <see cref="InputActionAsset"/> that has <see cref="InputActionMap"/>s that are <see cref="InputActionMap.enabled"/> | |
| /// -or- <paramref name="map"/> already contains an action called <paramref name="name"/> (case-insensitive).</exception> | |
| public static InputAction AddAction(this InputActionMap map, string name, InputActionType type = default, string binding = null, | |
| string interactions = null, string processors = null, string groups = null, string expectedControlLayout = null) | |
| { | |
| if (map == null) | |
| throw new ArgumentNullException(nameof(map)); | |
| if (string.IsNullOrEmpty(name)) | |
| throw new ArgumentException("Action must have name", nameof(name)); | |
| map.OnWantToChangeSetup(); | |
| if (map.FindAction(name) != null) | |
| throw new InvalidOperationException( | |
| $"Cannot add action with duplicate name '{name}' to set '{map.name}'"); | |
| // Append action to array. | |
| var action = new InputAction(name, type) | |
| { | |
| expectedControlType = expectedControlLayout | |
| }; | |
| action.GenerateId(); | |
| ArrayHelpers.Append(ref map.m_Actions, action); | |
| action.m_ActionMap = map; | |
| // Add binding, if supplied. | |
| if (!string.IsNullOrEmpty(binding)) | |
| { | |
| // Will trigger OnSetupChanged. | |
| action.AddBinding(binding, interactions: interactions, processors: processors, groups: groups); | |
| } | |
| else | |
| { | |
| if (!string.IsNullOrEmpty(groups)) | |
| throw new ArgumentException( | |
| $"No binding path was specified for action '{action}' but groups was specified ('{groups}'); cannot apply groups without binding", | |
| nameof(groups)); | |
| // If no binding has been supplied but there are interactions and processors, they go on the action itself. | |
| action.m_Interactions = interactions; | |
| action.m_Processors = processors; | |
| map.OnSetupChanged(); | |
| } | |
| return action; | |
| } | |
| /// <summary> | |
| /// Remove the given action from its <see cref="InputActionMap"/>. | |
| /// </summary> | |
| /// <param name="action">An input action that is part of an <see cref="InputActionMap"/>.</param> | |
| /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> | |
| /// <exception cref="ArgumentException"><paramref name="action"/> is a standalone action | |
| /// that is not part of an <see cref="InputActionMap"/> and thus cannot be removed from anything.</exception> | |
| /// <exception cref="InvalidOperationException"><paramref name="action"/> is part of an <see cref="InputActionMap"/> | |
| /// or <see cref="InputActionAsset"/> that has at least one enabled action.</exception> | |
| /// <remarks> | |
| /// After removal, the action's <see cref="InputAction.actionMap"/> will be set to <c>null</c> | |
| /// and the action will effectively become a standalone action that is not associated with | |
| /// any action map. Bindings on the action will be preserved. On the action map, the bindings | |
| /// for the action will be removed. | |
| /// </remarks> | |
| /// <seealso cref="AddAction"/> | |
| public static void RemoveAction(this InputAction action) | |
| { | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| var actionMap = action.actionMap; | |
| if (actionMap == null) | |
| throw new ArgumentException( | |
| $"Action '{action}' does not belong to an action map; nowhere to remove from", nameof(action)); | |
| actionMap.OnWantToChangeSetup(); | |
| var bindingsForAction = action.bindings.ToArray(); | |
| var index = actionMap.m_Actions.IndexOfReference(action); | |
| Debug.Assert(index != -1, "Could not find action in map"); | |
| ArrayHelpers.EraseAt(ref actionMap.m_Actions, index); | |
| action.m_ActionMap = null; | |
| action.m_SingletonActionBindings = bindingsForAction; | |
| // Remove bindings to action from map. | |
| var newActionMapBindingCount = actionMap.m_Bindings.Length - bindingsForAction.Length; | |
| if (newActionMapBindingCount == 0) | |
| { | |
| actionMap.m_Bindings = null; | |
| } | |
| else | |
| { | |
| var newActionMapBindings = new InputBinding[newActionMapBindingCount]; | |
| var oldActionMapBindings = actionMap.m_Bindings; | |
| var bindingIndex = 0; | |
| for (var i = 0; i < oldActionMapBindings.Length; ++i) | |
| { | |
| var binding = oldActionMapBindings[i]; | |
| if (bindingsForAction.IndexOf(b => b == binding) == -1) | |
| newActionMapBindings[bindingIndex++] = binding; | |
| } | |
| actionMap.m_Bindings = newActionMapBindings; | |
| } | |
| actionMap.OnSetupChanged(); | |
| } | |
| /// <summary> | |
| /// Remove the action with the given name from the asset. | |
| /// </summary> | |
| /// <param name="asset">Asset to remove the action from.</param> | |
| /// <param name="nameOrId">Name or ID of the action. See <see cref="InputActionAsset.FindAction(string,bool)"/> for | |
| /// details.</param> | |
| /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="nameOrId"/> | |
| /// is <c>null</c> or empty.</exception> | |
| /// <seealso cref="RemoveAction(InputAction)"/> | |
| public static void RemoveAction(this InputActionAsset asset, string nameOrId) | |
| { | |
| if (asset == null) | |
| throw new ArgumentNullException(nameof(asset)); | |
| if (nameOrId == null) | |
| throw new ArgumentNullException(nameof(nameOrId)); | |
| var action = asset.FindAction(nameOrId); | |
| action?.RemoveAction(); | |
| } | |
| /// <summary> | |
| /// Add a new binding to the given action. | |
| /// </summary> | |
| /// <param name="action">Action to add the binding to. If the action is part of an <see cref="InputActionMap"/>, | |
| /// the newly added binding will be visible on <see cref="InputActionMap.bindings"/>.</param> | |
| /// <param name="path">Binding path string. See <see cref="InputBinding.path"/> for details.</param> | |
| /// <param name="interactions">Optional list of interactions to apply to the binding. See <see | |
| /// cref="InputBinding.interactions"/> for details.</param> | |
| /// <param name="processors">Optional list of processors to apply to the binding. See <see | |
| /// cref="InputBinding.processors"/> for details.</param> | |
| /// <param name="groups">Optional list of binding groups that should be assigned to the binding. See | |
| /// <see cref="InputBinding.groups"/> for details.</param> | |
| /// <returns>Fluent-style syntax to further configure the binding.</returns> | |
| public static BindingSyntax AddBinding(this InputAction action, string path, string interactions = null, | |
| string processors = null, string groups = null) | |
| { | |
| return AddBinding(action, new InputBinding | |
| { | |
| path = path, | |
| interactions = interactions, | |
| processors = processors, | |
| groups = groups | |
| }); | |
| } | |
| /// <summary> | |
| /// Add a binding that references the given <paramref name="control"/> and triggers | |
| /// the given <paramref cref="action"/>. | |
| /// </summary> | |
| /// <param name="action">Action to trigger.</param> | |
| /// <param name="control">Control to bind to. The full <see cref="InputControl.path"/> of the control will | |
| /// be used in the resulting <see cref="InputBinding">binding</see>.</param> | |
| /// <returns>Syntax to configure the binding further.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="action"/> is null or <paramref name="control"/> is null.</exception> | |
| /// <seealso cref="InputAction.bindings"/> | |
| public static BindingSyntax AddBinding(this InputAction action, InputControl control) | |
| { | |
| if (control == null) | |
| throw new ArgumentNullException(nameof(control)); | |
| return AddBinding(action, control.path); | |
| } | |
| /// <summary> | |
| /// Add a new binding to the action. | |
| /// </summary> | |
| /// <param name="action">An action to add the binding to.</param> | |
| /// <param name="binding">Binding to add to the action or default. Binding can be further configured via | |
| /// the struct returned by the method.</param> | |
| /// <returns> | |
| /// Returns a fluent-style syntax structure that allows performing additional modifications | |
| /// based on the new binding. | |
| /// </returns> | |
| /// <remarks> | |
| /// This works both with actions that are part of an action set as well as with actions that aren't. | |
| /// | |
| /// Note that actions must be disabled while altering their binding sets. Also, if the action belongs | |
| /// to a set, all actions in the set must be disabled. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// fireAction.AddBinding() | |
| /// .WithPath("<Gamepad>/buttonSouth") | |
| /// .WithGroup("Gamepad"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| public static BindingSyntax AddBinding(this InputAction action, InputBinding binding = default) | |
| { | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| ////REVIEW: should this reference actions by ID? | |
| Debug.Assert(action.m_Name != null || action.isSingletonAction); | |
| binding.action = action.name; | |
| var actionMap = action.GetOrCreateActionMap(); | |
| var bindingIndex = AddBindingInternal(actionMap, binding); | |
| return new BindingSyntax(actionMap, bindingIndex); | |
| } | |
| /// <summary> | |
| /// Add a new binding to the given action map. | |
| /// </summary> | |
| /// <param name="actionMap">Action map to add the binding to.</param> | |
| /// <param name="path">Path of the control(s) to bind to. See <see cref="InputControlPath"/> and | |
| /// <see cref="InputBinding.path"/>.</param> | |
| /// <param name="interactions">Names and parameters for interactions to apply to the | |
| /// binding. See <see cref="InputBinding.interactions"/>.</param> | |
| /// <param name="groups">Optional list of groups to apply to the binding. See <see cref="InputBinding.groups"/>.</param> | |
| /// <param name="action">Action to trigger from the binding. See <see cref="InputBinding.action"/>.</param> | |
| /// <param name="processors">Optional list of processors to apply to the binding. See <see cref="InputBinding.processors"/>.</param> | |
| /// <returns>A write-accessor to the newly added binding.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Add a binding for the A button the gamepad and make it trigger | |
| /// // the "fire" action. | |
| /// var gameplayActions = playerInput.actions.FindActionMap("gameplay"); | |
| /// gameplayActions.AddBinding("<Gamepad>/buttonSouth", action: "fire"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <seealso cref="InputBinding"/> | |
| /// <seealso cref="InputActionMap.bindings"/> | |
| public static BindingSyntax AddBinding(this InputActionMap actionMap, string path, | |
| string interactions = null, string groups = null, string action = null, string processors = null) | |
| { | |
| if (path == null) | |
| throw new ArgumentNullException(nameof(path), "Binding path cannot be null"); | |
| return AddBinding(actionMap, | |
| new InputBinding | |
| { | |
| path = path, | |
| interactions = interactions, | |
| groups = groups, | |
| action = action, | |
| processors = processors, | |
| }); | |
| } | |
| /// <summary> | |
| /// Add a new binding that triggers the given action to the given action map. | |
| /// </summary> | |
| /// <param name="actionMap">Action map to add the binding to.</param> | |
| /// <param name="action">Action to trigger from the binding. See <see cref="InputBinding.action"/>. | |
| /// Must be part of <paramref name="actionMap"/>.</param> | |
| /// <param name="path">Path of the control(s) to bind to. See <see cref="InputControlPath"/> and | |
| /// <see cref="InputBinding.path"/>.</param> | |
| /// <param name="interactions">Names and parameters for interactions to apply to the | |
| /// binding. See <see cref="InputBinding.interactions"/>.</param> | |
| /// <param name="groups">Binding groups to apply to the binding. See <see cref="InputBinding.groups"/>.</param> | |
| /// <returns>A write-accessor to the newly added binding.</returns> | |
| /// <exception cref="ArgumentException"><paramref name="action"/> is not part of <paramref name="actionMap"/>.</exception> | |
| /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception> | |
| /// <seealso cref="InputBinding"/> | |
| /// <seealso cref="InputActionMap.bindings"/> | |
| public static BindingSyntax AddBinding(this InputActionMap actionMap, string path, InputAction action, | |
| string interactions = null, string groups = null) | |
| { | |
| if (action != null && action.actionMap != actionMap) | |
| throw new ArgumentException( | |
| $"Action '{action}' is not part of action map '{actionMap}'", nameof(action)); | |
| if (action == null) | |
| return AddBinding(actionMap, path: path, interactions: interactions, groups: groups); | |
| return AddBinding(actionMap, path: path, interactions: interactions, groups: groups, | |
| action: action.id); | |
| } | |
| /// <summary> | |
| /// Add a new binding that triggers the action given by GUID <paramref name="action"/>. | |
| /// </summary> | |
| /// <param name="actionMap">Action map to add the binding to.</param> | |
| /// <param name="path">Path of the control(s) to bind to. See <see cref="InputControlPath"/> and <see cref="InputBinding.path"/>.</param> | |
| /// <param name="action">ID of the action as per <see cref="InputAction.id"/>.</param> | |
| /// <param name="interactions">Optional list of names and parameters for interactions to apply to the | |
| /// binding. See <see cref="InputBinding.interactions"/>.</param> | |
| /// <param name="groups">Optional list of groups to apply to the binding. See <see cref="InputBinding.groups"/>.</param> | |
| /// <returns>A write-accessor to the newly added binding.</returns> | |
| /// <exception cref="ArgumentException"><paramref name="action"/> is not part of <paramref name="actionMap"/>.</exception> | |
| /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception> | |
| /// <seealso cref="InputBinding"/> | |
| /// <seealso cref="InputActionMap.bindings"/> | |
| /// <remarks> | |
| /// Example of adding a binding to an action map that binds to a Gamepad device "leftStick" control and associates it with an action: | |
| /// <example> | |
| /// <code> | |
| /// var map = new InputActionMap(); | |
| /// var action = map.AddAction("action"); | |
| /// map.AddBinding("<Gamepad>/leftStick", action: action.id); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| public static BindingSyntax AddBinding(this InputActionMap actionMap, string path, Guid action, | |
| string interactions = null, string groups = null) | |
| { | |
| if (action == Guid.Empty) | |
| return AddBinding(actionMap, path: path, interactions: interactions, groups: groups); | |
| return AddBinding(actionMap, path: path, interactions: interactions, groups: groups, | |
| action: action.ToString()); | |
| } | |
| /// <summary> | |
| /// Add a binding to the given action map. | |
| /// </summary> | |
| /// <param name="actionMap">Action map to add the binding to.</param> | |
| /// <param name="binding">Binding to add to the action map.</param> | |
| /// <returns>A write-accessor to the newly added binding.</returns> | |
| /// <exception cref="ArgumentException"><paramref name="action"/> is not part of <paramref name="actionMap"/>.</exception> | |
| /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception> | |
| /// <seealso cref="InputBinding"/> | |
| /// <seealso cref="InputActionMap.bindings"/> | |
| public static BindingSyntax AddBinding(this InputActionMap actionMap, InputBinding binding) | |
| { | |
| if (actionMap == null) | |
| throw new ArgumentNullException(nameof(actionMap)); | |
| if (binding.path == null) | |
| throw new ArgumentException("Binding path cannot be null", nameof(binding)); | |
| var bindingIndex = AddBindingInternal(actionMap, binding); | |
| return new BindingSyntax(actionMap, bindingIndex); | |
| } | |
| /// <summary> | |
| /// Add a composite binding to the <see cref="InputAction.bindings"/> of <paramref name="action"/>. | |
| /// </summary> | |
| /// <param name="action">Action to add the binding to.</param> | |
| /// <param name="composite">Type of composite to add. This needs to be the name the composite | |
| /// has been registered under using <see cref="InputSystem.RegisterBindingComposite{T}"/>. Case-insensitive.</param> | |
| /// <param name="interactions">Interactions to add to the binding. See <see cref="InputBinding.interactions"/>.</param> | |
| /// <param name="processors">Processors to add to the binding. See <see cref="InputBinding.processors"/>.</param> | |
| /// <returns>A write accessor to the newly added composite binding.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> | |
| /// <exception cref="ArgumentException"><paramref name="composite"/> is <c>null</c> or empty.</exception> | |
| public static CompositeSyntax AddCompositeBinding(this InputAction action, string composite, | |
| string interactions = null, string processors = null) | |
| { | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| if (string.IsNullOrEmpty(composite)) | |
| throw new ArgumentException("Composite name cannot be null or empty", nameof(composite)); | |
| var actionMap = action.GetOrCreateActionMap(); | |
| var binding = new InputBinding | |
| { | |
| name = NameAndParameters.ParseName(composite), | |
| path = composite, | |
| interactions = interactions, | |
| processors = processors, | |
| isComposite = true, | |
| action = action.name | |
| }; | |
| var bindingIndex = AddBindingInternal(actionMap, binding); | |
| return new CompositeSyntax(actionMap, action, bindingIndex); | |
| } | |
| ////TODO: AddCompositeBinding<T> | |
| private static int AddBindingInternal(InputActionMap map, InputBinding binding, int bindingIndex = -1) | |
| { | |
| Debug.Assert(map != null); | |
| // Make sure the binding has an ID. | |
| if (string.IsNullOrEmpty(binding.m_Id)) | |
| binding.GenerateId(); | |
| // Append to bindings in set. | |
| if (bindingIndex < 0) | |
| bindingIndex = ArrayHelpers.Append(ref map.m_Bindings, binding); | |
| else | |
| ArrayHelpers.InsertAt(ref map.m_Bindings, bindingIndex, binding); | |
| // Make sure this asset is reloaded from disk when exiting play mode so it isn't inadvertently | |
| // changed between play sessions. Only applies when running in the editor. | |
| if (map.asset != null) | |
| map.asset.MarkAsDirty(); | |
| // If we're looking at a singleton action, make sure m_Bindings is up to date just | |
| // in case the action gets serialized. | |
| if (map.m_SingletonAction != null) | |
| map.m_SingletonAction.m_SingletonActionBindings = map.m_Bindings; | |
| // NOTE: We treat this as a mere binding modification, even though we have added something. | |
| // InputAction.RestoreActionStatesAfterReResolvingBindings() can deal with bindings | |
| // having been removed or added. | |
| map.OnBindingModified(); | |
| return bindingIndex; | |
| } | |
| /// <summary> | |
| /// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> | |
| /// at the given <paramref name="index"/>. | |
| /// </summary> | |
| /// <param name="action">Action whose bindings to change.</param> | |
| /// <param name="index">Index in <paramref name="action"/>'s <see cref="InputAction.bindings"/> of the binding to be changed.</param> | |
| /// <returns>A write accessor to the given binding.</returns> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Grab "fire" action from PlayerInput. | |
| /// var fireAction = playerInput.actions["fire"]; | |
| /// | |
| /// // Change its second binding to go to the left mouse button. | |
| /// fireAction.ChangeBinding(1) | |
| /// .WithPath("<Mouse>/leftButton"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> | |
| /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range (as per <see cref="InputAction.bindings"/> | |
| /// of <paramref name="action"/>).</exception> | |
| public static BindingSyntax ChangeBinding(this InputAction action, int index) | |
| { | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| var indexOnMap = action.BindingIndexOnActionToBindingIndexOnMap(index); | |
| return new BindingSyntax(action.GetOrCreateActionMap(), indexOnMap, action); | |
| } | |
| /// <summary> | |
| /// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> | |
| /// with the given <paramref name="name"/>. | |
| /// </summary> | |
| /// <param name="action">Action whose bindings to change.</param> | |
| /// <param name="name">Name of the binding to be changed <see cref="InputAction.bindings"/>.</param> | |
| /// <returns>A write accessor to the given binding.</returns> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Grab "fire" action from PlayerInput. | |
| /// var fireAction = playerInput.actions["fire"]; | |
| /// | |
| /// // Change its second binding to go to the left mouse button. | |
| /// fireAction.ChangeBinding("fire") | |
| /// .WithPath("<Mouse>/leftButton"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> | |
| /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range (as per <see cref="InputAction.bindings"/> | |
| /// of <paramref name="action"/>).</exception> | |
| public static BindingSyntax ChangeBinding(this InputAction action, string name) | |
| { | |
| return action.ChangeBinding(new InputBinding { name = name }); | |
| } | |
| /// <summary> | |
| /// Get write access to the binding in <see cref="InputActionMap.bindings"/> of <paramref name="actionMap"/> | |
| /// at the given <paramref name="index"/>. | |
| /// </summary> | |
| /// <param name="actionMap">Action map whose bindings to change.</param> | |
| /// <param name="index">Index in <paramref name="actionMap"/>'s <see cref="InputActionMap.bindings"/> of the binding to be changed.</param> | |
| /// <returns>A write accessor to the given binding.</returns> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Grab "gameplay" actions from PlayerInput. | |
| /// var gameplayActions = playerInput.actions.FindActionMap("gameplay"); | |
| /// | |
| /// // Change its second binding to go to the left mouse button. | |
| /// gameplayActions.ChangeBinding(1) | |
| /// .WithPath("<Mouse>/leftButton"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <exception cref="ArgumentNullException"><paramref name="actionMap"/> is <c>null</c>.</exception> | |
| /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range (as per <see cref="InputActionMap.bindings"/> | |
| /// of <paramref name="actionMap"/>).</exception> | |
| public static BindingSyntax ChangeBinding(this InputActionMap actionMap, int index) | |
| { | |
| if (actionMap == null) | |
| throw new ArgumentNullException(nameof(actionMap)); | |
| if (index < 0 || index >= actionMap.m_Bindings.LengthSafe()) | |
| throw new ArgumentOutOfRangeException(nameof(index)); | |
| return new BindingSyntax(actionMap, index); | |
| } | |
| /// <summary> | |
| /// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> | |
| /// that has the given <paramref name="id"/>. | |
| /// </summary> | |
| /// <param name="action">Action whose bindings to change.</param> | |
| /// <param name="id">ID of the binding as per <see cref="InputBinding.id"/>.</param> | |
| /// <returns>A write accessor to the binding with the given ID.</returns> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Grab "fire" action from PlayerInput. | |
| /// var fireAction = playerInput.actions["fire"]; | |
| /// | |
| /// // Change the binding with the given ID to go to the left mouse button. | |
| /// fireAction.ChangeBindingWithId("c3de9215-31c3-4654-8562-854bf2f7864f") | |
| /// .WithPath("<Mouse>/leftButton"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> | |
| /// <exception cref="ArgumentException">No binding with the given <paramref name="id"/> exists | |
| /// on <paramref name="action"/>.</exception> | |
| public static BindingSyntax ChangeBindingWithId(this InputAction action, string id) | |
| { | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| return action.ChangeBinding(new InputBinding {m_Id = id}); | |
| } | |
| /// <summary> | |
| /// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> | |
| /// that has the given <paramref name="id"/>. | |
| /// </summary> | |
| /// <param name="action">Action whose bindings to change.</param> | |
| /// <param name="id">ID of the binding as per <see cref="InputBinding.id"/>.</param> | |
| /// <returns>A write accessor to the binding with the given ID.</returns> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Grab "fire" action from PlayerInput. | |
| /// var fireAction = playerInput.actions["fire"]; | |
| /// | |
| /// // Change the binding with the given ID to go to the left mouse button. | |
| /// fireAction.ChangeBindingWithId(new Guid("c3de9215-31c3-4654-8562-854bf2f7864f")) | |
| /// .WithPath("<Mouse>/leftButton"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> | |
| /// <exception cref="ArgumentException">No binding with the given <paramref name="id"/> exists | |
| /// on <paramref name="action"/>.</exception> | |
| public static BindingSyntax ChangeBindingWithId(this InputAction action, Guid id) | |
| { | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| return action.ChangeBinding(new InputBinding {id = id}); | |
| } | |
| /// <summary> | |
| /// Get write access to the first binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> | |
| /// that is assigned to the given binding <paramref name="group"/>. | |
| /// </summary> | |
| /// <param name="action">Action whose bindings to change.</param> | |
| /// <param name="group">Name of the binding group as per <see cref="InputBinding.groups"/>.</param> | |
| /// <returns>A write accessor to the first binding on <paramref name="action"/> that is assigned to the | |
| /// given binding <paramref name="group"/>.</returns> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Grab "fire" action from PlayerInput. | |
| /// var fireAction = playerInput.actions["fire"]; | |
| /// | |
| /// // Change the binding in the "Keyboard&Mouse" group to go to the left mouse button. | |
| /// fireAction.ChangeBindingWithGroup("Keyboard&Mouse") | |
| /// .WithPath("<Mouse>/leftButton"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> | |
| /// <exception cref="ArgumentException">No binding on the <paramref name="action"/> is assigned | |
| /// to the given binding <paramref name="group"/>.</exception> | |
| public static BindingSyntax ChangeBindingWithGroup(this InputAction action, string group) | |
| { | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| return action.ChangeBinding(new InputBinding {groups = group}); | |
| } | |
| /// <summary> | |
| /// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> | |
| /// that is bound to the given <paramref name="path"/>. | |
| /// </summary> | |
| /// <param name="action">Action whose bindings to change.</param> | |
| /// <param name="path">Path of the binding as per <see cref="InputBinding.path"/>.</param> | |
| /// <returns>A write accessor to the binding on <paramref name="action"/> that is assigned the | |
| /// given <paramref name="path"/>.</returns> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Grab "fire" action from PlayerInput. | |
| /// var fireAction = playerInput.actions["fire"]; | |
| /// | |
| /// // Change the binding to the right mouse button to go to the left mouse button instead. | |
| /// fireAction.ChangeBindingWithPath("<Mouse>/rightButton") | |
| /// .WithPath("<Mouse>/leftButton"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> | |
| /// <exception cref="ArgumentException">No binding on the <paramref name="action"/> is assigned | |
| /// the given <paramref name="path"/>.</exception> | |
| public static BindingSyntax ChangeBindingWithPath(this InputAction action, string path) | |
| { | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| return action.ChangeBinding(new InputBinding {path = path}); | |
| } | |
| /// <summary> | |
| /// Get write access to the binding on <paramref name="action"/> that matches the given | |
| /// <paramref name="match"/>. | |
| /// </summary> | |
| /// <param name="action">Action whose bindings to match against.</param> | |
| /// <param name="match">A binding mask. See <see cref="InputBinding.Matches"/> for | |
| /// details.</param> | |
| /// <returns>A write-accessor to the first binding matching <paramref name="match"/> or | |
| /// an invalid accessor (see <see cref="BindingSyntax.valid"/>) if no binding was found to | |
| /// match the mask.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> | |
| public static BindingSyntax ChangeBinding(this InputAction action, InputBinding match) | |
| { | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| var actionMap = action.GetOrCreateActionMap(); | |
| int bindingIndexInMap = -1; | |
| var id = action.idDontGenerate; | |
| if (id != null) | |
| { | |
| // Prio1: Attempt to match action id (stronger) | |
| match.action = action.id.ToString(); | |
| bindingIndexInMap = actionMap.FindBindingRelativeToMap(match); | |
| } | |
| if (bindingIndexInMap == -1) | |
| { | |
| // Prio2: Attempt to match action name (weaker) | |
| match.action = action.name; | |
| bindingIndexInMap = actionMap.FindBindingRelativeToMap(match); | |
| } | |
| if (bindingIndexInMap == -1) | |
| return default; | |
| return new BindingSyntax(actionMap, bindingIndexInMap, action); | |
| } | |
| /// <summary> | |
| /// Get a write accessor to the binding of <paramref name="action"/> that is both a composite | |
| /// (see <see cref="InputBinding.isComposite"/>) and has the given binding name or composite | |
| /// type. | |
| /// </summary> | |
| /// <param name="action">Action to look up the binding on. All bindings in the action's | |
| /// <see cref="InputAction.bindings"/> property will be considered.</param> | |
| /// <param name="compositeName">Either the name of the composite binding (see <see cref="InputBinding.name"/>) | |
| /// to look for or the name of the composite type used in the binding (such as "1DAxis"). Case-insensitive.</param> | |
| /// <returns>A write accessor to the given composite binding or an invalid accessor if no composite | |
| /// matching <paramref name="compositeName"/> could be found on <paramref name="action"/>.</returns> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Add arrow keys as alternatives to the WASD Vector2 composite. | |
| /// playerInput.actions["move"] | |
| /// .ChangeCompositeBinding("WASD") | |
| /// .InsertPartBinding("Up", "<Keyboard>/upArrow") | |
| /// .InsertPartBinding("Down", "<Keyboard>/downArrow") | |
| /// .InsertPartBinding("Left", "<Keyboard>/leftArrow") | |
| /// .InsertPartBinding("Right", "<Keyboard>/rightArrow"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="compositeName"/> | |
| /// is <c>null</c> or empty.</exception> | |
| /// <seealso cref="InputBinding.isComposite"/> | |
| /// <seealso cref="InputBindingComposite"/> | |
| public static BindingSyntax ChangeCompositeBinding(this InputAction action, string compositeName) | |
| { | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| if (string.IsNullOrEmpty(compositeName)) | |
| throw new ArgumentNullException(nameof(compositeName)); | |
| var actionMap = action.GetOrCreateActionMap(); | |
| var bindings = actionMap.m_Bindings; | |
| var numBindings = bindings.LengthSafe(); | |
| for (var i = 0; i < numBindings; ++i) | |
| { | |
| ref var binding = ref bindings[i]; | |
| if (!binding.isComposite || !binding.TriggersAction(action)) | |
| continue; | |
| ////REVIEW: should this do a registration lookup to deal with aliases? | |
| if (compositeName.Equals(binding.name, StringComparison.InvariantCultureIgnoreCase) | |
| || compositeName.Equals(NameAndParameters.ParseName(binding.path), | |
| StringComparison.InvariantCultureIgnoreCase)) | |
| return new BindingSyntax(actionMap, i, action); | |
| } | |
| return default; | |
| } | |
| ////TODO: update binding mask if necessary | |
| /// <summary> | |
| /// Rename an existing action. | |
| /// </summary> | |
| /// <param name="action">Action to assign a new name to. Can be singleton action or action that | |
| /// is part of a map.</param> | |
| /// <param name="newName">New name to assign to action. Cannot be empty.</param> | |
| /// <exception cref="ArgumentNullException"><paramref name="action"/> is null or <paramref name="newName"/> is | |
| /// null or empty.</exception> | |
| /// <exception cref="InvalidOperationException"><see cref="InputAction.actionMap"/> of <paramref name="action"/> | |
| /// already contains an action called <paramref name="newName"/>.</exception> | |
| /// <remarks> | |
| /// Renaming an action will also update the bindings that refer to the action. | |
| /// </remarks> | |
| public static void Rename(this InputAction action, string newName) | |
| { | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| if (string.IsNullOrEmpty(newName)) | |
| throw new ArgumentNullException(nameof(newName)); | |
| if (action.name == newName) | |
| return; | |
| // Make sure name isn't already taken in map. | |
| var actionMap = action.actionMap; | |
| if (actionMap?.FindAction(newName) != null) | |
| throw new InvalidOperationException( | |
| $"Cannot rename '{action}' to '{newName}' in map '{actionMap}' as the map already contains an action with that name"); | |
| var oldName = action.m_Name; | |
| action.m_Name = newName; | |
| actionMap?.ClearActionLookupTable(); | |
| if (actionMap?.asset != null) | |
| actionMap?.asset.MarkAsDirty(); | |
| // Update bindings. | |
| var bindings = action.GetOrCreateActionMap().m_Bindings; | |
| var bindingCount = bindings.LengthSafe(); | |
| for (var i = 0; i < bindingCount; ++i) | |
| if (string.Compare(bindings[i].action, oldName, StringComparison.InvariantCultureIgnoreCase) == 0) | |
| bindings[i].action = newName; | |
| } | |
| /// <summary> | |
| /// Add a new control scheme to the asset. | |
| /// </summary> | |
| /// <param name="asset">Asset to add the control scheme to.</param> | |
| /// <param name="controlScheme">Control scheme to add.</param> | |
| /// <exception cref="ArgumentException"><paramref name="controlScheme"/> has no name.</exception> | |
| /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c>.</exception> | |
| /// <exception cref="InvalidOperationException">A control scheme with the same name as <paramref name="controlScheme"/> | |
| /// already exists in the asset.</exception> | |
| /// <remarks> | |
| /// </remarks> | |
| public static void AddControlScheme(this InputActionAsset asset, InputControlScheme controlScheme) | |
| { | |
| if (asset == null) | |
| throw new ArgumentNullException(nameof(asset)); | |
| if (string.IsNullOrEmpty(controlScheme.name)) | |
| throw new ArgumentException("Cannot add control scheme without name to asset " + asset.name, nameof(controlScheme)); | |
| if (asset.FindControlScheme(controlScheme.name) != null) | |
| throw new InvalidOperationException( | |
| $"Asset '{asset.name}' already contains a control scheme called '{controlScheme.name}'"); | |
| ArrayHelpers.Append(ref asset.m_ControlSchemes, controlScheme); | |
| asset.MarkAsDirty(); | |
| } | |
| /// <summary> | |
| /// Add a new control scheme to the given <paramref name="asset"/>. | |
| /// </summary> | |
| /// <param name="asset">Asset to add the control scheme to.</param> | |
| /// <param name="name">Name to give to the control scheme. Must be unique within the control schemes of the | |
| /// asset. Also used as default name of <see cref="InputControlScheme.bindingGroup">binding group</see> associated | |
| /// with the control scheme.</param> | |
| /// <returns>Syntax to allow providing additional configuration for the newly added control scheme.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="name"/> | |
| /// is <c>null</c> or empty.</exception> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Create an .inputactions asset. | |
| /// var asset = ScriptableObject.CreateInstance<InputActionAsset>(); | |
| /// | |
| /// // Add an action map to it. | |
| /// var actionMap = asset.AddActionMap("actions"); | |
| /// | |
| /// // Add an action to it and bind it to the A button on the gamepad. | |
| /// // Also, associate that binding with the "Gamepad" control scheme. | |
| /// var action = actionMap.AddAction("action"); | |
| /// action.AddBinding("<Gamepad>/buttonSouth", groups: "Gamepad"); | |
| /// | |
| /// // Add a control scheme called "Gamepad" that requires a Gamepad device. | |
| /// asset.AddControlScheme("Gamepad") | |
| /// .WithRequiredDevice<Gamepad>(); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| public static ControlSchemeSyntax AddControlScheme(this InputActionAsset asset, string name) | |
| { | |
| if (asset == null) | |
| throw new ArgumentNullException(nameof(asset)); | |
| if (string.IsNullOrEmpty(name)) | |
| throw new ArgumentNullException(nameof(name)); | |
| var index = asset.controlSchemes.Count; | |
| asset.AddControlScheme(new InputControlScheme(name)); | |
| return new ControlSchemeSyntax(asset, index); | |
| } | |
| /// <summary> | |
| /// Remove the control scheme with the given name from the asset. | |
| /// </summary> | |
| /// <param name="asset">Asset to remove the control scheme from.</param> | |
| /// <param name="name">Name of the control scheme. Matching is case-insensitive.</param> | |
| /// <exception cref="ArgumentNullException"><paramref name="asset"/> is null -or- <paramref name="name"/> | |
| /// is <c>null</c> or empty.</exception> | |
| /// <remarks> | |
| /// If no control scheme with the given name can be found, the method does nothing. | |
| /// </remarks> | |
| public static void RemoveControlScheme(this InputActionAsset asset, string name) | |
| { | |
| if (asset == null) | |
| throw new ArgumentNullException(nameof(asset)); | |
| if (string.IsNullOrEmpty(name)) | |
| throw new ArgumentNullException(nameof(name)); | |
| var index = asset.FindControlSchemeIndex(name); | |
| if (index != -1) | |
| ArrayHelpers.EraseAt(ref asset.m_ControlSchemes, index); | |
| asset.MarkAsDirty(); | |
| } | |
| /// <summary> | |
| /// Associates the control scheme given by <paramref name="scheme"/> with the binding group given by <paramref name="bindingGroup"/>. | |
| /// </summary> | |
| /// <param name="scheme">The control scheme to modify.</param> | |
| /// <param name="bindingGroup">The binding group to be associated with the control scheme.</param> | |
| /// <returns><paramref name="scheme"/></returns> | |
| public static InputControlScheme WithBindingGroup(this InputControlScheme scheme, string bindingGroup) | |
| { | |
| return new ControlSchemeSyntax(scheme).WithBindingGroup(bindingGroup).Done(); | |
| } | |
| public static InputControlScheme WithDevice(this InputControlScheme scheme, string controlPath, bool required) | |
| { | |
| if (required) | |
| return new ControlSchemeSyntax(scheme).WithRequiredDevice(controlPath).Done(); | |
| return new ControlSchemeSyntax(scheme).WithOptionalDevice(controlPath).Done(); | |
| } | |
| public static InputControlScheme WithRequiredDevice(this InputControlScheme scheme, string controlPath) | |
| { | |
| return new ControlSchemeSyntax(scheme).WithRequiredDevice(controlPath).Done(); | |
| } | |
| public static InputControlScheme WithOptionalDevice(this InputControlScheme scheme, string controlPath) | |
| { | |
| return new ControlSchemeSyntax(scheme).WithOptionalDevice(controlPath).Done(); | |
| } | |
| public static InputControlScheme OrWithRequiredDevice(this InputControlScheme scheme, string controlPath) | |
| { | |
| return new ControlSchemeSyntax(scheme).OrWithRequiredDevice(controlPath).Done(); | |
| } | |
| public static InputControlScheme OrWithOptionalDevice(this InputControlScheme scheme, string controlPath) | |
| { | |
| return new ControlSchemeSyntax(scheme).OrWithOptionalDevice(controlPath).Done(); | |
| } | |
| /// <summary> | |
| /// Write accessor to a binding on either an <see cref="InputAction"/> or an | |
| /// <see cref="InputActionMap"/>. | |
| /// </summary> | |
| /// <remarks> | |
| /// Both <see cref="InputAction.bindings"/> and <see cref="InputActionMap.bindings"/> are | |
| /// read-only. To modify bindings (other than setting overrides which you can do | |
| /// through <see cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,int,InputBinding)"/>), | |
| /// it is necessary to gain indirect write access through this structure. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// playerInput.actions["fire"] | |
| /// .ChangeBinding(0) | |
| /// .WithPath("<Keyboard>/space"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <seealso cref="AddBinding(InputAction,InputBinding)"/> | |
| /// <seealso cref="ChangeBinding(InputAction,int)"/> | |
| public struct BindingSyntax | |
| { | |
| private readonly InputActionMap m_ActionMap; | |
| private readonly InputAction m_Action; | |
| internal readonly int m_BindingIndexInMap; | |
| /// <summary> | |
| /// True if the if binding accessor is valid. | |
| /// </summary> | |
| public bool valid => m_ActionMap != null && m_BindingIndexInMap >= 0 && m_BindingIndexInMap < m_ActionMap.m_Bindings.LengthSafe(); | |
| /// <summary> | |
| /// Index of the binding that the accessor refers to. | |
| /// </summary> | |
| /// <remarks> | |
| /// When accessing bindings on an <see cref="InputAction"/>, this is the index in | |
| /// <see cref="InputAction.bindings"/> of the action. When accessing bindings on an | |
| /// <see cref="InputActionMap"/>, it is the index <see cref="InputActionMap.bindings"/> | |
| /// of the map. | |
| /// </remarks> | |
| public int bindingIndex | |
| { | |
| get | |
| { | |
| if (!valid) | |
| return -1; | |
| if (m_Action != null) | |
| return m_Action.BindingIndexOnMapToBindingIndexOnAction(m_BindingIndexInMap); | |
| return m_BindingIndexInMap; | |
| } | |
| } | |
| /// <summary> | |
| /// The current binding in entirety. | |
| /// </summary> | |
| /// <exception cref="InvalidOperationException">The accessor is not <see cref="valid"/>.</exception> | |
| public InputBinding binding | |
| { | |
| get | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("BindingSyntax accessor is not valid"); | |
| return m_ActionMap.m_Bindings[m_BindingIndexInMap]; | |
| } | |
| } | |
| internal BindingSyntax(InputActionMap map, int bindingIndexInMap, InputAction action = null) | |
| { | |
| m_ActionMap = map; | |
| m_BindingIndexInMap = bindingIndexInMap; | |
| m_Action = action; | |
| } | |
| /// <summary> | |
| /// Set the <see cref="InputBinding.name"/> of the binding. | |
| /// </summary> | |
| /// <param name="name">Name for the binding.</param> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <exception cref="InvalidOperationException">The binding accessor is not <see cref="valid"/>.</exception> | |
| /// <seealso cref="InputBinding.name"/> | |
| public BindingSyntax WithName(string name) | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Accessor is not valid"); | |
| m_ActionMap.m_Bindings[m_BindingIndexInMap].name = name; | |
| m_ActionMap.OnBindingModified(); | |
| return this; | |
| } | |
| /// <summary> | |
| /// Set the <see cref="InputBinding.path"/> of the binding. | |
| /// </summary> | |
| /// <param name="path">Path for the binding.</param> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <exception cref="InvalidOperationException">The binding accessor is not <see cref="valid"/>.</exception> | |
| /// <seealso cref="InputBinding.path"/> | |
| public BindingSyntax WithPath(string path) | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Accessor is not valid"); | |
| m_ActionMap.m_Bindings[m_BindingIndexInMap].path = path; | |
| m_ActionMap.OnBindingModified(); | |
| return this; | |
| } | |
| /// <summary> | |
| /// Add <paramref name="group"/> to the list of <see cref="InputBinding.groups"/> of the binding. | |
| /// </summary> | |
| /// <param name="group">Name of the binding group (such as "Gamepad").</param> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <exception cref="ArgumentException"><paramref name="group"/> is <c>null</c> or empty -or- it contains | |
| /// a <see cref="InputBinding.Separator"/> character.</exception> | |
| public BindingSyntax WithGroup(string group) | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Accessor is not valid"); | |
| if (string.IsNullOrEmpty(group)) | |
| throw new ArgumentException("Group name cannot be null or empty", nameof(group)); | |
| if (group.IndexOf(InputBinding.Separator) != -1) | |
| throw new ArgumentException( | |
| $"Group name cannot contain separator character '{InputBinding.Separator}'", nameof(group)); | |
| return WithGroups(group); | |
| } | |
| /// <summary> | |
| /// Add all the groups specified in <paramref name="groups"/> to the list of <see cref="InputBinding.groups"/> of the binding. | |
| /// </summary> | |
| /// <param name="groups">A semi-colon separated list of group names.</param> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> | |
| public BindingSyntax WithGroups(string groups) | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Accessor is not valid"); | |
| if (string.IsNullOrEmpty(groups)) | |
| return this; | |
| // Join with existing group, if any. | |
| var currentGroups = m_ActionMap.m_Bindings[m_BindingIndexInMap].groups; | |
| if (!string.IsNullOrEmpty(currentGroups)) | |
| groups = string.Join(InputBinding.kSeparatorString, currentGroups, groups); | |
| // Set groups on binding. | |
| m_ActionMap.m_Bindings[m_BindingIndexInMap].groups = groups; | |
| m_ActionMap.OnBindingModified(); | |
| return this; | |
| } | |
| /// <summary> | |
| /// Add an interaction via specified name in <paramref name="interaction"/> to the list of interactions. | |
| /// </summary> | |
| /// <param name="interaction">Interaction class name</param> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> | |
| /// <exception cref="ArgumentException">If interaction name is null or empty.</exception> | |
| public BindingSyntax WithInteraction(string interaction) | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Accessor is not valid"); | |
| if (string.IsNullOrEmpty(interaction)) | |
| throw new ArgumentException("Interaction cannot be null or empty", nameof(interaction)); | |
| if (interaction.IndexOf(InputBinding.Separator) != -1) | |
| throw new ArgumentException( | |
| $"Interaction string cannot contain separator character '{InputBinding.Separator}'", nameof(interaction)); | |
| return WithInteractions(interaction); | |
| } | |
| /// <summary> | |
| /// Add the set of interactions specified in <paramref name="interactions"/> to the list of interactions. | |
| /// </summary> | |
| /// <param name="interactions">A semi-colon separated list of interaction names.</param> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> | |
| public BindingSyntax WithInteractions(string interactions) | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Accessor is not valid"); | |
| if (string.IsNullOrEmpty(interactions)) | |
| return this; | |
| // Join with existing interaction string, if any. | |
| var currentInteractions = m_ActionMap.m_Bindings[m_BindingIndexInMap].interactions; | |
| if (!string.IsNullOrEmpty(currentInteractions)) | |
| interactions = string.Join(InputBinding.kSeparatorString, currentInteractions, interactions); | |
| // Set interactions on binding. | |
| m_ActionMap.m_Bindings[m_BindingIndexInMap].interactions = interactions; | |
| m_ActionMap.OnBindingModified(); | |
| return this; | |
| } | |
| /// <summary> | |
| /// Add an interaction of type specified in <typeparamref name="TInteraction"/> to the list of interactions. | |
| /// </summary> | |
| /// <typeparam name="TInteraction"></typeparam> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> | |
| /// <exception cref="NotSupportedException">Interaction type has not been registered.</exception> | |
| public BindingSyntax WithInteraction<TInteraction>() | |
| where TInteraction : IInputInteraction | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Accessor is not valid"); | |
| var interactionName = InputProcessor.s_Processors.FindNameForType(typeof(TInteraction)); | |
| if (interactionName.IsEmpty()) | |
| throw new NotSupportedException($"Type '{typeof(TInteraction)}' has not been registered as a processor"); | |
| return WithInteraction(interactionName); | |
| } | |
| /// <summary> | |
| /// Add a processor to the list of <see cref="InputBinding.processors"/> of the binding. | |
| /// </summary> | |
| /// <param name="processor">Name of the processor, such as "Scale".</param> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> | |
| /// <exception cref="ArgumentException">The processor name is null or empty.</exception> | |
| /// <exception cref="ArgumentException">The processor name contains a semi-colon.</exception> | |
| public BindingSyntax WithProcessor(string processor) | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Accessor is not valid"); | |
| if (string.IsNullOrEmpty(processor)) | |
| throw new ArgumentException("Processor cannot be null or empty", nameof(processor)); | |
| if (processor.IndexOf(InputBinding.Separator) != -1) | |
| throw new ArgumentException( | |
| $"Processor string cannot contain separator character '{InputBinding.Separator}'", nameof(processor)); | |
| return WithProcessors(processor); | |
| } | |
| /// <summary> | |
| /// Add processors to the list of <see cref="InputBinding.processors"/> of the binding. | |
| /// </summary> | |
| /// <param name="processors">A semi-colon separated list of processor names.</param> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> | |
| public BindingSyntax WithProcessors(string processors) | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Accessor is not valid"); | |
| if (string.IsNullOrEmpty(processors)) | |
| return this; | |
| // Join with existing processor string, if any. | |
| var currentProcessors = m_ActionMap.m_Bindings[m_BindingIndexInMap].processors; | |
| if (!string.IsNullOrEmpty(currentProcessors)) | |
| processors = string.Join(InputBinding.kSeparatorString, currentProcessors, processors); | |
| // Set processors on binding. | |
| m_ActionMap.m_Bindings[m_BindingIndexInMap].processors = processors; | |
| m_ActionMap.OnBindingModified(); | |
| return this; | |
| } | |
| /// <summary> | |
| /// Add a processor to the list of <see cref="InputBinding.processors"/> of the binding. | |
| /// </summary> | |
| /// <typeparam name="TProcessor">Type of processor.</typeparam> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> | |
| /// <exception cref="NotSupportedException">Processor type has not been registered.</exception> | |
| public BindingSyntax WithProcessor<TProcessor>() | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Accessor is not valid"); | |
| var processorName = InputProcessor.s_Processors.FindNameForType(typeof(TProcessor)); | |
| if (processorName.IsEmpty()) | |
| throw new NotSupportedException($"Type '{typeof(TProcessor)}' has not been registered as a processor"); | |
| return WithProcessor(processorName); | |
| } | |
| /// <summary> | |
| /// Specify which action to trigger. | |
| /// </summary> | |
| /// <param name="action">Action to trigger.</param> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> | |
| /// <exception cref="ArgumentNullException">Provided action is null.</exception> | |
| /// <exception cref="ArgumentException">Provided action is a singleton action (not part of any action maps).</exception> | |
| public BindingSyntax Triggering(InputAction action) | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Accessor is not valid"); | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| if (action.isSingletonAction) | |
| throw new ArgumentException( | |
| $"Cannot change the action a binding triggers on singleton action '{action}'", nameof(action)); | |
| m_ActionMap.m_Bindings[m_BindingIndexInMap].action = action.name; | |
| m_ActionMap.OnBindingModified(); | |
| return this; | |
| } | |
| /// <summary> | |
| /// Replace the current binding with the given one. | |
| /// </summary> | |
| /// <param name="binding">An input binding.</param> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <remarks> | |
| /// This method replaces the current binding wholesale, i.e. it will overwrite all fields. | |
| /// Be aware that this has the potential of corrupting the binding data in case the given | |
| /// binding is a composite. | |
| /// </remarks> | |
| public BindingSyntax To(InputBinding binding) | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Accessor is not valid"); | |
| m_ActionMap.m_Bindings[m_BindingIndexInMap] = binding; | |
| // If it's a singleton action, we force the binding to stay with the action. | |
| if (m_ActionMap.m_SingletonAction != null) | |
| m_ActionMap.m_Bindings[m_BindingIndexInMap].action = m_ActionMap.m_SingletonAction.name; | |
| m_ActionMap.OnBindingModified(); | |
| return this; | |
| } | |
| /// <summary> | |
| /// Switch to configuring the next binding. | |
| /// </summary> | |
| /// <returns>An instance configured to edit the next binding or an invalid (see <see cref="valid"/>) instance if | |
| /// there is no next binding.</returns> | |
| /// <remarks>If the BindingSyntax is restricted to a single action, the result will be invalid (see <see cref="valid"/>) | |
| /// if there is no next binding on the action. If the BindingSyntax is restricted to an <see cref="InputActionMap"/>, the result will | |
| /// be be invalid if there is no next binding in the map.</remarks> | |
| public BindingSyntax NextBinding() | |
| { | |
| return Iterate(true); | |
| } | |
| /// <summary> | |
| /// Switch to configuring the previous binding. | |
| /// </summary> | |
| /// <returns>An instance configured to edit the previous binding or an invalid (see <see cref="valid"/>) instance if | |
| /// there is no previous binding.</returns> | |
| /// <remarks>If the BindingSyntax is restricted to a single action, the result will be invalid (see <see cref="valid"/>) | |
| /// if there is no previous binding on the action. If the BindingSyntax is restricted to an <see cref="InputActionMap"/>, the result will | |
| /// be be invalid if there is no previous binding in the map.</remarks> | |
| public BindingSyntax PreviousBinding() | |
| { | |
| return Iterate(false); | |
| } | |
| /// <summary> | |
| /// Iterate to the next part binding of the current composite with the given part name. | |
| /// </summary> | |
| /// <param name="partName">Name of the part of the binding, such as <c>"Positive"</c>.</param> | |
| /// <returns>An accessor to the next part binding with the given name or an invalid (see <see cref="valid"/>) | |
| /// accessor if there is no such binding.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="partName"/> is <c>null</c> or empty.</exception> | |
| /// <remarks> | |
| /// Each binding that is part of a composite is marked with <see cref="InputBinding.isPartOfComposite"/> | |
| /// set to true. The name of the part is determined by <see cref="InputBinding.name"/> (comparison is | |
| /// case-insensitive). Which parts are relevant to a specific composite is determined by the type of | |
| /// composite. An <see cref="Composites.AxisComposite"/>, for example, has <c>"Negative"</c> and a | |
| /// <c>"Positive"</c> part. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// // Delete first "Positive" part of "Axis" composite. | |
| /// action.ChangeCompositeBinding("Axis") | |
| /// .NextPartBinding("Positive").Erase(); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <seealso cref="InputBinding.isPartOfComposite"/> | |
| /// <seealso cref="InputBinding.isComposite"/> | |
| /// <seealso cref="InputBindingComposite"/> | |
| public BindingSyntax NextPartBinding(string partName) | |
| { | |
| if (string.IsNullOrEmpty(partName)) | |
| throw new ArgumentNullException(nameof(partName)); | |
| return IteratePartBinding(true, partName); | |
| } | |
| /// <summary> | |
| /// Iterate to the previous part binding of the current composite with the given part name. | |
| /// </summary> | |
| /// <param name="partName">Name of the part of the binding, such as <c>"Positive"</c>.</param> | |
| /// <returns>An accessor to the previous part binding with the given name or an invalid (see <see cref="valid"/>) | |
| /// accessor if there is no such binding.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="partName"/> is <c>null</c> or empty.</exception> | |
| /// <remarks> | |
| /// Each binding that is part of a composite is marked with <see cref="InputBinding.isPartOfComposite"/> | |
| /// set to true. The name of the part is determined by <see cref="InputBinding.name"/> (comparison is | |
| /// case-insensitive). Which parts are relevant to a specific composite is determined by the type of | |
| /// composite. An <see cref="Composites.AxisComposite"/>, for example, has <c>"Negative"</c> and a | |
| /// <c>"Positive"</c> part. | |
| /// </remarks> | |
| /// <seealso cref="InputBinding.isPartOfComposite"/> | |
| /// <seealso cref="InputBinding.isComposite"/> | |
| /// <seealso cref="InputBindingComposite"/> | |
| public BindingSyntax PreviousPartBinding(string partName) | |
| { | |
| if (string.IsNullOrEmpty(partName)) | |
| throw new ArgumentNullException(nameof(partName)); | |
| return IteratePartBinding(false, partName); | |
| } | |
| /// <summary> | |
| /// Iterate to the next composite binding. | |
| /// </summary> | |
| /// <param name="compositeName">If <c>null</c> (default), an accessor to the next composite binding, | |
| /// regardless of name or type, is returned. If it is not <c>null</c>, can be either the name of | |
| /// the binding (see <see cref="InputBinding.name"/>) or the name of the composite used in the | |
| /// binding (see <see cref="InputSystem.RegisterBindingComposite"/></param>). | |
| /// <returns>A write accessor to the next composite binding or an invalid accessor (see | |
| /// <see cref="valid"/>) if no such binding was found.</returns> | |
| public BindingSyntax NextCompositeBinding(string compositeName = null) | |
| { | |
| return IterateCompositeBinding(true, compositeName); | |
| } | |
| /// <summary> | |
| /// Iterate to the previous composite binding. | |
| /// </summary> | |
| /// <param name="compositeName">If <c>null</c> (default), an accessor to the previous composite binding, | |
| /// regardless of name or type, is returned. If it is not <c>null</c>, can be either the name of | |
| /// the binding (see <see cref="InputBinding.name"/>) or the name of the composite used in the | |
| /// binding (see <see cref="InputSystem.RegisterBindingComposite"/>).</param> | |
| /// <returns>A write accessor to the previous composite binding or an invalid accessor (see | |
| /// <see cref="valid"/>) if no such binding was found.</returns> | |
| public BindingSyntax PreviousCompositeBinding(string compositeName = null) | |
| { | |
| return IterateCompositeBinding(false, compositeName); | |
| } | |
| private BindingSyntax Iterate(bool next) | |
| { | |
| if (m_ActionMap == null) | |
| return default; | |
| var bindings = m_ActionMap.m_Bindings; | |
| if (bindings == null) | |
| return default; | |
| // To find the next binding for a specific action, we may have to jump | |
| // over unrelated bindings in-between. | |
| var index = m_BindingIndexInMap; | |
| while (true) | |
| { | |
| index += next ? 1 : -1; | |
| if (index < 0 || index >= bindings.Length) | |
| return default; | |
| if (m_Action == null || bindings[index].TriggersAction(m_Action)) | |
| break; | |
| } | |
| return new BindingSyntax(m_ActionMap, index, m_Action); | |
| } | |
| private BindingSyntax IterateCompositeBinding(bool next, string compositeName) | |
| { | |
| for (var accessor = Iterate(next); accessor.valid; accessor = accessor.Iterate(next)) | |
| { | |
| if (!accessor.binding.isComposite) | |
| continue; | |
| if (compositeName == null) | |
| return accessor; | |
| // Try name of binding. | |
| if (compositeName.Equals(accessor.binding.name, StringComparison.InvariantCultureIgnoreCase)) | |
| return accessor; | |
| // Try composite type name. | |
| var name = NameAndParameters.ParseName(accessor.binding.path); | |
| if (compositeName.Equals(name, StringComparison.InvariantCultureIgnoreCase)) | |
| return accessor; | |
| } | |
| return default; | |
| } | |
| private BindingSyntax IteratePartBinding(bool next, string partName) | |
| { | |
| if (!valid) | |
| return default; | |
| if (binding.isComposite) | |
| { | |
| // If we're at the composite, only proceed if we're iterating down | |
| // instead of up. | |
| if (!next) | |
| return default; | |
| } | |
| else if (!binding.isPartOfComposite) | |
| return default; | |
| for (var accessor = Iterate(next); accessor.valid; accessor = accessor.Iterate(next)) | |
| { | |
| if (!accessor.binding.isPartOfComposite) | |
| return default; | |
| if (partName.Equals(accessor.binding.name, StringComparison.InvariantCultureIgnoreCase)) | |
| return accessor; | |
| } | |
| return default; | |
| } | |
| ////TODO: allow setting overrides through this accessor | |
| /// <summary> | |
| /// Remove the binding. | |
| /// </summary> | |
| /// <remarks> | |
| /// If the binding is a composite (see <see cref="InputBinding.isComposite"/>), part bindings of the | |
| /// composite will be removed as well. | |
| /// | |
| /// Note that the accessor will not necessarily be invalidated. Instead, it will point to what used | |
| /// to be the next binding in the array (though that means the accessor will be invalid if the binding | |
| /// that got erased was the last one in the array). | |
| /// </remarks> | |
| /// <exception cref="InvalidOperationException">The instance is not <see cref="valid"/>.</exception> | |
| public void Erase() | |
| { | |
| if (!valid) | |
| throw new InvalidOperationException("Instance not valid"); | |
| var isComposite = m_ActionMap.m_Bindings[m_BindingIndexInMap].isComposite; | |
| ArrayHelpers.EraseAt(ref m_ActionMap.m_Bindings, m_BindingIndexInMap); | |
| // If it's a composite, also erase part bindings. | |
| if (isComposite) | |
| { | |
| while (m_BindingIndexInMap < m_ActionMap.m_Bindings.LengthSafe() && m_ActionMap.m_Bindings[m_BindingIndexInMap].isPartOfComposite) | |
| { | |
| ArrayHelpers.EraseAt(ref m_ActionMap.m_Bindings, m_BindingIndexInMap); | |
| } | |
| } | |
| m_Action.m_BindingsCount = m_ActionMap.m_Bindings.LengthSafe(); | |
| m_ActionMap.OnBindingModified(); | |
| // We have switched to a different binding array. For singleton actions, we need to | |
| // sync up the reference that the action itself has. | |
| if (m_ActionMap.m_SingletonAction != null) | |
| m_ActionMap.m_SingletonAction.m_SingletonActionBindings = m_ActionMap.m_Bindings; | |
| } | |
| /// <summary> | |
| /// Insert a composite part into a composite binding. | |
| /// </summary> | |
| /// <param name="partName">Name of the part in composite binding.</param> | |
| /// <param name="path">Control path to bind to.</param> | |
| /// <returns>The same binding syntax for further configuration.</returns> | |
| /// <exception cref="ArgumentNullException">Part name is null or empty.</exception> | |
| /// <exception cref="InvalidOperationException">The binding accessor is invalid or the binding accessor is not pointing to composite or part binding.</exception> | |
| public BindingSyntax InsertPartBinding(string partName, string path) | |
| { | |
| if (string.IsNullOrEmpty(partName)) | |
| throw new ArgumentNullException(nameof(partName)); | |
| if (!valid) | |
| throw new InvalidOperationException("Binding accessor is not valid"); | |
| var binding = this.binding; | |
| if (!binding.isPartOfComposite && !binding.isComposite) | |
| throw new InvalidOperationException("Binding accessor must point to composite or part binding"); | |
| AddBindingInternal(m_ActionMap, | |
| new InputBinding { path = path, isPartOfComposite = true, name = partName }, | |
| m_BindingIndexInMap + 1); | |
| return new BindingSyntax(m_ActionMap, m_BindingIndexInMap + 1, m_Action); | |
| } | |
| } | |
| ////TODO: remove this and merge it into BindingSyntax | |
| /// <summary> | |
| /// Write accessor to a composite binding. | |
| /// </summary> | |
| /// <remarks> | |
| /// To add a composite binding to an action, you must first call <see cref="InputActionSetupExtensions.AddCompositeBinding"/> | |
| /// and then use the CompositeSyntax struct to add composite parts. | |
| /// <example> | |
| /// <code> | |
| /// playerInput.actions["fire"] | |
| /// .ChangeBinding(0) | |
| /// .AddCompositeBinding("2DVector") | |
| /// .With("Up", "<Keyboard>/w") | |
| /// .With("Down", "<Keyboard>/s") | |
| /// .With("Left", "<Keyboard>/a") | |
| /// .With("Right", "<Keyboard>/d"); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <seealso cref="AddBinding(InputAction,InputBinding)"/> | |
| /// <seealso cref="ChangeBinding(InputAction,int)"/> | |
| public struct CompositeSyntax | |
| { | |
| private readonly InputAction m_Action; | |
| private readonly InputActionMap m_ActionMap; | |
| private int m_BindingIndexInMap; | |
| /// <summary> | |
| /// Index of the binding that the accessor refers to. | |
| /// </summary> | |
| /// <remarks> | |
| /// When accessing bindings on an <see cref="InputAction"/>, this is the index in | |
| /// <see cref="InputAction.bindings"/> of the action. When accessing bindings on an | |
| /// <see cref="InputActionMap"/>, it is the index <see cref="InputActionMap.bindings"/> | |
| /// of the map. | |
| /// </remarks> | |
| public int bindingIndex | |
| { | |
| get | |
| { | |
| if (m_ActionMap == null) | |
| return -1; | |
| if (m_Action != null) | |
| return m_Action.BindingIndexOnMapToBindingIndexOnAction(m_BindingIndexInMap); | |
| return m_BindingIndexInMap; | |
| } | |
| } | |
| internal CompositeSyntax(InputActionMap map, InputAction action, int compositeIndex) | |
| { | |
| m_Action = action; | |
| m_ActionMap = map; | |
| m_BindingIndexInMap = compositeIndex; | |
| } | |
| /// <summary> | |
| /// Add a part binding to the composite. | |
| /// </summary> | |
| /// <param name="name">Name of the part. This is dependent on the type of composite. For | |
| /// <see cref="Composites.Vector2Composite"/>, for example, the valid parts are <c>"Up"</c>, <c>"Down"</c>, | |
| /// <c>"Left"</c>, and <c>"Right"</c>.</param> | |
| /// <param name="binding">Control path to binding to. See <see cref="InputBinding.path"/>.</param> | |
| /// <param name="groups">Binding groups to assign to the part binding. See <see cref="InputBinding.groups"/>.</param> | |
| /// <param name="processors">Optional list of processors to apply to the binding. See <see cref="InputBinding.processors"/>.</param> | |
| /// <returns>The same composite syntax for further configuration.</returns> | |
| public CompositeSyntax With(string name, string binding, string groups = null, string processors = null) | |
| { | |
| ////TODO: check whether non-composite bindings have been added in-between | |
| using (InputActionRebindingExtensions.DeferBindingResolution()) | |
| { | |
| int bindingIndex; | |
| if (m_Action != null) | |
| bindingIndex = m_Action.AddBinding(path: binding, groups: groups, processors: processors) | |
| .m_BindingIndexInMap; | |
| else | |
| bindingIndex = m_ActionMap.AddBinding(path: binding, groups: groups, processors: processors) | |
| .m_BindingIndexInMap; | |
| m_ActionMap.m_Bindings[bindingIndex].name = name; | |
| m_ActionMap.m_Bindings[bindingIndex].isPartOfComposite = true; | |
| } | |
| return this; | |
| } | |
| } | |
| /// <summary> | |
| /// Write accessor to a control scheme. | |
| /// </summary> | |
| public struct ControlSchemeSyntax | |
| { | |
| // REVIEW: Consider removing this for major revision, e.g. v2 or let functionality used by it be internal to reduce API surface. | |
| private readonly InputActionAsset m_Asset; | |
| private readonly int m_ControlSchemeIndex; | |
| private InputControlScheme m_ControlScheme; | |
| internal ControlSchemeSyntax(InputActionAsset asset, int index) | |
| { | |
| m_Asset = asset; | |
| m_ControlSchemeIndex = index; | |
| m_ControlScheme = new InputControlScheme(); | |
| } | |
| internal ControlSchemeSyntax(InputControlScheme controlScheme) | |
| { | |
| m_Asset = null; | |
| m_ControlSchemeIndex = -1; | |
| m_ControlScheme = controlScheme; | |
| } | |
| /// <summary> | |
| /// Sets or overwrite the binding group for control scheme. | |
| /// </summary> | |
| /// <param name="bindingGroup">A binding group. See <see cref="InputBinding.groups"/>.</param> | |
| /// <returns>The same control scheme syntax for further configuration.</returns> | |
| /// <exception cref="ArgumentNullException">If provided name is null or empty.</exception> | |
| public ControlSchemeSyntax WithBindingGroup(string bindingGroup) | |
| { | |
| if (string.IsNullOrEmpty(bindingGroup)) | |
| throw new ArgumentNullException(nameof(bindingGroup)); | |
| if (m_Asset == null) | |
| m_ControlScheme.m_BindingGroup = bindingGroup; | |
| else | |
| m_Asset.m_ControlSchemes[m_ControlSchemeIndex].bindingGroup = bindingGroup; | |
| return this; | |
| } | |
| /// <summary> | |
| /// Adds a required device to control scheme. | |
| /// </summary> | |
| /// <typeparam name="TDevice">Type of device.</typeparam> | |
| /// <returns>The same control scheme syntax for further configuration.</returns> | |
| public ControlSchemeSyntax WithRequiredDevice<TDevice>() | |
| where TDevice : InputDevice | |
| { | |
| return WithRequiredDevice(DeviceTypeToControlPath<TDevice>()); | |
| } | |
| /// <summary> | |
| /// Adds an optional device to control scheme. | |
| /// </summary> | |
| /// <typeparam name="TDevice">Type of device.</typeparam> | |
| /// <returns>The same control scheme syntax for further configuration.</returns> | |
| /// <see cref="InputControlScheme.DeviceRequirement.isOptional"/> | |
| public ControlSchemeSyntax WithOptionalDevice<TDevice>() | |
| where TDevice : InputDevice | |
| { | |
| return WithOptionalDevice(DeviceTypeToControlPath<TDevice>()); | |
| } | |
| /// <summary> | |
| /// Combines another required device with the previous device using boolean OR | |
| /// logic such that either the previous device or this device are required to be present. | |
| /// </summary> | |
| /// <typeparam name="TDevice">Type of device.</typeparam> | |
| /// <returns>The same control scheme syntax for further configuration.</returns> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Create an .inputactions asset. | |
| /// var asset = ScriptableObject.CreateInstance<InputActionAsset>(); | |
| /// | |
| /// asset.AddControlScheme("KeyboardAndMouseOrPen") | |
| /// .WithRequiredDevice("<Keyboard>") | |
| /// .WithRequiredDevice("<Mouse>") | |
| /// .OrWithRequiredDevice("<Pen>") | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <see cref="InputControlScheme.DeviceRequirement.isOR"/> | |
| public ControlSchemeSyntax OrWithRequiredDevice<TDevice>() | |
| where TDevice : InputDevice | |
| { | |
| return OrWithRequiredDevice(DeviceTypeToControlPath<TDevice>()); | |
| } | |
| /// <summary> | |
| /// Combines another optional device with the previous device using boolean OR | |
| /// logic such that either the previous device or this device are required to be present. | |
| /// If this is the last device in a chain of OR'd devices, the entire chain of | |
| /// devices becomes optional. | |
| /// </summary> | |
| /// <typeparam name="TDevice">Type of device.</typeparam> | |
| /// <returns>The same control scheme syntax for further configuration.</returns> | |
| /// <see cref="InputControlScheme.DeviceRequirement.isOR"/> | |
| public ControlSchemeSyntax OrWithOptionalDevice<TDevice>() | |
| where TDevice : InputDevice | |
| { | |
| return OrWithOptionalDevice(DeviceTypeToControlPath<TDevice>()); | |
| } | |
| /// <summary> | |
| /// Adds a required device to control scheme. | |
| /// </summary> | |
| /// <param name="controlPath">Device path, like <Gamepad>.</param> | |
| /// <returns>The same control scheme syntax for further configuration.</returns> | |
| public ControlSchemeSyntax WithRequiredDevice(string controlPath) | |
| { | |
| AddDeviceEntry(controlPath, InputControlScheme.DeviceRequirement.Flags.None); | |
| return this; | |
| } | |
| /// <summary> | |
| /// Add an optional device to the control scheme. | |
| /// </summary> | |
| /// <param name="controlPath">The device path, like <Gamepad>.</param> | |
| /// <returns>The same control scheme syntax for further configuration.</returns> | |
| public ControlSchemeSyntax WithOptionalDevice(string controlPath) | |
| { | |
| AddDeviceEntry(controlPath, InputControlScheme.DeviceRequirement.Flags.Optional); | |
| return this; | |
| } | |
| /// <summary> | |
| /// Adds another possible required device to the control scheme. | |
| /// </summary> | |
| /// <param name="controlPath">Device path, like <Gamepad>.</param> | |
| /// <returns>The same control scheme syntax for further configuration.</returns> | |
| public ControlSchemeSyntax OrWithRequiredDevice(string controlPath) | |
| { | |
| AddDeviceEntry(controlPath, InputControlScheme.DeviceRequirement.Flags.Or); | |
| return this; | |
| } | |
| /// <summary> | |
| /// Adds another possible optional device to control scheme. | |
| /// </summary> | |
| /// <param name="controlPath">Device path, like <Gamepad>.</param> | |
| /// <returns>The same control scheme syntax for further configuration.</returns> | |
| public ControlSchemeSyntax OrWithOptionalDevice(string controlPath) | |
| { | |
| AddDeviceEntry(controlPath, | |
| InputControlScheme.DeviceRequirement.Flags.Optional | | |
| InputControlScheme.DeviceRequirement.Flags.Or); | |
| return this; | |
| } | |
| private string DeviceTypeToControlPath<TDevice>() | |
| where TDevice : InputDevice | |
| { | |
| var layoutName = InputControlLayout.s_Layouts.TryFindLayoutForType(typeof(TDevice)).ToString(); | |
| if (string.IsNullOrEmpty(layoutName)) | |
| layoutName = typeof(TDevice).Name; | |
| return $"<{layoutName}>"; | |
| } | |
| /// <summary> | |
| /// Call this method when done building out the control scheme to return the associated instance. | |
| /// </summary> | |
| /// <returns>The associated control scheme</returns> | |
| public InputControlScheme Done() | |
| { | |
| if (m_Asset != null) | |
| return m_Asset.m_ControlSchemes[m_ControlSchemeIndex]; | |
| return m_ControlScheme; | |
| } | |
| private void AddDeviceEntry(string controlPath, InputControlScheme.DeviceRequirement.Flags flags) | |
| { | |
| if (string.IsNullOrEmpty(controlPath)) | |
| throw new ArgumentNullException(nameof(controlPath)); | |
| var scheme = m_Asset != null ? m_Asset.m_ControlSchemes[m_ControlSchemeIndex] : m_ControlScheme; | |
| ArrayHelpers.Append(ref scheme.m_DeviceRequirements, | |
| new InputControlScheme.DeviceRequirement | |
| { | |
| m_ControlPath = controlPath, | |
| m_Flags = flags, | |
| }); | |
| if (m_Asset == null) | |
| m_ControlScheme = scheme; | |
| else | |
| m_Asset.m_ControlSchemes[m_ControlSchemeIndex] = scheme; | |
| } | |
| } | |
| } | |
| } | |