/// <summary> /// Returns all instances matching pathName /// </summary> /// <param name="pathName">is interpreted as either a path or a name</param> /// <returns></returns> /// <remarks> /// Counterpart to GameObject.Find(string) this returns all matching instances, instead of one. /// Names beginning with '/' indicate objects at the root of the scene. /// Names including '/' will be traversed as a path beginning from the first matched object. /// </remarks> public static GameObject[] FindAll(string pathName) { // PROBLEM: Actually, empty names are allowed... // Empty object names are not allowed if (pathName.Length == 0) { return(new GameObject[0]); } // This behavior is consistent with GameObject.Find() if (pathName[0] != '/') { return(NameFind(pathName)); } // Search by path // IMPORTANT: PathName names begin with the name of a GameObject, so "/" is dropped var path = new PathName(pathName.Substring(1), PathName.PathStep.Step.Path); var transformList = path.Find(); var gameObjectList = new List <GameObject>(); foreach (var transform in transformList) { gameObjectList.Add(transform.gameObject); } return(gameObjectList.ToArray()); }
// TODO: Find should identify unique instance // TODO: Check if path is unique for object (with optional root) // TODO: Implement forcing of unique name for GameObject // - Rename GameObject // - Rename all identically named siblings // TODO: This should return GameObjects /// <returns>the GameObjects specified by this path relative to parent</returns> /// <remarks> /// If the path matches no GameObjects the returned array will be empty. /// An empty path matches parent. /// A null parent matches active scene. /// </remarks> public Transform[] Find(Transform parent = null) { if (path.Count == 0) { return new Transform[] { parent } } ; List <Transform> findList = new List <Transform>(); Transform[] childList = TransformExtensions.Children(parent); foreach (var childItem in childList) { // Evaluate match var lhsRecurse = Clone() as PathName; var rhsRecurse = new PathName(childItem.name, PathStep.Step.Name); if (!MatchStep(lhsRecurse, rhsRecurse)) { continue; } findList.AddRange(lhsRecurse.Find(childItem)); } return(findList.ToArray()); }
public EditGameObject(GameObject gameObject) { if (useEditorAction) { #if UNITY_EDITOR editObjectType = GetGameObjectType(gameObject); switch (editObjectType) { case GameObjectType.Persistent: { // Load as PreviewScene Object editAssetPath = AssetDatabase.GetAssetPath(gameObject); editPrefab = PrefabUtility.LoadPrefabContents(editAssetPath); editObject = editPrefab; break; } case GameObjectType.Connected: { // Path to gameObject relative to prefab // NOTE: EditorSceneManager.IsPreviewSceneObject(editPrefab) == true var prefab = PrefabUtility.GetNearestPrefabInstanceRoot(gameObject); var path = new PathName(gameObject, prefab); // Instantiate a copy of prefab and locate copy of gameObject var asset = PrefabUtility.GetCorrespondingObjectFromSource(prefab); editAssetPath = AssetDatabase.GetAssetPath(asset); editPrefab = PrefabUtility.InstantiatePrefab(asset) as GameObject; var editObjectList = path.Find(editPrefab.transform); if (editObjectList.Length == 1) { editObject = editObjectList[0].gameObject; } break; } case GameObjectType.Instance: editObject = gameObject; break; } if (Application.isBatchMode) { EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); } else { // QUESTION: Does this work for prefabs? Undo.RecordObject(gameObject, $"Edit {gameObject.name}"); } #endif } else { editObject = gameObject; } }
/// <returns>an array of all GameObjects identified by path from parent</returns> /// <remarks> /// When parent = null the search begins with the scene root. /// </remarks> public static GameObject[] PathFind(PathName path, Transform parent = null) { var transformList = path.Find(parent); var gameObjectList = new List <GameObject>(); foreach (var transform in transformList) { gameObjectList.Add(transform.gameObject); } return(gameObjectList.ToArray()); }
/// <returns>deep-copy yielding an independent instance of path</returns> public object Clone() { var pathName = new PathName(); pathName.path = new List <PathStep>(); foreach (var step in path) { pathName.path.Add(step.Clone() as PathStep); } return(pathName); }
public static void ApplyTo(GameObject gameObject) { using (var editScope = new EP.EditGameObject(gameObject)) { var editObject = editScope.editObject; // Gather all MeshRenderer components that are leaf nodes // NOTE: Prefab components will be managed by applying AutoLOD to the prefab var children = editObject.Children(true); var groups = new Dictionary <PathName, List <GameObject> >(); foreach (var child in children) { // Only gather leaf node renderers if (child.transform.childCount > 0) { continue; } // AutoLOD will be applied to child prefabs separately var childPrefab = PrefabUtility.GetNearestPrefabInstanceRoot(child); if (childPrefab != null && childPrefab != editObject) { continue; } var hasRenderer = child.GetComponent <Renderer>(); if (!hasRenderer) { continue; } // MeshRenderers will be managed by LODGroup var inGroup = child.GetComponentInParent <LODGroup>(); if (inGroup) { continue; } // Add renderer to group var path = new PathName(child); if (!groups.ContainsKey(path)) { groups.Add(path, new List <GameObject>()); } groups[path].Add(child); } // Combine group renderers under LODGroup managers foreach (var pathGroup in groups) { MergeGroup(pathGroup.Key, pathGroup.Value); } } }
/// <returns>an array of all GameObject matching name that are children of parent</returns> /// <remarks> /// Unlike Transform.Find() path separators are assumed to be a part of the name. /// When parent = null the search begins with the scene root. /// </remarks> public static GameObject[] NameFind(PathName name, Transform parent = null, bool recurse = false) { List <GameObject> findList = new List <GameObject>(); Transform[] childList = TransformExtensions.Children(parent); foreach (var child in childList) { if (name == child.name) { findList.Add(child.gameObject); } if (recurse) { findList.AddRange(NameFind(name, child, recurse)); } } return(findList.ToArray()); }
// WARNING: If there is a name override, PathName will not resolve! // TODO: Find a way to clone prefab with overrides intact. // QUESTION: Is there a way to accomplish instatiation using object serializations? // Ideally, this would handle the connection and override persistence. // https://docs.unity3d.com/ScriptReference/SerializedObject.html // Construct, then iterate & copy? static GameObject InstantiateChild(GameObject original, GameObject parent) { GameObject child = null; // IMPORTANT: PrefabUtility.InstantiatePrefab applies only to assets, not to instances // IMPORTANT: PrefabUtility.GetCorrespondingObjectFromSource applies only to instances, not to assets var asset = PrefabUtility.GetCorrespondingObjectFromSource(parent); GameObject copy_asset = null; if (asset) { copy_asset = asset; } else { copy_asset = original; } var copy = PrefabUtility.InstantiatePrefab(copy_asset) as GameObject; if (parent != original) { var path = new PathName(original, parent); var find = path.Find(copy.transform); if (find.Length == 1) { child = find[0].gameObject; // Unpack to enable orphaning, only once since nearest root was instantiated var unpack = PrefabUtility.GetOutermostPrefabInstanceRoot(child); while (unpack) { PrefabUtility.UnpackPrefabInstance(unpack, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction); unpack = PrefabUtility.GetOutermostPrefabInstanceRoot(child); } child.transform.SetParent(null); } EP.Destroy(copy); } else { child = copy; } return(child); }
/// <returns>an array of all GameObjects identified by path from this</returns> public static GameObject[] PathFindInChildren(this GameObject gameObject, PathName pathName) { return(PathFind(pathName, gameObject?.transform)); }
/// <returns>an array of all GameObjects identified by path from this</returns> public static GameObject[] PathFindInChildren(this Component component, PathName pathName) { return(PathFind(pathName, component?.transform)); }
/// <returns>an array of all child GameObjects matching name</returns> public static GameObject[] NameFindInChildren(this GameObject gameObject, PathName name, bool recurse = false) { return(NameFind(name, gameObject?.transform, recurse)); }
/// <summary> /// Evaluates a single step of a match. /// </summary> /// <returns>false when incompatibility is found</returns> /// <remarks> /// In the case of a match, arguments will be modified according /// to their respective recursion types. /// </remarks> public static bool MatchStep(PathName lhs, PathName rhs) { // Empty path denotes root, so empty paths are equal if (lhs.path.Count == 0 && rhs.path.Count == 0) { return(true); } if (lhs.path.Count == 0 || rhs.path.Count == 0) { return(false); } // Count the matching characters from start of path step name int same = 0; for (int c = 0; c < lhs.path[0].name.Length && c < rhs.path[0].name.Length; ++c) { if (lhs.path[0].name[c] != rhs.path[0].name[c]) { break; } ++same; } switch (lhs.path[0].step) { case PathStep.Step.Name: if (same < lhs.path[0].name.Length) { return(false); } lhs.path.RemoveAt(0); // Recurse by reduction break; case PathStep.Step.Path: if (same < lhs.path[0].name.Length) { if (lhs.path[0].name[same] != '/') { return(false); } lhs.path[0].name = lhs.path[0].name.Substring(same + 1); // Recurse by partition break; } lhs.path.RemoveAt(0); // Recurse by reduction break; } switch (rhs.path[0].step) { case PathStep.Step.Name: if (same < rhs.path[0].name.Length) { return(false); } rhs.path.RemoveAt(0); // Recurse by reduction break; case PathStep.Step.Path: if (same < rhs.path[0].name.Length) { if (rhs.path[0].name[same] != '/') { return(false); } rhs.path[0].name = rhs.path[0].name.Substring(same + 1); // Recurse by partition break; } rhs.path.RemoveAt(0); // Recurse by reduction break; } return(true); }