A game about forced loneliness, made by TACStudios
at master 101 kB view raw
1using System; 2using UnityEngine.InputSystem.Layouts; 3using UnityEngine.InputSystem.Utilities; 4 5////TODO: Rename all the xxxSyntax structs to xxxAccessor 6 7////TODO: Replace all 'WithXXX' in the accessors with just 'SetXXX'; the 'WithXXX' reads too awkwardly 8 9namespace UnityEngine.InputSystem 10{ 11 /// <summary> 12 /// Methods to change the setup of <see cref="InputAction"/>, <see cref="InputActionMap"/>, 13 /// and <see cref="InputActionAsset"/> objects. 14 /// </summary> 15 /// <remarks> 16 /// Unlike the methods in <see cref="InputActionRebindingExtensions"/>, the methods here are 17 /// generally destructive, i.e. they will rearrange the data for actions. 18 /// </remarks> 19 public static class InputActionSetupExtensions 20 { 21 /// <summary> 22 /// Create an action map with the given name and add it to the asset. 23 /// </summary> 24 /// <param name="asset">Asset to add the action map to</param> 25 /// <param name="name">Name to assign to the </param> 26 /// <returns>The newly added action map.</returns> 27 /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> or 28 /// <exception cref="InvalidOperationException">An action map with the given <paramref name="name"/> 29 /// already exists in <paramref name="asset"/>.</exception> 30 /// <paramref name="name"/> is <c>null</c> or empty.</exception> 31 public static InputActionMap AddActionMap(this InputActionAsset asset, string name) 32 { 33 if (asset == null) 34 throw new ArgumentNullException(nameof(asset)); 35 if (string.IsNullOrEmpty(name)) 36 throw new ArgumentNullException(nameof(name)); 37 if (asset.FindActionMap(name) != null) 38 throw new InvalidOperationException( 39 $"An action map called '{name}' already exists in the asset"); 40 41 var map = new InputActionMap(name); 42 map.GenerateId(); 43 asset.AddActionMap(map); 44 return map; 45 } 46 47 /// <summary> 48 /// Add an action map to the asset. 49 /// </summary> 50 /// <param name="asset">Asset to add the map to.</param> 51 /// <param name="map">A named action map.</param> 52 /// <exception cref="ArgumentNullException"><paramref name="map"/> or <paramref name="asset"/> is <c>null</c>.</exception> 53 /// <exception cref="InvalidOperationException"><paramref name="map"/> has no name or asset already contains a 54 /// map with the same name -or- <paramref name="map"/> is currently enabled -or- <paramref name="map"/> is part of 55 /// an <see cref="InputActionAsset"/> that has <see cref="InputActionMap"/>s that are enabled.</exception> 56 /// <seealso cref="InputActionAsset.actionMaps"/> 57 public static void AddActionMap(this InputActionAsset asset, InputActionMap map) 58 { 59 if (asset == null) 60 throw new ArgumentNullException(nameof(asset)); 61 if (map == null) 62 throw new ArgumentNullException(nameof(map)); 63 if (string.IsNullOrEmpty(map.name)) 64 throw new InvalidOperationException("Maps added to an input action asset must be named"); 65 if (map.asset != null) 66 throw new InvalidOperationException( 67 $"Cannot add map '{map}' to asset '{asset}' as it has already been added to asset '{map.asset}'"); 68 ////REVIEW: some of the rules here seem stupid; just replace? 69 if (asset.FindActionMap(map.name) != null) 70 throw new InvalidOperationException( 71 $"An action map called '{map.name}' already exists in the asset"); 72 73 map.OnWantToChangeSetup(); 74 asset.OnWantToChangeSetup(); 75 76 ArrayHelpers.Append(ref asset.m_ActionMaps, map); 77 map.m_Asset = asset; 78 asset.OnSetupChanged(); 79 } 80 81 /// <summary> 82 /// Remove the given action map from the asset. 83 /// </summary> 84 /// <param name="asset">Asset to add the action map to.</param> 85 /// <param name="map">An action map. If the given map is not part of the asset, the method 86 /// does nothing.</param> 87 /// <exception cref="ArgumentNullException"><paramref name="asset"/> or <paramref name="map"/> is <c>null</c>.</exception> 88 /// <exception cref="InvalidOperationException"><paramref name="map"/> is currently enabled (see <see 89 /// cref="InputActionMap.enabled"/>) or is part of an <see cref="InputActionAsset"/> that has <see cref="InputActionMap"/>s 90 /// that are currently enabled.</exception> 91 /// <seealso cref="RemoveActionMap(InputActionAsset,string)"/> 92 /// <seealso cref="InputActionAsset.actionMaps"/> 93 public static void RemoveActionMap(this InputActionAsset asset, InputActionMap map) 94 { 95 if (asset == null) 96 throw new ArgumentNullException(nameof(asset)); 97 if (map == null) 98 throw new ArgumentNullException(nameof(map)); 99 100 map.OnWantToChangeSetup(); 101 asset.OnWantToChangeSetup(); 102 103 // Ignore if not part of this asset. 104 if (map.m_Asset != asset) 105 return; 106 107 ArrayHelpers.Erase(ref asset.m_ActionMaps, map); 108 map.m_Asset = null; 109 asset.OnSetupChanged(); 110 } 111 112 /// <summary> 113 /// Remove the action map with the given name or ID from the asset. 114 /// </summary> 115 /// <param name="asset">Asset to remove the action map from.</param> 116 /// <param name="nameOrId">The name or ID (see <see cref="InputActionMap.id"/>) of a map in the 117 /// asset. Note that lookup is case-insensitive. If no map with the given name or ID is found, 118 /// the method does nothing.</param> 119 /// <exception cref="ArgumentNullException"><paramref name="asset"/> or <paramref name="nameOrId"/> is <c>null</c>.</exception> 120 /// <exception cref="InvalidOperationException">The map referenced by <paramref name="nameOrId"/> is currently enabled 121 /// (see <see cref="InputActionMap.enabled"/>).</exception> 122 /// <seealso cref="RemoveActionMap(InputActionAsset,string)"/> 123 /// <seealso cref="InputActionAsset.actionMaps"/> 124 public static void RemoveActionMap(this InputActionAsset asset, string nameOrId) 125 { 126 if (asset == null) 127 throw new ArgumentNullException(nameof(asset)); 128 if (nameOrId == null) 129 throw new ArgumentNullException(nameof(nameOrId)); 130 var map = asset.FindActionMap(nameOrId); 131 if (map != null) 132 asset.RemoveActionMap(map); 133 } 134 135 ////TODO: add method to add an existing InputAction to a map 136 137 /// <summary> 138 /// Add a new <see cref="InputAction"/> to the given <paramref name="map"/>. 139 /// </summary> 140 /// <param name="map">Action map to add the action to. The action will be appended to 141 /// <see cref="InputActionMap.actions"/> of the map. The map must be disabled (see 142 /// <see cref="InputActionMap.enabled"/>).</param> 143 /// <param name="name">Name to give to the action. Must not be <c>null</c> or empty. Also, 144 /// no other action that already exists in <paramref name="map"/> must have this name already.</param> 145 /// <param name="type">Action type. See <see cref="InputAction.type"/>.</param> 146 /// <param name="binding">If not <c>null</c>, a binding is automatically added to the newly created action 147 /// with the value of this parameter being used as the binding's <see cref="InputBinding.path"/>.</param> 148 /// <param name="interactions">If <paramref name="binding"/> is not <c>null</c>, this string is used for 149 /// <see cref="InputBinding.interactions"/> of the binding that is automatically added for the action.</param> 150 /// <param name="processors">If <paramref name="binding"/> is not <c>null</c>, this string is used for 151 /// <see cref="InputBinding.processors"/> of the binding that is automatically added for the action.</param> 152 /// <param name="groups">If <paramref name="binding"/> is not <c>null</c>, this string is used for 153 /// <see cref="InputBinding.groups"/> of the binding that is automatically added for the action.</param> 154 /// <param name="expectedControlLayout">Value for <see cref="InputAction.expectedControlType"/>; <c>null</c> 155 /// by default.</param> 156 /// <returns>The newly added input action.</returns> 157 /// <exception cref="ArgumentNullException"><paramref name="map"/> is <c>null</c>.</exception> 158 /// <exception cref="ArgumentException"><paramref name="name"/> is <c>null</c> or empty.</exception> 159 /// <exception cref="InvalidOperationException"><paramref name="map"/> is enabled (see <see cref="InputActionMap.enabled"/>) 160 /// or is part of an <see cref="InputActionAsset"/> that has <see cref="InputActionMap"/>s that are <see cref="InputActionMap.enabled"/> 161 /// -or- <paramref name="map"/> already contains an action called <paramref name="name"/> (case-insensitive).</exception> 162 public static InputAction AddAction(this InputActionMap map, string name, InputActionType type = default, string binding = null, 163 string interactions = null, string processors = null, string groups = null, string expectedControlLayout = null) 164 { 165 if (map == null) 166 throw new ArgumentNullException(nameof(map)); 167 if (string.IsNullOrEmpty(name)) 168 throw new ArgumentException("Action must have name", nameof(name)); 169 map.OnWantToChangeSetup(); 170 if (map.FindAction(name) != null) 171 throw new InvalidOperationException( 172 $"Cannot add action with duplicate name '{name}' to set '{map.name}'"); 173 174 // Append action to array. 175 var action = new InputAction(name, type) 176 { 177 expectedControlType = expectedControlLayout 178 }; 179 action.GenerateId(); 180 ArrayHelpers.Append(ref map.m_Actions, action); 181 action.m_ActionMap = map; 182 183 // Add binding, if supplied. 184 if (!string.IsNullOrEmpty(binding)) 185 { 186 // Will trigger OnSetupChanged. 187 action.AddBinding(binding, interactions: interactions, processors: processors, groups: groups); 188 } 189 else 190 { 191 if (!string.IsNullOrEmpty(groups)) 192 throw new ArgumentException( 193 $"No binding path was specified for action '{action}' but groups was specified ('{groups}'); cannot apply groups without binding", 194 nameof(groups)); 195 196 // If no binding has been supplied but there are interactions and processors, they go on the action itself. 197 action.m_Interactions = interactions; 198 action.m_Processors = processors; 199 200 map.OnSetupChanged(); 201 } 202 203 return action; 204 } 205 206 /// <summary> 207 /// Remove the given action from its <see cref="InputActionMap"/>. 208 /// </summary> 209 /// <param name="action">An input action that is part of an <see cref="InputActionMap"/>.</param> 210 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> 211 /// <exception cref="ArgumentException"><paramref name="action"/> is a standalone action 212 /// that is not part of an <see cref="InputActionMap"/> and thus cannot be removed from anything.</exception> 213 /// <exception cref="InvalidOperationException"><paramref name="action"/> is part of an <see cref="InputActionMap"/> 214 /// or <see cref="InputActionAsset"/> that has at least one enabled action.</exception> 215 /// <remarks> 216 /// After removal, the action's <see cref="InputAction.actionMap"/> will be set to <c>null</c> 217 /// and the action will effectively become a standalone action that is not associated with 218 /// any action map. Bindings on the action will be preserved. On the action map, the bindings 219 /// for the action will be removed. 220 /// </remarks> 221 /// <seealso cref="AddAction"/> 222 public static void RemoveAction(this InputAction action) 223 { 224 if (action == null) 225 throw new ArgumentNullException(nameof(action)); 226 227 var actionMap = action.actionMap; 228 if (actionMap == null) 229 throw new ArgumentException( 230 $"Action '{action}' does not belong to an action map; nowhere to remove from", nameof(action)); 231 actionMap.OnWantToChangeSetup(); 232 233 var bindingsForAction = action.bindings.ToArray(); 234 235 var index = actionMap.m_Actions.IndexOfReference(action); 236 Debug.Assert(index != -1, "Could not find action in map"); 237 ArrayHelpers.EraseAt(ref actionMap.m_Actions, index); 238 239 action.m_ActionMap = null; 240 action.m_SingletonActionBindings = bindingsForAction; 241 242 // Remove bindings to action from map. 243 var newActionMapBindingCount = actionMap.m_Bindings.Length - bindingsForAction.Length; 244 if (newActionMapBindingCount == 0) 245 { 246 actionMap.m_Bindings = null; 247 } 248 else 249 { 250 var newActionMapBindings = new InputBinding[newActionMapBindingCount]; 251 var oldActionMapBindings = actionMap.m_Bindings; 252 var bindingIndex = 0; 253 for (var i = 0; i < oldActionMapBindings.Length; ++i) 254 { 255 var binding = oldActionMapBindings[i]; 256 if (bindingsForAction.IndexOf(b => b == binding) == -1) 257 newActionMapBindings[bindingIndex++] = binding; 258 } 259 actionMap.m_Bindings = newActionMapBindings; 260 } 261 262 actionMap.OnSetupChanged(); 263 } 264 265 /// <summary> 266 /// Remove the action with the given name from the asset. 267 /// </summary> 268 /// <param name="asset">Asset to remove the action from.</param> 269 /// <param name="nameOrId">Name or ID of the action. See <see cref="InputActionAsset.FindAction(string,bool)"/> for 270 /// details.</param> 271 /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="nameOrId"/> 272 /// is <c>null</c> or empty.</exception> 273 /// <seealso cref="RemoveAction(InputAction)"/> 274 public static void RemoveAction(this InputActionAsset asset, string nameOrId) 275 { 276 if (asset == null) 277 throw new ArgumentNullException(nameof(asset)); 278 if (nameOrId == null) 279 throw new ArgumentNullException(nameof(nameOrId)); 280 var action = asset.FindAction(nameOrId); 281 action?.RemoveAction(); 282 } 283 284 /// <summary> 285 /// Add a new binding to the given action. 286 /// </summary> 287 /// <param name="action">Action to add the binding to. If the action is part of an <see cref="InputActionMap"/>, 288 /// the newly added binding will be visible on <see cref="InputActionMap.bindings"/>.</param> 289 /// <param name="path">Binding path string. See <see cref="InputBinding.path"/> for details.</param> 290 /// <param name="interactions">Optional list of interactions to apply to the binding. See <see 291 /// cref="InputBinding.interactions"/> for details.</param> 292 /// <param name="processors">Optional list of processors to apply to the binding. See <see 293 /// cref="InputBinding.processors"/> for details.</param> 294 /// <param name="groups">Optional list of binding groups that should be assigned to the binding. See 295 /// <see cref="InputBinding.groups"/> for details.</param> 296 /// <returns>Fluent-style syntax to further configure the binding.</returns> 297 public static BindingSyntax AddBinding(this InputAction action, string path, string interactions = null, 298 string processors = null, string groups = null) 299 { 300 return AddBinding(action, new InputBinding 301 { 302 path = path, 303 interactions = interactions, 304 processors = processors, 305 groups = groups 306 }); 307 } 308 309 /// <summary> 310 /// Conditionally compiled helper for logging API usage of code-authored actions. 311 /// </summary> 312 /// <param name="api">The associated API function.</param> 313 /// <remarks> 314 /// Be extremely carefully to review for indirect calls and overloads to not register analytics twice. 315 /// Be extremely careful in enabling/disabling tracking before internal calls since those may otherwise 316 /// be incorrectly registered. 317 /// </remarks> 318 #if UNITY_EDITOR 319 private static void RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api api) 320 { 321 UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Register(api); 322 } 323 324 #endif 325 326 /// <summary> 327 /// Add a binding that references the given <paramref name="control"/> and triggers 328 /// the given <paramref cref="action"/>. 329 /// </summary> 330 /// <param name="action">Action to trigger.</param> 331 /// <param name="control">Control to bind to. The full <see cref="InputControl.path"/> of the control will 332 /// be used in the resulting <see cref="InputBinding">binding</see>.</param> 333 /// <returns>Syntax to configure the binding further.</returns> 334 /// <exception cref="ArgumentNullException"><paramref name="action"/> is null or <paramref name="control"/> is null.</exception> 335 /// <seealso cref="InputAction.bindings"/> 336 public static BindingSyntax AddBinding(this InputAction action, InputControl control) 337 { 338 if (control == null) 339 throw new ArgumentNullException(nameof(control)); 340 return AddBinding(action, control.path); 341 } 342 343 /// <summary> 344 /// Add a new binding to the action. 345 /// </summary> 346 /// <param name="action">An action to add the binding to.</param> 347 /// <param name="binding">Binding to add to the action or default. Binding can be further configured via 348 /// the struct returned by the method.</param> 349 /// <returns> 350 /// Returns a fluent-style syntax structure that allows performing additional modifications 351 /// based on the new binding. 352 /// </returns> 353 /// <remarks> 354 /// This works both with actions that are part of an action set as well as with actions that aren't. 355 /// 356 /// Note that actions must be disabled while altering their binding sets. Also, if the action belongs 357 /// to a set, all actions in the set must be disabled. 358 /// 359 /// <example> 360 /// <code> 361 /// fireAction.AddBinding() 362 /// .WithPath("&lt;Gamepad&gt;/buttonSouth") 363 /// .WithGroup("Gamepad"); 364 /// </code> 365 /// </example> 366 /// </remarks> 367 public static BindingSyntax AddBinding(this InputAction action, InputBinding binding = default) 368 { 369 #if UNITY_EDITOR 370 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.AddBinding); 371 #endif 372 373 if (action == null) 374 throw new ArgumentNullException(nameof(action)); 375 376 ////REVIEW: should this reference actions by ID? 377 Debug.Assert(action.m_Name != null || action.isSingletonAction); 378 binding.action = action.name; 379 380 var actionMap = action.GetOrCreateActionMap(); 381 var bindingIndex = AddBindingInternal(actionMap, binding); 382 return new BindingSyntax(actionMap, bindingIndex); 383 } 384 385 /// <summary> 386 /// Add a new binding to the given action map. 387 /// </summary> 388 /// <param name="actionMap">Action map to add the binding to.</param> 389 /// <param name="path">Path of the control(s) to bind to. See <see cref="InputControlPath"/> and 390 /// <see cref="InputBinding.path"/>.</param> 391 /// <param name="interactions">Names and parameters for interactions to apply to the 392 /// binding. See <see cref="InputBinding.interactions"/>.</param> 393 /// <param name="groups">Optional list of groups to apply to the binding. See <see cref="InputBinding.groups"/>.</param> 394 /// <param name="action">Action to trigger from the binding. See <see cref="InputBinding.action"/>.</param> 395 /// <param name="processors">Optional list of processors to apply to the binding. See <see cref="InputBinding.processors"/>.</param> 396 /// <returns>A write-accessor to the newly added binding.</returns> 397 /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception> 398 /// <remarks> 399 /// <example> 400 /// <code> 401 /// // Add a binding for the A button the gamepad and make it trigger 402 /// // the "fire" action. 403 /// var gameplayActions = playerInput.actions.FindActionMap("gameplay"); 404 /// gameplayActions.AddBinding("&lt;Gamepad&gt;/buttonSouth", action: "fire"); 405 /// </code> 406 /// </example> 407 /// </remarks> 408 /// <seealso cref="InputBinding"/> 409 /// <seealso cref="InputActionMap.bindings"/> 410 public static BindingSyntax AddBinding(this InputActionMap actionMap, string path, 411 string interactions = null, string groups = null, string action = null, string processors = null) 412 { 413 if (path == null) 414 throw new ArgumentNullException(nameof(path), "Binding path cannot be null"); 415 416 return AddBinding(actionMap, 417 new InputBinding 418 { 419 path = path, 420 interactions = interactions, 421 groups = groups, 422 action = action, 423 processors = processors, 424 }); 425 } 426 427 /// <summary> 428 /// Add a new binding that triggers the given action to the given action map. 429 /// </summary> 430 /// <param name="actionMap">Action map to add the binding to.</param> 431 /// <param name="action">Action to trigger from the binding. See <see cref="InputBinding.action"/>. 432 /// Must be part of <paramref name="actionMap"/>.</param> 433 /// <param name="path">Path of the control(s) to bind to. See <see cref="InputControlPath"/> and 434 /// <see cref="InputBinding.path"/>.</param> 435 /// <param name="interactions">Names and parameters for interactions to apply to the 436 /// binding. See <see cref="InputBinding.interactions"/>.</param> 437 /// <param name="groups">Binding groups to apply to the binding. See <see cref="InputBinding.groups"/>.</param> 438 /// <returns>A write-accessor to the newly added binding.</returns> 439 /// <exception cref="ArgumentException"><paramref name="action"/> is not part of <paramref name="actionMap"/>.</exception> 440 /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception> 441 /// <seealso cref="InputBinding"/> 442 /// <seealso cref="InputActionMap.bindings"/> 443 public static BindingSyntax AddBinding(this InputActionMap actionMap, string path, InputAction action, 444 string interactions = null, string groups = null) 445 { 446 if (action != null && action.actionMap != actionMap) 447 throw new ArgumentException( 448 $"Action '{action}' is not part of action map '{actionMap}'", nameof(action)); 449 450 if (action == null) 451 return AddBinding(actionMap, path: path, interactions: interactions, groups: groups); 452 453 return AddBinding(actionMap, path: path, interactions: interactions, groups: groups, 454 action: action.id); 455 } 456 457 /// <summary> 458 /// Add a new binding that triggers the action given by GUID <paramref name="action"/>. 459 /// </summary> 460 /// <param name="actionMap">Action map to add the binding to.</param> 461 /// <param name="path">Path of the control(s) to bind to. See <see cref="InputControlPath"/> and <see cref="InputBinding.path"/>.</param> 462 /// <param name="action">ID of the action as per <see cref="InputAction.id"/>.</param> 463 /// <param name="interactions">Optional list of names and parameters for interactions to apply to the 464 /// binding. See <see cref="InputBinding.interactions"/>.</param> 465 /// <param name="groups">Optional list of groups to apply to the binding. See <see cref="InputBinding.groups"/>.</param> 466 /// <returns>A write-accessor to the newly added binding.</returns> 467 /// <exception cref="ArgumentException"><paramref name="action"/> is not part of <paramref name="actionMap"/>.</exception> 468 /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception> 469 /// <seealso cref="InputBinding"/> 470 /// <seealso cref="InputActionMap.bindings"/> 471 /// <remarks> 472 /// Example of adding a binding to an action map that binds to a Gamepad device "leftStick" control and associates it with an action: 473 /// <example> 474 /// <code> 475 /// var map = new InputActionMap(); 476 /// var action = map.AddAction("action"); 477 /// map.AddBinding("&lt;Gamepad&gt;/leftStick", action: action.id); 478 /// </code> 479 /// </example> 480 /// </remarks> 481 public static BindingSyntax AddBinding(this InputActionMap actionMap, string path, Guid action, 482 string interactions = null, string groups = null) 483 { 484 if (action == Guid.Empty) 485 return AddBinding(actionMap, path: path, interactions: interactions, groups: groups); 486 return AddBinding(actionMap, path: path, interactions: interactions, groups: groups, 487 action: action.ToString()); 488 } 489 490 /// <summary> 491 /// Add a binding to the given action map. 492 /// </summary> 493 /// <param name="actionMap">Action map to add the binding to.</param> 494 /// <param name="binding">Binding to add to the action map.</param> 495 /// <returns>A write-accessor to the newly added binding.</returns> 496 /// <exception cref="ArgumentException"><paramref name="action"/> is not part of <paramref name="actionMap"/>.</exception> 497 /// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception> 498 /// <seealso cref="InputBinding"/> 499 /// <seealso cref="InputActionMap.bindings"/> 500 public static BindingSyntax AddBinding(this InputActionMap actionMap, InputBinding binding) 501 { 502 #if UNITY_EDITOR 503 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.AddBinding); 504 #endif 505 506 if (actionMap == null) 507 throw new ArgumentNullException(nameof(actionMap)); 508 if (binding.path == null) 509 throw new ArgumentException("Binding path cannot be null", nameof(binding)); 510 511 var bindingIndex = AddBindingInternal(actionMap, binding); 512 return new BindingSyntax(actionMap, bindingIndex); 513 } 514 515 /// <summary> 516 /// Add a composite binding to the <see cref="InputAction.bindings"/> of <paramref name="action"/>. 517 /// </summary> 518 /// <param name="action">Action to add the binding to.</param> 519 /// <param name="composite">Type of composite to add. This needs to be the name the composite 520 /// has been registered under using <see cref="InputSystem.RegisterBindingComposite{T}"/>. Case-insensitive.</param> 521 /// <param name="interactions">Interactions to add to the binding. See <see cref="InputBinding.interactions"/>.</param> 522 /// <param name="processors">Processors to add to the binding. See <see cref="InputBinding.processors"/>.</param> 523 /// <returns>A write accessor to the newly added composite binding.</returns> 524 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> 525 /// <exception cref="ArgumentException"><paramref name="composite"/> is <c>null</c> or empty.</exception> 526 public static CompositeSyntax AddCompositeBinding(this InputAction action, string composite, 527 string interactions = null, string processors = null) 528 { 529 #if UNITY_EDITOR 530 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.AddCompositeBinding); 531 #endif 532 533 if (action == null) 534 throw new ArgumentNullException(nameof(action)); 535 if (string.IsNullOrEmpty(composite)) 536 throw new ArgumentException("Composite name cannot be null or empty", nameof(composite)); 537 538 var actionMap = action.GetOrCreateActionMap(); 539 540 var binding = new InputBinding 541 { 542 name = NameAndParameters.ParseName(composite), 543 path = composite, 544 interactions = interactions, 545 processors = processors, 546 isComposite = true, 547 action = action.name 548 }; 549 550 var bindingIndex = AddBindingInternal(actionMap, binding); 551 return new CompositeSyntax(actionMap, action, bindingIndex); 552 } 553 554 ////TODO: AddCompositeBinding<T> 555 556 private static int AddBindingInternal(InputActionMap map, InputBinding binding, int bindingIndex = -1) 557 { 558 Debug.Assert(map != null); 559 560 // Make sure the binding has an ID. 561 if (string.IsNullOrEmpty(binding.m_Id)) 562 binding.GenerateId(); 563 564 // Append to bindings in set. 565 if (bindingIndex < 0) 566 bindingIndex = ArrayHelpers.Append(ref map.m_Bindings, binding); 567 else 568 ArrayHelpers.InsertAt(ref map.m_Bindings, bindingIndex, binding); 569 570 // Make sure this asset is reloaded from disk when exiting play mode so it isn't inadvertently 571 // changed between play sessions. Only applies when running in the editor. 572 if (map.asset != null) 573 map.asset.MarkAsDirty(); 574 575 // If we're looking at a singleton action, make sure m_Bindings is up to date just 576 // in case the action gets serialized. 577 if (map.m_SingletonAction != null) 578 map.m_SingletonAction.m_SingletonActionBindings = map.m_Bindings; 579 580 // NOTE: We treat this as a mere binding modification, even though we have added something. 581 // InputAction.RestoreActionStatesAfterReResolvingBindings() can deal with bindings 582 // having been removed or added. 583 map.OnBindingModified(); 584 585 return bindingIndex; 586 } 587 588 /// <summary> 589 /// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> 590 /// at the given <paramref name="index"/>. 591 /// </summary> 592 /// <param name="action">Action whose bindings to change.</param> 593 /// <param name="index">Index in <paramref name="action"/>'s <see cref="InputAction.bindings"/> of the binding to be changed.</param> 594 /// <returns>A write accessor to the given binding.</returns> 595 /// <remarks> 596 /// <example> 597 /// <code> 598 /// // Grab "fire" action from PlayerInput. 599 /// var fireAction = playerInput.actions["fire"]; 600 /// 601 /// // Change its second binding to go to the left mouse button. 602 /// fireAction.ChangeBinding(1) 603 /// .WithPath("&lt;Mouse&gt;/leftButton"); 604 /// </code> 605 /// </example> 606 /// </remarks> 607 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> 608 /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range (as per <see cref="InputAction.bindings"/> 609 /// of <paramref name="action"/>).</exception> 610 public static BindingSyntax ChangeBinding(this InputAction action, int index) 611 { 612 #if UNITY_EDITOR 613 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.ChangeBinding); 614 #endif 615 616 if (action == null) 617 throw new ArgumentNullException(nameof(action)); 618 619 var indexOnMap = action.BindingIndexOnActionToBindingIndexOnMap(index); 620 return new BindingSyntax(action.GetOrCreateActionMap(), indexOnMap, action); 621 } 622 623 /// <summary> 624 /// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> 625 /// with the given <paramref name="name"/>. 626 /// </summary> 627 /// <param name="action">Action whose bindings to change.</param> 628 /// <param name="name">Name of the binding to be changed <see cref="InputAction.bindings"/>.</param> 629 /// <returns>A write accessor to the given binding.</returns> 630 /// <remarks> 631 /// <example> 632 /// <code> 633 /// // Grab "fire" action from PlayerInput. 634 /// var fireAction = playerInput.actions["fire"]; 635 /// 636 /// // Change its second binding to go to the left mouse button. 637 /// fireAction.ChangeBinding("fire") 638 /// .WithPath("&lt;Mouse&gt;/leftButton"); 639 /// </code> 640 /// </example> 641 /// </remarks> 642 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> 643 /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range (as per <see cref="InputAction.bindings"/> 644 /// of <paramref name="action"/>).</exception> 645 public static BindingSyntax ChangeBinding(this InputAction action, string name) 646 { 647 return action.ChangeBinding(new InputBinding { name = name }); 648 } 649 650 /// <summary> 651 /// Get write access to the binding in <see cref="InputActionMap.bindings"/> of <paramref name="actionMap"/> 652 /// at the given <paramref name="index"/>. 653 /// </summary> 654 /// <param name="actionMap">Action map whose bindings to change.</param> 655 /// <param name="index">Index in <paramref name="actionMap"/>'s <see cref="InputActionMap.bindings"/> of the binding to be changed.</param> 656 /// <returns>A write accessor to the given binding.</returns> 657 /// <remarks> 658 /// <example> 659 /// <code> 660 /// // Grab "gameplay" actions from PlayerInput. 661 /// var gameplayActions = playerInput.actions.FindActionMap("gameplay"); 662 /// 663 /// // Change its second binding to go to the left mouse button. 664 /// gameplayActions.ChangeBinding(1) 665 /// .WithPath("&lt;Mouse&gt;/leftButton"); 666 /// </code> 667 /// </example> 668 /// </remarks> 669 /// <exception cref="ArgumentNullException"><paramref name="actionMap"/> is <c>null</c>.</exception> 670 /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range (as per <see cref="InputActionMap.bindings"/> 671 /// of <paramref name="actionMap"/>).</exception> 672 public static BindingSyntax ChangeBinding(this InputActionMap actionMap, int index) 673 { 674 #if UNITY_EDITOR 675 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.ChangeBinding); 676 #endif 677 678 if (actionMap == null) 679 throw new ArgumentNullException(nameof(actionMap)); 680 if (index < 0 || index >= actionMap.m_Bindings.LengthSafe()) 681 throw new ArgumentOutOfRangeException(nameof(index)); 682 683 return new BindingSyntax(actionMap, index); 684 } 685 686 /// <summary> 687 /// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> 688 /// that has the given <paramref name="id"/>. 689 /// </summary> 690 /// <param name="action">Action whose bindings to change.</param> 691 /// <param name="id">ID of the binding as per <see cref="InputBinding.id"/>.</param> 692 /// <returns>A write accessor to the binding with the given ID.</returns> 693 /// <remarks> 694 /// <example> 695 /// <code> 696 /// // Grab "fire" action from PlayerInput. 697 /// var fireAction = playerInput.actions["fire"]; 698 /// 699 /// // Change the binding with the given ID to go to the left mouse button. 700 /// fireAction.ChangeBindingWithId("c3de9215-31c3-4654-8562-854bf2f7864f") 701 /// .WithPath("&lt;Mouse&gt;/leftButton"); 702 /// </code> 703 /// </example> 704 /// </remarks> 705 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> 706 /// <exception cref="ArgumentException">No binding with the given <paramref name="id"/> exists 707 /// on <paramref name="action"/>.</exception> 708 public static BindingSyntax ChangeBindingWithId(this InputAction action, string id) 709 { 710 if (action == null) 711 throw new ArgumentNullException(nameof(action)); 712 713 return action.ChangeBinding(new InputBinding {m_Id = id}); 714 } 715 716 /// <summary> 717 /// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> 718 /// that has the given <paramref name="id"/>. 719 /// </summary> 720 /// <param name="action">Action whose bindings to change.</param> 721 /// <param name="id">ID of the binding as per <see cref="InputBinding.id"/>.</param> 722 /// <returns>A write accessor to the binding with the given ID.</returns> 723 /// <remarks> 724 /// <example> 725 /// <code> 726 /// // Grab "fire" action from PlayerInput. 727 /// var fireAction = playerInput.actions["fire"]; 728 /// 729 /// // Change the binding with the given ID to go to the left mouse button. 730 /// fireAction.ChangeBindingWithId(new Guid("c3de9215-31c3-4654-8562-854bf2f7864f")) 731 /// .WithPath("&lt;Mouse&gt;/leftButton"); 732 /// </code> 733 /// </example> 734 /// </remarks> 735 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> 736 /// <exception cref="ArgumentException">No binding with the given <paramref name="id"/> exists 737 /// on <paramref name="action"/>.</exception> 738 public static BindingSyntax ChangeBindingWithId(this InputAction action, Guid id) 739 { 740 if (action == null) 741 throw new ArgumentNullException(nameof(action)); 742 743 return action.ChangeBinding(new InputBinding {id = id}); 744 } 745 746 /// <summary> 747 /// Get write access to the first binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> 748 /// that is assigned to the given binding <paramref name="group"/>. 749 /// </summary> 750 /// <param name="action">Action whose bindings to change.</param> 751 /// <param name="group">Name of the binding group as per <see cref="InputBinding.groups"/>.</param> 752 /// <returns>A write accessor to the first binding on <paramref name="action"/> that is assigned to the 753 /// given binding <paramref name="group"/>.</returns> 754 /// <remarks> 755 /// <example> 756 /// <code> 757 /// // Grab "fire" action from PlayerInput. 758 /// var fireAction = playerInput.actions["fire"]; 759 /// 760 /// // Change the binding in the "Keyboard&amp;Mouse" group to go to the left mouse button. 761 /// fireAction.ChangeBindingWithGroup("Keyboard&amp;Mouse") 762 /// .WithPath("&lt;Mouse&gt;/leftButton"); 763 /// </code> 764 /// </example> 765 /// </remarks> 766 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> 767 /// <exception cref="ArgumentException">No binding on the <paramref name="action"/> is assigned 768 /// to the given binding <paramref name="group"/>.</exception> 769 public static BindingSyntax ChangeBindingWithGroup(this InputAction action, string group) 770 { 771 if (action == null) 772 throw new ArgumentNullException(nameof(action)); 773 774 return action.ChangeBinding(new InputBinding {groups = group}); 775 } 776 777 /// <summary> 778 /// Get write access to the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> 779 /// that is bound to the given <paramref name="path"/>. 780 /// </summary> 781 /// <param name="action">Action whose bindings to change.</param> 782 /// <param name="path">Path of the binding as per <see cref="InputBinding.path"/>.</param> 783 /// <returns>A write accessor to the binding on <paramref name="action"/> that is assigned the 784 /// given <paramref name="path"/>.</returns> 785 /// <remarks> 786 /// <example> 787 /// <code> 788 /// // Grab "fire" action from PlayerInput. 789 /// var fireAction = playerInput.actions["fire"]; 790 /// 791 /// // Change the binding to the right mouse button to go to the left mouse button instead. 792 /// fireAction.ChangeBindingWithPath("&lt;Mouse&gt;/rightButton") 793 /// .WithPath("&lt;Mouse&gt;/leftButton"); 794 /// </code> 795 /// </example> 796 /// </remarks> 797 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> 798 /// <exception cref="ArgumentException">No binding on the <paramref name="action"/> is assigned 799 /// the given <paramref name="path"/>.</exception> 800 public static BindingSyntax ChangeBindingWithPath(this InputAction action, string path) 801 { 802 if (action == null) 803 throw new ArgumentNullException(nameof(action)); 804 805 return action.ChangeBinding(new InputBinding {path = path}); 806 } 807 808 /// <summary> 809 /// Get write access to the binding on <paramref name="action"/> that matches the given 810 /// <paramref name="match"/>. 811 /// </summary> 812 /// <param name="action">Action whose bindings to match against.</param> 813 /// <param name="match">A binding mask. See <see cref="InputBinding.Matches"/> for 814 /// details.</param> 815 /// <returns>A write-accessor to the first binding matching <paramref name="match"/> or 816 /// an invalid accessor (see <see cref="BindingSyntax.valid"/>) if no binding was found to 817 /// match the mask.</returns> 818 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception> 819 public static BindingSyntax ChangeBinding(this InputAction action, InputBinding match) 820 { 821 if (action == null) 822 throw new ArgumentNullException(nameof(action)); 823 824 var actionMap = action.GetOrCreateActionMap(); 825 826 int bindingIndexInMap = -1; 827 var id = action.idDontGenerate; 828 if (id != null) 829 { 830 // Prio1: Attempt to match action id (stronger) 831 match.action = action.id.ToString(); 832 bindingIndexInMap = actionMap.FindBindingRelativeToMap(match); 833 } 834 if (bindingIndexInMap == -1) 835 { 836 // Prio2: Attempt to match action name (weaker) 837 match.action = action.name; 838 bindingIndexInMap = actionMap.FindBindingRelativeToMap(match); 839 } 840 if (bindingIndexInMap == -1) 841 return default; 842 843 return new BindingSyntax(actionMap, bindingIndexInMap, action); 844 } 845 846 /// <summary> 847 /// Get a write accessor to the binding of <paramref name="action"/> that is both a composite 848 /// (see <see cref="InputBinding.isComposite"/>) and has the given binding name or composite 849 /// type. 850 /// </summary> 851 /// <param name="action">Action to look up the binding on. All bindings in the action's 852 /// <see cref="InputAction.bindings"/> property will be considered.</param> 853 /// <param name="compositeName">Either the name of the composite binding (see <see cref="InputBinding.name"/>) 854 /// to look for or the name of the composite type used in the binding (such as "1DAxis"). Case-insensitive.</param> 855 /// <returns>A write accessor to the given composite binding or an invalid accessor if no composite 856 /// matching <paramref name="compositeName"/> could be found on <paramref name="action"/>.</returns> 857 /// <remarks> 858 /// <example> 859 /// <code> 860 /// // Add arrow keys as alternatives to the WASD Vector2 composite. 861 /// playerInput.actions["move"] 862 /// .ChangeCompositeBinding("WASD") 863 /// .InsertPartBinding("Up", "&lt;Keyboard&gt;/upArrow") 864 /// .InsertPartBinding("Down", "&lt;Keyboard&gt;/downArrow") 865 /// .InsertPartBinding("Left", "&lt;Keyboard&gt;/leftArrow") 866 /// .InsertPartBinding("Right", "&lt;Keyboard&gt;/rightArrow"); 867 /// </code> 868 /// </example> 869 /// </remarks> 870 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="compositeName"/> 871 /// is <c>null</c> or empty.</exception> 872 /// <seealso cref="InputBinding.isComposite"/> 873 /// <seealso cref="InputBindingComposite"/> 874 public static BindingSyntax ChangeCompositeBinding(this InputAction action, string compositeName) 875 { 876 #if UNITY_EDITOR 877 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.ChangeCompositeBinding); 878 #endif 879 880 if (action == null) 881 throw new ArgumentNullException(nameof(action)); 882 if (string.IsNullOrEmpty(compositeName)) 883 throw new ArgumentNullException(nameof(compositeName)); 884 885 var actionMap = action.GetOrCreateActionMap(); 886 var bindings = actionMap.m_Bindings; 887 var numBindings = bindings.LengthSafe(); 888 889 for (var i = 0; i < numBindings; ++i) 890 { 891 ref var binding = ref bindings[i]; 892 if (!binding.isComposite || !binding.TriggersAction(action)) 893 continue; 894 895 ////REVIEW: should this do a registration lookup to deal with aliases? 896 if (compositeName.Equals(binding.name, StringComparison.InvariantCultureIgnoreCase) 897 || compositeName.Equals(NameAndParameters.ParseName(binding.path), 898 StringComparison.InvariantCultureIgnoreCase)) 899 return new BindingSyntax(actionMap, i, action); 900 } 901 902 return default; 903 } 904 905 ////TODO: update binding mask if necessary 906 /// <summary> 907 /// Rename an existing action. 908 /// </summary> 909 /// <param name="action">Action to assign a new name to. Can be singleton action or action that 910 /// is part of a map.</param> 911 /// <param name="newName">New name to assign to action. Cannot be empty.</param> 912 /// <exception cref="ArgumentNullException"><paramref name="action"/> is null or <paramref name="newName"/> is 913 /// null or empty.</exception> 914 /// <exception cref="InvalidOperationException"><see cref="InputAction.actionMap"/> of <paramref name="action"/> 915 /// already contains an action called <paramref name="newName"/>.</exception> 916 /// <remarks> 917 /// Renaming an action will also update the bindings that refer to the action. 918 /// </remarks> 919 public static void Rename(this InputAction action, string newName) 920 { 921 #if UNITY_EDITOR 922 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.Rename); 923 #endif 924 925 if (action == null) 926 throw new ArgumentNullException(nameof(action)); 927 if (string.IsNullOrEmpty(newName)) 928 throw new ArgumentNullException(nameof(newName)); 929 930 if (action.name == newName) 931 return; 932 933 // Make sure name isn't already taken in map. 934 var actionMap = action.actionMap; 935 if (actionMap?.FindAction(newName) != null) 936 throw new InvalidOperationException( 937 $"Cannot rename '{action}' to '{newName}' in map '{actionMap}' as the map already contains an action with that name"); 938 939 var oldName = action.m_Name; 940 action.m_Name = newName; 941 actionMap?.ClearActionLookupTable(); 942 943 if (actionMap?.asset != null) 944 actionMap?.asset.MarkAsDirty(); 945 946 // Update bindings. 947 var bindings = action.GetOrCreateActionMap().m_Bindings; 948 var bindingCount = bindings.LengthSafe(); 949 for (var i = 0; i < bindingCount; ++i) 950 if (string.Compare(bindings[i].action, oldName, StringComparison.InvariantCultureIgnoreCase) == 0) 951 bindings[i].action = newName; 952 } 953 954 /// <summary> 955 /// Add a new control scheme to the asset. 956 /// </summary> 957 /// <param name="asset">Asset to add the control scheme to.</param> 958 /// <param name="controlScheme">Control scheme to add.</param> 959 /// <exception cref="ArgumentException"><paramref name="controlScheme"/> has no name.</exception> 960 /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c>.</exception> 961 /// <exception cref="InvalidOperationException">A control scheme with the same name as <paramref name="controlScheme"/> 962 /// already exists in the asset.</exception> 963 /// <remarks> 964 /// </remarks> 965 public static void AddControlScheme(this InputActionAsset asset, InputControlScheme controlScheme) 966 { 967 #if UNITY_EDITOR 968 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.AddControlScheme); 969 #endif 970 971 if (asset == null) 972 throw new ArgumentNullException(nameof(asset)); 973 if (string.IsNullOrEmpty(controlScheme.name)) 974 throw new ArgumentException("Cannot add control scheme without name to asset " + asset.name, nameof(controlScheme)); 975 if (asset.FindControlScheme(controlScheme.name) != null) 976 throw new InvalidOperationException( 977 $"Asset '{asset.name}' already contains a control scheme called '{controlScheme.name}'"); 978 979 ArrayHelpers.Append(ref asset.m_ControlSchemes, controlScheme); 980 981 asset.MarkAsDirty(); 982 } 983 984 /// <summary> 985 /// Add a new control scheme to the given <paramref name="asset"/>. 986 /// </summary> 987 /// <param name="asset">Asset to add the control scheme to.</param> 988 /// <param name="name">Name to give to the control scheme. Must be unique within the control schemes of the 989 /// asset. Also used as default name of <see cref="InputControlScheme.bindingGroup">binding group</see> associated 990 /// with the control scheme.</param> 991 /// <returns>Syntax to allow providing additional configuration for the newly added control scheme.</returns> 992 /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="name"/> 993 /// is <c>null</c> or empty.</exception> 994 /// <remarks> 995 /// <example> 996 /// <code> 997 /// // Create an .inputactions asset. 998 /// var asset = ScriptableObject.CreateInstance&lt;InputActionAsset&gt;(); 999 /// 1000 /// // Add an action map to it. 1001 /// var actionMap = asset.AddActionMap("actions"); 1002 /// 1003 /// // Add an action to it and bind it to the A button on the gamepad. 1004 /// // Also, associate that binding with the "Gamepad" control scheme. 1005 /// var action = actionMap.AddAction("action"); 1006 /// action.AddBinding("&lt;Gamepad&gt;/buttonSouth", groups: "Gamepad"); 1007 /// 1008 /// // Add a control scheme called "Gamepad" that requires a Gamepad device. 1009 /// asset.AddControlScheme("Gamepad") 1010 /// .WithRequiredDevice&lt;Gamepad&gt;(); 1011 /// </code> 1012 /// </example> 1013 /// </remarks> 1014 public static ControlSchemeSyntax AddControlScheme(this InputActionAsset asset, string name) 1015 { 1016 if (asset == null) 1017 throw new ArgumentNullException(nameof(asset)); 1018 if (string.IsNullOrEmpty(name)) 1019 throw new ArgumentNullException(nameof(name)); 1020 1021 var index = asset.controlSchemes.Count; 1022 asset.AddControlScheme(new InputControlScheme(name)); 1023 1024 return new ControlSchemeSyntax(asset, index); 1025 } 1026 1027 /// <summary> 1028 /// Remove the control scheme with the given name from the asset. 1029 /// </summary> 1030 /// <param name="asset">Asset to remove the control scheme from.</param> 1031 /// <param name="name">Name of the control scheme. Matching is case-insensitive.</param> 1032 /// <exception cref="ArgumentNullException"><paramref name="asset"/> is null -or- <paramref name="name"/> 1033 /// is <c>null</c> or empty.</exception> 1034 /// <remarks> 1035 /// If no control scheme with the given name can be found, the method does nothing. 1036 /// </remarks> 1037 public static void RemoveControlScheme(this InputActionAsset asset, string name) 1038 { 1039 #if UNITY_EDITOR 1040 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.RemoveControlScheme); 1041 #endif 1042 1043 if (asset == null) 1044 throw new ArgumentNullException(nameof(asset)); 1045 if (string.IsNullOrEmpty(name)) 1046 throw new ArgumentNullException(nameof(name)); 1047 1048 var index = asset.FindControlSchemeIndex(name); 1049 if (index != -1) 1050 ArrayHelpers.EraseAt(ref asset.m_ControlSchemes, index); 1051 1052 asset.MarkAsDirty(); 1053 } 1054 1055 /// <summary> 1056 /// Associates the control scheme given by <paramref name="scheme"/> with the binding group given by <paramref name="bindingGroup"/>. 1057 /// </summary> 1058 /// <param name="scheme">The control scheme to modify.</param> 1059 /// <param name="bindingGroup">The binding group to be associated with the control scheme.</param> 1060 /// <returns><paramref name="scheme"/></returns> 1061 public static InputControlScheme WithBindingGroup(this InputControlScheme scheme, string bindingGroup) 1062 { 1063 #if UNITY_EDITOR 1064 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.ControlSchemeWithBindingGroup); 1065 #endif 1066 1067 return new ControlSchemeSyntax(scheme).WithBindingGroup(bindingGroup).Done(); 1068 } 1069 1070 public static InputControlScheme WithDevice(this InputControlScheme scheme, string controlPath, bool required) 1071 { 1072 #if UNITY_EDITOR 1073 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.ControlSchemeWithDevice); 1074 #endif 1075 1076 if (required) 1077 return new ControlSchemeSyntax(scheme).WithRequiredDevice(controlPath).Done(); 1078 return new ControlSchemeSyntax(scheme).WithOptionalDevice(controlPath).Done(); 1079 } 1080 1081 public static InputControlScheme WithRequiredDevice(this InputControlScheme scheme, string controlPath) 1082 { 1083 #if UNITY_EDITOR 1084 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.ControlSchemeWithRequiredDevice); 1085 #endif 1086 1087 return new ControlSchemeSyntax(scheme).WithRequiredDevice(controlPath).Done(); 1088 } 1089 1090 public static InputControlScheme WithOptionalDevice(this InputControlScheme scheme, string controlPath) 1091 { 1092 #if UNITY_EDITOR 1093 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.ControlSchemeWithOptionalDevice); 1094 #endif 1095 1096 return new ControlSchemeSyntax(scheme).WithOptionalDevice(controlPath).Done(); 1097 } 1098 1099 public static InputControlScheme OrWithRequiredDevice(this InputControlScheme scheme, string controlPath) 1100 { 1101 #if UNITY_EDITOR 1102 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.ControlSchemeOrWithRequiredDevice); 1103 #endif 1104 1105 return new ControlSchemeSyntax(scheme).OrWithRequiredDevice(controlPath).Done(); 1106 } 1107 1108 public static InputControlScheme OrWithOptionalDevice(this InputControlScheme scheme, string controlPath) 1109 { 1110 #if UNITY_EDITOR 1111 RegisterApiUsage(UnityEngine.InputSystem.Editor.InputExitPlayModeAnalytic.Api.ControlSchemeOrWithOptionalDevice); 1112 #endif 1113 1114 return new ControlSchemeSyntax(scheme).OrWithOptionalDevice(controlPath).Done(); 1115 } 1116 1117 /// <summary> 1118 /// Write accessor to a binding on either an <see cref="InputAction"/> or an 1119 /// <see cref="InputActionMap"/>. 1120 /// </summary> 1121 /// <remarks> 1122 /// Both <see cref="InputAction.bindings"/> and <see cref="InputActionMap.bindings"/> are 1123 /// read-only. To modify bindings (other than setting overrides which you can do 1124 /// through <see cref="InputActionRebindingExtensions.ApplyBindingOverride(InputAction,int,InputBinding)"/>), 1125 /// it is necessary to gain indirect write access through this structure. 1126 /// 1127 /// <example> 1128 /// <code> 1129 /// playerInput.actions["fire"] 1130 /// .ChangeBinding(0) 1131 /// .WithPath("&lt;Keyboard&gt;/space"); 1132 /// </code> 1133 /// </example> 1134 /// </remarks> 1135 /// <seealso cref="AddBinding(InputAction,InputBinding)"/> 1136 /// <seealso cref="ChangeBinding(InputAction,int)"/> 1137 public struct BindingSyntax 1138 { 1139 private readonly InputActionMap m_ActionMap; 1140 private readonly InputAction m_Action; 1141 internal readonly int m_BindingIndexInMap; 1142 1143 /// <summary> 1144 /// True if the if binding accessor is valid. 1145 /// </summary> 1146 public bool valid => m_ActionMap != null && m_BindingIndexInMap >= 0 && m_BindingIndexInMap < m_ActionMap.m_Bindings.LengthSafe(); 1147 1148 /// <summary> 1149 /// Index of the binding that the accessor refers to. 1150 /// </summary> 1151 /// <remarks> 1152 /// When accessing bindings on an <see cref="InputAction"/>, this is the index in 1153 /// <see cref="InputAction.bindings"/> of the action. When accessing bindings on an 1154 /// <see cref="InputActionMap"/>, it is the index <see cref="InputActionMap.bindings"/> 1155 /// of the map. 1156 /// </remarks> 1157 public int bindingIndex 1158 { 1159 get 1160 { 1161 if (!valid) 1162 return -1; 1163 if (m_Action != null) 1164 return m_Action.BindingIndexOnMapToBindingIndexOnAction(m_BindingIndexInMap); 1165 return m_BindingIndexInMap; 1166 } 1167 } 1168 1169 /// <summary> 1170 /// The current binding in entirety. 1171 /// </summary> 1172 /// <exception cref="InvalidOperationException">The accessor is not <see cref="valid"/>.</exception> 1173 public InputBinding binding 1174 { 1175 get 1176 { 1177 if (!valid) 1178 throw new InvalidOperationException("BindingSyntax accessor is not valid"); 1179 return m_ActionMap.m_Bindings[m_BindingIndexInMap]; 1180 } 1181 } 1182 1183 internal BindingSyntax(InputActionMap map, int bindingIndexInMap, InputAction action = null) 1184 { 1185 m_ActionMap = map; 1186 m_BindingIndexInMap = bindingIndexInMap; 1187 m_Action = action; 1188 } 1189 1190 /// <summary> 1191 /// Set the <see cref="InputBinding.name"/> of the binding. 1192 /// </summary> 1193 /// <param name="name">Name for the binding.</param> 1194 /// <returns>The same binding syntax for further configuration.</returns> 1195 /// <exception cref="InvalidOperationException">The binding accessor is not <see cref="valid"/>.</exception> 1196 /// <seealso cref="InputBinding.name"/> 1197 public BindingSyntax WithName(string name) 1198 { 1199 if (!valid) 1200 throw new InvalidOperationException("Accessor is not valid"); 1201 m_ActionMap.m_Bindings[m_BindingIndexInMap].name = name; 1202 m_ActionMap.OnBindingModified(); 1203 return this; 1204 } 1205 1206 /// <summary> 1207 /// Set the <see cref="InputBinding.path"/> of the binding. 1208 /// </summary> 1209 /// <param name="path">Path for the binding.</param> 1210 /// <returns>The same binding syntax for further configuration.</returns> 1211 /// <exception cref="InvalidOperationException">The binding accessor is not <see cref="valid"/>.</exception> 1212 /// <seealso cref="InputBinding.path"/> 1213 public BindingSyntax WithPath(string path) 1214 { 1215 if (!valid) 1216 throw new InvalidOperationException("Accessor is not valid"); 1217 m_ActionMap.m_Bindings[m_BindingIndexInMap].path = path; 1218 m_ActionMap.OnBindingModified(); 1219 return this; 1220 } 1221 1222 /// <summary> 1223 /// Add <paramref name="group"/> to the list of <see cref="InputBinding.groups"/> of the binding. 1224 /// </summary> 1225 /// <param name="group">Name of the binding group (such as "Gamepad").</param> 1226 /// <returns>The same binding syntax for further configuration.</returns> 1227 /// <exception cref="ArgumentException"><paramref name="group"/> is <c>null</c> or empty -or- it contains 1228 /// a <see cref="InputBinding.Separator"/> character.</exception> 1229 public BindingSyntax WithGroup(string group) 1230 { 1231 if (!valid) 1232 throw new InvalidOperationException("Accessor is not valid"); 1233 if (string.IsNullOrEmpty(group)) 1234 throw new ArgumentException("Group name cannot be null or empty", nameof(group)); 1235 if (group.IndexOf(InputBinding.Separator) != -1) 1236 throw new ArgumentException( 1237 $"Group name cannot contain separator character '{InputBinding.Separator}'", nameof(group)); 1238 1239 return WithGroups(group); 1240 } 1241 1242 /// <summary> 1243 /// Add all the groups specified in <paramref name="groups"/> to the list of <see cref="InputBinding.groups"/> of the binding. 1244 /// </summary> 1245 /// <param name="groups">A semi-colon separated list of group names.</param> 1246 /// <returns>The same binding syntax for further configuration.</returns> 1247 /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> 1248 public BindingSyntax WithGroups(string groups) 1249 { 1250 if (!valid) 1251 throw new InvalidOperationException("Accessor is not valid"); 1252 if (string.IsNullOrEmpty(groups)) 1253 return this; 1254 1255 // Join with existing group, if any. 1256 var currentGroups = m_ActionMap.m_Bindings[m_BindingIndexInMap].groups; 1257 if (!string.IsNullOrEmpty(currentGroups)) 1258 groups = string.Join(InputBinding.kSeparatorString, currentGroups, groups); 1259 1260 // Set groups on binding. 1261 m_ActionMap.m_Bindings[m_BindingIndexInMap].groups = groups; 1262 m_ActionMap.OnBindingModified(); 1263 1264 return this; 1265 } 1266 1267 /// <summary> 1268 /// Add an interaction via specified name in <paramref name="interaction"/> to the list of interactions. 1269 /// </summary> 1270 /// <param name="interaction">Interaction class name</param> 1271 /// <returns>The same binding syntax for further configuration.</returns> 1272 /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> 1273 /// <exception cref="ArgumentException">If interaction name is null or empty.</exception> 1274 public BindingSyntax WithInteraction(string interaction) 1275 { 1276 if (!valid) 1277 throw new InvalidOperationException("Accessor is not valid"); 1278 if (string.IsNullOrEmpty(interaction)) 1279 throw new ArgumentException("Interaction cannot be null or empty", nameof(interaction)); 1280 if (interaction.IndexOf(InputBinding.Separator) != -1) 1281 throw new ArgumentException( 1282 $"Interaction string cannot contain separator character '{InputBinding.Separator}'", nameof(interaction)); 1283 1284 return WithInteractions(interaction); 1285 } 1286 1287 /// <summary> 1288 /// Add the set of interactions specified in <paramref name="interactions"/> to the list of interactions. 1289 /// </summary> 1290 /// <param name="interactions">A semi-colon separated list of interaction names.</param> 1291 /// <returns>The same binding syntax for further configuration.</returns> 1292 /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> 1293 public BindingSyntax WithInteractions(string interactions) 1294 { 1295 if (!valid) 1296 throw new InvalidOperationException("Accessor is not valid"); 1297 if (string.IsNullOrEmpty(interactions)) 1298 return this; 1299 1300 // Join with existing interaction string, if any. 1301 var currentInteractions = m_ActionMap.m_Bindings[m_BindingIndexInMap].interactions; 1302 if (!string.IsNullOrEmpty(currentInteractions)) 1303 interactions = string.Join(InputBinding.kSeparatorString, currentInteractions, interactions); 1304 1305 // Set interactions on binding. 1306 m_ActionMap.m_Bindings[m_BindingIndexInMap].interactions = interactions; 1307 m_ActionMap.OnBindingModified(); 1308 1309 return this; 1310 } 1311 1312 /// <summary> 1313 /// Add an interaction of type specified in <typeparamref name="TInteraction"/> to the list of interactions. 1314 /// </summary> 1315 /// <typeparam name="TInteraction"></typeparam> 1316 /// <returns>The same binding syntax for further configuration.</returns> 1317 /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> 1318 /// <exception cref="NotSupportedException">Interaction type has not been registered.</exception> 1319 public BindingSyntax WithInteraction<TInteraction>() 1320 where TInteraction : IInputInteraction 1321 { 1322 if (!valid) 1323 throw new InvalidOperationException("Accessor is not valid"); 1324 1325 var interactionName = InputInteraction.s_Interactions.FindNameForType(typeof(TInteraction)); 1326 if (interactionName.IsEmpty()) 1327 throw new NotSupportedException($"Type '{typeof(TInteraction)}' has not been registered as a interaction"); 1328 1329 return WithInteraction(interactionName); 1330 } 1331 1332 /// <summary> 1333 /// Add a processor to the list of <see cref="InputBinding.processors"/> of the binding. 1334 /// </summary> 1335 /// <param name="processor">Name of the processor, such as &quot;Scale&quot;.</param> 1336 /// <returns>The same binding syntax for further configuration.</returns> 1337 /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> 1338 /// <exception cref="ArgumentException">The processor name is null or empty.</exception> 1339 /// <exception cref="ArgumentException">The processor name contains a semi-colon.</exception> 1340 public BindingSyntax WithProcessor(string processor) 1341 { 1342 if (!valid) 1343 throw new InvalidOperationException("Accessor is not valid"); 1344 if (string.IsNullOrEmpty(processor)) 1345 throw new ArgumentException("Processor cannot be null or empty", nameof(processor)); 1346 if (processor.IndexOf(InputBinding.Separator) != -1) 1347 throw new ArgumentException( 1348 $"Processor string cannot contain separator character '{InputBinding.Separator}'", nameof(processor)); 1349 1350 return WithProcessors(processor); 1351 } 1352 1353 /// <summary> 1354 /// Add processors to the list of <see cref="InputBinding.processors"/> of the binding. 1355 /// </summary> 1356 /// <param name="processors">A semi-colon separated list of processor names.</param> 1357 /// <returns>The same binding syntax for further configuration.</returns> 1358 /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> 1359 public BindingSyntax WithProcessors(string processors) 1360 { 1361 if (!valid) 1362 throw new InvalidOperationException("Accessor is not valid"); 1363 if (string.IsNullOrEmpty(processors)) 1364 return this; 1365 1366 // Join with existing processor string, if any. 1367 var currentProcessors = m_ActionMap.m_Bindings[m_BindingIndexInMap].processors; 1368 if (!string.IsNullOrEmpty(currentProcessors)) 1369 processors = string.Join(InputBinding.kSeparatorString, currentProcessors, processors); 1370 1371 // Set processors on binding. 1372 m_ActionMap.m_Bindings[m_BindingIndexInMap].processors = processors; 1373 m_ActionMap.OnBindingModified(); 1374 1375 return this; 1376 } 1377 1378 /// <summary> 1379 /// Add a processor to the list of <see cref="InputBinding.processors"/> of the binding. 1380 /// </summary> 1381 /// <typeparam name="TProcessor">Type of processor.</typeparam> 1382 /// <returns>The same binding syntax for further configuration.</returns> 1383 /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> 1384 /// <exception cref="NotSupportedException">Processor type has not been registered.</exception> 1385 public BindingSyntax WithProcessor<TProcessor>() 1386 { 1387 if (!valid) 1388 throw new InvalidOperationException("Accessor is not valid"); 1389 1390 var processorName = InputProcessor.s_Processors.FindNameForType(typeof(TProcessor)); 1391 if (processorName.IsEmpty()) 1392 throw new NotSupportedException($"Type '{typeof(TProcessor)}' has not been registered as a processor"); 1393 1394 return WithProcessor(processorName); 1395 } 1396 1397 /// <summary> 1398 /// Specify which action to trigger. 1399 /// </summary> 1400 /// <param name="action">Action to trigger.</param> 1401 /// <returns>The same binding syntax for further configuration.</returns> 1402 /// <exception cref="InvalidOperationException">The binding accessor is invalid.</exception> 1403 /// <exception cref="ArgumentNullException">Provided action is null.</exception> 1404 /// <exception cref="ArgumentException">Provided action is a singleton action (not part of any action maps).</exception> 1405 public BindingSyntax Triggering(InputAction action) 1406 { 1407 if (!valid) 1408 throw new InvalidOperationException("Accessor is not valid"); 1409 if (action == null) 1410 throw new ArgumentNullException(nameof(action)); 1411 if (action.isSingletonAction) 1412 throw new ArgumentException( 1413 $"Cannot change the action a binding triggers on singleton action '{action}'", nameof(action)); 1414 m_ActionMap.m_Bindings[m_BindingIndexInMap].action = action.name; 1415 m_ActionMap.OnBindingModified(); 1416 return this; 1417 } 1418 1419 /// <summary> 1420 /// Replace the current binding with the given one. 1421 /// </summary> 1422 /// <param name="binding">An input binding.</param> 1423 /// <returns>The same binding syntax for further configuration.</returns> 1424 /// <remarks> 1425 /// This method replaces the current binding wholesale, i.e. it will overwrite all fields. 1426 /// Be aware that this has the potential of corrupting the binding data in case the given 1427 /// binding is a composite. 1428 /// </remarks> 1429 public BindingSyntax To(InputBinding binding) 1430 { 1431 if (!valid) 1432 throw new InvalidOperationException("Accessor is not valid"); 1433 1434 m_ActionMap.m_Bindings[m_BindingIndexInMap] = binding; 1435 1436 // If it's a singleton action, we force the binding to stay with the action. 1437 if (m_ActionMap.m_SingletonAction != null) 1438 m_ActionMap.m_Bindings[m_BindingIndexInMap].action = m_ActionMap.m_SingletonAction.name; 1439 1440 m_ActionMap.OnBindingModified(); 1441 1442 return this; 1443 } 1444 1445 /// <summary> 1446 /// Switch to configuring the next binding. 1447 /// </summary> 1448 /// <returns>An instance configured to edit the next binding or an invalid (see <see cref="valid"/>) instance if 1449 /// there is no next binding.</returns> 1450 /// <remarks>If the BindingSyntax is restricted to a single action, the result will be invalid (see <see cref="valid"/>) 1451 /// if there is no next binding on the action. If the BindingSyntax is restricted to an <see cref="InputActionMap"/>, the result will 1452 /// be be invalid if there is no next binding in the map.</remarks> 1453 public BindingSyntax NextBinding() 1454 { 1455 return Iterate(true); 1456 } 1457 1458 /// <summary> 1459 /// Switch to configuring the previous binding. 1460 /// </summary> 1461 /// <returns>An instance configured to edit the previous binding or an invalid (see <see cref="valid"/>) instance if 1462 /// there is no previous binding.</returns> 1463 /// <remarks>If the BindingSyntax is restricted to a single action, the result will be invalid (see <see cref="valid"/>) 1464 /// if there is no previous binding on the action. If the BindingSyntax is restricted to an <see cref="InputActionMap"/>, the result will 1465 /// be be invalid if there is no previous binding in the map.</remarks> 1466 public BindingSyntax PreviousBinding() 1467 { 1468 return Iterate(false); 1469 } 1470 1471 /// <summary> 1472 /// Iterate to the next part binding of the current composite with the given part name. 1473 /// </summary> 1474 /// <param name="partName">Name of the part of the binding, such as <c>"Positive"</c>.</param> 1475 /// <returns>An accessor to the next part binding with the given name or an invalid (see <see cref="valid"/>) 1476 /// accessor if there is no such binding.</returns> 1477 /// <exception cref="ArgumentNullException"><paramref name="partName"/> is <c>null</c> or empty.</exception> 1478 /// <remarks> 1479 /// Each binding that is part of a composite is marked with <see cref="InputBinding.isPartOfComposite"/> 1480 /// set to true. The name of the part is determined by <see cref="InputBinding.name"/> (comparison is 1481 /// case-insensitive). Which parts are relevant to a specific composite is determined by the type of 1482 /// composite. An <see cref="Composites.AxisComposite"/>, for example, has <c>"Negative"</c> and a 1483 /// <c>"Positive"</c> part. 1484 /// 1485 /// <example> 1486 /// <code> 1487 /// // Delete first "Positive" part of "Axis" composite. 1488 /// action.ChangeCompositeBinding("Axis") 1489 /// .NextPartBinding("Positive").Erase(); 1490 /// </code> 1491 /// </example> 1492 /// </remarks> 1493 /// <seealso cref="InputBinding.isPartOfComposite"/> 1494 /// <seealso cref="InputBinding.isComposite"/> 1495 /// <seealso cref="InputBindingComposite"/> 1496 public BindingSyntax NextPartBinding(string partName) 1497 { 1498 if (string.IsNullOrEmpty(partName)) 1499 throw new ArgumentNullException(nameof(partName)); 1500 return IteratePartBinding(true, partName); 1501 } 1502 1503 /// <summary> 1504 /// Iterate to the previous part binding of the current composite with the given part name. 1505 /// </summary> 1506 /// <param name="partName">Name of the part of the binding, such as <c>"Positive"</c>.</param> 1507 /// <returns>An accessor to the previous part binding with the given name or an invalid (see <see cref="valid"/>) 1508 /// accessor if there is no such binding.</returns> 1509 /// <exception cref="ArgumentNullException"><paramref name="partName"/> is <c>null</c> or empty.</exception> 1510 /// <remarks> 1511 /// Each binding that is part of a composite is marked with <see cref="InputBinding.isPartOfComposite"/> 1512 /// set to true. The name of the part is determined by <see cref="InputBinding.name"/> (comparison is 1513 /// case-insensitive). Which parts are relevant to a specific composite is determined by the type of 1514 /// composite. An <see cref="Composites.AxisComposite"/>, for example, has <c>"Negative"</c> and a 1515 /// <c>"Positive"</c> part. 1516 /// </remarks> 1517 /// <seealso cref="InputBinding.isPartOfComposite"/> 1518 /// <seealso cref="InputBinding.isComposite"/> 1519 /// <seealso cref="InputBindingComposite"/> 1520 public BindingSyntax PreviousPartBinding(string partName) 1521 { 1522 if (string.IsNullOrEmpty(partName)) 1523 throw new ArgumentNullException(nameof(partName)); 1524 return IteratePartBinding(false, partName); 1525 } 1526 1527 /// <summary> 1528 /// Iterate to the next composite binding. 1529 /// </summary> 1530 /// <param name="compositeName">If <c>null</c> (default), an accessor to the next composite binding, 1531 /// regardless of name or type, is returned. If it is not <c>null</c>, can be either the name of 1532 /// the binding (see <see cref="InputBinding.name"/>) or the name of the composite used in the 1533 /// binding (see <see cref="InputSystem.RegisterBindingComposite"/></param>). 1534 /// <returns>A write accessor to the next composite binding or an invalid accessor (see 1535 /// <see cref="valid"/>) if no such binding was found.</returns> 1536 public BindingSyntax NextCompositeBinding(string compositeName = null) 1537 { 1538 return IterateCompositeBinding(true, compositeName); 1539 } 1540 1541 /// <summary> 1542 /// Iterate to the previous composite binding. 1543 /// </summary> 1544 /// <param name="compositeName">If <c>null</c> (default), an accessor to the previous composite binding, 1545 /// regardless of name or type, is returned. If it is not <c>null</c>, can be either the name of 1546 /// the binding (see <see cref="InputBinding.name"/>) or the name of the composite used in the 1547 /// binding (see <see cref="InputSystem.RegisterBindingComposite"/>).</param> 1548 /// <returns>A write accessor to the previous composite binding or an invalid accessor (see 1549 /// <see cref="valid"/>) if no such binding was found.</returns> 1550 public BindingSyntax PreviousCompositeBinding(string compositeName = null) 1551 { 1552 return IterateCompositeBinding(false, compositeName); 1553 } 1554 1555 private BindingSyntax Iterate(bool next) 1556 { 1557 if (m_ActionMap == null) 1558 return default; 1559 1560 var bindings = m_ActionMap.m_Bindings; 1561 if (bindings == null) 1562 return default; 1563 1564 // To find the next binding for a specific action, we may have to jump 1565 // over unrelated bindings in-between. 1566 var index = m_BindingIndexInMap; 1567 while (true) 1568 { 1569 index += next ? 1 : -1; 1570 if (index < 0 || index >= bindings.Length) 1571 return default; 1572 1573 if (m_Action == null || bindings[index].TriggersAction(m_Action)) 1574 break; 1575 } 1576 1577 return new BindingSyntax(m_ActionMap, index, m_Action); 1578 } 1579 1580 private BindingSyntax IterateCompositeBinding(bool next, string compositeName) 1581 { 1582 for (var accessor = Iterate(next); accessor.valid; accessor = accessor.Iterate(next)) 1583 { 1584 if (!accessor.binding.isComposite) 1585 continue; 1586 1587 if (compositeName == null) 1588 return accessor; 1589 1590 // Try name of binding. 1591 if (compositeName.Equals(accessor.binding.name, StringComparison.InvariantCultureIgnoreCase)) 1592 return accessor; 1593 1594 // Try composite type name. 1595 var name = NameAndParameters.ParseName(accessor.binding.path); 1596 if (compositeName.Equals(name, StringComparison.InvariantCultureIgnoreCase)) 1597 return accessor; 1598 } 1599 1600 return default; 1601 } 1602 1603 private BindingSyntax IteratePartBinding(bool next, string partName) 1604 { 1605 if (!valid) 1606 return default; 1607 1608 if (binding.isComposite) 1609 { 1610 // If we're at the composite, only proceed if we're iterating down 1611 // instead of up. 1612 if (!next) 1613 return default; 1614 } 1615 else if (!binding.isPartOfComposite) 1616 return default; 1617 1618 for (var accessor = Iterate(next); accessor.valid; accessor = accessor.Iterate(next)) 1619 { 1620 if (!accessor.binding.isPartOfComposite) 1621 return default; 1622 1623 if (partName.Equals(accessor.binding.name, StringComparison.InvariantCultureIgnoreCase)) 1624 return accessor; 1625 } 1626 1627 return default; 1628 } 1629 1630 ////TODO: allow setting overrides through this accessor 1631 1632 /// <summary> 1633 /// Remove the binding. 1634 /// </summary> 1635 /// <remarks> 1636 /// If the binding is a composite (see <see cref="InputBinding.isComposite"/>), part bindings of the 1637 /// composite will be removed as well. 1638 /// 1639 /// Note that the accessor will not necessarily be invalidated. Instead, it will point to what used 1640 /// to be the next binding in the array (though that means the accessor will be invalid if the binding 1641 /// that got erased was the last one in the array). 1642 /// </remarks> 1643 /// <exception cref="InvalidOperationException">The instance is not <see cref="valid"/>.</exception> 1644 public void Erase() 1645 { 1646 if (!valid) 1647 throw new InvalidOperationException("Instance not valid"); 1648 1649 var isComposite = m_ActionMap.m_Bindings[m_BindingIndexInMap].isComposite; 1650 ArrayHelpers.EraseAt(ref m_ActionMap.m_Bindings, m_BindingIndexInMap); 1651 1652 // If it's a composite, also erase part bindings. 1653 if (isComposite) 1654 { 1655 while (m_BindingIndexInMap < m_ActionMap.m_Bindings.LengthSafe() && m_ActionMap.m_Bindings[m_BindingIndexInMap].isPartOfComposite) 1656 { 1657 ArrayHelpers.EraseAt(ref m_ActionMap.m_Bindings, m_BindingIndexInMap); 1658 } 1659 } 1660 1661 m_Action.m_BindingsCount = m_ActionMap.m_Bindings.LengthSafe(); 1662 m_ActionMap.OnBindingModified(); 1663 1664 // We have switched to a different binding array. For singleton actions, we need to 1665 // sync up the reference that the action itself has. 1666 if (m_ActionMap.m_SingletonAction != null) 1667 m_ActionMap.m_SingletonAction.m_SingletonActionBindings = m_ActionMap.m_Bindings; 1668 } 1669 1670 /// <summary> 1671 /// Insert a composite part into a composite binding. 1672 /// </summary> 1673 /// <param name="partName">Name of the part in composite binding.</param> 1674 /// <param name="path">Control path to bind to.</param> 1675 /// <returns>The same binding syntax for further configuration.</returns> 1676 /// <exception cref="ArgumentNullException">Part name is null or empty.</exception> 1677 /// <exception cref="InvalidOperationException">The binding accessor is invalid or the binding accessor is not pointing to composite or part binding.</exception> 1678 public BindingSyntax InsertPartBinding(string partName, string path) 1679 { 1680 if (string.IsNullOrEmpty(partName)) 1681 throw new ArgumentNullException(nameof(partName)); 1682 if (!valid) 1683 throw new InvalidOperationException("Binding accessor is not valid"); 1684 var binding = this.binding; 1685 if (!binding.isPartOfComposite && !binding.isComposite) 1686 throw new InvalidOperationException("Binding accessor must point to composite or part binding"); 1687 1688 AddBindingInternal(m_ActionMap, 1689 new InputBinding { path = path, isPartOfComposite = true, name = partName, action = m_Action?.name }, 1690 m_BindingIndexInMap + 1); 1691 1692 return new BindingSyntax(m_ActionMap, m_BindingIndexInMap + 1, m_Action); 1693 } 1694 } 1695 1696 ////TODO: remove this and merge it into BindingSyntax 1697 /// <summary> 1698 /// Write accessor to a composite binding. 1699 /// </summary> 1700 /// <remarks> 1701 /// To add a composite binding to an action, you must first call <see cref="InputActionSetupExtensions.AddCompositeBinding"/> 1702 /// and then use the CompositeSyntax struct to add composite parts. 1703 /// <example> 1704 /// <code> 1705 /// playerInput.actions["fire"] 1706 /// .ChangeBinding(0) 1707 /// .AddCompositeBinding("2DVector") 1708 /// .With("Up", "&lt;Keyboard&gt;/w") 1709 /// .With("Down", "&lt;Keyboard&gt;/s") 1710 /// .With("Left", "&lt;Keyboard&gt;/a") 1711 /// .With("Right", "&lt;Keyboard&gt;/d"); 1712 /// </code> 1713 /// </example> 1714 /// </remarks> 1715 /// <seealso cref="AddBinding(InputAction,InputBinding)"/> 1716 /// <seealso cref="ChangeBinding(InputAction,int)"/> 1717 public struct CompositeSyntax 1718 { 1719 private readonly InputAction m_Action; 1720 private readonly InputActionMap m_ActionMap; 1721 private int m_BindingIndexInMap; 1722 1723 /// <summary> 1724 /// Index of the binding that the accessor refers to. 1725 /// </summary> 1726 /// <remarks> 1727 /// When accessing bindings on an <see cref="InputAction"/>, this is the index in 1728 /// <see cref="InputAction.bindings"/> of the action. When accessing bindings on an 1729 /// <see cref="InputActionMap"/>, it is the index <see cref="InputActionMap.bindings"/> 1730 /// of the map. 1731 /// </remarks> 1732 public int bindingIndex 1733 { 1734 get 1735 { 1736 if (m_ActionMap == null) 1737 return -1; 1738 if (m_Action != null) 1739 return m_Action.BindingIndexOnMapToBindingIndexOnAction(m_BindingIndexInMap); 1740 return m_BindingIndexInMap; 1741 } 1742 } 1743 1744 internal CompositeSyntax(InputActionMap map, InputAction action, int compositeIndex) 1745 { 1746 m_Action = action; 1747 m_ActionMap = map; 1748 m_BindingIndexInMap = compositeIndex; 1749 } 1750 1751 /// <summary> 1752 /// Add a part binding to the composite. 1753 /// </summary> 1754 /// <param name="name">Name of the part. This is dependent on the type of composite. For 1755 /// <see cref="Composites.Vector2Composite"/>, for example, the valid parts are <c>"Up"</c>, <c>"Down"</c>, 1756 /// <c>"Left"</c>, and <c>"Right"</c>.</param> 1757 /// <param name="binding">Control path to binding to. See <see cref="InputBinding.path"/>.</param> 1758 /// <param name="groups">Binding groups to assign to the part binding. See <see cref="InputBinding.groups"/>.</param> 1759 /// <param name="processors">Optional list of processors to apply to the binding. See <see cref="InputBinding.processors"/>.</param> 1760 /// <returns>The same composite syntax for further configuration.</returns> 1761 public CompositeSyntax With(string name, string binding, string groups = null, string processors = null) 1762 { 1763 ////TODO: check whether non-composite bindings have been added in-between 1764 1765 using (InputActionRebindingExtensions.DeferBindingResolution()) 1766 { 1767 int bindingIndex; 1768 if (m_Action != null) 1769 bindingIndex = m_Action.AddBinding(path: binding, groups: groups, processors: processors) 1770 .m_BindingIndexInMap; 1771 else 1772 bindingIndex = m_ActionMap.AddBinding(path: binding, groups: groups, processors: processors) 1773 .m_BindingIndexInMap; 1774 1775 m_ActionMap.m_Bindings[bindingIndex].name = name; 1776 m_ActionMap.m_Bindings[bindingIndex].isPartOfComposite = true; 1777 } 1778 1779 return this; 1780 } 1781 } 1782 1783 /// <summary> 1784 /// Write accessor to a control scheme. 1785 /// </summary> 1786 public struct ControlSchemeSyntax 1787 { 1788 // REVIEW: Consider removing this for major revision, e.g. v2 or let functionality used by it be internal to reduce API surface. 1789 1790 private readonly InputActionAsset m_Asset; 1791 private readonly int m_ControlSchemeIndex; 1792 private InputControlScheme m_ControlScheme; 1793 1794 internal ControlSchemeSyntax(InputActionAsset asset, int index) 1795 { 1796 m_Asset = asset; 1797 m_ControlSchemeIndex = index; 1798 m_ControlScheme = new InputControlScheme(); 1799 } 1800 1801 internal ControlSchemeSyntax(InputControlScheme controlScheme) 1802 { 1803 m_Asset = null; 1804 m_ControlSchemeIndex = -1; 1805 m_ControlScheme = controlScheme; 1806 } 1807 1808 /// <summary> 1809 /// Sets or overwrite the binding group for control scheme. 1810 /// </summary> 1811 /// <param name="bindingGroup">A binding group. See <see cref="InputBinding.groups"/>.</param> 1812 /// <returns>The same control scheme syntax for further configuration.</returns> 1813 /// <exception cref="ArgumentNullException">If provided name is null or empty.</exception> 1814 public ControlSchemeSyntax WithBindingGroup(string bindingGroup) 1815 { 1816 if (string.IsNullOrEmpty(bindingGroup)) 1817 throw new ArgumentNullException(nameof(bindingGroup)); 1818 1819 if (m_Asset == null) 1820 m_ControlScheme.m_BindingGroup = bindingGroup; 1821 else 1822 m_Asset.m_ControlSchemes[m_ControlSchemeIndex].bindingGroup = bindingGroup; 1823 1824 return this; 1825 } 1826 1827 /// <summary> 1828 /// Adds a required device to control scheme. 1829 /// </summary> 1830 /// <typeparam name="TDevice">Type of device.</typeparam> 1831 /// <returns>The same control scheme syntax for further configuration.</returns> 1832 public ControlSchemeSyntax WithRequiredDevice<TDevice>() 1833 where TDevice : InputDevice 1834 { 1835 return WithRequiredDevice(DeviceTypeToControlPath<TDevice>()); 1836 } 1837 1838 /// <summary> 1839 /// Adds an optional device to control scheme. 1840 /// </summary> 1841 /// <typeparam name="TDevice">Type of device.</typeparam> 1842 /// <returns>The same control scheme syntax for further configuration.</returns> 1843 /// <see cref="InputControlScheme.DeviceRequirement.isOptional"/> 1844 public ControlSchemeSyntax WithOptionalDevice<TDevice>() 1845 where TDevice : InputDevice 1846 { 1847 return WithOptionalDevice(DeviceTypeToControlPath<TDevice>()); 1848 } 1849 1850 /// <summary> 1851 /// Combines another required device with the previous device using boolean OR 1852 /// logic such that either the previous device or this device are required to be present. 1853 /// </summary> 1854 /// <typeparam name="TDevice">Type of device.</typeparam> 1855 /// <returns>The same control scheme syntax for further configuration.</returns> 1856 /// <remarks> 1857 /// <example> 1858 /// <code> 1859 /// // Create an .inputactions asset. 1860 /// var asset = ScriptableObject.CreateInstance&lt;InputActionAsset&gt;(); 1861 /// 1862 /// asset.AddControlScheme("KeyboardAndMouseOrPen") 1863 /// .WithRequiredDevice("&lt;Keyboard&gt;") 1864 /// .WithRequiredDevice("&lt;Mouse&gt;") 1865 /// .OrWithRequiredDevice("&lt;Pen&gt;") 1866 /// </code> 1867 /// </example> 1868 /// </remarks> 1869 /// <see cref="InputControlScheme.DeviceRequirement.isOR"/> 1870 public ControlSchemeSyntax OrWithRequiredDevice<TDevice>() 1871 where TDevice : InputDevice 1872 { 1873 return OrWithRequiredDevice(DeviceTypeToControlPath<TDevice>()); 1874 } 1875 1876 /// <summary> 1877 /// Combines another optional device with the previous device using boolean OR 1878 /// logic such that either the previous device or this device are required to be present. 1879 /// If this is the last device in a chain of OR'd devices, the entire chain of 1880 /// devices becomes optional. 1881 /// </summary> 1882 /// <typeparam name="TDevice">Type of device.</typeparam> 1883 /// <returns>The same control scheme syntax for further configuration.</returns> 1884 /// <see cref="InputControlScheme.DeviceRequirement.isOR"/> 1885 public ControlSchemeSyntax OrWithOptionalDevice<TDevice>() 1886 where TDevice : InputDevice 1887 { 1888 return OrWithOptionalDevice(DeviceTypeToControlPath<TDevice>()); 1889 } 1890 1891 /// <summary> 1892 /// Adds a required device to control scheme. 1893 /// </summary> 1894 /// <param name="controlPath">Device path, like &lt;Gamepad&gt;.</param> 1895 /// <returns>The same control scheme syntax for further configuration.</returns> 1896 public ControlSchemeSyntax WithRequiredDevice(string controlPath) 1897 { 1898 AddDeviceEntry(controlPath, InputControlScheme.DeviceRequirement.Flags.None); 1899 return this; 1900 } 1901 1902 /// <summary> 1903 /// Add an optional device to the control scheme. 1904 /// </summary> 1905 /// <param name="controlPath">The device path, like &lt;Gamepad&gt;.</param> 1906 /// <returns>The same control scheme syntax for further configuration.</returns> 1907 public ControlSchemeSyntax WithOptionalDevice(string controlPath) 1908 { 1909 AddDeviceEntry(controlPath, InputControlScheme.DeviceRequirement.Flags.Optional); 1910 return this; 1911 } 1912 1913 /// <summary> 1914 /// Adds another possible required device to the control scheme. 1915 /// </summary> 1916 /// <param name="controlPath">Device path, like &lt;Gamepad&gt;.</param> 1917 /// <returns>The same control scheme syntax for further configuration.</returns> 1918 public ControlSchemeSyntax OrWithRequiredDevice(string controlPath) 1919 { 1920 AddDeviceEntry(controlPath, InputControlScheme.DeviceRequirement.Flags.Or); 1921 return this; 1922 } 1923 1924 /// <summary> 1925 /// Adds another possible optional device to control scheme. 1926 /// </summary> 1927 /// <param name="controlPath">Device path, like &lt;Gamepad&gt;.</param> 1928 /// <returns>The same control scheme syntax for further configuration.</returns> 1929 public ControlSchemeSyntax OrWithOptionalDevice(string controlPath) 1930 { 1931 AddDeviceEntry(controlPath, 1932 InputControlScheme.DeviceRequirement.Flags.Optional | 1933 InputControlScheme.DeviceRequirement.Flags.Or); 1934 return this; 1935 } 1936 1937 private string DeviceTypeToControlPath<TDevice>() 1938 where TDevice : InputDevice 1939 { 1940 var layoutName = InputControlLayout.s_Layouts.TryFindLayoutForType(typeof(TDevice)).ToString(); 1941 if (string.IsNullOrEmpty(layoutName)) 1942 layoutName = typeof(TDevice).Name; 1943 return $"<{layoutName}>"; 1944 } 1945 1946 /// <summary> 1947 /// Call this method when done building out the control scheme to return the associated instance. 1948 /// </summary> 1949 /// <returns>The associated control scheme</returns> 1950 public InputControlScheme Done() 1951 { 1952 if (m_Asset != null) 1953 return m_Asset.m_ControlSchemes[m_ControlSchemeIndex]; 1954 return m_ControlScheme; 1955 } 1956 1957 private void AddDeviceEntry(string controlPath, InputControlScheme.DeviceRequirement.Flags flags) 1958 { 1959 if (string.IsNullOrEmpty(controlPath)) 1960 throw new ArgumentNullException(nameof(controlPath)); 1961 1962 var scheme = m_Asset != null ? m_Asset.m_ControlSchemes[m_ControlSchemeIndex] : m_ControlScheme; 1963 ArrayHelpers.Append(ref scheme.m_DeviceRequirements, 1964 new InputControlScheme.DeviceRequirement 1965 { 1966 m_ControlPath = controlPath, 1967 m_Flags = flags, 1968 }); 1969 1970 if (m_Asset == null) 1971 m_ControlScheme = scheme; 1972 else 1973 m_Asset.m_ControlSchemes[m_ControlSchemeIndex] = scheme; 1974 } 1975 } 1976 } 1977}