488 lines
15 KiB
C#
488 lines
15 KiB
C#
#if UNITY_EDITOR
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
[InitializeOnLoad]
|
|
public static class SceneSwitcherToolbar
|
|
{
|
|
private const string NotInBuildSuffix = " (not in build)";
|
|
private const string FetchAllScenesKey = "SceneSwitcher_FetchAllScenes";
|
|
|
|
private static string[] sceneNames = new string[0];
|
|
private static int selectedIndex = 0;
|
|
private static string lastActiveScene = string.Empty;
|
|
private static VisualElement toolbarUI;
|
|
|
|
private static int lastToolbarInstanceId = -1;
|
|
private static double nextToolbarCheckTime = 0d;
|
|
private const double ToolbarCheckInterval = 0.25d;
|
|
|
|
private static bool fetchAllScenes
|
|
{
|
|
get => EditorPrefs.GetBool(FetchAllScenesKey, false);
|
|
set => EditorPrefs.SetBool(FetchAllScenesKey, value);
|
|
}
|
|
|
|
static SceneSwitcherToolbar()
|
|
{
|
|
RefreshSceneList();
|
|
SelectCurrentScene();
|
|
|
|
EditorSceneManager.activeSceneChangedInEditMode += (_, __) => UpdateSceneSelection();
|
|
EditorApplication.playModeStateChanged += OnPlayModeChanged;
|
|
EditorApplication.update += EnsureToolbarExists;
|
|
|
|
AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload;
|
|
|
|
EditorApplication.delayCall += AddToolbarUI;
|
|
}
|
|
|
|
private static void OnAfterAssemblyReload()
|
|
{
|
|
EditorApplication.delayCall += AddToolbarUI;
|
|
}
|
|
|
|
private static void EnsureToolbarExists()
|
|
{
|
|
if (EditorApplication.timeSinceStartup < nextToolbarCheckTime)
|
|
return;
|
|
|
|
nextToolbarCheckTime = EditorApplication.timeSinceStartup + ToolbarCheckInterval;
|
|
|
|
var toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
|
|
if (toolbarType == null)
|
|
return;
|
|
|
|
var toolbars = Resources.FindObjectsOfTypeAll(toolbarType);
|
|
if (toolbars == null || toolbars.Length == 0)
|
|
return;
|
|
|
|
foreach (var toolbar in toolbars)
|
|
{
|
|
TryInjectIntoToolbar(toolbarType, toolbar);
|
|
}
|
|
}
|
|
private static void TryInjectIntoToolbar(System.Type toolbarType, UnityEngine.Object toolbar)
|
|
{
|
|
var rootField = toolbarType.GetField("m_Root", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (rootField == null)
|
|
return;
|
|
|
|
if (rootField.GetValue(toolbar) is not VisualElement root)
|
|
return;
|
|
|
|
var leftContainer = root.Q("ToolbarZoneLeftAlign");
|
|
if (leftContainer == null)
|
|
return;
|
|
|
|
var existing = leftContainer.Q<IMGUIContainer>("SceneSwitcherToolbar");
|
|
if (existing != null)
|
|
{
|
|
toolbarUI = existing;
|
|
return;
|
|
}
|
|
|
|
var newToolbarUI = new IMGUIContainer(DrawToolbarGUI);
|
|
newToolbarUI.name = "SceneSwitcherToolbar";
|
|
newToolbarUI.style.flexGrow = 0;
|
|
newToolbarUI.style.flexShrink = 1;
|
|
newToolbarUI.style.minWidth = 80;
|
|
newToolbarUI.style.maxWidth = 320;
|
|
newToolbarUI.style.marginLeft = 4;
|
|
newToolbarUI.style.marginRight = 4;
|
|
newToolbarUI.style.overflow = Overflow.Hidden;
|
|
|
|
leftContainer.Add(newToolbarUI);
|
|
toolbarUI = newToolbarUI;
|
|
}
|
|
|
|
private static void AddToolbarUI()
|
|
{
|
|
var toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
|
|
if (toolbarType == null)
|
|
return;
|
|
|
|
var toolbars = Resources.FindObjectsOfTypeAll(toolbarType);
|
|
if (toolbars == null || toolbars.Length == 0)
|
|
return;
|
|
|
|
foreach (var toolbar in toolbars)
|
|
{
|
|
TryInjectIntoToolbar(toolbarType, toolbar);
|
|
}
|
|
}
|
|
|
|
private static void DrawToolbarGUI()
|
|
{
|
|
CheckAndRefreshScenes();
|
|
|
|
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
|
|
|
if (sceneNames == null || sceneNames.Length == 0)
|
|
{
|
|
GUILayout.Label("No scenes", EditorStyles.toolbarButton, GUILayout.Width(100), GUILayout.Height(18));
|
|
GUILayout.EndHorizontal();
|
|
return;
|
|
}
|
|
|
|
if (selectedIndex < 0 || selectedIndex >= sceneNames.Length)
|
|
selectedIndex = 0;
|
|
|
|
bool isPlaying = EditorApplication.isPlaying;
|
|
bool currentSceneNotInBuild =
|
|
selectedIndex >= 0 &&
|
|
selectedIndex < sceneNames.Length &&
|
|
sceneNames[selectedIndex].EndsWith(NotInBuildSuffix);
|
|
|
|
float viewWidth = Mathf.Max(120f, EditorGUIUtility.currentViewWidth);
|
|
|
|
const float toggleWidth = 22f;
|
|
const float addWidth = 22f;
|
|
const float spacing = 0f;
|
|
const float controlHeight = 18f;
|
|
|
|
// Порог узкого режима
|
|
bool compactMode = viewWidth < 1400f;
|
|
|
|
float sceneButtonWidth = compactMode
|
|
? 22f
|
|
: Mathf.Clamp(
|
|
viewWidth - toggleWidth - addWidth - 10f,
|
|
90f,
|
|
190f
|
|
);
|
|
|
|
EditorGUI.BeginDisabledGroup(isPlaying);
|
|
|
|
bool newFetchAllScenes = GUILayout.Toggle(
|
|
fetchAllScenes,
|
|
new GUIContent("A", "Toggle All Scenes / Build Settings scenes"),
|
|
EditorStyles.toolbarButton,
|
|
GUILayout.Width(toggleWidth),
|
|
GUILayout.Height(controlHeight)
|
|
);
|
|
|
|
if (newFetchAllScenes != fetchAllScenes)
|
|
{
|
|
fetchAllScenes = newFetchAllScenes;
|
|
RefreshSceneList();
|
|
SelectCurrentScene();
|
|
GUIUtility.ExitGUI();
|
|
}
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
GUILayout.Space(spacing);
|
|
|
|
EditorGUI.BeginDisabledGroup(isPlaying || fetchAllScenes);
|
|
|
|
if (GUILayout.Button(
|
|
new GUIContent("+", "Add current scene to Build Settings"),
|
|
EditorStyles.toolbarButton,
|
|
GUILayout.Width(addWidth),
|
|
GUILayout.Height(controlHeight)))
|
|
{
|
|
AddCurrentSceneToBuildSettings();
|
|
GUIUtility.ExitGUI();
|
|
}
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
GUILayout.Space(spacing);
|
|
|
|
EditorGUI.BeginDisabledGroup(isPlaying);
|
|
|
|
Rect sceneButtonRect = GUILayoutUtility.GetRect(
|
|
GUIContent.none,
|
|
GetSceneButtonStyle(currentSceneNotInBuild),
|
|
GUILayout.Width(sceneButtonWidth),
|
|
GUILayout.Height(controlHeight)
|
|
);
|
|
|
|
string buttonText = compactMode ? "▼" : GetDisplaySceneName();
|
|
string tooltip = compactMode ? "Open scene list" : "Current scene";
|
|
|
|
if (GUI.Button(
|
|
sceneButtonRect,
|
|
new GUIContent(buttonText, tooltip),
|
|
compactMode ? EditorStyles.toolbarButton : GetSceneButtonStyle(currentSceneNotInBuild)))
|
|
{
|
|
ShowSceneMenu(sceneButtonRect);
|
|
}
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
private static GUIStyle GetSceneButtonStyle(bool highlight)
|
|
{
|
|
var style = new GUIStyle(EditorStyles.toolbarDropDown)
|
|
{
|
|
alignment = TextAnchor.MiddleLeft,
|
|
fixedHeight = 20,
|
|
clipping = TextClipping.Clip,
|
|
imagePosition = ImagePosition.TextOnly,
|
|
padding = new RectOffset(6, 18, 0, 0),
|
|
margin = new RectOffset(0, 0, 0, 0),
|
|
fontStyle = highlight ? FontStyle.Italic : FontStyle.Normal
|
|
};
|
|
|
|
if (highlight)
|
|
style.normal.textColor = new Color(1f, 0.82f, 0.35f);
|
|
|
|
return style;
|
|
}
|
|
|
|
private static string GetDisplaySceneName()
|
|
{
|
|
if (sceneNames == null || sceneNames.Length == 0)
|
|
return "No scenes";
|
|
|
|
if (selectedIndex < 0 || selectedIndex >= sceneNames.Length)
|
|
return "No scene";
|
|
|
|
return sceneNames[selectedIndex];
|
|
}
|
|
|
|
private static void ShowSceneMenu(Rect buttonRect)
|
|
{
|
|
var menu = new GenericMenu();
|
|
|
|
string currentDisplayName = GetDisplaySceneName();
|
|
|
|
foreach (string displayName in sceneNames)
|
|
{
|
|
string cleanName = StripNotInBuildSuffix(displayName);
|
|
bool isCurrent = displayName == currentDisplayName;
|
|
|
|
menu.AddItem(new GUIContent(cleanName), isCurrent, () =>
|
|
{
|
|
OpenSceneByName(cleanName, additive: false);
|
|
});
|
|
}
|
|
|
|
buttonRect.y += buttonRect.height - 10f;
|
|
menu.DropDown(buttonRect);
|
|
}
|
|
|
|
private static void PingSceneAsset(string scenePath)
|
|
{
|
|
var asset = AssetDatabase.LoadAssetAtPath<SceneAsset>(scenePath);
|
|
if (asset != null)
|
|
{
|
|
EditorGUIUtility.PingObject(asset);
|
|
Selection.activeObject = asset;
|
|
}
|
|
}
|
|
|
|
private static string StripNotInBuildSuffix(string displaySceneName)
|
|
{
|
|
return displaySceneName.EndsWith(NotInBuildSuffix)
|
|
? displaySceneName.Substring(0, displaySceneName.Length - NotInBuildSuffix.Length)
|
|
: displaySceneName;
|
|
}
|
|
|
|
private static void OpenSceneByName(string sceneName, bool additive)
|
|
{
|
|
string scenePath = FindScenePathByName(sceneName, fetchAllScenes);
|
|
if (string.IsNullOrEmpty(scenePath))
|
|
{
|
|
Debug.LogError("Scene not found: " + sceneName);
|
|
return;
|
|
}
|
|
|
|
if (!additive)
|
|
{
|
|
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
|
|
return;
|
|
|
|
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
|
|
}
|
|
else
|
|
{
|
|
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
|
|
return;
|
|
|
|
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
|
|
}
|
|
}
|
|
|
|
private static string FindScenePathByName(string sceneName, bool searchAllAssets)
|
|
{
|
|
if (searchAllAssets)
|
|
{
|
|
return Directory.GetFiles("Assets", "*.unity", SearchOption.AllDirectories)
|
|
.FirstOrDefault(path => Path.GetFileNameWithoutExtension(path) == sceneName);
|
|
}
|
|
|
|
return EditorBuildSettings.scenes
|
|
.FirstOrDefault(scene =>
|
|
scene.enabled &&
|
|
Path.GetFileNameWithoutExtension(scene.path) == sceneName
|
|
)?.path;
|
|
}
|
|
|
|
private static void AddCurrentSceneToBuildSettings()
|
|
{
|
|
string currentScenePath = EditorSceneManager.GetActiveScene().path;
|
|
|
|
if (string.IsNullOrEmpty(currentScenePath))
|
|
{
|
|
EditorUtility.DisplayDialog(
|
|
"Scene Not Saved",
|
|
"Please save the scene before adding it to Build Settings.",
|
|
"OK"
|
|
);
|
|
return;
|
|
}
|
|
|
|
var buildScenes = EditorBuildSettings.scenes.ToList();
|
|
int existingIndex = buildScenes.FindIndex(s => s.path == currentScenePath);
|
|
|
|
if (existingIndex >= 0)
|
|
{
|
|
if (!buildScenes[existingIndex].enabled)
|
|
{
|
|
buildScenes[existingIndex] = new EditorBuildSettingsScene(currentScenePath, true);
|
|
EditorBuildSettings.scenes = buildScenes.ToArray();
|
|
Debug.Log($"<color=green>Scene '{Path.GetFileNameWithoutExtension(currentScenePath)}' enabled in Build Settings.</color>");
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"Scene '{Path.GetFileNameWithoutExtension(currentScenePath)}' is already in Build Settings.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buildScenes.Add(new EditorBuildSettingsScene(currentScenePath, true));
|
|
EditorBuildSettings.scenes = buildScenes.ToArray();
|
|
Debug.Log($"<color=green>Scene '{Path.GetFileNameWithoutExtension(currentScenePath)}' added to Build Settings.</color>");
|
|
}
|
|
|
|
RefreshSceneList();
|
|
SelectCurrentScene();
|
|
}
|
|
|
|
private static void RefreshSceneList()
|
|
{
|
|
if (fetchAllScenes)
|
|
{
|
|
var buildSceneSet = new HashSet<string>(
|
|
EditorBuildSettings.scenes
|
|
.Where(scene => scene.enabled)
|
|
.Select(scene => Path.GetFileNameWithoutExtension(scene.path))
|
|
);
|
|
|
|
sceneNames = Directory.GetFiles("Assets", "*.unity", SearchOption.AllDirectories)
|
|
.Select(Path.GetFileNameWithoutExtension)
|
|
.Where(n => !string.IsNullOrWhiteSpace(n))
|
|
.Distinct()
|
|
.OrderBy(n => n)
|
|
.Select(name => buildSceneSet.Contains(name) ? name : name + NotInBuildSuffix)
|
|
.ToArray();
|
|
}
|
|
else
|
|
{
|
|
sceneNames = EditorBuildSettings.scenes
|
|
.Where(scene => scene.enabled)
|
|
.Select(scene => Path.GetFileNameWithoutExtension(scene.path))
|
|
.Where(n => !string.IsNullOrWhiteSpace(n))
|
|
.Distinct()
|
|
.OrderBy(n => n)
|
|
.ToArray();
|
|
}
|
|
}
|
|
|
|
private static void CheckAndRefreshScenes()
|
|
{
|
|
string[] currentScenes;
|
|
|
|
if (fetchAllScenes)
|
|
{
|
|
var buildSceneSet = new HashSet<string>(
|
|
EditorBuildSettings.scenes
|
|
.Where(scene => scene.enabled)
|
|
.Select(scene => Path.GetFileNameWithoutExtension(scene.path))
|
|
);
|
|
|
|
currentScenes = Directory.GetFiles("Assets", "*.unity", SearchOption.AllDirectories)
|
|
.Select(Path.GetFileNameWithoutExtension)
|
|
.Where(n => !string.IsNullOrWhiteSpace(n))
|
|
.Distinct()
|
|
.OrderBy(n => n)
|
|
.Select(name => buildSceneSet.Contains(name) ? name : name + NotInBuildSuffix)
|
|
.ToArray();
|
|
}
|
|
else
|
|
{
|
|
currentScenes = EditorBuildSettings.scenes
|
|
.Where(scene => scene.enabled)
|
|
.Select(scene => Path.GetFileNameWithoutExtension(scene.path))
|
|
.Where(n => !string.IsNullOrWhiteSpace(n))
|
|
.Distinct()
|
|
.OrderBy(n => n)
|
|
.ToArray();
|
|
}
|
|
|
|
if (!currentScenes.SequenceEqual(sceneNames))
|
|
{
|
|
sceneNames = currentScenes;
|
|
SelectCurrentScene();
|
|
}
|
|
}
|
|
|
|
private static void SelectCurrentScene()
|
|
{
|
|
string currentScene = Path.GetFileNameWithoutExtension(EditorSceneManager.GetActiveScene().path);
|
|
|
|
if (string.IsNullOrEmpty(currentScene))
|
|
{
|
|
selectedIndex = 0;
|
|
lastActiveScene = string.Empty;
|
|
return;
|
|
}
|
|
|
|
int index = System.Array.FindIndex(sceneNames, n => StripNotInBuildSuffix(n) == currentScene);
|
|
|
|
if (index >= 0)
|
|
{
|
|
selectedIndex = index;
|
|
}
|
|
else
|
|
{
|
|
string displayName = currentScene + NotInBuildSuffix;
|
|
sceneNames = new[] { displayName }.Concat(sceneNames).ToArray();
|
|
selectedIndex = 0;
|
|
}
|
|
|
|
lastActiveScene = currentScene;
|
|
}
|
|
|
|
private static void UpdateSceneSelection()
|
|
{
|
|
string currentScene = Path.GetFileNameWithoutExtension(EditorSceneManager.GetActiveScene().path);
|
|
if (currentScene != lastActiveScene)
|
|
{
|
|
SelectCurrentScene();
|
|
}
|
|
}
|
|
|
|
private static void OnPlayModeChanged(PlayModeStateChange state)
|
|
{
|
|
if (state == PlayModeStateChange.EnteredPlayMode ||
|
|
state == PlayModeStateChange.ExitingPlayMode ||
|
|
state == PlayModeStateChange.EnteredEditMode)
|
|
{
|
|
EditorApplication.delayCall += AddToolbarUI;
|
|
}
|
|
}
|
|
}
|
|
#endif |