InputActionAsset.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine.InputSystem.Utilities;
  5. ////TODO: control schemes, like actions and maps, should have stable IDs so that they can be renamed
  6. ////REVIEW: have some way of expressing 'contracts' on action maps? I.e. something like
  7. //// "I expect a 'look' and a 'move' action in here"
  8. ////REVIEW: rename this from "InputActionAsset" to something else that emphasizes the asset aspect less
  9. //// and instead emphasizes the map collection aspect more?
  10. namespace UnityEngine.InputSystem
  11. {
  12. /// <summary>
  13. /// An asset containing action maps and control schemes.
  14. /// </summary>
  15. /// <remarks>
  16. /// InputActionAssets can be created in code but are usually stored in JSON format on
  17. /// disk with the ".inputactions" extension and are imported by Unity using a custom
  18. /// importer.
  19. ///
  20. /// To create an InputActionAsset in code, use the <c>Singleton</c> API and populate the
  21. /// asset with the methods found in <see cref="InputActionSetupExtensions"/>. Alternatively,
  22. /// you can load an InputActionAsset directly from a string in JSON format using <see cref="FromJson"/>.
  23. ///
  24. /// <example>
  25. /// <code>
  26. /// // Create and configure an asset in code.
  27. /// var asset1 = ScriptableObject.CreateInstance&lt;InputActionAsset&gt;();
  28. /// var actionMap1 = asset1.CreateActionMap("map1");
  29. /// action1Map.AddAction("action1", binding: "&lt;Keyboard&gt;/space");
  30. /// </code>
  31. /// </example>
  32. ///
  33. /// Each asset can contain arbitrary many action maps that can be enabled and disabled individually
  34. /// (see <see cref="InputActionMap.Enable"/> and <see cref="InputActionMap.Disable"/>) or in bulk
  35. /// (see <see cref="Enable"/> and <see cref="Disable"/>). The name of each action map must be unique.
  36. /// The list of action maps can be queried from <see cref="actionMaps"/>.
  37. ///
  38. /// InputActionAssets can only define <see cref="InputControlScheme"/>s. They can be added to
  39. /// an asset with <see cref="InputActionSetupExtensions.AddControlScheme(InputActionAsset,string)"/>
  40. /// and can be queried from <see cref="controlSchemes"/>.
  41. ///
  42. /// Be aware that input action assets do not separate between static (configuration) data and dynamic
  43. /// (instance) data. For audio, for example, <c>AudioClip</c> represents the static,
  44. /// shared data portion of audio playback whereas <c>AudioSource"</c> represents the
  45. /// dynamic, per-instance audio playback portion (referencing the clip through <c>AudioSource.clip</c>).
  46. ///
  47. /// For input, such a split is less beneficial as the same input is generally not exercised
  48. /// multiple times in parallel. Keeping both static and dynamic data together simplifies
  49. /// using the system.
  50. ///
  51. /// However, there are scenarios where you indeed want to take the same input action and
  52. /// exercise it multiple times in parallel. A prominent example of such a use case is
  53. /// local multiplayer where each player gets the same set of actions but is controlling
  54. /// them with a different device (or devices) each. This is easily achieved by simply
  55. /// using <c>UnityEngine.Object.Instantiate</c> to instantiate the input action
  56. /// asset multiple times. <see cref="PlayerInput"/> will automatically do so in its
  57. /// internals.
  58. ///
  59. /// Note also that all action maps in an asset share binding state. This means that if
  60. /// one map in an asset has to resolve its bindings, all maps in the asset have to.
  61. /// </remarks>
  62. public class InputActionAsset : ScriptableObject, IInputActionCollection
  63. {
  64. /// <summary>
  65. /// File extension (without the dot) for InputActionAssets in JSON format.
  66. /// </summary>
  67. /// <value>File extension for InputActionAsset source files.</value>
  68. /// <remarks>
  69. /// Files with this extension will automatically be imported by Unity as
  70. /// InputActionAssets.
  71. /// </remarks>
  72. public const string Extension = "inputactions";
  73. /// <summary>
  74. /// True if any action in the asset is currently enabled.
  75. /// </summary>
  76. /// <seealso cref="InputAction.enabled"/>
  77. /// <seealso cref="InputActionMap.enabled"/>
  78. /// <seealso cref="InputAction.Enable"/>
  79. /// <seealso cref="InputActionMap.Enable"/>
  80. /// <seealso cref="Enable"/>
  81. public bool enabled
  82. {
  83. get
  84. {
  85. foreach (var actionMap in actionMaps)
  86. if (actionMap.enabled)
  87. return true;
  88. return false;
  89. }
  90. }
  91. /// <summary>
  92. /// List of action maps defined in the asset.
  93. /// </summary>
  94. /// <value>Action maps contained in the asset.</value>
  95. /// <seealso cref="InputActionSetupExtensions.AddActionMap(InputActionAsset,string)"/>
  96. /// <seealso cref="InputActionSetupExtensions.RemoveActionMap(InputActionAsset,InputActionMap)"/>
  97. /// <seealso cref="FindActionMap(string,bool)"/>
  98. public ReadOnlyArray<InputActionMap> actionMaps => new ReadOnlyArray<InputActionMap>(m_ActionMaps);
  99. /// <summary>
  100. /// List of control schemes defined in the asset.
  101. /// </summary>
  102. /// <value>Control schemes defined for the asset.</value>
  103. /// <seealso cref="InputActionSetupExtensions.AddControlScheme(InputActionAsset,string)"/>
  104. /// <seealso cref="InputActionSetupExtensions.RemoveControlScheme"/>
  105. public ReadOnlyArray<InputControlScheme> controlSchemes => new ReadOnlyArray<InputControlScheme>(m_ControlSchemes);
  106. /// <summary>
  107. /// Binding mask to apply to all action maps and actions in the asset.
  108. /// </summary>
  109. /// <value>Optional mask that determines which bindings in the asset to enable.</value>
  110. /// <remarks>
  111. /// Binding masks can be applied at three different levels: for an entire asset through
  112. /// this property, for a specific map through <see cref="InputActionMap.bindingMask"/>,
  113. /// and for single actions through <see cref="InputAction.bindingMask"/>. By default,
  114. /// none of the masks will be set (i.e. they will be <c>null</c>).
  115. ///
  116. /// When an action is enabled, all the binding masks that apply to it are taken into
  117. /// account. Specifically, this means that any given binding on the action will be
  118. /// enabled only if it matches the mask applied to the asset, the mask applied
  119. /// to the map that contains the action, and the mask applied to the action itself.
  120. /// All the masks are individually optional.
  121. ///
  122. /// Masks are matched against bindings using <see cref="InputBinding.Matches"/>.
  123. ///
  124. /// Note that if you modify the masks applicable to an action while it is
  125. /// enabled, the action's <see cref="InputAction.controls"/> will get updated immediately to
  126. /// respect the mask. To avoid repeated binding resolution, it is most efficient
  127. /// to apply binding masks before enabling actions.
  128. ///
  129. /// Binding masks are non-destructive. All the bindings on the action are left
  130. /// in place. Setting a mask will not affect the value of the <see cref="InputAction.bindings"/>
  131. /// and <see cref="InputActionMap.bindings"/> properties.
  132. /// </remarks>
  133. /// <seealso cref="InputBinding.MaskByGroup"/>
  134. /// <seealso cref="InputAction.bindingMask"/>
  135. /// <seealso cref="InputActionMap.bindingMask"/>
  136. public InputBinding? bindingMask
  137. {
  138. get => m_BindingMask;
  139. set
  140. {
  141. if (m_BindingMask == value)
  142. return;
  143. m_BindingMask = value;
  144. ReResolveIfNecessary();
  145. }
  146. }
  147. /// <summary>
  148. /// Set of devices that bindings in the asset can bind to.
  149. /// </summary>
  150. /// <value>Optional set of devices to use by bindings in the asset.</value>
  151. /// <remarks>
  152. /// By default (with this property being <c>null</c>), bindings will bind to any of the
  153. /// controls available through <see cref="InputSystem.devices"/>, i.e. controls from all
  154. /// devices in the system will be used.
  155. ///
  156. /// By setting this property, binding resolution can instead be restricted to just specific
  157. /// devices. This restriction can either be applied to an entire asset using this property
  158. /// or to specific action maps by using <see cref="InputActionMap.devices"/>. Note that if
  159. /// both this property and <see cref="InputActionMap.devices"/> is set for a specific action
  160. /// map, the list of devices on the action map will take precedence and the list on the
  161. /// asset will be ignored for bindings in that action map.
  162. ///
  163. /// <example>
  164. /// <code>
  165. /// // Create an asset with a single action map and a single action with a
  166. /// // gamepad binding.
  167. /// var asset = ScriptableObject.CreateInstance&lt;InputActionAsset&gt;();
  168. /// var actionMap = new InputActionMap();
  169. /// var fireAction = actionMap.AddAction("Fire", binding: "&lt;Gamepad&gt;/buttonSouth");
  170. /// asset.AddActionMap(actionMap);
  171. ///
  172. /// // Let's assume we have two gamepads connected. If we enable the
  173. /// // action map now, the 'Fire' action will bind to both.
  174. /// actionMap.Enable();
  175. ///
  176. /// // This will print two controls.
  177. /// Debug.Log(string.Join("\n", fireAction.controls));
  178. ///
  179. /// // To restrict the setup to just the first gamepad, we can assign
  180. /// // to the 'devices' property (in this case, we could do so on either
  181. /// // the action map or on the asset; we choose the latter here).
  182. /// asset.devices = new InputDevice[] { Gamepad.all[0] };
  183. ///
  184. /// // Now this will print only one control.
  185. /// Debug.Log(string.Join("\n", fireAction.controls));
  186. /// </code>
  187. /// </example>
  188. /// </remarks>
  189. /// <seealso cref="InputActionMap.devices"/>
  190. public ReadOnlyArray<InputDevice>? devices
  191. {
  192. get
  193. {
  194. if (m_DevicesCount < 0)
  195. return null;
  196. return new ReadOnlyArray<InputDevice>(m_DevicesArray, 0, m_DevicesCount);
  197. }
  198. set
  199. {
  200. if (value == null)
  201. {
  202. if (m_DevicesCount < 0)
  203. return; // No change.
  204. if (m_DevicesArray != null & m_DevicesCount > 0)
  205. Array.Clear(m_DevicesArray, 0, m_DevicesCount);
  206. m_DevicesCount = -1;
  207. }
  208. else
  209. {
  210. // See if the array actually changes content. Avoids re-resolving when there
  211. // is no need to.
  212. if (m_DevicesCount == value.Value.Count)
  213. {
  214. var noChange = true;
  215. for (var i = 0; i < m_DevicesCount; ++i)
  216. {
  217. if (!ReferenceEquals(m_DevicesArray[i], value.Value[i]))
  218. {
  219. noChange = false;
  220. break;
  221. }
  222. }
  223. if (noChange)
  224. return;
  225. }
  226. if (m_DevicesCount > 0)
  227. m_DevicesArray.Clear(ref m_DevicesCount);
  228. m_DevicesCount = 0;
  229. ArrayHelpers.AppendListWithCapacity(ref m_DevicesArray, ref m_DevicesCount, value.Value);
  230. }
  231. ReResolveIfNecessary();
  232. }
  233. }
  234. /// <summary>
  235. /// Look up an action by name or ID.
  236. /// </summary>
  237. /// <param name="actionNameOrId">Name of the action as either a "map/action" combination (e.g. "gameplay/fire") or
  238. /// a simple name. In the former case, the name is split at the '/' slash and the first part is used to find
  239. /// a map with that name and the second part is used to find an action with that name inside the map. In the
  240. /// latter case, all maps are searched in order and the first action that has the given name in any of the maps
  241. /// is returned. Note that name comparisons are case-insensitive.
  242. ///
  243. /// Alternatively, the given string can be a GUID as given by <see cref="InputAction.id"/>.</param>
  244. /// <returns>The action with the corresponding name or null if no matching action could be found.</returns>
  245. /// <remarks>
  246. /// This method is equivalent to <see cref="FindAction(string)"/> except that it throws
  247. /// <see cref="KeyNotFoundException"/> if no action with the given name or ID
  248. /// could be found.
  249. /// </remarks>
  250. /// <exception cref="KeyNotFoundException">No action was found matching <paramref name="actionNameOrId"/>.</exception>
  251. /// <exception cref="ArgumentNullException"><paramref name="actionNameOrId"/> is <c>null</c> or empty.</exception>
  252. /// <seealso cref="FindAction(string)"/>
  253. public InputAction this[string actionNameOrId]
  254. {
  255. get
  256. {
  257. var action = FindAction(actionNameOrId);
  258. if (action == null)
  259. throw new KeyNotFoundException($"Cannot find action '{actionNameOrId}' in '{this}'");
  260. return action;
  261. }
  262. }
  263. /// <summary>
  264. /// Return a JSON representation of the asset.
  265. /// </summary>
  266. /// <returns>A string in JSON format that represents the static/configuration data present
  267. /// in the asset.</returns>
  268. /// <remarks>
  269. /// This will not save dynamic execution state such as callbacks installed on
  270. /// <see cref="InputAction">actions</see> or enabled/disabled states of individual
  271. /// maps and actions.
  272. ///
  273. /// Use <see cref="LoadFromJson"/> to deserialize the JSON data back into an InputActionAsset.
  274. ///
  275. /// Be aware that the format used by this method is <em>different</em> than what you
  276. /// get if you call <c>JsonUtility.ToJson</c> on an InputActionAsset instance. In other
  277. /// words, the JSON format is not identical to the Unity serialized object representation
  278. /// of the asset.
  279. /// </remarks>
  280. /// <seealso cref="FromJson"/>
  281. public string ToJson()
  282. {
  283. var fileJson = new WriteFileJson
  284. {
  285. name = name,
  286. maps = InputActionMap.WriteFileJson.FromMaps(m_ActionMaps).maps,
  287. controlSchemes = InputControlScheme.SchemeJson.ToJson(m_ControlSchemes),
  288. };
  289. return JsonUtility.ToJson(fileJson, true);
  290. }
  291. /// <summary>
  292. /// Replace the contents of the asset with the data in the given JSON string.
  293. /// </summary>
  294. /// <param name="json">JSON contents of an <c>.inputactions</c> asset.</param>
  295. /// <remarks>
  296. /// <c>.inputactions</c> assets are stored in JSON format. This method allows reading
  297. /// the JSON source text of such an asset into an existing <c>InputActionMap</c> instance.
  298. ///
  299. /// <example>
  300. /// <code>
  301. /// var asset = ScriptableObject.CreateInstance&lt;InputActionAsset&gt;();
  302. /// asset.LoadFromJson(@"
  303. /// {
  304. /// ""maps"" : [
  305. /// {
  306. /// ""name"" : ""gameplay"",
  307. /// ""actions"" : [
  308. /// { ""name"" : ""fire"", ""type"" : ""button"" },
  309. /// { ""name"" : ""look"", ""type"" : ""value"" },
  310. /// { ""name"" : ""move"", ""type"" : ""value"" }
  311. /// ],
  312. /// ""bindings"" : [
  313. /// { ""path"" : ""&lt;Gamepad&gt;/buttonSouth"", ""action"" : ""fire"", ""groups"" : ""Gamepad"" },
  314. /// { ""path"" : ""&lt;Gamepad&gt;/leftTrigger"", ""action"" : ""fire"", ""groups"" : ""Gamepad"" },
  315. /// { ""path"" : ""&lt;Gamepad&gt;/leftStick"", ""action"" : ""move"", ""groups"" : ""Gamepad"" },
  316. /// { ""path"" : ""&lt;Gamepad&gt;/rightStick"", ""action"" : ""look"", ""groups"" : ""Gamepad"" },
  317. /// { ""path"" : ""dpad"", ""action"" : ""move"", ""groups"" : ""Gamepad"", ""isComposite"" : true },
  318. /// { ""path"" : ""&lt;Keyboard&gt;/a"", ""name"" : ""left"", ""action"" : ""move"", ""groups"" : ""Keyboard&amp;Mouse"", ""isPartOfComposite"" : true },
  319. /// { ""path"" : ""&lt;Keyboard&gt;/d"", ""name"" : ""right"", ""action"" : ""move"", ""groups"" : ""Keyboard&amp;Mouse"", ""isPartOfComposite"" : true },
  320. /// { ""path"" : ""&lt;Keyboard&gt;/w"", ""name"" : ""up"", ""action"" : ""move"", ""groups"" : ""Keyboard&amp;Mouse"", ""isPartOfComposite"" : true },
  321. /// { ""path"" : ""&lt;Keyboard&gt;/s"", ""name"" : ""down"", ""action"" : ""move"", ""groups"" : ""Keyboard&amp;Mouse"", ""isPartOfComposite"" : true },
  322. /// { ""path"" : ""&lt;Mouse&gt;/delta"", ""action"" : ""look"", ""groups"" : ""Keyboard&amp;Mouse"" },
  323. /// { ""path"" : ""&lt;Mouse&gt;/leftButton"", ""action"" : ""fire"", ""groups"" : ""Keyboard&amp;Mouse"" }
  324. /// ]
  325. /// },
  326. /// {
  327. /// ""name"" : ""ui"",
  328. /// ""actions"" : [
  329. /// { ""name"" : ""navigate"" }
  330. /// ],
  331. /// ""bindings"" : [
  332. /// { ""path"" : ""&lt;Gamepad&gt;/dpad"", ""action"" : ""navigate"", ""groups"" : ""Gamepad"" }
  333. /// ]
  334. /// }
  335. /// ],
  336. /// ""controlSchemes"" : [
  337. /// {
  338. /// ""name"" : ""Gamepad"",
  339. /// ""bindingGroup"" : ""Gamepad"",
  340. /// ""devices"" : [
  341. /// { ""devicePath"" : ""&lt;Gamepad&gt;"" }
  342. /// ]
  343. /// },
  344. /// {
  345. /// ""name"" : ""Keyboard&amp;Mouse"",
  346. /// ""bindingGroup"" : ""Keyboard&amp;Mouse"",
  347. /// ""devices"" : [
  348. /// { ""devicePath"" : ""&lt;Keyboard&gt;"" },
  349. /// { ""devicePath"" : ""&lt;Mouse&gt;"" }
  350. /// ]
  351. /// }
  352. /// ]
  353. /// }");
  354. /// </code>
  355. /// </example>
  356. /// </remarks>
  357. /// <exception cref="ArgumentNullException"><paramref name="json"/> is <c>null</c> or empty.</exception>
  358. /// <seealso cref="FromJson"/>
  359. /// <seealso cref="ToJson"/>
  360. public void LoadFromJson(string json)
  361. {
  362. if (string.IsNullOrEmpty(json))
  363. throw new ArgumentNullException(nameof(json));
  364. var parsedJson = JsonUtility.FromJson<ReadFileJson>(json);
  365. parsedJson.ToAsset(this);
  366. }
  367. /// <summary>
  368. /// Replace the contents of the asset with the data in the given JSON string.
  369. /// </summary>
  370. /// <param name="json">JSON contents of an <c>.inputactions</c> asset.</param>
  371. /// <returns>The InputActionAsset instance created from the given JSON string.</returns>
  372. /// <remarks>
  373. /// <c>.inputactions</c> assets are stored in JSON format. This method allows turning
  374. /// the JSON source text of such an asset into a new <c>InputActionMap</c> instance.
  375. ///
  376. /// Be aware that the format used by this method is <em>different</em> than what you
  377. /// get if you call <c>JsonUtility.ToJson</c> on an InputActionAsset instance. In other
  378. /// words, the JSON format is not identical to the Unity serialized object representation
  379. /// of the asset.
  380. ///
  381. /// <example>
  382. /// <code>
  383. /// var asset = InputActionAsset.FromJson(@"
  384. /// {
  385. /// ""maps"" : [
  386. /// {
  387. /// ""name"" : ""gameplay"",
  388. /// ""actions"" : [
  389. /// { ""name"" : ""fire"", ""type"" : ""button"" },
  390. /// { ""name"" : ""look"", ""type"" : ""value"" },
  391. /// { ""name"" : ""move"", ""type"" : ""value"" }
  392. /// ],
  393. /// ""bindings"" : [
  394. /// { ""path"" : ""&lt;Gamepad&gt;/buttonSouth"", ""action"" : ""fire"", ""groups"" : ""Gamepad"" },
  395. /// { ""path"" : ""&lt;Gamepad&gt;/leftTrigger"", ""action"" : ""fire"", ""groups"" : ""Gamepad"" },
  396. /// { ""path"" : ""&lt;Gamepad&gt;/leftStick"", ""action"" : ""move"", ""groups"" : ""Gamepad"" },
  397. /// { ""path"" : ""&lt;Gamepad&gt;/rightStick"", ""action"" : ""look"", ""groups"" : ""Gamepad"" },
  398. /// { ""path"" : ""dpad"", ""action"" : ""move"", ""groups"" : ""Gamepad"", ""isComposite"" : true },
  399. /// { ""path"" : ""&lt;Keyboard&gt;/a"", ""name"" : ""left"", ""action"" : ""move"", ""groups"" : ""Keyboard&amp;Mouse"", ""isPartOfComposite"" : true },
  400. /// { ""path"" : ""&lt;Keyboard&gt;/d"", ""name"" : ""right"", ""action"" : ""move"", ""groups"" : ""Keyboard&amp;Mouse"", ""isPartOfComposite"" : true },
  401. /// { ""path"" : ""&lt;Keyboard&gt;/w"", ""name"" : ""up"", ""action"" : ""move"", ""groups"" : ""Keyboard&amp;Mouse"", ""isPartOfComposite"" : true },
  402. /// { ""path"" : ""&lt;Keyboard&gt;/s"", ""name"" : ""down"", ""action"" : ""move"", ""groups"" : ""Keyboard&amp;Mouse"", ""isPartOfComposite"" : true },
  403. /// { ""path"" : ""&lt;Mouse&gt;/delta"", ""action"" : ""look"", ""groups"" : ""Keyboard&amp;Mouse"" },
  404. /// { ""path"" : ""&lt;Mouse&gt;/leftButton"", ""action"" : ""fire"", ""groups"" : ""Keyboard&amp;Mouse"" }
  405. /// ]
  406. /// },
  407. /// {
  408. /// ""name"" : ""ui"",
  409. /// ""actions"" : [
  410. /// { ""name"" : ""navigate"" }
  411. /// ],
  412. /// ""bindings"" : [
  413. /// { ""path"" : ""&lt;Gamepad&gt;/dpad"", ""action"" : ""navigate"", ""groups"" : ""Gamepad"" }
  414. /// ]
  415. /// }
  416. /// ],
  417. /// ""controlSchemes"" : [
  418. /// {
  419. /// ""name"" : ""Gamepad"",
  420. /// ""bindingGroup"" : ""Gamepad"",
  421. /// ""devices"" : [
  422. /// { ""devicePath"" : ""&lt;Gamepad&gt;"" }
  423. /// ]
  424. /// },
  425. /// {
  426. /// ""name"" : ""Keyboard&amp;Mouse"",
  427. /// ""bindingGroup"" : ""Keyboard&amp;Mouse"",
  428. /// ""devices"" : [
  429. /// { ""devicePath"" : ""&lt;Keyboard&gt;"" },
  430. /// { ""devicePath"" : ""&lt;Mouse&gt;"" }
  431. /// ]
  432. /// }
  433. /// ]
  434. /// }");
  435. /// </code>
  436. /// </example>
  437. /// </remarks>
  438. /// <exception cref="ArgumentNullException"><paramref name="json"/> is <c>null</c> or empty.</exception>
  439. /// <seealso cref="LoadFromJson"/>
  440. /// <seealso cref="ToJson"/>
  441. public static InputActionAsset FromJson(string json)
  442. {
  443. if (string.IsNullOrEmpty(json))
  444. throw new ArgumentNullException(nameof(json));
  445. var asset = CreateInstance<InputActionAsset>();
  446. asset.LoadFromJson(json);
  447. return asset;
  448. }
  449. /// <summary>
  450. /// Find an <see cref="InputAction"/> by its name in one of the <see cref="InputActionMap"/>s
  451. /// in the asset.
  452. /// </summary>
  453. /// <param name="actionNameOrId">Name of the action as either a "map/action" combination (e.g. "gameplay/fire") or
  454. /// a simple name. In the former case, the name is split at the '/' slash and the first part is used to find
  455. /// a map with that name and the second part is used to find an action with that name inside the map. In the
  456. /// latter case, all maps are searched in order and the first action that has the given name in any of the maps
  457. /// is returned. Note that name comparisons are case-insensitive.
  458. ///
  459. /// Alternatively, the given string can be a GUID as given by <see cref="InputAction.id"/>.</param>
  460. /// <param name="throwIfNotFound">If <c>true</c>, instead of returning <c>null</c> when the action
  461. /// cannot be found, throw <c>ArgumentException</c>.</param>
  462. /// <returns>The action with the corresponding name or <c>null</c> if no matching action could be found.</returns>
  463. /// <remarks>
  464. /// <example>
  465. /// <code>
  466. /// var asset = ScriptableObject.CreateInstance&lt;InputActionAsset&gt;();
  467. ///
  468. /// var map1 = new InputActionMap("map1");
  469. /// var map2 = new InputActionMap("map2");
  470. ///
  471. /// asset.AddActionMap(map1);
  472. /// asset.AddActionMap(map2);
  473. ///
  474. /// var action1 = map1.AddAction("action1");
  475. /// var action2 = map1.AddAction("action2");
  476. /// var action3 = map2.AddAction("action3");
  477. ///
  478. /// // Search all maps in the asset for any action that has the given name.
  479. /// asset.FindAction("action1") // Returns action1.
  480. /// asset.FindAction("action2") // Returns action2
  481. /// asset.FindAction("action3") // Returns action3.
  482. ///
  483. /// // Search for a specific action in a specific map.
  484. /// asset.FindAction("map1/action1") // Returns action1.
  485. /// asset.FindAction("map2/action2") // Returns action2.
  486. /// asset.FindAction("map3/action3") // Returns action3.
  487. ///
  488. /// // Search by unique action ID.
  489. /// asset.FindAction(action1.id.ToString()) // Returns action1.
  490. /// asset.FindAction(action2.id.ToString()) // Returns action2.
  491. /// asset.FindAction(action3.id.ToString()) // Returns action3.
  492. /// </code>
  493. /// </example>
  494. /// </remarks>
  495. /// <exception cref="ArgumentNullException"><paramref name="actionNameOrId"/> is <c>null</c>.</exception>
  496. /// <exception cref="ArgumentException">Thrown if <paramref name="throwIfNotFound"/> is true and the
  497. /// action could not be found. -Or- If <paramref name="actionNameOrId"/> contains a slash but is missing
  498. /// either the action or the map name.</exception>
  499. public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false)
  500. {
  501. if (actionNameOrId == null)
  502. throw new ArgumentNullException(nameof(actionNameOrId));
  503. if (m_ActionMaps != null)
  504. {
  505. // Check if we have a "map/action" path.
  506. var indexOfSlash = actionNameOrId.IndexOf('/');
  507. if (indexOfSlash == -1)
  508. {
  509. // No slash so it's just a simple action name.
  510. for (var i = 0; i < m_ActionMaps.Length; ++i)
  511. {
  512. var action = m_ActionMaps[i].FindAction(actionNameOrId);
  513. if (action != null)
  514. return action;
  515. }
  516. }
  517. else
  518. {
  519. // Have a path. First search for the map, then for the action.
  520. var mapName = new Substring(actionNameOrId, 0, indexOfSlash);
  521. var actionName = new Substring(actionNameOrId, indexOfSlash + 1);
  522. if (mapName.isEmpty || actionName.isEmpty)
  523. throw new ArgumentException("Malformed action path: " + actionNameOrId, nameof(actionNameOrId));
  524. for (var i = 0; i < m_ActionMaps.Length; ++i)
  525. {
  526. var map = m_ActionMaps[i];
  527. if (Substring.Compare(map.name, mapName, StringComparison.InvariantCultureIgnoreCase) != 0)
  528. continue;
  529. var actions = map.m_Actions;
  530. for (var n = 0; n < actions.Length; ++n)
  531. {
  532. var action = actions[n];
  533. if (Substring.Compare(action.name, actionName,
  534. StringComparison.InvariantCultureIgnoreCase) == 0)
  535. return action;
  536. }
  537. break;
  538. }
  539. }
  540. }
  541. if (throwIfNotFound)
  542. throw new ArgumentException($"No action '{actionNameOrId}' in '{this}'");
  543. return null;
  544. }
  545. /// <summary>
  546. /// Find an <see cref="InputActionMap"/> in the asset by its name or ID.
  547. /// </summary>
  548. /// <param name="nameOrId">Name or ID (see <see cref="InputActionMap.id"/>) of the action map
  549. /// to look for. Matching is case-insensitive.</param>
  550. /// <param name="throwIfNotFound">If true, instead of returning <c>null</c>, throw <c>ArgumentException</c>.</param>
  551. /// <returns>The <see cref="InputActionMap"/> with a name or ID matching <paramref name="nameOrId"/> or
  552. /// <c>null</c> if no matching map could be found.</returns>
  553. /// <exception cref="ArgumentNullException"><paramref name="nameOrId"/> is <c>null</c>.</exception>
  554. /// <exception cref="ArgumentException">If <paramref name="throwIfNotFound"/> is <c>true</c>, thrown if
  555. /// the action map cannot be found.</exception>
  556. /// <seealso cref="actionMaps"/>
  557. /// <seealso cref="FindActionMap(System.Guid)"/>
  558. public InputActionMap FindActionMap(string nameOrId, bool throwIfNotFound = false)
  559. {
  560. if (nameOrId == null)
  561. throw new ArgumentNullException(nameof(nameOrId));
  562. if (m_ActionMaps == null)
  563. return null;
  564. // If the name contains a hyphen, it may be a GUID.
  565. if (nameOrId.Contains('-') && Guid.TryParse(nameOrId, out var id))
  566. {
  567. for (var i = 0; i < m_ActionMaps.Length; ++i)
  568. {
  569. var map = m_ActionMaps[i];
  570. if (map.idDontGenerate == id)
  571. return map;
  572. }
  573. }
  574. // Default lookup is by name (case-insensitive).
  575. for (var i = 0; i < m_ActionMaps.Length; ++i)
  576. {
  577. var map = m_ActionMaps[i];
  578. if (string.Compare(nameOrId, map.name, StringComparison.InvariantCultureIgnoreCase) == 0)
  579. return map;
  580. }
  581. if (throwIfNotFound)
  582. throw new ArgumentException($"Cannot find action map '{nameOrId}' in '{this}'");
  583. return null;
  584. }
  585. /// <summary>
  586. /// Find an <see cref="InputActionMap"/> in the asset by its ID.
  587. /// </summary>
  588. /// <param name="id">ID (see <see cref="InputActionMap.id"/>) of the action map
  589. /// to look for.</param>
  590. /// <returns>The <see cref="InputActionMap"/> with ID matching <paramref name="id"/> or
  591. /// <c>null</c> if no map in the asset has the given ID.</returns>
  592. /// <seealso cref="actionMaps"/>
  593. /// <seealso cref="FindActionMap"/>
  594. public InputActionMap FindActionMap(Guid id)
  595. {
  596. if (m_ActionMaps == null)
  597. return null;
  598. for (var i = 0; i < m_ActionMaps.Length; ++i)
  599. {
  600. var map = m_ActionMaps[i];
  601. if (map.idDontGenerate == id)
  602. return map;
  603. }
  604. return null;
  605. }
  606. /// <summary>
  607. /// Find an action by its ID (see <see cref="InputAction.id"/>).
  608. /// </summary>
  609. /// <param name="guid">ID of the action to look for.</param>
  610. /// <returns>The action in the asset with the given ID or null if no action
  611. /// in the asset has the given ID.</returns>
  612. public InputAction FindAction(Guid guid)
  613. {
  614. if (m_ActionMaps == null)
  615. return null;
  616. for (var i = 0; i < m_ActionMaps.Length; ++i)
  617. {
  618. var map = m_ActionMaps[i];
  619. var action = map.FindAction(guid);
  620. if (action != null)
  621. return action;
  622. }
  623. return null;
  624. }
  625. /// <summary>
  626. /// Find the control scheme with the given name and return its index
  627. /// in <see cref="controlSchemes"/>.
  628. /// </summary>
  629. /// <param name="name">Name of the control scheme. Matching is case-insensitive.</param>
  630. /// <returns>The index of the given control scheme or -1 if no control scheme
  631. /// with the given name could be found.</returns>
  632. /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>
  633. /// or empty.</exception>
  634. public int FindControlSchemeIndex(string name)
  635. {
  636. if (string.IsNullOrEmpty(name))
  637. throw new ArgumentNullException(nameof(name));
  638. if (m_ControlSchemes == null)
  639. return -1;
  640. for (var i = 0; i < m_ControlSchemes.Length; ++i)
  641. if (string.Compare(name, m_ControlSchemes[i].name, StringComparison.InvariantCultureIgnoreCase) == 0)
  642. return i;
  643. return -1;
  644. }
  645. /// <summary>
  646. /// Find the control scheme with the given name and return it.
  647. /// </summary>
  648. /// <param name="name">Name of the control scheme. Matching is case-insensitive.</param>
  649. /// <returns>The control scheme with the given name or null if no scheme
  650. /// with the given name could be found in the asset.</returns>
  651. /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>
  652. /// or empty.</exception>
  653. public InputControlScheme? FindControlScheme(string name)
  654. {
  655. if (string.IsNullOrEmpty(name))
  656. throw new ArgumentNullException(nameof(name));
  657. var index = FindControlSchemeIndex(name);
  658. if (index == -1)
  659. return null;
  660. return m_ControlSchemes[index];
  661. }
  662. /// <summary>
  663. /// Enable all action maps in the asset.
  664. /// </summary>
  665. /// <remarks>
  666. /// This method is equivalent to calling <see cref="InputActionMap.Enable"/> on
  667. /// all maps in <see cref="actionMaps"/>.
  668. /// </remarks>
  669. public void Enable()
  670. {
  671. foreach (var map in actionMaps)
  672. map.Enable();
  673. }
  674. /// <summary>
  675. /// Disable all action maps in the asset.
  676. /// </summary>
  677. /// <remarks>
  678. /// This method is equivalent to calling <see cref="InputActionMap.Disable"/> on
  679. /// all maps in <see cref="actionMaps"/>.
  680. /// </remarks>
  681. public void Disable()
  682. {
  683. foreach (var map in actionMaps)
  684. map.Disable();
  685. }
  686. /// <summary>
  687. /// Return <c>true</c> if the given action is part of the asset.
  688. /// </summary>
  689. /// <param name="action">An action. Can be null.</param>
  690. /// <returns>True if the given action is part of the asset, false otherwise.</returns>
  691. public bool Contains(InputAction action)
  692. {
  693. var map = action?.actionMap;
  694. if (map == null)
  695. return false;
  696. return map.asset == this;
  697. }
  698. /// <summary>
  699. /// Enumerate all actions in the asset.
  700. /// </summary>
  701. /// <returns>Enumerate over all actions in the asset.</returns>
  702. /// <remarks>
  703. /// Actions will be enumerated one action map in <see cref="actionMaps"/>
  704. /// after the other. The actions from each map will be yielded in turn.
  705. ///
  706. /// This method will allocate GC heap memory.
  707. /// </remarks>
  708. public IEnumerator<InputAction> GetEnumerator()
  709. {
  710. if (m_ActionMaps == null)
  711. yield break;
  712. for (var i = 0; i < m_ActionMaps.Length; ++i)
  713. {
  714. var actions = m_ActionMaps[i].actions;
  715. var actionCount = actions.Count;
  716. for (var n = 0; n < actionCount; ++n)
  717. yield return actions[n];
  718. }
  719. }
  720. IEnumerator IEnumerable.GetEnumerator()
  721. {
  722. return GetEnumerator();
  723. }
  724. private void ReResolveIfNecessary()
  725. {
  726. if (m_SharedStateForAllMaps == null)
  727. return;
  728. Debug.Assert(m_ActionMaps != null && m_ActionMaps.Length > 0);
  729. // State is share between all action maps in the asset. Resolving bindings for the
  730. // first map will resolve them for all maps.
  731. m_ActionMaps[0].LazyResolveBindings();
  732. }
  733. private void OnDestroy()
  734. {
  735. Disable();
  736. if (m_SharedStateForAllMaps != null)
  737. {
  738. m_SharedStateForAllMaps.Dispose(); // Will clean up InputActionMap state.
  739. m_SharedStateForAllMaps = null;
  740. }
  741. }
  742. ////TODO: ApplyBindingOverrides, RemoveBindingOverrides, RemoveAllBindingOverrides
  743. [SerializeField] internal InputActionMap[] m_ActionMaps;
  744. [SerializeField] internal InputControlScheme[] m_ControlSchemes;
  745. ////TODO: make this persistent across domain reloads
  746. /// <summary>
  747. /// Shared state for all action maps in the asset.
  748. /// </summary>
  749. [NonSerialized] internal InputActionState m_SharedStateForAllMaps;
  750. [NonSerialized] internal InputBinding? m_BindingMask;
  751. [NonSerialized] private int m_DevicesCount = -1;
  752. [NonSerialized] private InputDevice[] m_DevicesArray;
  753. [Serializable]
  754. internal struct WriteFileJson
  755. {
  756. public string name;
  757. public InputActionMap.WriteMapJson[] maps;
  758. public InputControlScheme.SchemeJson[] controlSchemes;
  759. }
  760. [Serializable]
  761. internal struct ReadFileJson
  762. {
  763. public string name;
  764. public InputActionMap.ReadMapJson[] maps;
  765. public InputControlScheme.SchemeJson[] controlSchemes;
  766. public void ToAsset(InputActionAsset asset)
  767. {
  768. asset.name = name;
  769. asset.m_ActionMaps = new InputActionMap.ReadFileJson {maps = maps}.ToMaps();
  770. asset.m_ControlSchemes = InputControlScheme.SchemeJson.ToSchemes(controlSchemes);
  771. // Link maps to their asset.
  772. if (asset.m_ActionMaps != null)
  773. foreach (var map in asset.m_ActionMaps)
  774. map.m_Asset = asset;
  775. }
  776. }
  777. }
  778. }