using UnityEngine; using UnityEngine.UIElements; /// /// Adds a small UI Toolkit control panel for adjusting Time.timeScale at runtime. /// Buttons: Pause (0x), 0.5x, 1x (normal), 2x. /// Camera movement is independent of Time.timeScale (uses unscaledDeltaTime), /// so the simulation can be paused/slowed without affecting navigation. /// public class SimulationControlsUIPanel : MonoBehaviour { [Header("Speed Options")] [SerializeField] private float pausedScale = 0f; [SerializeField] private float halfScale = 0.5f; [SerializeField] private float normalScale = 1f; [SerializeField] private float doubleScale = 2f; private VisualElement root; private Button pauseButton; private Button halfButton; private Button normalButton; private Button doubleButton; private Label statusLabel; private float lastSpeedBeforePause = 1f; void Start() { InitializeUI(); ApplySpeed(normalScale); } void OnDestroy() { // Always restore normal speed when leaving the scene/play mode Time.timeScale = 1f; } private void InitializeUI() { var uiDocument = FindAnyObjectByType(); if (uiDocument == null) { var docGO = new GameObject("UIDocument"); uiDocument = docGO.AddComponent(); } root = uiDocument.rootVisualElement; // Container - bottom-center var panel = new VisualElement(); panel.name = "SimulationControlsPanel"; panel.style.position = Position.Absolute; panel.style.bottom = 20; panel.style.left = new StyleLength(new Length(50, LengthUnit.Percent)); panel.style.translate = new StyleTranslate(new Translate(new Length(-50, LengthUnit.Percent), 0)); panel.style.flexDirection = FlexDirection.Row; panel.style.alignItems = Align.Center; panel.style.backgroundColor = new Color(0.1f, 0.1f, 0.1f, 0.85f); panel.style.borderBottomLeftRadius = 6; panel.style.borderBottomRightRadius = 6; panel.style.borderTopLeftRadius = 6; panel.style.borderTopRightRadius = 6; panel.style.paddingBottom = 6; panel.style.paddingTop = 6; panel.style.paddingLeft = 10; panel.style.paddingRight = 10; var title = new Label("Speed:"); title.style.color = Color.white; title.style.fontSize = 13; title.style.unityFontStyleAndWeight = FontStyle.Bold; title.style.marginRight = 10; panel.Add(title); pauseButton = MakeButton("Pause", () => ApplySpeed(pausedScale)); halfButton = MakeButton("0.5x", () => ApplySpeed(halfScale)); normalButton = MakeButton("1x", () => ApplySpeed(normalScale)); doubleButton = MakeButton("2x", () => ApplySpeed(doubleScale)); panel.Add(pauseButton); panel.Add(halfButton); panel.Add(normalButton); panel.Add(doubleButton); statusLabel = new Label("1x"); statusLabel.style.color = new Color(0.7f, 0.9f, 1f); statusLabel.style.fontSize = 12; statusLabel.style.marginLeft = 10; statusLabel.style.minWidth = 50; statusLabel.style.unityTextAlign = TextAnchor.MiddleLeft; panel.Add(statusLabel); root.Add(panel); } private Button MakeButton(string text, System.Action onClick) { var btn = new Button(onClick) { text = text }; btn.style.width = 60; btn.style.height = 28; btn.style.marginLeft = 4; btn.style.marginRight = 4; btn.style.fontSize = 12; btn.style.color = Color.white; btn.style.backgroundColor = new Color(0.25f, 0.25f, 0.28f, 1f); btn.style.borderBottomLeftRadius = 4; btn.style.borderBottomRightRadius = 4; btn.style.borderTopLeftRadius = 4; btn.style.borderTopRightRadius = 4; return btn; } private void ApplySpeed(float scale) { if (scale > 0f) lastSpeedBeforePause = scale; Time.timeScale = scale; UpdateButtonHighlight(scale); if (statusLabel != null) statusLabel.text = scale <= 0f ? "PAUSED" : $"{scale}x"; } private void UpdateButtonHighlight(float scale) { SetActive(pauseButton, Mathf.Approximately(scale, pausedScale)); SetActive(halfButton, Mathf.Approximately(scale, halfScale)); SetActive(normalButton, Mathf.Approximately(scale, normalScale)); SetActive(doubleButton, Mathf.Approximately(scale, doubleScale)); } private void SetActive(Button btn, bool active) { if (btn == null) return; btn.style.backgroundColor = active ? new Color(0.2f, 0.6f, 0.9f, 1f) // highlighted : new Color(0.25f, 0.25f, 0.28f, 1f); btn.style.unityFontStyleAndWeight = active ? FontStyle.Bold : FontStyle.Normal; } void Update() { // Keyboard shortcuts: Space = pause/resume, 1/2/3/4 = speed presets var kb = UnityEngine.InputSystem.Keyboard.current; if (kb == null) return; if (kb.spaceKey.wasPressedThisFrame) { if (Time.timeScale > 0f) ApplySpeed(0f); else ApplySpeed(lastSpeedBeforePause); } else if (kb.digit1Key.wasPressedThisFrame) ApplySpeed(pausedScale); else if (kb.digit2Key.wasPressedThisFrame) ApplySpeed(halfScale); else if (kb.digit3Key.wasPressedThisFrame) ApplySpeed(normalScale); else if (kb.digit4Key.wasPressedThisFrame) ApplySpeed(doubleScale); } }