No Description
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.

InputActionSerializationHelpers.cs 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. #if UNITY_EDITOR
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Text;
  7. using UnityEditor;
  8. using UnityEngine.InputSystem.Layouts;
  9. using UnityEngine.InputSystem.Utilities;
  10. ////TODO: resolving bindings to actions needs to take "{id}" form into account
  11. namespace UnityEngine.InputSystem.Editor
  12. {
  13. // Helpers for doctoring around in InputActions using SerializedProperties.
  14. internal static class InputActionSerializationHelpers
  15. {
  16. public static string GetName(SerializedProperty element)
  17. {
  18. using (var nameProperty = element.FindPropertyRelative("m_Name"))
  19. {
  20. Debug.Assert(nameProperty != null, $"Cannot find m_Name property in {element.propertyPath}");
  21. return nameProperty.stringValue;
  22. }
  23. }
  24. public static Guid GetId(SerializedProperty element)
  25. {
  26. using (var idProperty = element.FindPropertyRelative("m_Id"))
  27. {
  28. Debug.Assert(idProperty != null, $"Cannot find m_Id property in {element.propertyPath}");
  29. return new Guid(idProperty.stringValue);
  30. }
  31. }
  32. public static int GetIndex(SerializedProperty arrayProperty, Guid id)
  33. {
  34. Debug.Assert(arrayProperty.isArray, $"Property {arrayProperty.propertyPath} is not an array");
  35. for (var i = 0; i < arrayProperty.arraySize; ++i)
  36. {
  37. using (var element = arrayProperty.GetArrayElementAtIndex(i))
  38. if (GetId(element) == id)
  39. return i;
  40. }
  41. return -1;
  42. }
  43. public static int GetIndex(SerializedProperty arrayProperty, SerializedProperty arrayElement)
  44. {
  45. return GetIndex(arrayProperty, GetId(arrayElement));
  46. }
  47. public static int GetIndex(SerializedProperty arrayElement)
  48. {
  49. var arrayProperty = arrayElement.GetArrayPropertyFromElement();
  50. return GetIndex(arrayProperty, arrayElement);
  51. }
  52. /// <summary>
  53. /// Starting with the given binding, find the composite that the binding belongs to. The given binding
  54. /// must either be the composite or be part of a composite.
  55. /// </summary>
  56. public static int GetCompositeStartIndex(SerializedProperty bindingArrayProperty, int bindingIndex)
  57. {
  58. for (var i = bindingIndex; i >= 0; --i)
  59. {
  60. var bindingProperty = bindingArrayProperty.GetArrayElementAtIndex(i);
  61. var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
  62. if ((bindingFlags & InputBinding.Flags.Composite) != 0)
  63. return i;
  64. Debug.Assert((bindingFlags & InputBinding.Flags.PartOfComposite) != 0,
  65. "Binding is neither a composite nor part of a composite");
  66. }
  67. return -1;
  68. }
  69. public static int GetCompositePartCount(SerializedProperty bindingArrayProperty, int bindingIndex)
  70. {
  71. var compositeStartIndex = GetCompositeStartIndex(bindingArrayProperty, bindingIndex);
  72. if (compositeStartIndex == -1)
  73. return 0;
  74. var numParts = 0;
  75. for (var i = compositeStartIndex + 1; i < bindingArrayProperty.arraySize; ++i, ++numParts)
  76. {
  77. var bindingProperty = bindingArrayProperty.GetArrayElementAtIndex(i);
  78. var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
  79. if ((bindingFlags & InputBinding.Flags.PartOfComposite) == 0)
  80. break;
  81. }
  82. return numParts;
  83. }
  84. public static int ConvertBindingIndexOnActionToBindingIndexInArray(SerializedProperty bindingArrayProperty, string actionName,
  85. int bindingIndexOnAction)
  86. {
  87. var bindingCount = bindingArrayProperty.arraySize;
  88. var indexOnAction = -1;
  89. var indexInArray = 0;
  90. for (; indexInArray < bindingCount; ++indexInArray)
  91. {
  92. var bindingActionName = bindingArrayProperty.GetArrayElementAtIndex(indexInArray).FindPropertyRelative("m_Action")
  93. .stringValue;
  94. if (actionName.Equals(bindingActionName, StringComparison.InvariantCultureIgnoreCase))
  95. {
  96. ++indexOnAction;
  97. if (indexOnAction == bindingIndexOnAction)
  98. return indexInArray;
  99. }
  100. }
  101. return indexInArray;
  102. }
  103. #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
  104. public static void AddActionMaps(SerializedObject asset, SerializedObject sourceAsset)
  105. {
  106. Debug.Assert(asset.targetObject is InputActionAsset);
  107. Debug.Assert(sourceAsset.targetObject is InputActionAsset);
  108. var mapArrayPropertySrc = sourceAsset.FindProperty(nameof(InputActionAsset.m_ActionMaps));
  109. var mapArrayPropertyDst = asset.FindProperty(nameof(InputActionAsset.m_ActionMaps));
  110. // Copy each action map from source and paste at the end of destination
  111. var buffer = new StringBuilder();
  112. for (var i = 0; i < mapArrayPropertySrc.arraySize; ++i)
  113. {
  114. buffer.Clear();
  115. var mapProperty = mapArrayPropertySrc.GetArrayElementAtIndex(i);
  116. CopyPasteHelper.CopyItems(new List<SerializedProperty> {mapProperty}, buffer, typeof(InputActionMap), mapProperty);
  117. CopyPasteHelper.PasteItems(buffer.ToString(), new[] { mapArrayPropertyDst.arraySize - 1 }, mapArrayPropertyDst);
  118. }
  119. }
  120. public static void AddControlSchemes(SerializedObject asset, SerializedObject sourceAsset)
  121. {
  122. Debug.Assert((asset.targetObject is InputActionAsset));
  123. Debug.Assert((sourceAsset.targetObject is InputActionAsset));
  124. var src = sourceAsset.FindProperty(nameof(InputActionAsset.m_ControlSchemes));
  125. var dst = asset.FindProperty(nameof(InputActionAsset.m_ControlSchemes));
  126. var buffer = new StringBuilder();
  127. src.CopyToJson(buffer, ignoreObjectReferences: true);
  128. dst.RestoreFromJson(buffer.ToString());
  129. }
  130. #endif
  131. public static SerializedProperty AddActionMap(SerializedObject asset, int index = -1)
  132. {
  133. if (!(asset.targetObject is InputActionAsset))
  134. throw new InvalidOperationException(
  135. $"Can only add action maps to InputActionAsset objects (actual object is {asset.targetObject}");
  136. var mapArrayProperty = asset.FindProperty("m_ActionMaps");
  137. var name = FindUniqueName(mapArrayProperty, "New action map");
  138. if (index < 0)
  139. index = mapArrayProperty.arraySize;
  140. mapArrayProperty.InsertArrayElementAtIndex(index);
  141. var mapProperty = mapArrayProperty.GetArrayElementAtIndex(index);
  142. mapProperty.FindPropertyRelative("m_Name").stringValue = name;
  143. mapProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString();
  144. mapProperty.FindPropertyRelative("m_Actions").ClearArray();
  145. mapProperty.FindPropertyRelative("m_Bindings").ClearArray();
  146. // NB: This isn't always required: If there's already values in the mapArrayProperty, then inserting a new
  147. // element will duplicate the values from the adjacent element to the new element.
  148. // However, if the array has been emptied - i.e. if all action maps have been deleted -
  149. // then the m_Asset property is null, and needs setting here.
  150. if (mapProperty.FindPropertyRelative("m_Asset").objectReferenceValue == null)
  151. mapProperty.FindPropertyRelative("m_Asset").objectReferenceValue = asset.targetObject;
  152. return mapProperty;
  153. }
  154. public static void DeleteActionMap(SerializedObject asset, Guid id)
  155. {
  156. var mapArrayProperty = asset.FindProperty("m_ActionMaps");
  157. var mapIndex = GetIndex(mapArrayProperty, id);
  158. if (mapIndex == -1)
  159. throw new ArgumentException($"No map with id {id} in {asset}", nameof(id));
  160. mapArrayProperty.DeleteArrayElementAtIndex(mapIndex);
  161. }
  162. public static void DeleteAllActionMaps(SerializedObject asset)
  163. {
  164. Debug.Assert(asset.targetObject is InputActionAsset);
  165. var mapArrayProperty = asset.FindProperty("m_ActionMaps");
  166. while (mapArrayProperty.arraySize > 0)
  167. mapArrayProperty.DeleteArrayElementAtIndex(0);
  168. }
  169. public static void MoveActionMap(SerializedObject asset, int fromIndex, int toIndex)
  170. {
  171. var mapArrayProperty = asset.FindProperty("m_ActionMaps");
  172. mapArrayProperty.MoveArrayElement(fromIndex, toIndex);
  173. }
  174. public static void MoveAction(SerializedProperty actionMap, int fromIndex, int toIndex)
  175. {
  176. var actionArrayProperty = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Actions));
  177. actionArrayProperty.MoveArrayElement(fromIndex, toIndex);
  178. }
  179. public static void MoveBinding(SerializedProperty actionMap, int fromIndex, int toIndex)
  180. {
  181. var arrayProperty = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Bindings));
  182. arrayProperty.MoveArrayElement(fromIndex, toIndex);
  183. }
  184. // Append a new action to the end of the set.
  185. public static SerializedProperty AddAction(SerializedProperty actionMap, int index = -1)
  186. {
  187. var actionsArrayProperty = actionMap.FindPropertyRelative("m_Actions");
  188. if (index < 0)
  189. index = actionsArrayProperty.arraySize;
  190. var actionName = FindUniqueName(actionsArrayProperty, "New action");
  191. actionsArrayProperty.InsertArrayElementAtIndex(index);
  192. var actionProperty = actionsArrayProperty.GetArrayElementAtIndex(index);
  193. actionProperty.FindPropertyRelative("m_Name").stringValue = actionName;
  194. actionProperty.FindPropertyRelative("m_Type").intValue = (int)InputActionType.Button; // Default to creating button actions.
  195. actionProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString();
  196. actionProperty.FindPropertyRelative("m_ExpectedControlType").stringValue = "Button";
  197. actionProperty.FindPropertyRelative("m_Flags").intValue = 0;
  198. actionProperty.FindPropertyRelative("m_Interactions").stringValue = "";
  199. actionProperty.FindPropertyRelative("m_Processors").stringValue = "";
  200. return actionProperty;
  201. }
  202. public static void DeleteActionAndBindings(SerializedProperty actionMap, Guid actionId)
  203. {
  204. using (var actionsArrayProperty = actionMap.FindPropertyRelative("m_Actions"))
  205. using (var bindingsArrayProperty = actionMap.FindPropertyRelative("m_Bindings"))
  206. {
  207. // Find index of action.
  208. var actionIndex = GetIndex(actionsArrayProperty, actionId);
  209. if (actionIndex == -1)
  210. throw new ArgumentException($"No action with ID {actionId} in {actionMap.propertyPath}",
  211. nameof(actionId));
  212. using (var actionsProperty = actionsArrayProperty.GetArrayElementAtIndex(actionIndex))
  213. {
  214. var actionName = GetName(actionsProperty);
  215. var actionIdString = actionId.ToString();
  216. // Delete all bindings that refer to the action by ID or name.
  217. for (var i = 0; i < bindingsArrayProperty.arraySize; ++i)
  218. {
  219. using (var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i))
  220. using (var bindingActionProperty = bindingProperty.FindPropertyRelative("m_Action"))
  221. {
  222. var targetAction = bindingActionProperty.stringValue;
  223. if (targetAction.Equals(actionName, StringComparison.InvariantCultureIgnoreCase) ||
  224. targetAction == actionIdString)
  225. {
  226. bindingsArrayProperty.DeleteArrayElementAtIndex(i);
  227. --i;
  228. }
  229. }
  230. }
  231. }
  232. actionsArrayProperty.DeleteArrayElementAtIndex(actionIndex);
  233. }
  234. }
  235. // Equivalent to InputAction.AddBinding().
  236. public static SerializedProperty AddBinding(SerializedProperty actionProperty,
  237. SerializedProperty actionMapProperty = null, SerializedProperty afterBinding = null,
  238. string groups = "", string path = "", string name = "",
  239. string interactions = "", string processors = "",
  240. InputBinding.Flags flags = InputBinding.Flags.None)
  241. {
  242. var bindingsArrayProperty = actionMapProperty != null
  243. ? actionMapProperty.FindPropertyRelative("m_Bindings")
  244. : actionProperty.FindPropertyRelative("m_SingletonActionBindings");
  245. var bindingsCount = bindingsArrayProperty.arraySize;
  246. var actionName = actionProperty.FindPropertyRelative("m_Name").stringValue;
  247. int bindingIndex;
  248. if (afterBinding != null)
  249. {
  250. // If we're supposed to put the binding right after another binding, find the
  251. // binding's index. Also, if it's a composite, skip past all its parts.
  252. bindingIndex = GetIndex(bindingsArrayProperty, afterBinding);
  253. if (IsCompositeBinding(afterBinding))
  254. bindingIndex += GetCompositePartCount(bindingsArrayProperty, bindingIndex);
  255. ++bindingIndex; // Put it *after* the binding.
  256. }
  257. else
  258. {
  259. // Find the index of the last binding for the action in the array.
  260. var indexOfLastBindingForAction = -1;
  261. for (var i = 0; i < bindingsCount; ++i)
  262. {
  263. var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i);
  264. var bindingActionName = bindingProperty.FindPropertyRelative("m_Action").stringValue;
  265. if (actionName.Equals(bindingActionName, StringComparison.InvariantCultureIgnoreCase))
  266. indexOfLastBindingForAction = i;
  267. }
  268. // Insert after last binding or at end of array.
  269. bindingIndex = indexOfLastBindingForAction != -1 ? indexOfLastBindingForAction + 1 : bindingsCount;
  270. }
  271. ////TODO: bind using {id} rather than action name
  272. return AddBindingToBindingArray(bindingsArrayProperty,
  273. bindingIndex: bindingIndex,
  274. actionName: actionName,
  275. groups: groups,
  276. path: path,
  277. name: name,
  278. interactions: interactions,
  279. processors: processors,
  280. flags: flags);
  281. }
  282. public static SerializedProperty AddBindingToBindingArray(SerializedProperty bindingsArrayProperty, int bindingIndex = -1,
  283. string actionName = "", string groups = "", string path = "", string name = "", string interactions = "", string processors = "",
  284. InputBinding.Flags flags = InputBinding.Flags.None)
  285. {
  286. Debug.Assert(bindingsArrayProperty != null);
  287. Debug.Assert(bindingsArrayProperty.isArray, "SerializedProperty is not an array of bindings");
  288. Debug.Assert(bindingIndex == -1 || (bindingIndex >= 0 && bindingIndex <= bindingsArrayProperty.arraySize));
  289. if (bindingIndex == -1)
  290. bindingIndex = bindingsArrayProperty.arraySize;
  291. bindingsArrayProperty.InsertArrayElementAtIndex(bindingIndex);
  292. var newBindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(bindingIndex);
  293. newBindingProperty.FindPropertyRelative("m_Path").stringValue = path;
  294. newBindingProperty.FindPropertyRelative("m_Groups").stringValue = groups;
  295. newBindingProperty.FindPropertyRelative("m_Interactions").stringValue = interactions;
  296. newBindingProperty.FindPropertyRelative("m_Processors").stringValue = processors;
  297. newBindingProperty.FindPropertyRelative("m_Flags").intValue = (int)flags;
  298. newBindingProperty.FindPropertyRelative("m_Action").stringValue = actionName;
  299. newBindingProperty.FindPropertyRelative("m_Name").stringValue = name;
  300. newBindingProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString();
  301. ////FIXME: this likely leaves m_Bindings in the map for singleton actions unsync'd in some cases
  302. return newBindingProperty;
  303. }
  304. public static void SetBindingPartName(SerializedProperty bindingProperty, string partName)
  305. {
  306. //expects beautified partName
  307. bindingProperty.FindPropertyRelative("m_Name").stringValue = partName;
  308. }
  309. public static void ChangeBinding(SerializedProperty bindingProperty, string path = null, string groups = null,
  310. string interactions = null, string processors = null, string action = null)
  311. {
  312. // Path.
  313. if (!string.IsNullOrEmpty(path))
  314. {
  315. var pathProperty = bindingProperty.FindPropertyRelative("m_Path");
  316. pathProperty.stringValue = path;
  317. }
  318. // Groups.
  319. if (!string.IsNullOrEmpty(groups))
  320. {
  321. var groupsProperty = bindingProperty.FindPropertyRelative("m_Groups");
  322. groupsProperty.stringValue = groups;
  323. }
  324. // Interactions.
  325. if (!string.IsNullOrEmpty(interactions))
  326. {
  327. var interactionsProperty = bindingProperty.FindPropertyRelative("m_Interactions");
  328. interactionsProperty.stringValue = interactions;
  329. }
  330. // Processors.
  331. if (!string.IsNullOrEmpty(processors))
  332. {
  333. var processorsProperty = bindingProperty.FindPropertyRelative("m_Processors");
  334. processorsProperty.stringValue = processors;
  335. }
  336. // Action.
  337. if (!string.IsNullOrEmpty(action))
  338. {
  339. var actionProperty = bindingProperty.FindPropertyRelative("m_Action");
  340. actionProperty.stringValue = action;
  341. }
  342. }
  343. public static void DeleteBinding(SerializedProperty binding, SerializedProperty actionMap)
  344. {
  345. var bindingsProperty = actionMap.FindPropertyRelative("m_Bindings");
  346. DeleteBinding(binding, bindingsProperty, binding.GetIndexOfArrayElement());
  347. }
  348. private static void DeleteBinding(SerializedProperty bindingProperty, SerializedProperty bindingArrayProperty, int bindingIndex)
  349. {
  350. var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue;
  351. var isComposite = (bindingFlags & InputBinding.Flags.Composite) != 0;
  352. // If it's a composite, delete all its parts first.
  353. if (isComposite)
  354. {
  355. for (var partIndex = bindingIndex + 1; partIndex < bindingArrayProperty.arraySize;)
  356. {
  357. var part = bindingArrayProperty.GetArrayElementAtIndex(partIndex);
  358. var flags = (InputBinding.Flags)part.FindPropertyRelative("m_Flags").intValue;
  359. if ((flags & InputBinding.Flags.PartOfComposite) == 0)
  360. break;
  361. bindingArrayProperty.DeleteArrayElementAtIndex(partIndex);
  362. }
  363. }
  364. bindingArrayProperty.DeleteArrayElementAtIndex(bindingIndex);
  365. }
  366. public static void DeleteBinding(SerializedProperty bindingArrayProperty, Guid id)
  367. {
  368. var bindingIndex = GetIndex(bindingArrayProperty, id);
  369. var bindingProperty = bindingArrayProperty.GetArrayElementAtIndex(bindingIndex);
  370. DeleteBinding(bindingProperty, bindingArrayProperty, bindingIndex);
  371. }
  372. public static void EnsureUniqueName(SerializedProperty arrayElement)
  373. {
  374. var arrayProperty = arrayElement.GetArrayPropertyFromElement();
  375. var arrayIndexOfElement = arrayElement.GetIndexOfArrayElement();
  376. var nameProperty = arrayElement.FindPropertyRelative("m_Name");
  377. var baseName = nameProperty.stringValue;
  378. nameProperty.stringValue = FindUniqueName(arrayProperty, baseName, ignoreIndex: arrayIndexOfElement);
  379. }
  380. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "False positive (possibly caused by lambda expression?).")]
  381. public static string FindUniqueName(SerializedProperty arrayProperty, string baseName, int ignoreIndex = -1)
  382. {
  383. return StringHelpers.MakeUniqueName(baseName,
  384. Enumerable.Range(0, arrayProperty.arraySize),
  385. index =>
  386. {
  387. if (index == ignoreIndex)
  388. return string.Empty;
  389. var elementProperty = arrayProperty.GetArrayElementAtIndex(index);
  390. var nameProperty = elementProperty.FindPropertyRelative("m_Name");
  391. if (nameProperty == null)
  392. throw new ArgumentException($"Cannot find m_Name property in elements of array",
  393. nameof(arrayProperty));
  394. return nameProperty.stringValue;
  395. });
  396. }
  397. public static void AssignUniqueIDs(SerializedProperty element)
  398. {
  399. AssignUniqueID(element);
  400. foreach (var child in element.GetChildren())
  401. {
  402. if (!child.isArray)
  403. continue;
  404. var fieldType = child.GetFieldType();
  405. if (fieldType == typeof(InputBinding[]) || fieldType == typeof(InputAction[]) ||
  406. fieldType == typeof(InputActionMap))
  407. {
  408. for (var i = 0; i < child.arraySize; ++i)
  409. using (var childElement = child.GetArrayElementAtIndex(i))
  410. AssignUniqueIDs(childElement);
  411. }
  412. }
  413. }
  414. private static void AssignUniqueID(SerializedProperty property)
  415. {
  416. var idProperty = property.FindPropertyRelative("m_Id");
  417. idProperty.stringValue = Guid.NewGuid().ToString();
  418. }
  419. public static void RenameAction(SerializedProperty actionProperty, SerializedProperty actionMapProperty, string newName)
  420. {
  421. // Make sure name is unique.
  422. var actionsArrayProperty = actionMapProperty.FindPropertyRelative("m_Actions");
  423. var uniqueName = FindUniqueName(actionsArrayProperty, newName, actionProperty.GetIndexOfArrayElement());
  424. // Update all bindings that refer to the action.
  425. var nameProperty = actionProperty.FindPropertyRelative("m_Name");
  426. var oldName = nameProperty.stringValue;
  427. var bindingsProperty = actionMapProperty.FindPropertyRelative("m_Bindings");
  428. for (var i = 0; i < bindingsProperty.arraySize; i++)
  429. {
  430. var element = bindingsProperty.GetArrayElementAtIndex(i);
  431. var actionNameProperty = element.FindPropertyRelative("m_Action");
  432. if (actionNameProperty.stringValue.Equals(oldName, StringComparison.InvariantCultureIgnoreCase))
  433. actionNameProperty.stringValue = uniqueName;
  434. }
  435. // Update name.
  436. nameProperty.stringValue = uniqueName;
  437. }
  438. public static void RenameActionMap(SerializedProperty actionMapProperty, string newName)
  439. {
  440. // Make sure name is unique in InputActionAsset.
  441. var assetObject = actionMapProperty.serializedObject;
  442. var mapsArrayProperty = assetObject.FindProperty("m_ActionMaps");
  443. var uniqueName = FindUniqueName(mapsArrayProperty, newName, actionMapProperty.GetIndexOfArrayElement());
  444. // Assign to map.
  445. var nameProperty = actionMapProperty.FindPropertyRelative("m_Name");
  446. nameProperty.stringValue = uniqueName;
  447. }
  448. public static void RenameComposite(SerializedProperty compositeGroupProperty, string newName)
  449. {
  450. var nameProperty = compositeGroupProperty.FindPropertyRelative("m_Name");
  451. nameProperty.stringValue = newName;
  452. }
  453. public static SerializedProperty AddCompositeBinding(SerializedProperty actionProperty, SerializedProperty actionMapProperty,
  454. string compositeName, Type compositeType = null, string groups = "", bool addPartBindings = true)
  455. {
  456. var newProperty = AddBinding(actionProperty, actionMapProperty);
  457. newProperty.FindPropertyRelative("m_Name").stringValue = ObjectNames.NicifyVariableName(compositeName);
  458. newProperty.FindPropertyRelative("m_Path").stringValue = compositeName;
  459. newProperty.FindPropertyRelative("m_Flags").intValue = (int)InputBinding.Flags.Composite;
  460. if (addPartBindings)
  461. {
  462. var fields = compositeType.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance);
  463. foreach (var field in fields)
  464. {
  465. // Skip fields that aren't marked with [InputControl] attribute.
  466. if (field.GetCustomAttribute<InputControlAttribute>(false) == null)
  467. continue;
  468. var partProperty = AddBinding(actionProperty, actionMapProperty, groups: groups);
  469. partProperty.FindPropertyRelative("m_Name").stringValue = field.Name;
  470. partProperty.FindPropertyRelative("m_Flags").intValue = (int)InputBinding.Flags.PartOfComposite;
  471. }
  472. }
  473. return newProperty;
  474. }
  475. public static bool IsCompositeBinding(SerializedProperty bindingProperty)
  476. {
  477. using (var flagsProperty = bindingProperty.FindPropertyRelative("m_Flags"))
  478. {
  479. var flags = (InputBinding.Flags)flagsProperty.intValue;
  480. return (flags & InputBinding.Flags.Composite) != 0;
  481. }
  482. }
  483. public static SerializedProperty ChangeCompositeBindingType(SerializedProperty bindingProperty,
  484. NameAndParameters nameAndParameters)
  485. {
  486. var bindingsArrayProperty = bindingProperty.GetArrayPropertyFromElement();
  487. Debug.Assert(bindingsArrayProperty != null, "SerializedProperty is not an array of bindings");
  488. var bindingIndex = bindingProperty.GetIndexOfArrayElement();
  489. Debug.Assert(IsCompositeBinding(bindingProperty),
  490. $"Binding {bindingProperty.propertyPath} is not a composite");
  491. // If the composite still has the default name, change it to the default
  492. // one for the new composite type.
  493. var pathProperty = bindingProperty.FindPropertyRelative("m_Path");
  494. var nameProperty = bindingProperty.FindPropertyRelative("m_Name");
  495. if (nameProperty.stringValue ==
  496. ObjectNames.NicifyVariableName(NameAndParameters.Parse(pathProperty.stringValue).name))
  497. nameProperty.stringValue = ObjectNames.NicifyVariableName(nameAndParameters.name);
  498. pathProperty.stringValue = nameAndParameters.ToString();
  499. // Adjust part bindings if we have information on the registered composite. If we don't have
  500. // a type, we don't know about the parts. In that case, leave part bindings untouched.
  501. var compositeType = InputBindingComposite.s_Composites.LookupTypeRegistration(nameAndParameters.name);
  502. if (compositeType != null)
  503. {
  504. var actionName = bindingProperty.FindPropertyRelative("m_Action").stringValue;
  505. // Repurpose existing part bindings for the new composite or add any part bindings that
  506. // we're missing.
  507. var fields = compositeType.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance);
  508. var partIndex = 0;
  509. var partBindingsStartIndex = bindingIndex + 1;
  510. foreach (var field in fields)
  511. {
  512. // Skip fields that aren't marked with [InputControl] attribute.
  513. if (field.GetCustomAttribute<InputControlAttribute>(false) == null)
  514. continue;
  515. // See if we can reuse an existing part binding.
  516. SerializedProperty partProperty = null;
  517. if (partBindingsStartIndex + partIndex < bindingsArrayProperty.arraySize)
  518. {
  519. ////REVIEW: this should probably look up part bindings by name rather than going sequentially
  520. var element = bindingsArrayProperty.GetArrayElementAtIndex(partBindingsStartIndex + partIndex);
  521. if (((InputBinding.Flags)element.FindPropertyRelative("m_Flags").intValue & InputBinding.Flags.PartOfComposite) != 0)
  522. partProperty = element;
  523. }
  524. // If not, insert a new binding.
  525. if (partProperty == null)
  526. {
  527. partProperty = AddBindingToBindingArray(bindingsArrayProperty, partBindingsStartIndex + partIndex,
  528. flags: InputBinding.Flags.PartOfComposite);
  529. }
  530. // Initialize.
  531. partProperty.FindPropertyRelative("m_Name").stringValue = ObjectNames.NicifyVariableName(field.Name);
  532. partProperty.FindPropertyRelative("m_Action").stringValue = actionName;
  533. ++partIndex;
  534. }
  535. ////REVIEW: when we allow adding the same part multiple times, we may want to do something smarter here
  536. // Delete extraneous part bindings.
  537. while (partBindingsStartIndex + partIndex < bindingsArrayProperty.arraySize)
  538. {
  539. var element = bindingsArrayProperty.GetArrayElementAtIndex(partBindingsStartIndex + partIndex);
  540. if (((InputBinding.Flags)element.FindPropertyRelative("m_Flags").intValue & InputBinding.Flags.PartOfComposite) == 0)
  541. break;
  542. bindingsArrayProperty.DeleteArrayElementAtIndex(partBindingsStartIndex + partIndex);
  543. // No incrementing of partIndex.
  544. }
  545. }
  546. return bindingProperty;
  547. }
  548. public static void ReplaceBindingGroup(SerializedObject asset, string oldBindingGroup, string newBindingGroup, bool deleteOrphanedBindings = false)
  549. {
  550. var mapArrayProperty = asset.FindProperty("m_ActionMaps");
  551. var mapCount = mapArrayProperty.arraySize;
  552. for (var k = 0; k < mapCount; ++k)
  553. {
  554. var actionMapProperty = mapArrayProperty.GetArrayElementAtIndex(k);
  555. var bindingsArrayProperty = actionMapProperty.FindPropertyRelative("m_Bindings");
  556. var bindingsCount = bindingsArrayProperty.arraySize;
  557. for (var i = 0; i < bindingsCount; ++i)
  558. {
  559. var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i);
  560. var groupsProperty = bindingProperty.FindPropertyRelative("m_Groups");
  561. var groups = groupsProperty.stringValue;
  562. // Ignore bindings not belonging to any control scheme.
  563. if (string.IsNullOrEmpty(groups))
  564. continue;
  565. var groupsArray = groups.Split(InputBinding.Separator);
  566. var numGroups = groupsArray.LengthSafe();
  567. var didRename = false;
  568. for (var n = 0; n < numGroups; ++n)
  569. {
  570. if (string.Compare(groupsArray[n], oldBindingGroup, StringComparison.InvariantCultureIgnoreCase) != 0)
  571. continue;
  572. if (string.IsNullOrEmpty(newBindingGroup))
  573. {
  574. ArrayHelpers.EraseAt(ref groupsArray, n);
  575. --n;
  576. --numGroups;
  577. }
  578. else
  579. groupsArray[n] = newBindingGroup;
  580. didRename = true;
  581. }
  582. if (!didRename)
  583. continue;
  584. if (groupsArray != null)
  585. groupsProperty.stringValue = string.Join(InputBinding.kSeparatorString, groupsArray);
  586. else
  587. {
  588. if (deleteOrphanedBindings)
  589. {
  590. // Binding no long belongs to any binding group. Delete it.
  591. bindingsArrayProperty.DeleteArrayElementAtIndex(i);
  592. --i;
  593. --bindingsCount;
  594. }
  595. else
  596. {
  597. groupsProperty.stringValue = string.Empty;
  598. }
  599. }
  600. }
  601. }
  602. }
  603. public static void RemoveUnusedBindingGroups(SerializedProperty binding, ReadOnlyArray<InputControlScheme> controlSchemes)
  604. {
  605. var groupsProperty = binding.FindPropertyRelative(nameof(InputBinding.m_Groups));
  606. groupsProperty.stringValue = string.Join(InputBinding.kSeparatorString,
  607. groupsProperty.stringValue
  608. .Split(InputBinding.Separator)
  609. .Where(g => controlSchemes.Any(c => c.bindingGroup.Equals(g, StringComparison.InvariantCultureIgnoreCase))));
  610. }
  611. #region Control Schemes
  612. public static void DeleteAllControlSchemes(SerializedObject asset)
  613. {
  614. var schemes = GetControlSchemesArray(asset);
  615. while (schemes.arraySize > 0)
  616. schemes.DeleteArrayElementAtIndex(0);
  617. }
  618. public static int IndexOfControlScheme(SerializedProperty controlSchemeArray, string controlSchemeName)
  619. {
  620. var serializedControlScheme = controlSchemeArray.FirstOrDefault(sp =>
  621. sp.FindPropertyRelative(nameof(InputControlScheme.m_Name)).stringValue == controlSchemeName);
  622. return serializedControlScheme?.GetIndexOfArrayElement() ?? -1;
  623. }
  624. public static SerializedProperty GetControlSchemesArray(SerializedObject asset)
  625. {
  626. return asset.FindProperty(nameof(InputActionAsset.m_ControlSchemes));
  627. }
  628. #endregion // Control Schemes
  629. }
  630. }
  631. #endif // UNITY_EDITOR