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);
}
}