Keine Beschreibung
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

InputActionAsset.cs 44KB

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