暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

InputActionSetupExtensions.cs 95KB

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