app.hypergames.hypersdk 1.11.0-beta.4 → 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 +13 -0
- package/Runtime/Internal/Core/HyperRuntime.cs +20 -1
- package/Runtime/Internal/Managers/GameManager.cs +85 -50
- package/Runtime/Internal/Managers/TimeManager.cs +1 -1
- package/Runtime/Internal/Replay/ReplaySessionState.cs +12 -0
- package/Runtime/Internal/UI/Components/ReplayController.cs +13 -9
- package/Runtime/Internal/UI/Modals/ReplayPausedModal.cs +142 -0
- package/Runtime/Internal/UI/Modals/ReplayPausedModal.cs.meta +2 -0
- package/Runtime/Internal/UI/Modals/StartingInstructionModal.cs +1 -0
- package/Runtime/Public/Core/HyperSDK.cs +8 -0
- package/Runtime/Resources/Fonts/Nunito/Nunito-Bold SDF.asset +500 -457
- package/Runtime/Resources/Sprites/Replays/Replay.png +0 -0
- package/Runtime/Resources/Sprites/Replays/{Recording.png.meta → Replay.png.meta} +9 -9
- package/Runtime/Resources/UIPrefabs/ControlBar.prefab +5 -5
- package/Runtime/Resources/UIPrefabs/ReplayController.prefab +301 -182
- package/Runtime/Resources/UIPrefabs/ReplayPausedModal.prefab +2165 -0
- package/Runtime/Resources/UIPrefabs/ReplayPausedModal.prefab.meta +7 -0
- package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs +1 -0
- package/package.json +1 -1
- package/Runtime/Resources/Sprites/Replays/Recording.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
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
|
+
|
|
1
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)
|
|
2
15
|
|
|
3
16
|
|
|
@@ -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;
|
|
@@ -41,6 +42,7 @@ namespace Hyper.Internal
|
|
|
41
42
|
get => _isSoftRestarting;
|
|
42
43
|
set => _isSoftRestarting = value;
|
|
43
44
|
}
|
|
45
|
+
internal static bool IsReplayCustomRestartPending => _replayCustomRestartPending;
|
|
44
46
|
internal static bool CustomLoadingComplete
|
|
45
47
|
{
|
|
46
48
|
get => _customLoadingComplete;
|
|
@@ -147,12 +149,12 @@ namespace Hyper.Internal
|
|
|
147
149
|
if (_settings != null && _settings.useCustomRestartFlow &&
|
|
148
150
|
_gameManager != null && _eventManager != null)
|
|
149
151
|
{
|
|
152
|
+
_replayCustomRestartPending = true;
|
|
150
153
|
_replayManager?.PrepareForRestart();
|
|
151
154
|
// Custom cleanup may use random streams. Rewind after cleanup, immediately before OnGameStart.
|
|
152
155
|
DeferReplayRandomReset();
|
|
153
156
|
_gameManager.ResetUIStateForCustomRestart();
|
|
154
157
|
_eventManager.InvokeCustomRestartEvent();
|
|
155
|
-
_isSoftRestarting = false;
|
|
156
158
|
return;
|
|
157
159
|
}
|
|
158
160
|
|
|
@@ -193,6 +195,21 @@ namespace Hyper.Internal
|
|
|
193
195
|
_replayRandomResetPending = true;
|
|
194
196
|
}
|
|
195
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
|
+
|
|
196
213
|
/// <summary>
|
|
197
214
|
/// Coroutine-based restart that properly unloads all scenes before reloading.
|
|
198
215
|
/// Handles async scene operations and ensures clean state before restart.
|
|
@@ -433,6 +450,7 @@ namespace Hyper.Internal
|
|
|
433
450
|
_customLoadingActive = false;
|
|
434
451
|
_customLoadingProgress = 0f;
|
|
435
452
|
_replayRandomResetPending = false;
|
|
453
|
+
_replayCustomRestartPending = false;
|
|
436
454
|
|
|
437
455
|
if (_singletonObject != null)
|
|
438
456
|
{
|
|
@@ -492,6 +510,7 @@ namespace Hyper.Internal
|
|
|
492
510
|
_customLoadingActive = false;
|
|
493
511
|
_customLoadingProgress = 0f;
|
|
494
512
|
_replayRandomResetPending = false;
|
|
513
|
+
_replayCustomRestartPending = false;
|
|
495
514
|
_fixedSimulationTimeSeconds = 0d;
|
|
496
515
|
}
|
|
497
516
|
}
|
|
@@ -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);
|
|
@@ -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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
161
|
+
private void PauseReplay()
|
|
162
162
|
{
|
|
163
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -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();
|