#if UNITY_EDITOR using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using UnityEditor; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.Utilities; ////TODO: resolving bindings to actions needs to take "{id}" form into account namespace UnityEngine.InputSystem.Editor { // Helpers for doctoring around in InputActions using SerializedProperties. internal static class InputActionSerializationHelpers { public static string GetName(SerializedProperty element) { using (var nameProperty = element.FindPropertyRelative("m_Name")) { Debug.Assert(nameProperty != null, $"Cannot find m_Name property in {element.propertyPath}"); return nameProperty.stringValue; } } public static Guid GetId(SerializedProperty element) { using (var idProperty = element.FindPropertyRelative("m_Id")) { Debug.Assert(idProperty != null, $"Cannot find m_Id property in {element.propertyPath}"); return new Guid(idProperty.stringValue); } } public static int GetIndex(SerializedProperty arrayProperty, Guid id) { Debug.Assert(arrayProperty.isArray, $"Property {arrayProperty.propertyPath} is not an array"); for (var i = 0; i < arrayProperty.arraySize; ++i) { using (var element = arrayProperty.GetArrayElementAtIndex(i)) if (GetId(element) == id) return i; } return -1; } public static int GetIndex(SerializedProperty arrayProperty, SerializedProperty arrayElement) { return GetIndex(arrayProperty, GetId(arrayElement)); } public static int GetIndex(SerializedProperty arrayElement) { var arrayProperty = arrayElement.GetArrayPropertyFromElement(); return GetIndex(arrayProperty, arrayElement); } /// /// Starting with the given binding, find the composite that the binding belongs to. The given binding /// must either be the composite or be part of a composite. /// public static int GetCompositeStartIndex(SerializedProperty bindingArrayProperty, int bindingIndex) { for (var i = bindingIndex; i >= 0; --i) { var bindingProperty = bindingArrayProperty.GetArrayElementAtIndex(i); var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue; if ((bindingFlags & InputBinding.Flags.Composite) != 0) return i; Debug.Assert((bindingFlags & InputBinding.Flags.PartOfComposite) != 0, "Binding is neither a composite nor part of a composite"); } return -1; } public static int GetCompositePartCount(SerializedProperty bindingArrayProperty, int bindingIndex) { var compositeStartIndex = GetCompositeStartIndex(bindingArrayProperty, bindingIndex); if (compositeStartIndex == -1) return 0; var numParts = 0; for (var i = compositeStartIndex + 1; i < bindingArrayProperty.arraySize; ++i, ++numParts) { var bindingProperty = bindingArrayProperty.GetArrayElementAtIndex(i); var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue; if ((bindingFlags & InputBinding.Flags.PartOfComposite) == 0) break; } return numParts; } public static int ConvertBindingIndexOnActionToBindingIndexInArray(SerializedProperty bindingArrayProperty, string actionName, int bindingIndexOnAction) { var bindingCount = bindingArrayProperty.arraySize; var indexOnAction = -1; var indexInArray = 0; for (; indexInArray < bindingCount; ++indexInArray) { var bindingActionName = bindingArrayProperty.GetArrayElementAtIndex(indexInArray).FindPropertyRelative("m_Action") .stringValue; if (actionName.Equals(bindingActionName, StringComparison.InvariantCultureIgnoreCase)) { ++indexOnAction; if (indexOnAction == bindingIndexOnAction) return indexInArray; } } return indexInArray; } #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS public static void AddActionMaps(SerializedObject asset, SerializedObject sourceAsset) { Debug.Assert(asset.targetObject is InputActionAsset); Debug.Assert(sourceAsset.targetObject is InputActionAsset); var mapArrayPropertySrc = sourceAsset.FindProperty(nameof(InputActionAsset.m_ActionMaps)); var mapArrayPropertyDst = asset.FindProperty(nameof(InputActionAsset.m_ActionMaps)); // Copy each action map from source and paste at the end of destination var buffer = new StringBuilder(); for (var i = 0; i < mapArrayPropertySrc.arraySize; ++i) { buffer.Clear(); var mapProperty = mapArrayPropertySrc.GetArrayElementAtIndex(i); CopyPasteHelper.CopyItems(new List {mapProperty}, buffer, typeof(InputActionMap), mapProperty); CopyPasteHelper.PasteItems(buffer.ToString(), new[] { mapArrayPropertyDst.arraySize - 1 }, mapArrayPropertyDst); } } public static void AddControlSchemes(SerializedObject asset, SerializedObject sourceAsset) { Debug.Assert((asset.targetObject is InputActionAsset)); Debug.Assert((sourceAsset.targetObject is InputActionAsset)); var src = sourceAsset.FindProperty(nameof(InputActionAsset.m_ControlSchemes)); var dst = asset.FindProperty(nameof(InputActionAsset.m_ControlSchemes)); var buffer = new StringBuilder(); src.CopyToJson(buffer, ignoreObjectReferences: true); dst.RestoreFromJson(buffer.ToString()); } #endif public static SerializedProperty AddActionMap(SerializedObject asset, int index = -1) { if (!(asset.targetObject is InputActionAsset)) throw new InvalidOperationException( $"Can only add action maps to InputActionAsset objects (actual object is {asset.targetObject}"); var mapArrayProperty = asset.FindProperty("m_ActionMaps"); var name = FindUniqueName(mapArrayProperty, "New action map"); if (index < 0) index = mapArrayProperty.arraySize; mapArrayProperty.InsertArrayElementAtIndex(index); var mapProperty = mapArrayProperty.GetArrayElementAtIndex(index); mapProperty.FindPropertyRelative("m_Name").stringValue = name; mapProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString(); mapProperty.FindPropertyRelative("m_Actions").ClearArray(); mapProperty.FindPropertyRelative("m_Bindings").ClearArray(); // NB: This isn't always required: If there's already values in the mapArrayProperty, then inserting a new // element will duplicate the values from the adjacent element to the new element. // However, if the array has been emptied - i.e. if all action maps have been deleted - // then the m_Asset property is null, and needs setting here. if (mapProperty.FindPropertyRelative("m_Asset").objectReferenceValue == null) mapProperty.FindPropertyRelative("m_Asset").objectReferenceValue = asset.targetObject; return mapProperty; } public static void DeleteActionMap(SerializedObject asset, Guid id) { var mapArrayProperty = asset.FindProperty("m_ActionMaps"); var mapIndex = GetIndex(mapArrayProperty, id); if (mapIndex == -1) throw new ArgumentException($"No map with id {id} in {asset}", nameof(id)); mapArrayProperty.DeleteArrayElementAtIndex(mapIndex); } public static void DeleteAllActionMaps(SerializedObject asset) { Debug.Assert(asset.targetObject is InputActionAsset); var mapArrayProperty = asset.FindProperty("m_ActionMaps"); while (mapArrayProperty.arraySize > 0) mapArrayProperty.DeleteArrayElementAtIndex(0); } public static void MoveActionMap(SerializedObject asset, int fromIndex, int toIndex) { var mapArrayProperty = asset.FindProperty("m_ActionMaps"); mapArrayProperty.MoveArrayElement(fromIndex, toIndex); } public static void MoveAction(SerializedProperty actionMap, int fromIndex, int toIndex) { var actionArrayProperty = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Actions)); actionArrayProperty.MoveArrayElement(fromIndex, toIndex); } public static void MoveBinding(SerializedProperty actionMap, int fromIndex, int toIndex) { var arrayProperty = actionMap.FindPropertyRelative(nameof(InputActionMap.m_Bindings)); arrayProperty.MoveArrayElement(fromIndex, toIndex); } // Append a new action to the end of the set. public static SerializedProperty AddAction(SerializedProperty actionMap, int index = -1) { var actionsArrayProperty = actionMap.FindPropertyRelative("m_Actions"); if (index < 0) index = actionsArrayProperty.arraySize; var actionName = FindUniqueName(actionsArrayProperty, "New action"); actionsArrayProperty.InsertArrayElementAtIndex(index); var actionProperty = actionsArrayProperty.GetArrayElementAtIndex(index); actionProperty.FindPropertyRelative("m_Name").stringValue = actionName; actionProperty.FindPropertyRelative("m_Type").intValue = (int)InputActionType.Button; // Default to creating button actions. actionProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString(); actionProperty.FindPropertyRelative("m_ExpectedControlType").stringValue = "Button"; actionProperty.FindPropertyRelative("m_Flags").intValue = 0; actionProperty.FindPropertyRelative("m_Interactions").stringValue = ""; actionProperty.FindPropertyRelative("m_Processors").stringValue = ""; return actionProperty; } public static void DeleteActionAndBindings(SerializedProperty actionMap, Guid actionId) { using (var actionsArrayProperty = actionMap.FindPropertyRelative("m_Actions")) using (var bindingsArrayProperty = actionMap.FindPropertyRelative("m_Bindings")) { // Find index of action. var actionIndex = GetIndex(actionsArrayProperty, actionId); if (actionIndex == -1) throw new ArgumentException($"No action with ID {actionId} in {actionMap.propertyPath}", nameof(actionId)); using (var actionsProperty = actionsArrayProperty.GetArrayElementAtIndex(actionIndex)) { var actionName = GetName(actionsProperty); var actionIdString = actionId.ToString(); // Delete all bindings that refer to the action by ID or name. for (var i = 0; i < bindingsArrayProperty.arraySize; ++i) { using (var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i)) using (var bindingActionProperty = bindingProperty.FindPropertyRelative("m_Action")) { var targetAction = bindingActionProperty.stringValue; if (targetAction.Equals(actionName, StringComparison.InvariantCultureIgnoreCase) || targetAction == actionIdString) { bindingsArrayProperty.DeleteArrayElementAtIndex(i); --i; } } } } actionsArrayProperty.DeleteArrayElementAtIndex(actionIndex); } } // Equivalent to InputAction.AddBinding(). public static SerializedProperty AddBinding(SerializedProperty actionProperty, SerializedProperty actionMapProperty = null, SerializedProperty afterBinding = null, string groups = "", string path = "", string name = "", string interactions = "", string processors = "", InputBinding.Flags flags = InputBinding.Flags.None) { var bindingsArrayProperty = actionMapProperty != null ? actionMapProperty.FindPropertyRelative("m_Bindings") : actionProperty.FindPropertyRelative("m_SingletonActionBindings"); var bindingsCount = bindingsArrayProperty.arraySize; var actionName = actionProperty.FindPropertyRelative("m_Name").stringValue; int bindingIndex; if (afterBinding != null) { // If we're supposed to put the binding right after another binding, find the // binding's index. Also, if it's a composite, skip past all its parts. bindingIndex = GetIndex(bindingsArrayProperty, afterBinding); if (IsCompositeBinding(afterBinding)) bindingIndex += GetCompositePartCount(bindingsArrayProperty, bindingIndex); ++bindingIndex; // Put it *after* the binding. } else { // Find the index of the last binding for the action in the array. var indexOfLastBindingForAction = -1; for (var i = 0; i < bindingsCount; ++i) { var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i); var bindingActionName = bindingProperty.FindPropertyRelative("m_Action").stringValue; if (actionName.Equals(bindingActionName, StringComparison.InvariantCultureIgnoreCase)) indexOfLastBindingForAction = i; } // Insert after last binding or at end of array. bindingIndex = indexOfLastBindingForAction != -1 ? indexOfLastBindingForAction + 1 : bindingsCount; } ////TODO: bind using {id} rather than action name return AddBindingToBindingArray(bindingsArrayProperty, bindingIndex: bindingIndex, actionName: actionName, groups: groups, path: path, name: name, interactions: interactions, processors: processors, flags: flags); } public static SerializedProperty AddBindingToBindingArray(SerializedProperty bindingsArrayProperty, int bindingIndex = -1, string actionName = "", string groups = "", string path = "", string name = "", string interactions = "", string processors = "", InputBinding.Flags flags = InputBinding.Flags.None) { Debug.Assert(bindingsArrayProperty != null); Debug.Assert(bindingsArrayProperty.isArray, "SerializedProperty is not an array of bindings"); Debug.Assert(bindingIndex == -1 || (bindingIndex >= 0 && bindingIndex <= bindingsArrayProperty.arraySize)); if (bindingIndex == -1) bindingIndex = bindingsArrayProperty.arraySize; bindingsArrayProperty.InsertArrayElementAtIndex(bindingIndex); var newBindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(bindingIndex); newBindingProperty.FindPropertyRelative("m_Path").stringValue = path; newBindingProperty.FindPropertyRelative("m_Groups").stringValue = groups; newBindingProperty.FindPropertyRelative("m_Interactions").stringValue = interactions; newBindingProperty.FindPropertyRelative("m_Processors").stringValue = processors; newBindingProperty.FindPropertyRelative("m_Flags").intValue = (int)flags; newBindingProperty.FindPropertyRelative("m_Action").stringValue = actionName; newBindingProperty.FindPropertyRelative("m_Name").stringValue = name; newBindingProperty.FindPropertyRelative("m_Id").stringValue = Guid.NewGuid().ToString(); ////FIXME: this likely leaves m_Bindings in the map for singleton actions unsync'd in some cases return newBindingProperty; } public static void SetBindingPartName(SerializedProperty bindingProperty, string partName) { //expects beautified partName bindingProperty.FindPropertyRelative("m_Name").stringValue = partName; } public static void ChangeBinding(SerializedProperty bindingProperty, string path = null, string groups = null, string interactions = null, string processors = null, string action = null) { // Path. if (!string.IsNullOrEmpty(path)) { var pathProperty = bindingProperty.FindPropertyRelative("m_Path"); pathProperty.stringValue = path; } // Groups. if (!string.IsNullOrEmpty(groups)) { var groupsProperty = bindingProperty.FindPropertyRelative("m_Groups"); groupsProperty.stringValue = groups; } // Interactions. if (!string.IsNullOrEmpty(interactions)) { var interactionsProperty = bindingProperty.FindPropertyRelative("m_Interactions"); interactionsProperty.stringValue = interactions; } // Processors. if (!string.IsNullOrEmpty(processors)) { var processorsProperty = bindingProperty.FindPropertyRelative("m_Processors"); processorsProperty.stringValue = processors; } // Action. if (!string.IsNullOrEmpty(action)) { var actionProperty = bindingProperty.FindPropertyRelative("m_Action"); actionProperty.stringValue = action; } } public static void DeleteBinding(SerializedProperty binding, SerializedProperty actionMap) { var bindingsProperty = actionMap.FindPropertyRelative("m_Bindings"); DeleteBinding(binding, bindingsProperty, binding.GetIndexOfArrayElement()); } private static void DeleteBinding(SerializedProperty bindingProperty, SerializedProperty bindingArrayProperty, int bindingIndex) { var bindingFlags = (InputBinding.Flags)bindingProperty.FindPropertyRelative("m_Flags").intValue; var isComposite = (bindingFlags & InputBinding.Flags.Composite) != 0; // If it's a composite, delete all its parts first. if (isComposite) { for (var partIndex = bindingIndex + 1; partIndex < bindingArrayProperty.arraySize;) { var part = bindingArrayProperty.GetArrayElementAtIndex(partIndex); var flags = (InputBinding.Flags)part.FindPropertyRelative("m_Flags").intValue; if ((flags & InputBinding.Flags.PartOfComposite) == 0) break; bindingArrayProperty.DeleteArrayElementAtIndex(partIndex); } } bindingArrayProperty.DeleteArrayElementAtIndex(bindingIndex); } public static void DeleteBinding(SerializedProperty bindingArrayProperty, Guid id) { var bindingIndex = GetIndex(bindingArrayProperty, id); var bindingProperty = bindingArrayProperty.GetArrayElementAtIndex(bindingIndex); DeleteBinding(bindingProperty, bindingArrayProperty, bindingIndex); } public static void EnsureUniqueName(SerializedProperty arrayElement) { var arrayProperty = arrayElement.GetArrayPropertyFromElement(); var arrayIndexOfElement = arrayElement.GetIndexOfArrayElement(); var nameProperty = arrayElement.FindPropertyRelative("m_Name"); var baseName = nameProperty.stringValue; nameProperty.stringValue = FindUniqueName(arrayProperty, baseName, ignoreIndex: arrayIndexOfElement); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "False positive (possibly caused by lambda expression?).")] public static string FindUniqueName(SerializedProperty arrayProperty, string baseName, int ignoreIndex = -1) { return StringHelpers.MakeUniqueName(baseName, Enumerable.Range(0, arrayProperty.arraySize), index => { if (index == ignoreIndex) return string.Empty; var elementProperty = arrayProperty.GetArrayElementAtIndex(index); var nameProperty = elementProperty.FindPropertyRelative("m_Name"); if (nameProperty == null) throw new ArgumentException($"Cannot find m_Name property in elements of array", nameof(arrayProperty)); return nameProperty.stringValue; }); } public static void AssignUniqueIDs(SerializedProperty element) { AssignUniqueID(element); foreach (var child in element.GetChildren()) { if (!child.isArray) continue; var fieldType = child.GetFieldType(); if (fieldType == typeof(InputBinding[]) || fieldType == typeof(InputAction[]) || fieldType == typeof(InputActionMap)) { for (var i = 0; i < child.arraySize; ++i) using (var childElement = child.GetArrayElementAtIndex(i)) AssignUniqueIDs(childElement); } } } private static void AssignUniqueID(SerializedProperty property) { var idProperty = property.FindPropertyRelative("m_Id"); idProperty.stringValue = Guid.NewGuid().ToString(); } public static void RenameAction(SerializedProperty actionProperty, SerializedProperty actionMapProperty, string newName) { // Make sure name is unique. var actionsArrayProperty = actionMapProperty.FindPropertyRelative("m_Actions"); var uniqueName = FindUniqueName(actionsArrayProperty, newName, actionProperty.GetIndexOfArrayElement()); // Update all bindings that refer to the action. var nameProperty = actionProperty.FindPropertyRelative("m_Name"); var oldName = nameProperty.stringValue; var bindingsProperty = actionMapProperty.FindPropertyRelative("m_Bindings"); for (var i = 0; i < bindingsProperty.arraySize; i++) { var element = bindingsProperty.GetArrayElementAtIndex(i); var actionNameProperty = element.FindPropertyRelative("m_Action"); if (actionNameProperty.stringValue.Equals(oldName, StringComparison.InvariantCultureIgnoreCase)) actionNameProperty.stringValue = uniqueName; } // Update name. nameProperty.stringValue = uniqueName; } public static void RenameActionMap(SerializedProperty actionMapProperty, string newName) { // Make sure name is unique in InputActionAsset. var assetObject = actionMapProperty.serializedObject; var mapsArrayProperty = assetObject.FindProperty("m_ActionMaps"); var uniqueName = FindUniqueName(mapsArrayProperty, newName, actionMapProperty.GetIndexOfArrayElement()); // Assign to map. var nameProperty = actionMapProperty.FindPropertyRelative("m_Name"); nameProperty.stringValue = uniqueName; } public static void RenameComposite(SerializedProperty compositeGroupProperty, string newName) { var nameProperty = compositeGroupProperty.FindPropertyRelative("m_Name"); nameProperty.stringValue = newName; } public static SerializedProperty AddCompositeBinding(SerializedProperty actionProperty, SerializedProperty actionMapProperty, string compositeName, Type compositeType = null, string groups = "", bool addPartBindings = true) { var newProperty = AddBinding(actionProperty, actionMapProperty); newProperty.FindPropertyRelative("m_Name").stringValue = ObjectNames.NicifyVariableName(compositeName); newProperty.FindPropertyRelative("m_Path").stringValue = compositeName; newProperty.FindPropertyRelative("m_Flags").intValue = (int)InputBinding.Flags.Composite; if (addPartBindings) { var fields = compositeType.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance); foreach (var field in fields) { // Skip fields that aren't marked with [InputControl] attribute. if (field.GetCustomAttribute(false) == null) continue; var partProperty = AddBinding(actionProperty, actionMapProperty, groups: groups); partProperty.FindPropertyRelative("m_Name").stringValue = field.Name; partProperty.FindPropertyRelative("m_Flags").intValue = (int)InputBinding.Flags.PartOfComposite; } } return newProperty; } public static bool IsCompositeBinding(SerializedProperty bindingProperty) { using (var flagsProperty = bindingProperty.FindPropertyRelative("m_Flags")) { var flags = (InputBinding.Flags)flagsProperty.intValue; return (flags & InputBinding.Flags.Composite) != 0; } } public static SerializedProperty ChangeCompositeBindingType(SerializedProperty bindingProperty, NameAndParameters nameAndParameters) { var bindingsArrayProperty = bindingProperty.GetArrayPropertyFromElement(); Debug.Assert(bindingsArrayProperty != null, "SerializedProperty is not an array of bindings"); var bindingIndex = bindingProperty.GetIndexOfArrayElement(); Debug.Assert(IsCompositeBinding(bindingProperty), $"Binding {bindingProperty.propertyPath} is not a composite"); // If the composite still has the default name, change it to the default // one for the new composite type. var pathProperty = bindingProperty.FindPropertyRelative("m_Path"); var nameProperty = bindingProperty.FindPropertyRelative("m_Name"); if (nameProperty.stringValue == ObjectNames.NicifyVariableName(NameAndParameters.Parse(pathProperty.stringValue).name)) nameProperty.stringValue = ObjectNames.NicifyVariableName(nameAndParameters.name); pathProperty.stringValue = nameAndParameters.ToString(); // Adjust part bindings if we have information on the registered composite. If we don't have // a type, we don't know about the parts. In that case, leave part bindings untouched. var compositeType = InputBindingComposite.s_Composites.LookupTypeRegistration(nameAndParameters.name); if (compositeType != null) { var actionName = bindingProperty.FindPropertyRelative("m_Action").stringValue; // Repurpose existing part bindings for the new composite or add any part bindings that // we're missing. var fields = compositeType.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance); var partIndex = 0; var partBindingsStartIndex = bindingIndex + 1; foreach (var field in fields) { // Skip fields that aren't marked with [InputControl] attribute. if (field.GetCustomAttribute(false) == null) continue; // See if we can reuse an existing part binding. SerializedProperty partProperty = null; if (partBindingsStartIndex + partIndex < bindingsArrayProperty.arraySize) { ////REVIEW: this should probably look up part bindings by name rather than going sequentially var element = bindingsArrayProperty.GetArrayElementAtIndex(partBindingsStartIndex + partIndex); if (((InputBinding.Flags)element.FindPropertyRelative("m_Flags").intValue & InputBinding.Flags.PartOfComposite) != 0) partProperty = element; } // If not, insert a new binding. if (partProperty == null) { partProperty = AddBindingToBindingArray(bindingsArrayProperty, partBindingsStartIndex + partIndex, flags: InputBinding.Flags.PartOfComposite); } // Initialize. partProperty.FindPropertyRelative("m_Name").stringValue = ObjectNames.NicifyVariableName(field.Name); partProperty.FindPropertyRelative("m_Action").stringValue = actionName; ++partIndex; } ////REVIEW: when we allow adding the same part multiple times, we may want to do something smarter here // Delete extraneous part bindings. while (partBindingsStartIndex + partIndex < bindingsArrayProperty.arraySize) { var element = bindingsArrayProperty.GetArrayElementAtIndex(partBindingsStartIndex + partIndex); if (((InputBinding.Flags)element.FindPropertyRelative("m_Flags").intValue & InputBinding.Flags.PartOfComposite) == 0) break; bindingsArrayProperty.DeleteArrayElementAtIndex(partBindingsStartIndex + partIndex); // No incrementing of partIndex. } } return bindingProperty; } public static void ReplaceBindingGroup(SerializedObject asset, string oldBindingGroup, string newBindingGroup, bool deleteOrphanedBindings = false) { var mapArrayProperty = asset.FindProperty("m_ActionMaps"); var mapCount = mapArrayProperty.arraySize; for (var k = 0; k < mapCount; ++k) { var actionMapProperty = mapArrayProperty.GetArrayElementAtIndex(k); var bindingsArrayProperty = actionMapProperty.FindPropertyRelative("m_Bindings"); var bindingsCount = bindingsArrayProperty.arraySize; for (var i = 0; i < bindingsCount; ++i) { var bindingProperty = bindingsArrayProperty.GetArrayElementAtIndex(i); var groupsProperty = bindingProperty.FindPropertyRelative("m_Groups"); var groups = groupsProperty.stringValue; // Ignore bindings not belonging to any control scheme. if (string.IsNullOrEmpty(groups)) continue; var groupsArray = groups.Split(InputBinding.Separator); var numGroups = groupsArray.LengthSafe(); var didRename = false; for (var n = 0; n < numGroups; ++n) { if (string.Compare(groupsArray[n], oldBindingGroup, StringComparison.InvariantCultureIgnoreCase) != 0) continue; if (string.IsNullOrEmpty(newBindingGroup)) { ArrayHelpers.EraseAt(ref groupsArray, n); --n; --numGroups; } else groupsArray[n] = newBindingGroup; didRename = true; } if (!didRename) continue; if (groupsArray != null) groupsProperty.stringValue = string.Join(InputBinding.kSeparatorString, groupsArray); else { if (deleteOrphanedBindings) { // Binding no long belongs to any binding group. Delete it. bindingsArrayProperty.DeleteArrayElementAtIndex(i); --i; --bindingsCount; } else { groupsProperty.stringValue = string.Empty; } } } } } public static void RemoveUnusedBindingGroups(SerializedProperty binding, ReadOnlyArray controlSchemes) { var groupsProperty = binding.FindPropertyRelative(nameof(InputBinding.m_Groups)); groupsProperty.stringValue = string.Join(InputBinding.kSeparatorString, groupsProperty.stringValue .Split(InputBinding.Separator) .Where(g => controlSchemes.Any(c => c.bindingGroup.Equals(g, StringComparison.InvariantCultureIgnoreCase)))); } #region Control Schemes public static void DeleteAllControlSchemes(SerializedObject asset) { var schemes = GetControlSchemesArray(asset); while (schemes.arraySize > 0) schemes.DeleteArrayElementAtIndex(0); } public static int IndexOfControlScheme(SerializedProperty controlSchemeArray, string controlSchemeName) { var serializedControlScheme = controlSchemeArray.FirstOrDefault(sp => sp.FindPropertyRelative(nameof(InputControlScheme.m_Name)).stringValue == controlSchemeName); return serializedControlScheme?.GetIndexOfArrayElement() ?? -1; } public static SerializedProperty GetControlSchemesArray(SerializedObject asset) { return asset.FindProperty(nameof(InputActionAsset.m_ControlSchemes)); } #endregion // Control Schemes } } #endif // UNITY_EDITOR