using Unity.Animations.SpringBones.GameObjectExtensions; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using UnityEditor.Localization.Editor; namespace Unity.Animations.SpringBones { public class FindGameObjectsWindow : EditorWindow { private static class Styles { public static readonly string editorWindowTitle = Localization.Tr("Name Selector Tool"); public static readonly string textName = Localization.Tr("Name"); public static readonly string textIgnore = Localization.Tr("Ignore"); public static readonly string textComponent = Localization.Tr("Component"); public static readonly string textSelect = Localization.Tr("Select"); public static readonly string textSelectingMultipleObjectFormat = Localization.Tr("Selecting {0} objects"); } [MenuItem("Window/Animation/SpringBone/Name Selector Tool")] public static void ShowWindow() { var window = GetWindow(Styles.editorWindowTitle); window.OnShow(); } public static IEnumerable FindObjectsByComponent ( string componentPattern, IEnumerable ignorePatterns = null ) { var gameObjects = GameObjectUtil.GetAllGameObjects(); var matchingObjects = FindObjectsWithComponentByPattern(gameObjects, componentPattern); if (ignorePatterns != null) { foreach (var ignorePattern in ignorePatterns) { matchingObjects = RemoveObjectsByPattern(matchingObjects, ignorePattern); } } return matchingObjects; } public static IEnumerable FindObjectsByPattern ( IEnumerable namePatterns, IEnumerable ignorePatterns, string componentPattern ) { if (namePatterns == null || !namePatterns.Any()) { return !System.String.IsNullOrEmpty(componentPattern) ? FindObjectsByComponent(componentPattern, ignorePatterns) : new List(); } // The first pattern is required // If there any patterns after the first, then at least one must match var requiredPattern = "*" + namePatterns.First() + "*"; var additionalPatterns = namePatterns.Skip(1) .Select(pattern => "*" + pattern + "*") .ToArray(); System.Func isNameAMatch = (name) => { return StringUtil.GlobMatch(name, requiredPattern) && (additionalPatterns.Length == 0 || additionalPatterns.Any(pattern => StringUtil.GlobMatch(name, pattern))); }; var matchingObjects = GameObjectUtil.GetAllGameObjects() .Where(gameObject => isNameAMatch(gameObject.name)); if (ignorePatterns != null) { foreach (var ignorePattern in ignorePatterns) { matchingObjects = RemoveObjectsByPattern(matchingObjects, ignorePattern); } } if (!System.String.IsNullOrEmpty(componentPattern)) { matchingObjects = FindObjectsWithComponentByPattern(matchingObjects, componentPattern); } return matchingObjects; } // private private const int SearchFrameCount = 2; private const string SearchNameField = "SearchNameField"; private string objectPattern = ""; private string ignorePattern = ""; private string componentPattern = ""; private int searchCountdown; private bool isInitialShow; private static bool HasExactComponentMatch(GameObject gameObject, string componentTypeToFind) { var componentTypes = gameObject.GetComponents() .Where(component => component != null) .Select(component => component.GetType().ToString().ToLowerInvariant()); var dottedComponentTypeToFind = "." + componentTypeToFind; return componentTypes.Any(type => type == componentTypeToFind || type.EndsWith(dottedComponentTypeToFind)); } private static IEnumerable FindObjectsWithComponentByPattern ( IEnumerable sourceObjects, string componentPattern ) { // Remove all whitespace string[] whitespaceList = { " ", "\t", " ", "\r", "\n" }; foreach (var whitespaceString in whitespaceList) { componentPattern = componentPattern.Replace(whitespaceString, ""); } // For components, if there are any objects whose components match exactly (whole word), // then only return those componentPattern = componentPattern.ToLowerInvariant(); var exactMatches = sourceObjects.Where(gameObject => HasExactComponentMatch(gameObject, componentPattern)); if (exactMatches.Any()) { return exactMatches; } // Otherwise do a find-anywhere search componentPattern = "*" + componentPattern + "*"; return sourceObjects .Where(gameObject => gameObject.GetComponents().Any( component => component != null && StringUtil.GlobMatch(component.GetType().ToString(), componentPattern))); } private static IEnumerable RemoveObjectsByPattern ( IEnumerable sourceObjects, string removePattern ) { removePattern = "*" + removePattern + "*"; return sourceObjects .Where(gameObject => !StringUtil.GlobMatch(gameObject.name, removePattern)); } private void ReacquireData() { if (objectPattern == null) { objectPattern = ""; } if (ignorePattern == null) { ignorePattern = ""; } if (componentPattern == null) { componentPattern = ""; } } private void FindAndSelectObjects() { const string WhitespaceString = " \t\r\n "; var whitespaceList = WhitespaceString.ToCharArray(); var objectPatterns = objectPattern.Split(whitespaceList, System.StringSplitOptions.RemoveEmptyEntries); var ignorePatterns = ignorePattern.Split(whitespaceList, System.StringSplitOptions.RemoveEmptyEntries); Selection.objects = FindObjectsByPattern(objectPatterns, ignorePatterns, componentPattern).ToArray(); searchCountdown = 0; } private void RestartSearchCountdown() { searchCountdown = SearchFrameCount; } private static void SetRectYPositions(IList rects, float yPosition) { for (var rectIndex = 0; rectIndex < rects.Count; ++rectIndex) { var rect = rects[rectIndex]; rect.y = yPosition; rects[rectIndex] = rect; } } private static void DoTextFieldWithClearButton ( ref TextFieldPositions fieldPositions, string label, ref string textItem, System.Action onTextChanged, string textFieldControlName = null ) { GUI.Label(fieldPositions.GetLabelRect(), label); if (textFieldControlName != null) { GUI.SetNextControlName(textFieldControlName); } var newText = GUI.TextField(fieldPositions.GetFieldRect(), textItem); if (newText != textItem) { textItem = newText; onTextChanged(); } if (GUI.Button(fieldPositions.GetButtonRect(), "×") && textItem.Length > 0) { textItem = ""; onTextChanged(); } } private void OnSelectionChange() { Repaint(); } private void OnInspectorUpdate() { if (searchCountdown > 0) { searchCountdown--; if (searchCountdown == 0) { FindAndSelectObjects(); } } } private static Rect[] GetRowRects(float x, float y, IList widths, float height, float spacing) { var rectCount = widths.Count; var rects = new Rect[rectCount]; for (int rectIndex = 0; rectIndex < rectCount; rectIndex++) { rects[rectIndex] = new Rect(x, y, widths[rectIndex], height); x += widths[rectIndex] + spacing; } return rects; } private struct TextFieldPositions { public float x; public float y; public float labelWidth; public float fieldWidth; public float buttonWidth; public float height; public float spacing; public Rect GetLabelRect() { return new Rect(x, y, labelWidth, height); } public Rect GetFieldRect() { return new Rect(x + labelWidth + spacing, y, fieldWidth, height); } public Rect GetButtonRect() { return new Rect(x + labelWidth + fieldWidth + spacing * 2f, y, buttonWidth, height); } } private static TextFieldPositions GetTextFieldPositions(float x, float y, float rowWidth, float rowHeight, float spacing) { const int LabelMinWidth = 40; const int LabelMaxWidth = 120; const float ButtonWidth = 20f; var labelWidth = Mathf.Clamp(rowWidth * 0.25f, LabelMinWidth, LabelMaxWidth); return new TextFieldPositions { x = x, y = y, labelWidth = labelWidth, fieldWidth = rowWidth - labelWidth - ButtonWidth - spacing * 2f, buttonWidth = ButtonWidth, height = rowHeight, spacing = spacing }; } private void OnGUI() { ReacquireData(); const float RowHeight = 30f; const float Spacing = 8f; const float RowOffset = RowHeight + Spacing; var rowWidth = position.width - Spacing * 2f; var fieldPositions = GetTextFieldPositions(Spacing, Spacing, rowWidth, RowHeight, Spacing); DoTextFieldWithClearButton(ref fieldPositions, Styles.textName, ref objectPattern, RestartSearchCountdown, SearchNameField); fieldPositions.y += RowOffset; DoTextFieldWithClearButton(ref fieldPositions, Styles.textIgnore, ref ignorePattern, RestartSearchCountdown); fieldPositions.y += RowOffset; DoTextFieldWithClearButton(ref fieldPositions, Styles.textComponent, ref componentPattern, RestartSearchCountdown); fieldPositions.y += RowOffset; var rowRect = new Rect(fieldPositions.x, fieldPositions.y, rowWidth, RowHeight); if (GUI.Button(rowRect, Styles.textSelect)) { FindAndSelectObjects(); } rowRect.y += RowOffset; var message = string.Format(Styles.textSelectingMultipleObjectFormat, Selection.objects.Length); GUI.Label(rowRect, message); if (isInitialShow) { EditorGUI.FocusTextInControl(SearchNameField); isInitialShow = false; } } private void OnShow() { searchCountdown = 0; isInitialShow = true; } } }