app.hypergames.hypersdk 1.11.0-beta.3 → 1.11.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # [1.11.0-beta.5](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.4...v1.11.0-beta.5) (2026-08-28)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **replay:** wait for custom restart readiness ([43f1aeb](https://github.com/mvmhyper/hyper-sdk/commit/43f1aeb6a127c8c12dbb8379e7230d64ef5e0763))
7
+
8
+
9
+ ### Features
10
+
11
+ * **replay:** add replay pause controls ([b9fb470](https://github.com/mvmhyper/hyper-sdk/commit/b9fb470e65c69e66a8a9611b0caa7e5417b1e4bf))
12
+ * **replay:** update replay control bar assets ([79ce8c0](https://github.com/mvmhyper/hyper-sdk/commit/79ce8c0f1b68129ae5b8bcf18a302117cf11c885))
13
+
14
+ # [1.11.0-beta.4](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.3...v1.11.0-beta.4) (2026-08-28)
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * **replay:** align recording with fixed simulation ([ae2b322](https://github.com/mvmhyper/hyper-sdk/commit/ae2b32216a0a9ea125572d788eee3bfd846414b0))
20
+
1
21
  # [1.11.0-beta.3](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.2...v1.11.0-beta.3) (2026-08-28)
2
22
 
3
23
 
@@ -21,6 +21,7 @@ namespace Hyper.Internal
21
21
  private static bool _isInitialized;
22
22
  private static bool _isSoftRestarting;
23
23
  private static bool _replayRandomResetPending;
24
+ private static bool _replayCustomRestartPending;
24
25
  private static bool _customLoadingComplete;
25
26
  private static bool _customLoadingActive;
26
27
  private static float _customLoadingProgress;
@@ -33,6 +34,7 @@ namespace Hyper.Internal
33
34
  private static EventManager _eventManager;
34
35
  private static TimerManager _timerManager;
35
36
  private static ReplayManager _replayManager;
37
+ private static double _fixedSimulationTimeSeconds;
36
38
 
37
39
  internal static bool IsInitialized => _isInitialized;
38
40
  internal static bool IsSoftRestarting
@@ -40,6 +42,7 @@ namespace Hyper.Internal
40
42
  get => _isSoftRestarting;
41
43
  set => _isSoftRestarting = value;
42
44
  }
45
+ internal static bool IsReplayCustomRestartPending => _replayCustomRestartPending;
43
46
  internal static bool CustomLoadingComplete
44
47
  {
45
48
  get => _customLoadingComplete;
@@ -59,6 +62,7 @@ namespace Hyper.Internal
59
62
  internal static EventManager EventManager => _eventManager;
60
63
  internal static TimerManager Timer => _timerManager;
61
64
  internal static ReplayManager ReplayManager => _replayManager;
65
+ internal static double FixedSimulationTimeSeconds => _fixedSimulationTimeSeconds;
62
66
 
63
67
  internal static event Action CustomLoadingStarted
64
68
  {
@@ -145,12 +149,12 @@ namespace Hyper.Internal
145
149
  if (_settings != null && _settings.useCustomRestartFlow &&
146
150
  _gameManager != null && _eventManager != null)
147
151
  {
152
+ _replayCustomRestartPending = true;
148
153
  _replayManager?.PrepareForRestart();
149
154
  // Custom cleanup may use random streams. Rewind after cleanup, immediately before OnGameStart.
150
155
  DeferReplayRandomReset();
151
156
  _gameManager.ResetUIStateForCustomRestart();
152
157
  _eventManager.InvokeCustomRestartEvent();
153
- _isSoftRestarting = false;
154
158
  return;
155
159
  }
156
160
 
@@ -191,6 +195,21 @@ namespace Hyper.Internal
191
195
  _replayRandomResetPending = true;
192
196
  }
193
197
 
198
+ internal static void CompleteCustomRestart()
199
+ {
200
+ if (!_replayCustomRestartPending || _host == null) return;
201
+
202
+ _replayCustomRestartPending = false;
203
+ _host.StartCoroutine(CompleteReplayCustomRestartNextFrame());
204
+ }
205
+
206
+ private static IEnumerator CompleteReplayCustomRestartNextFrame()
207
+ {
208
+ yield return null;
209
+ _gameManager?.CompleteReplayCustomRestart();
210
+ _isSoftRestarting = false;
211
+ }
212
+
194
213
  /// <summary>
195
214
  /// Coroutine-based restart that properly unloads all scenes before reloading.
196
215
  /// Handles async scene operations and ensures clean state before restart.
@@ -396,6 +415,7 @@ namespace Hyper.Internal
396
415
  internal static void FixedTick()
397
416
  {
398
417
  if (!_isInitialized) return;
418
+ _fixedSimulationTimeSeconds += Time.fixedDeltaTime;
399
419
  _replayManager?.FixedTick();
400
420
  _timerManager?.FixedTick();
401
421
  }
@@ -430,6 +450,7 @@ namespace Hyper.Internal
430
450
  _customLoadingActive = false;
431
451
  _customLoadingProgress = 0f;
432
452
  _replayRandomResetPending = false;
453
+ _replayCustomRestartPending = false;
433
454
 
434
455
  if (_singletonObject != null)
435
456
  {
@@ -489,6 +510,8 @@ namespace Hyper.Internal
489
510
  _customLoadingActive = false;
490
511
  _customLoadingProgress = 0f;
491
512
  _replayRandomResetPending = false;
513
+ _replayCustomRestartPending = false;
514
+ _fixedSimulationTimeSeconds = 0d;
492
515
  }
493
516
  }
494
517
  }
@@ -462,7 +462,7 @@ namespace Hyper.Internal.Managers
462
462
 
463
463
  private static double GetSimulationTimeSeconds()
464
464
  {
465
- return Time.fixedTimeAsDouble;
465
+ return HyperRuntime.FixedSimulationTimeSeconds;
466
466
  }
467
467
 
468
468
  private ScoreData GetPooledScoreData()
@@ -191,6 +191,41 @@ namespace Hyper.Internal.Managers
191
191
  Resources.UnloadUnusedAssets();
192
192
  }
193
193
 
194
+ internal void PauseReplay()
195
+ {
196
+ if (!IsReplayMode || !_hasGameStarted || _isPaused || _hasGameEnded)
197
+ {
198
+ return;
199
+ }
200
+
201
+ _isPaused = true;
202
+ _savedReplayTimeScale = Time.timeScale > 0f
203
+ ? Time.timeScale
204
+ : ReplaySessionState.PlaybackSpeed;
205
+ Time.timeScale = 0f;
206
+ HyperRuntime.Timer.PauseGameplayTimers();
207
+
208
+ _previousTimeScale = 0f;
209
+ _eventManager.InvokeTimeScaleFrozenEvent();
210
+ ReplayPresentationCanvas.SetSeekingLayer(false);
211
+ SetBlockerActive(true);
212
+ ShowReplayPauseModal();
213
+ }
214
+
215
+ internal void ResumeReplay()
216
+ {
217
+ if (!IsReplayMode || !_isPaused || _hasGameEnded) return;
218
+
219
+ _isPaused = false;
220
+ HidePauseModals();
221
+ Time.timeScale = _savedReplayTimeScale > 0f ? _savedReplayTimeScale : 1f;
222
+ HyperRuntime.Timer.ResumeGameTimersAfterPause();
223
+
224
+ _previousTimeScale = Time.timeScale;
225
+ _eventManager.InvokeTimeScaleResumedEvent();
226
+ ReplayPresentationCanvas.SetSeekingLayer(ReplaySessionState.IsSeeking);
227
+ }
228
+
194
229
  internal void ResumeGame()
195
230
  {
196
231
  _isPaused = false;
@@ -222,51 +257,7 @@ namespace Hyper.Internal.Managers
222
257
  private void HandleReplayFocus(bool hasFocus)
223
258
  {
224
259
  if (!_hasGameStarted || _hasGameEnded) return;
225
-
226
- if (!hasFocus && _replayFocusResumeCoroutine != null)
227
- {
228
- StopCoroutine(_replayFocusResumeCoroutine);
229
- _replayFocusResumeCoroutine = null;
230
- }
231
-
232
- if (!hasFocus && !_replayFocusSuspended)
233
- {
234
- _replayFocusSuspended = true;
235
- _savedReplayTimeScale = Time.timeScale;
236
- Time.timeScale = 0f;
237
- HyperRuntime.Timer.PauseGameplayTimers();
238
-
239
- // WebGL may stop producing frames immediately after focus loss,
240
- // so suspend replay timing here instead of waiting for Update.
241
- _previousTimeScale = 0f;
242
- _eventManager.InvokeTimeScaleFrozenEvent();
243
- HyperDebug.Log("Time scale frozen event invoked");
244
- }
245
- else if (hasFocus && _replayFocusSuspended)
246
- {
247
- if (_replayFocusResumeCoroutine == null)
248
- {
249
- _replayFocusResumeCoroutine = StartCoroutine(ResumeReplayAfterFocus());
250
- }
251
- }
252
- }
253
-
254
- private IEnumerator ResumeReplayAfterFocus()
255
- {
256
- // WebGL reports focus before Unity refreshes its unscaled clock. Keep
257
- // gameplay and replay frozen briefly while session time continues.
258
- yield return new WaitForSecondsRealtime(1f);
259
-
260
- _replayFocusResumeCoroutine = null;
261
- if (!_replayFocusSuspended || _hasGameEnded) yield break;
262
-
263
- _replayFocusSuspended = false;
264
- Time.timeScale = _savedReplayTimeScale > 0f ? _savedReplayTimeScale : 1f;
265
- HyperRuntime.Timer.ResumeGameTimersAfterPause();
266
-
267
- _previousTimeScale = Time.timeScale;
268
- _eventManager.InvokeTimeScaleResumedEvent();
269
- HyperDebug.Log("Time scale resumed event invoked");
260
+ if (!hasFocus) PauseReplay();
270
261
  }
271
262
 
272
263
  void HandlePauseState(bool shouldPause)
@@ -552,7 +543,7 @@ namespace Hyper.Internal.Managers
552
543
 
553
544
  internal void ShowStartGameDeadline(int timeLeft)
554
545
  {
555
- if (_hasGameStarted || !IsInGameScene) return;
546
+ if (_hasGameStarted || !IsInGameScene || IsReplayMode) return;
556
547
 
557
548
  EnsureStartGameDeadlineBar();
558
549
  _eventManager.OnSessionAboutToExpire += ChangeDeadlineColorToRed;
@@ -728,7 +719,6 @@ namespace Hyper.Internal.Managers
728
719
  private bool _isPortrait;
729
720
  private float _previousTimeScale = 1f;
730
721
  private float _savedTimeScaleBeforePause = 1f;
731
- private bool _replayFocusSuspended;
732
722
  private float _savedReplayTimeScale = 1f;
733
723
 
734
724
  #endregion
@@ -1077,7 +1067,6 @@ namespace Hyper.Internal.Managers
1077
1067
  _gameStartRequested = false;
1078
1068
  _hasGameEnded = false;
1079
1069
  _replayEndHandled = false;
1080
- _replayFocusSuspended = false;
1081
1070
  _isPaused = false;
1082
1071
  _isFrozenBeforePause = false;
1083
1072
 
@@ -1088,6 +1077,12 @@ namespace Hyper.Internal.Managers
1088
1077
  ShowControlBarElements();
1089
1078
  _replayControllerComponent?.ResetForReplayRestart();
1090
1079
 
1080
+ if (IsReplayMode && HyperRuntime.IsReplayCustomRestartPending)
1081
+ {
1082
+ HideStartingInstructionModal();
1083
+ return;
1084
+ }
1085
+
1091
1086
  if (IsReplayMode && ReplaySessionState.IsSeeking)
1092
1087
  {
1093
1088
  HideStartingInstructionModal();
@@ -1099,6 +1094,20 @@ namespace Hyper.Internal.Managers
1099
1094
  }
1100
1095
  }
1101
1096
 
1097
+ internal void CompleteReplayCustomRestart()
1098
+ {
1099
+ if (!IsReplayMode || _gameStartRequested || _hasGameStarted || _hasGameEnded) return;
1100
+
1101
+ if (ReplaySessionState.IsSeeking)
1102
+ {
1103
+ StartCoroutine(StartSeekingReplayWhenReady());
1104
+ }
1105
+ else
1106
+ {
1107
+ ShowStartingInstructionModal();
1108
+ }
1109
+ }
1110
+
1102
1111
  #endregion
1103
1112
 
1104
1113
  #region Game End Logic
@@ -1140,7 +1149,6 @@ namespace Hyper.Internal.Managers
1140
1149
 
1141
1150
  _replayEndHandled = true;
1142
1151
  _hasGameEnded = true;
1143
- _replayFocusSuspended = false;
1144
1152
  Time.timeScale = 1f;
1145
1153
  _eventManager.InvokeGameEndedEvent();
1146
1154
  HyperRuntime.Timer.StopAllTimers();
@@ -1162,10 +1170,12 @@ namespace Hyper.Internal.Managers
1162
1170
  _eventManager.InvokeGameEndedEvent();
1163
1171
  }
1164
1172
 
1173
+ _isPaused = false;
1165
1174
  Time.timeScale = 1f;
1166
1175
  HyperRuntime.Timer.StopAllTimers();
1167
1176
  _dataManager.StopCapturingScores();
1168
1177
  HyperRuntime.BackendManager.StopSendingScores();
1178
+ HidePauseModals();
1169
1179
  _replayControllerComponent?.SetReplayEnded();
1170
1180
  ReplaySessionState.Clear();
1171
1181
  ReplayPresentationCanvas.DestroyCurrent();
@@ -1276,8 +1286,8 @@ namespace Hyper.Internal.Managers
1276
1286
  private GameObject _replayControllerObject;
1277
1287
  private ReplayController _replayControllerComponent;
1278
1288
  private GameObject _replayOverModal;
1289
+ private GameObject _replayPausedModal;
1279
1290
  private Coroutine _replayConnectionCheckCoroutine;
1280
- private Coroutine _replayFocusResumeCoroutine;
1281
1291
 
1282
1292
  private GameObject _connectionStatusBar;
1283
1293
  private GameObject _startGameDeadlineStatusBar;
@@ -1507,6 +1517,30 @@ namespace Hyper.Internal.Managers
1507
1517
  _replayOverModal.transform.SetAsLastSibling();
1508
1518
  }
1509
1519
 
1520
+ private void ShowReplayPauseModal()
1521
+ {
1522
+ if (_replayPausedModal == null)
1523
+ {
1524
+ if (_canvas == null) CreateCanvas();
1525
+ GameObject prefab = Resources.Load<GameObject>("UIPrefabs/ReplayPausedModal");
1526
+ if (prefab == null)
1527
+ {
1528
+ HyperDebug.LogWarning("[GameManager] ReplayPausedModal prefab not found.");
1529
+ ResumeReplay();
1530
+ return;
1531
+ }
1532
+
1533
+ _replayPausedModal = InstantiateModal(prefab);
1534
+ if (_replayPausedModal.GetComponent<ReplayPausedModal>() == null)
1535
+ {
1536
+ _replayPausedModal.AddComponent<ReplayPausedModal>();
1537
+ }
1538
+ }
1539
+
1540
+ _replayPausedModal.SetActive(true);
1541
+ _replayPausedModal.transform.SetAsLastSibling();
1542
+ }
1543
+
1510
1544
  private void EnsureConnectionStatusBar()
1511
1545
  {
1512
1546
  if (_connectionStatusBar != null) return;
@@ -1755,6 +1789,7 @@ namespace Hyper.Internal.Managers
1755
1789
  internal void HidePauseModals()
1756
1790
  {
1757
1791
  SetBlockerActive(false);
1792
+ if (_replayPausedModal != null) _replayPausedModal.SetActive(false);
1758
1793
  if (_battleGamePausedModal != null) _battleGamePausedModal.SetActive(false);
1759
1794
  if (_practicePausedModal != null) _practicePausedModal.SetActive(false);
1760
1795
  if (_battleGamePausedLandscapeModal != null) _battleGamePausedLandscapeModal.SetActive(false);
@@ -129,7 +129,7 @@ namespace Hyper.Internal.Managers
129
129
 
130
130
  public void ResumeGameTimersAfterPause()
131
131
  {
132
- if (_gameManager.IsBattleMode)
132
+ if (_gameManager.IsBattleMode && !_gameManager.IsReplayMode)
133
133
  {
134
134
  StartBattle();
135
135
  }
@@ -31,6 +31,7 @@ namespace Hyper.Internal
31
31
  public static event Action<float> SeekCompleted;
32
32
 
33
33
  private static bool _seekPlaybackReady;
34
+ private static bool _watchAgainAutoStartPending;
34
35
 
35
36
  public static void Configure(
36
37
  string playerName,
@@ -51,6 +52,7 @@ namespace Hyper.Internal
51
52
  IsSeeking = false;
52
53
  SeekTarget01 = 0f;
53
54
  _seekPlaybackReady = false;
55
+ _watchAgainAutoStartPending = false;
54
56
  HasEnded = false;
55
57
  }
56
58
 
@@ -124,6 +126,7 @@ namespace Hyper.Internal
124
126
  if (!IsActive || !IsSeeking) return;
125
127
  Progress01 = 0f;
126
128
  _seekPlaybackReady = false;
129
+ _watchAgainAutoStartPending = false;
127
130
  HasEnded = false;
128
131
  }
129
132
 
@@ -142,9 +145,17 @@ namespace Hyper.Internal
142
145
  IsSeeking = false;
143
146
  SeekTarget01 = 0f;
144
147
  _seekPlaybackReady = false;
148
+ _watchAgainAutoStartPending = true;
145
149
  HasEnded = false;
146
150
  }
147
151
 
152
+ public static bool ConsumeWatchAgainAutoStart()
153
+ {
154
+ bool pending = _watchAgainAutoStartPending;
155
+ _watchAgainAutoStartPending = false;
156
+ return pending;
157
+ }
158
+
148
159
  public static void Clear()
149
160
  {
150
161
  IsActive = false;
@@ -160,6 +171,7 @@ namespace Hyper.Internal
160
171
  IsSeeking = false;
161
172
  SeekTarget01 = 0f;
162
173
  _seekPlaybackReady = false;
174
+ _watchAgainAutoStartPending = false;
163
175
  HasEnded = false;
164
176
  SeekCompleted = null;
165
177
  }
@@ -15,7 +15,7 @@ namespace Hyper.Internal
15
15
  [SerializeField] private Slider progressSlider;
16
16
  [SerializeField] private TextMeshProUGUI replayLabel;
17
17
  [SerializeField] private RawImage playerAvatar;
18
- [SerializeField] private Button exitButton;
18
+ [SerializeField] private Button pauseButton;
19
19
  [SerializeField] private Button playbackSpeedButton;
20
20
  [SerializeField] private TextMeshProUGUI playbackSpeedLabel;
21
21
  [SerializeField] private float[] playbackSpeeds = { 1.5f, 2f, 3f, 4f, 5f, 1f };
@@ -41,7 +41,7 @@ namespace Hyper.Internal
41
41
  ConfigureSeekSnapshot();
42
42
  ApplyInitialPlaybackSpeed();
43
43
  ReplaySessionState.SeekCompleted += RestorePlaybackAfterSeek;
44
- if (exitButton != null) exitButton.onClick.AddListener(ExitReplay);
44
+ if (pauseButton != null) pauseButton.onClick.AddListener(PauseReplay);
45
45
  if (playbackSpeedButton != null) playbackSpeedButton.onClick.AddListener(CyclePlaybackSpeed);
46
46
  }
47
47
 
@@ -94,7 +94,7 @@ namespace Hyper.Internal
94
94
 
95
95
  private void OnDestroy()
96
96
  {
97
- if (exitButton != null) exitButton.onClick.RemoveListener(ExitReplay);
97
+ if (pauseButton != null) pauseButton.onClick.RemoveListener(PauseReplay);
98
98
  if (playbackSpeedButton != null) playbackSpeedButton.onClick.RemoveListener(CyclePlaybackSpeed);
99
99
  ReplaySessionState.SeekCompleted -= RestorePlaybackAfterSeek;
100
100
  seekInput?.Clear();
@@ -124,7 +124,7 @@ namespace Hyper.Internal
124
124
 
125
125
  internal void SetReplayEnded()
126
126
  {
127
- if (exitButton != null) exitButton.interactable = false;
127
+ if (pauseButton != null) pauseButton.interactable = false;
128
128
  if (playbackSpeedButton != null) playbackSpeedButton.interactable = false;
129
129
  if (progressSlider != null) progressSlider.interactable = false;
130
130
  if (seekLoadingOverlay != null) seekLoadingOverlay.SetActive(false);
@@ -158,10 +158,9 @@ namespace Hyper.Internal
158
158
  SetSeekingPresentation(ReplaySessionState.IsSeeking);
159
159
  }
160
160
 
161
- private void ExitReplay()
161
+ private void PauseReplay()
162
162
  {
163
- if (exitButton != null) exitButton.interactable = false;
164
- HyperRuntime.GameManager?.ExitReplayEarly();
163
+ HyperRuntime.GameManager?.PauseReplay();
165
164
  }
166
165
 
167
166
  private void CyclePlaybackSpeed()
@@ -268,7 +267,7 @@ namespace Hyper.Internal
268
267
  playbackSpeedButton.interactable = !isSeeking && !ReplaySessionState.HasEnded;
269
268
  }
270
269
 
271
- if (exitButton != null) exitButton.interactable = !isSeeking && !ReplaySessionState.HasEnded;
270
+ if (pauseButton != null) pauseButton.interactable = !isSeeking && !ReplaySessionState.HasEnded;
272
271
  if (!isSeeking) ClearSeekSnapshot();
273
272
  }
274
273
 
@@ -341,7 +340,12 @@ namespace Hyper.Internal
341
340
  private void AutoBindReferences()
342
341
  {
343
342
  if (progressSlider == null) progressSlider = GetComponentInChildren<Slider>(true);
344
- if (exitButton == null) exitButton = GetComponentInChildren<Button>(true);
343
+
344
+ Button[] buttons = GetComponentsInChildren<Button>(true);
345
+ for (int i = 0; i < buttons.Length; i++)
346
+ {
347
+ if (pauseButton == null && buttons[i].name == "PauseButton") pauseButton = buttons[i];
348
+ }
345
349
 
346
350
  TextMeshProUGUI[] labels = GetComponentsInChildren<TextMeshProUGUI>(true);
347
351
  for (int i = 0; replayLabel == null && i < labels.Length; i++)
@@ -0,0 +1,142 @@
1
+ using TMPro;
2
+ using UnityEngine;
3
+ using UnityEngine.UI;
4
+
5
+ namespace Hyper.Internal
6
+ {
7
+ internal sealed class ReplayPausedModal : MonoBehaviour
8
+ {
9
+ [SerializeField] private TextMeshProUGUI scoreValue;
10
+ [SerializeField] private TextMeshProUGUI scoreOwnerName;
11
+ [SerializeField] private TextMeshProUGUI soundText;
12
+ [SerializeField] private Sprite soundIconOn;
13
+ [SerializeField] private Sprite soundIconOff;
14
+ [SerializeField] private Image soundIcon;
15
+ [SerializeField] private GameObject restartButton;
16
+ [SerializeField] private Button resumeButton;
17
+ [SerializeField] private Button exitButton;
18
+ [SerializeField] private Button soundButton;
19
+ [SerializeField] private Button restartActionButton;
20
+
21
+ private bool handled;
22
+
23
+ private void Awake()
24
+ {
25
+ AutoBindButtons();
26
+ resumeButton?.onClick.AddListener(ResumeReplay);
27
+ exitButton?.onClick.AddListener(ExitReplay);
28
+ soundButton?.onClick.AddListener(ToggleSound);
29
+ restartActionButton?.onClick.AddListener(RestartReplay);
30
+ }
31
+
32
+ private void OnEnable()
33
+ {
34
+ handled = false;
35
+ SetButtonsInteractable(true);
36
+
37
+ if (scoreValue != null)
38
+ {
39
+ scoreValue.text = (HyperRuntime.DataManager?.CurrentScore ?? 0).ToString("N0");
40
+ }
41
+
42
+ if (scoreOwnerName != null)
43
+ {
44
+ scoreOwnerName.text = FormatScoreOwner(ReplaySessionState.PlayerName);
45
+ }
46
+
47
+ UpdateSoundPresentation();
48
+ }
49
+
50
+ private void OnDestroy()
51
+ {
52
+ resumeButton?.onClick.RemoveListener(ResumeReplay);
53
+ exitButton?.onClick.RemoveListener(ExitReplay);
54
+ soundButton?.onClick.RemoveListener(ToggleSound);
55
+ restartActionButton?.onClick.RemoveListener(RestartReplay);
56
+ }
57
+
58
+ public void ResumeReplay()
59
+ {
60
+ if (handled) return;
61
+ handled = true;
62
+ SetButtonsInteractable(false);
63
+ HyperRuntime.GameManager?.ResumeReplay();
64
+ }
65
+
66
+ public void ExitReplay()
67
+ {
68
+ if (handled) return;
69
+ handled = true;
70
+ SetButtonsInteractable(false);
71
+ HyperRuntime.GameManager?.ExitReplayEarly();
72
+ }
73
+
74
+ public void ToggleSound()
75
+ {
76
+ if (handled || HyperRuntime.DataManager == null) return;
77
+
78
+ int sound = HyperRuntime.DataManager.GetSound() == 1 ? 0 : 1;
79
+ HyperRuntime.DataManager.SetSound(sound);
80
+ UpdateSoundPresentation();
81
+ }
82
+
83
+ public void RestartReplay()
84
+ {
85
+ if (handled) return;
86
+ handled = true;
87
+ SetButtonsInteractable(false);
88
+ HyperRuntime.RestartReplaySession();
89
+ }
90
+
91
+ internal static string FormatScoreOwner(string playerName)
92
+ {
93
+ string normalizedName = (playerName ?? string.Empty).Trim().TrimStart('@');
94
+ return string.IsNullOrEmpty(normalizedName)
95
+ ? "PLAYER'S SCORE"
96
+ : $"@{normalizedName.ToUpperInvariant()}'S SCORE";
97
+ }
98
+
99
+ private void UpdateSoundPresentation()
100
+ {
101
+ bool isSoundOn = HyperRuntime.DataManager == null || HyperRuntime.DataManager.GetSound() == 1;
102
+ if (soundText != null) soundText.text = isSoundOn ? "Sound On" : "Sound Off";
103
+ if (soundIcon != null) soundIcon.sprite = isSoundOn ? soundIconOn : soundIconOff;
104
+ }
105
+
106
+ private void AutoBindButtons()
107
+ {
108
+ Button[] buttons = GetComponentsInChildren<Button>(true);
109
+ for (int i = 0; i < buttons.Length; i++)
110
+ {
111
+ switch (buttons[i].name)
112
+ {
113
+ case "Resume":
114
+ if (resumeButton == null) resumeButton = buttons[i];
115
+ break;
116
+ case "Exit":
117
+ if (exitButton == null) exitButton = buttons[i];
118
+ break;
119
+ case "SoundButton":
120
+ if (soundButton == null) soundButton = buttons[i];
121
+ break;
122
+ case "RestartButton":
123
+ if (restartActionButton == null) restartActionButton = buttons[i];
124
+ break;
125
+ }
126
+ }
127
+
128
+ if (restartButton == null && restartActionButton != null)
129
+ {
130
+ restartButton = restartActionButton.gameObject;
131
+ }
132
+ }
133
+
134
+ private void SetButtonsInteractable(bool interactable)
135
+ {
136
+ if (resumeButton != null) resumeButton.interactable = interactable;
137
+ if (exitButton != null) exitButton.interactable = interactable;
138
+ if (soundButton != null) soundButton.interactable = interactable;
139
+ if (restartActionButton != null) restartActionButton.interactable = interactable;
140
+ }
141
+ }
142
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: b26c59e3565391f4daa22e917b6156b4
@@ -59,6 +59,7 @@ namespace Hyper.Internal
59
59
  {
60
60
  float elapsed = 0f;
61
61
  float delay = Mathf.Max(0f, replayAutoStartDelay);
62
+ if (ReplaySessionState.ConsumeWatchAgainAutoStart()) delay *= 0.5f;
62
63
  while (elapsed < delay)
63
64
  {
64
65
  if (Application.isFocused) elapsed += Time.unscaledDeltaTime;
@@ -226,6 +226,12 @@ namespace Hyper
226
226
  /// </summary>
227
227
  void ExitPracticeMenu();
228
228
 
229
+ /// <summary>
230
+ /// Notifies the SDK that game-owned custom restart cleanup is complete.
231
+ /// Replay Watch Again and seeking wait for this signal before restarting playback.
232
+ /// </summary>
233
+ void CompleteCustomRestart();
234
+
229
235
  // Lives Management (only available when ControlBarLayout is HeartsLeft)
230
236
  /// <summary>
231
237
  /// Initialize the lives system with a maximum and optional initial value.
@@ -616,6 +622,8 @@ namespace Hyper
616
622
 
617
623
  public void ExitPracticeMenu() => Manager?.ExitPracticeMenu();
618
624
 
625
+ public void CompleteCustomRestart() => HyperRuntime.CompleteCustomRestart();
626
+
619
627
  public void InitializeLives(int maxLives, int initialLives = -1) => Manager?.InitializeLives(maxLives, initialLives);
620
628
  public void SetLives(int lives) => Manager?.SetLives(lives);
621
629
  public void AddLife() => Manager?.AddLife();