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