#if UNITY_EDITOR using System; using System.Reflection; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; using UnityEditor.SceneManagement; [InitializeOnLoad] public static class PlaySpeedToolbar { private const string ToolbarElementName = "PlaySpeedToolbar"; private const double ToolbarCheckInterval = 0.25; private static readonly float[] Speeds = { 1f, 2f, 4f }; private static VisualElement toolbarElement; private static double nextToolbarCheckTime; static PlaySpeedToolbar() { EditorApplication.update += OnEditorUpdate; EditorApplication.playModeStateChanged += OnPlayModeChanged; EditorApplication.delayCall += AddToolbarUI; AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload; } private static void OnAfterAssemblyReload() { EditorApplication.delayCall += AddToolbarUI; } private static void OnPlayModeChanged(PlayModeStateChange state) { if (state == PlayModeStateChange.EnteredEditMode) Time.timeScale = 1f; EditorApplication.delayCall += AddToolbarUI; } private static void OnEditorUpdate() { EnsureToolbarExists(); } 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 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 TryInjectIntoToolbar(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; // Центр тулбара, рядом с play/pause/stop var middleContainer = root.Q("ToolbarZonePlayMode"); if (middleContainer == null) return; var existing = middleContainer.Q(ToolbarElementName); if (existing != null) { toolbarElement = existing; return; } var newElement = new IMGUIContainer(DrawGUI); newElement.name = ToolbarElementName; newElement.style.flexGrow = 0; newElement.style.flexShrink = 0; newElement.style.marginLeft = 6; newElement.style.marginRight = 2; newElement.style.minWidth = 118; newElement.style.maxWidth = 140; middleContainer.Add(newElement); toolbarElement = newElement; } private static void DrawGUI() { GUILayout.BeginHorizontal(); // Bootstrap toggle bool bootstrapEnabled = SceneBootstrapLoader.Enabled; Color oldBg = GUI.backgroundColor; if (bootstrapEnabled) GUI.backgroundColor = new Color(0.72f, 0.84f, 1f); if (GUILayout.Button( new GUIContent("B", bootstrapEnabled ? "Bootstrap: ON" : "Bootstrap: OFF"), EditorStyles.miniButtonLeft, GUILayout.Width(24), GUILayout.Height(18))) { SceneBootstrapLoader.Enabled = !SceneBootstrapLoader.Enabled; if (!SceneBootstrapLoader.Enabled) EditorSceneManager.playModeStartScene = null; } GUI.backgroundColor = oldBg; bool isPlaying = EditorApplication.isPlaying; EditorGUI.BeginDisabledGroup(!isPlaying); for (int i = 0; i < Speeds.Length; i++) { float speed = Speeds[i]; bool selected = Mathf.Approximately(Time.timeScale, speed); GUIStyle buttonStyle; if (i == Speeds.Length - 1) buttonStyle = EditorStyles.miniButtonRight; else buttonStyle = EditorStyles.miniButtonMid; oldBg = GUI.backgroundColor; if (selected) GUI.backgroundColor = new Color(0.72f, 0.84f, 1f); if (GUILayout.Button( new GUIContent($"{speed:0}x", $"Set Time.timeScale = {speed}"), buttonStyle, GUILayout.Width(24), GUILayout.Height(18))) { Time.timeScale = speed; } GUI.backgroundColor = oldBg; } EditorGUI.EndDisabledGroup(); GUILayout.EndHorizontal(); } } #endif