SimulationControlsUIPanel.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. using UnityEngine;
  2. using UnityEngine.UIElements;
  3. /// <summary>
  4. /// Adds a small UI Toolkit control panel for adjusting Time.timeScale at runtime.
  5. /// Buttons: Pause (0x), 0.5x, 1x (normal), 2x.
  6. /// Camera movement is independent of Time.timeScale (uses unscaledDeltaTime),
  7. /// so the simulation can be paused/slowed without affecting navigation.
  8. /// </summary>
  9. public class SimulationControlsUIPanel : MonoBehaviour
  10. {
  11. [Header("Speed Options")]
  12. [SerializeField] private float pausedScale = 0f;
  13. [SerializeField] private float halfScale = 0.5f;
  14. [SerializeField] private float normalScale = 1f;
  15. [SerializeField] private float doubleScale = 2f;
  16. private VisualElement root;
  17. private Button pauseButton;
  18. private Button halfButton;
  19. private Button normalButton;
  20. private Button doubleButton;
  21. private Label statusLabel;
  22. private float lastSpeedBeforePause = 1f;
  23. void Start()
  24. {
  25. InitializeUI();
  26. ApplySpeed(normalScale);
  27. }
  28. void OnDestroy()
  29. {
  30. // Always restore normal speed when leaving the scene/play mode
  31. Time.timeScale = 1f;
  32. }
  33. private void InitializeUI()
  34. {
  35. var uiDocument = FindAnyObjectByType<UIDocument>();
  36. if (uiDocument == null)
  37. {
  38. var docGO = new GameObject("UIDocument");
  39. uiDocument = docGO.AddComponent<UIDocument>();
  40. }
  41. root = uiDocument.rootVisualElement;
  42. // Container - bottom-center
  43. var panel = new VisualElement();
  44. panel.name = "SimulationControlsPanel";
  45. panel.style.position = Position.Absolute;
  46. panel.style.bottom = 20;
  47. panel.style.left = new StyleLength(new Length(50, LengthUnit.Percent));
  48. panel.style.translate = new StyleTranslate(new Translate(new Length(-50, LengthUnit.Percent), 0));
  49. panel.style.flexDirection = FlexDirection.Row;
  50. panel.style.alignItems = Align.Center;
  51. panel.style.backgroundColor = new Color(0.1f, 0.1f, 0.1f, 0.85f);
  52. panel.style.borderBottomLeftRadius = 6;
  53. panel.style.borderBottomRightRadius = 6;
  54. panel.style.borderTopLeftRadius = 6;
  55. panel.style.borderTopRightRadius = 6;
  56. panel.style.paddingBottom = 6;
  57. panel.style.paddingTop = 6;
  58. panel.style.paddingLeft = 10;
  59. panel.style.paddingRight = 10;
  60. var title = new Label("Speed:");
  61. title.style.color = Color.white;
  62. title.style.fontSize = 13;
  63. title.style.unityFontStyleAndWeight = FontStyle.Bold;
  64. title.style.marginRight = 10;
  65. panel.Add(title);
  66. pauseButton = MakeButton("Pause", () => ApplySpeed(pausedScale));
  67. halfButton = MakeButton("0.5x", () => ApplySpeed(halfScale));
  68. normalButton = MakeButton("1x", () => ApplySpeed(normalScale));
  69. doubleButton = MakeButton("2x", () => ApplySpeed(doubleScale));
  70. panel.Add(pauseButton);
  71. panel.Add(halfButton);
  72. panel.Add(normalButton);
  73. panel.Add(doubleButton);
  74. statusLabel = new Label("1x");
  75. statusLabel.style.color = new Color(0.7f, 0.9f, 1f);
  76. statusLabel.style.fontSize = 12;
  77. statusLabel.style.marginLeft = 10;
  78. statusLabel.style.minWidth = 50;
  79. statusLabel.style.unityTextAlign = TextAnchor.MiddleLeft;
  80. panel.Add(statusLabel);
  81. root.Add(panel);
  82. }
  83. private Button MakeButton(string text, System.Action onClick)
  84. {
  85. var btn = new Button(onClick) { text = text };
  86. btn.style.width = 60;
  87. btn.style.height = 28;
  88. btn.style.marginLeft = 4;
  89. btn.style.marginRight = 4;
  90. btn.style.fontSize = 12;
  91. btn.style.color = Color.white;
  92. btn.style.backgroundColor = new Color(0.25f, 0.25f, 0.28f, 1f);
  93. btn.style.borderBottomLeftRadius = 4;
  94. btn.style.borderBottomRightRadius = 4;
  95. btn.style.borderTopLeftRadius = 4;
  96. btn.style.borderTopRightRadius = 4;
  97. return btn;
  98. }
  99. private void ApplySpeed(float scale)
  100. {
  101. if (scale > 0f)
  102. lastSpeedBeforePause = scale;
  103. Time.timeScale = scale;
  104. UpdateButtonHighlight(scale);
  105. if (statusLabel != null)
  106. statusLabel.text = scale <= 0f ? "PAUSED" : $"{scale}x";
  107. }
  108. private void UpdateButtonHighlight(float scale)
  109. {
  110. SetActive(pauseButton, Mathf.Approximately(scale, pausedScale));
  111. SetActive(halfButton, Mathf.Approximately(scale, halfScale));
  112. SetActive(normalButton, Mathf.Approximately(scale, normalScale));
  113. SetActive(doubleButton, Mathf.Approximately(scale, doubleScale));
  114. }
  115. private void SetActive(Button btn, bool active)
  116. {
  117. if (btn == null) return;
  118. btn.style.backgroundColor = active
  119. ? new Color(0.2f, 0.6f, 0.9f, 1f) // highlighted
  120. : new Color(0.25f, 0.25f, 0.28f, 1f);
  121. btn.style.unityFontStyleAndWeight = active ? FontStyle.Bold : FontStyle.Normal;
  122. }
  123. void Update()
  124. {
  125. // Keyboard shortcuts: Space = pause/resume, 1/2/3/4 = speed presets
  126. var kb = UnityEngine.InputSystem.Keyboard.current;
  127. if (kb == null) return;
  128. if (kb.spaceKey.wasPressedThisFrame)
  129. {
  130. if (Time.timeScale > 0f)
  131. ApplySpeed(0f);
  132. else
  133. ApplySpeed(lastSpeedBeforePause);
  134. }
  135. else if (kb.digit1Key.wasPressedThisFrame) ApplySpeed(pausedScale);
  136. else if (kb.digit2Key.wasPressedThisFrame) ApplySpeed(halfScale);
  137. else if (kb.digit3Key.wasPressedThisFrame) ApplySpeed(normalScale);
  138. else if (kb.digit4Key.wasPressedThisFrame) ApplySpeed(doubleScale);
  139. }
  140. }