using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.SceneManagement; namespace Module.NavigationTool.Editor.Toolbar { internal sealed class ScenePickerList { public GUIContent[] labels = new GUIContent[0]; public readonly List scenes = new List(); public readonly List selected = new List(); public void Refresh() { scenes.Clear(); selected.Clear(); var projectSettings = new ToolbarProjectSettings(); projectSettings.Load(); var settings = projectSettings.GetValueAs(); List assets = GetWorkingSet(); FetchAllScenesFromBuildSettings(assets); FetchAllScenesFromSceneGroups(assets, settings.sceneGroups); FetchAllRemainingScenes(assets); FetchAllLabels(); FetchAllSelectedIndices(); } private void FetchAllScenesFromBuildSettings(List assets) { for (var i = 0; i < assets.Count; i++) { WorkingSetScene asset = assets[i]; if (!asset.inBuildSettings) continue; scenes.Add(new SceneElement { name = asset.name, shortname = asset.shortname, paths = { asset.path } }); } } private void FetchAllScenesFromSceneGroups(List assets, SceneGroupArray sceneGroups) { for (var i = 0; i < sceneGroups.Count; i++) { SceneGroup sceneGroup = sceneGroups[i]; string name = sceneGroup.name; List filteredScenes; if (sceneGroup.filterType == ESceneGroupFilterType.AssetLabels) { filteredScenes = GetAllWorkingSetScenesWithLabels(assets, sceneGroup.filters); } else if (sceneGroup.filterType == ESceneGroupFilterType.NameContains) { filteredScenes = GetAllWorkingSetScenesWithNameContaining(assets, sceneGroup.filters); } else { filteredScenes = GetAllWorkingSetScenesWithPathContaining(assets, sceneGroup.filters); name = sceneGroup.name.Replace("/", "\\"); } if (sceneGroup.behaviourType == ESceneGroupBehaviourType.LoadAsGroup) { if (sceneGroups.Count != 0) scenes.Add(new SceneElement{ includeAsSelectable = false }); if (!string.IsNullOrEmpty(sceneGroup.optionalMainScenePath)) SetWorkingSceneAsFirst(filteredScenes, sceneGroup.optionalMainScenePath); filteredScenes = FilterToUniques(filteredScenes); var scene = new SceneElement { name = name, shortname = name, isGroup = true }; for (var j = 0; j < filteredScenes.Count; j++) { scene.paths.Add(filteredScenes[j].path); } scene.name += $" ({scene.paths.Count})"; scenes.Add(scene); } else if (sceneGroup.behaviourType == ESceneGroupBehaviourType.SortAsGroup) { filteredScenes = FilterToUniques(filteredScenes); filteredScenes = FilterAllExcept(filteredScenes, scenes); if (filteredScenes.Count != 0) scenes.Add(new SceneElement{ includeAsSelectable = false }); for (var j = 0; j < filteredScenes.Count; j++) { scenes.Add(new SceneElement { name = filteredScenes[j].name, shortname = filteredScenes[j].shortname, paths = { filteredScenes[j].path } }); } } else if (sceneGroup.behaviourType == ESceneGroupBehaviourType.SortInSubmenuAsGroup) { if (string.IsNullOrEmpty(sceneGroup.subMenuPath)) continue; filteredScenes = FilterToUniques(filteredScenes); filteredScenes = FilterAllExcept(filteredScenes, scenes); if (filteredScenes.Count != 0) scenes.Add(new SceneElement{ includeAsSelectable = false }); for (var j = 0; j < filteredScenes.Count; j++) { scenes.Add(new SceneElement { name = $"{sceneGroup.subMenuPath}/{filteredScenes[j].name}", shortname = filteredScenes[j].shortname, paths = { filteredScenes[j].path } }); } } } } private void FetchAllRemainingScenes(List assets) { List filteredScenes = FilterAllExcept(assets, scenes); if (filteredScenes.Count != 0) scenes.Add(new SceneElement{ includeAsSelectable = false }); for (var i = 0; i < filteredScenes.Count; i++) { scenes.Add(new SceneElement { name = filteredScenes[i].name, shortname = filteredScenes[i].shortname, paths = { filteredScenes[i].path } }); } } private static List FilterToUniques(List list) { var uniques = new List(list.Count); for (var i = 0; i < list.Count; i++) { if (!uniques.Contains(list[i])) uniques.Add(list[i]); } return uniques; } private static List FilterAllExcept(List list, List except, bool includeGroups = false) { var filtered = new List(list.Count); for (var i = 0; i < list.Count; i++) { var contains = false; for (var j = 0; j < except.Count; j++) { if (except[j].isGroup && !includeGroups) continue; if (!except[j].paths.Contains(list[i].path)) continue; contains = true; break; } if (!contains) filtered.Add(list[i]); } return filtered; } private void FetchAllLabels() { labels = new GUIContent[scenes.Count]; for (var i = 0; i < scenes.Count; i++) { labels[i] = new GUIContent(scenes[i].name); } } private void FetchAllSelectedIndices() { for (var i = 0; i < SceneManager.sceneCount; i++) { Scene scene = SceneManager.GetSceneAt(i); if (string.IsNullOrEmpty(scene.path)) continue; int index = IndexOfPath(scenes, scene.path, false); if (index != -1) selected.Add(index); } } private static List GetAllWorkingSetScenesWithLabels(List assets, List labels) { var list = new List(); for (var i = 0; i < labels.Count; i++) { list.AddRange(GetAllWorkingSetScenesWithLabel(assets, labels[i])); } return list; } private static List GetAllWorkingSetScenesWithLabel(List assets, string label) { var list = new List(); for (var i = 0; i < assets.Count; i++) { if (assets[i].labels.Contains(label)) list.Add(assets[i]); } return list; } private static List GetAllWorkingSetScenesWithNameContaining(List assets, List filters) { var list = new List(); for (var i = 0; i < assets.Count; i++) { for (var j = 0; j < filters.Count; j++) { if (!assets[i].shortname.Contains(filters[j])) continue; list.Add(assets[i]); break; } } return list; } private static List GetAllWorkingSetScenesWithPathContaining(List assets, List filters) { var list = new List(); for (var i = 0; i < assets.Count; i++) { for (var j = 0; j < filters.Count; j++) { if (!PathContains(assets[i].path, filters[j])) continue; list.Add(assets[i]); break; } } return list; } private static bool IsValidScene(string path) { if (string.IsNullOrEmpty(path)) return false; if (!path.StartsWith("Assets")) return false; return AssetDatabase.LoadAssetAtPath(path) != null; } public int IndexOfPath(string path, bool includeGroups) { return IndexOfPath(scenes, path, includeGroups); } private static int IndexOfPath(List list, string path, bool includeGroups) { for (var i = 0; i < list.Count; i++) { if (!includeGroups && list[i].isGroup) continue; if (!list[i].includeAsSelectable) continue; if (list[i].paths.IndexOf(path) != -1) return i; } return -1; } private static bool PathContains(string path, string subpath) { path = path.Replace("\\", "/"); subpath = subpath.Replace("\\", "/"); return path.Contains(subpath); } private static bool PathEquals(string path, string subpath) { path = path.Replace("\\", "/"); subpath = subpath.Replace("\\", "/"); return path.Equals(subpath); } private static void SetWorkingSceneAsFirst(List scenes, string path) { int index = -1; for (var i = 0; i < scenes.Count; i++) { if (!PathEquals(scenes[i].path, path)) continue; index = i; break; } if (index == -1) return; WorkingSetScene temp = scenes[0]; scenes[0] = scenes[index]; scenes[index] = temp; } private static List GetWorkingSet() { var workingSet = new List(); string[] guids = AssetDatabase.FindAssets("t:scene"); EditorBuildSettingsScene[] buildSettingsScenes = EditorBuildSettings.scenes; for (var i = 0; i < guids.Length; i++) { string path = AssetDatabase.GUIDToAssetPath(guids[i]); if (!IsValidScene(path)) continue; var asset = AssetDatabase.LoadAssetAtPath(path); string sceneName = path.Substring(7, path.Length - 13).Replace('/', '\\'); var inBuildSettings = false; for (var j = 0; j < buildSettingsScenes.Length; j++) { if (!buildSettingsScenes[j].path.Equals(path)) continue; inBuildSettings = true; break; } workingSet.Add(new WorkingSetScene { path = path, name = sceneName, shortname = Path.GetFileNameWithoutExtension(path), labels = AssetDatabase.GetLabels(asset), inBuildSettings = inBuildSettings }); } return workingSet; } public sealed class SceneElement { public string name; public string shortname; public bool includeAsSelectable = true; public bool isGroup; public readonly List paths = new List(); } private sealed class WorkingSetScene { public string path; public string name; public string shortname; public string[] labels; public bool inBuildSettings; } } }