app.hypergames.hypersdk 1.11.0-beta.9 → 1.11.0
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 +181 -0
- package/Editor/Scripts/Inspectors/HyperSDKSettingsEditor.cs +15 -0
- package/HyperLoader.unity +2 -2
- package/Runtime/Internal/Bootstrap/HyperLoader.cs +5 -1
- package/Runtime/Internal/Core/HyperRuntime.cs +63 -1
- package/Runtime/Internal/Managers/BackendManager.cs +26 -3
- package/Runtime/Internal/Managers/DataManager.cs +8 -0
- package/Runtime/Internal/Managers/EventManager.cs +7 -1
- package/Runtime/Internal/Managers/GameManager.cs +1 -1
- package/Runtime/Internal/Managers/ReplayManager.cs +4 -1
- package/Runtime/Internal/Managers/TimeManager.cs +21 -6
- package/Runtime/Internal/Replay/ReplaySessionState.cs +15 -4
- package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +10 -0
- package/Runtime/Internal/UI/Components/ReplayController.cs +205 -27
- package/Runtime/Internal/UI/Components/ReplayPresentationCanvas.cs +1 -1
- package/Runtime/Internal/UI/Components/ReplaySeekOverlay.cs +18 -0
- package/Runtime/Internal/UI/Components/ReplaySeekOverlay.cs.meta +2 -0
- package/Runtime/Internal/UI/Modals/ReplayPausedModal.cs +9 -1
- package/Runtime/Internal/UI/Modals/StartingInstructionModal.cs +1 -1
- package/Runtime/Public/Replay/ReplayPlaybackEngine.cs +71 -2
- package/Runtime/Resources/Fonts/Wonder Boys/Wonder Boys.asset +394 -379
- package/Runtime/Resources/HyperSDKSettings.asset +2 -0
- package/Runtime/Resources/Sprites/Circle Loader/Property 1=1.png.meta +7 -7
- package/Runtime/Resources/Sprites/Circle Loader/Property 1=2.png.meta +7 -7
- package/Runtime/Resources/Sprites/Circle Loader/Property 1=3.png.meta +7 -7
- package/Runtime/Resources/Sprites/Circle Loader/Property 1=4.png.meta +7 -7
- package/Runtime/Resources/Sprites/Circle Loader/Property 1=5.png.meta +7 -7
- package/Runtime/Resources/UIPrefabs/ReplayController.prefab +295 -15
- package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab +291 -6
- package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab +1 -1
- package/Samples~/LaneRunner/Prefabs/Obstacle.prefab +1 -1
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs +1 -1
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerTrackItem.cs +9 -2
- package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs +1 -0
- package/package.json +1 -1
- /package/Runtime/{Public → Shared}/PlayerDeviceClassification.cs +0 -0
- /package/Runtime/{Public → Shared}/PlayerDeviceClassification.cs.meta +0 -0
|
@@ -12,35 +12,59 @@ namespace Hyper.Internal
|
|
|
12
12
|
{
|
|
13
13
|
internal const int AvatarTextureSize = 128;
|
|
14
14
|
|
|
15
|
-
[SerializeField] private Slider
|
|
15
|
+
[SerializeField] private Slider seekableSlider;
|
|
16
|
+
[SerializeField] private Slider readOnlySlider;
|
|
16
17
|
[SerializeField] private TextMeshProUGUI replayLabel;
|
|
17
18
|
[SerializeField] private RawImage playerAvatar;
|
|
18
19
|
[SerializeField] private Button pauseButton;
|
|
19
20
|
[SerializeField] private Button playbackSpeedButton;
|
|
20
21
|
[SerializeField] private TextMeshProUGUI playbackSpeedLabel;
|
|
21
|
-
[SerializeField] private float[] playbackSpeeds = { 1.5f, 2f,
|
|
22
|
-
[SerializeField, Min(1f)] private float seekPlaybackSpeed =
|
|
22
|
+
[SerializeField] private float[] playbackSpeeds = { 1f, 1.5f, 2f, 0.5f };
|
|
23
|
+
[SerializeField, Min(1f)] private float seekPlaybackSpeed = 15f;
|
|
24
|
+
[SerializeField, Min(0f)] private float readOnlyProgressSmoothTime = 0.08f;
|
|
23
25
|
[SerializeField, Range(0.25f, 1f)] private float seekSnapshotScale = 0.55f;
|
|
26
|
+
[SerializeField, Range(0f, 0.5f)] private float seekPreparationProgress = 0.2f;
|
|
27
|
+
[SerializeField, Range(0f, 0.15f)] private float seekPreparationProgressVariation = 0.05f;
|
|
28
|
+
[SerializeField, Min(0.1f)] private float seekPreparationDuration = 3f;
|
|
24
29
|
[SerializeField] private GameObject seekLoadingOverlay;
|
|
25
30
|
[SerializeField] private RawImage seekSnapshotImage;
|
|
26
31
|
|
|
27
32
|
private int playbackSpeedIndex;
|
|
33
|
+
private Slider progressSlider;
|
|
28
34
|
private ReplaySeekInput seekInput;
|
|
35
|
+
private bool seekingAllowed;
|
|
29
36
|
private bool wasSeeking;
|
|
30
37
|
private bool isCapturingSeek;
|
|
31
38
|
private float pendingSeekTarget;
|
|
39
|
+
private float displayedReadOnlyProgress;
|
|
40
|
+
private float readOnlyProgressVelocity;
|
|
32
41
|
private Texture2D seekSnapshotTexture;
|
|
33
42
|
private Image seekOverlayBackground;
|
|
34
43
|
private Color seekOverlayBackgroundColor;
|
|
44
|
+
private ReplaySeekOverlay seekOverlay;
|
|
45
|
+
private static readonly System.Random SeekProgressRandom = new System.Random();
|
|
46
|
+
private static float displayedSeekProgress;
|
|
47
|
+
private static float firstSeekPreparationTarget;
|
|
48
|
+
private static float secondSeekPreparationTarget;
|
|
49
|
+
private static float seekPreparationCeiling;
|
|
35
50
|
|
|
36
51
|
private void Awake()
|
|
37
52
|
{
|
|
53
|
+
seekingAllowed = HyperRuntime.Settings != null && HyperRuntime.Settings.allowReplaySeeking;
|
|
38
54
|
AutoBindReferences();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
ConfigureProgressSliders();
|
|
56
|
+
if (seekingAllowed)
|
|
57
|
+
{
|
|
58
|
+
ConfigureSeekOverlay();
|
|
59
|
+
ConfigureSeekSnapshot();
|
|
60
|
+
}
|
|
61
|
+
else if (seekLoadingOverlay != null)
|
|
62
|
+
{
|
|
63
|
+
seekLoadingOverlay.SetActive(false);
|
|
64
|
+
}
|
|
65
|
+
|
|
42
66
|
ApplyInitialPlaybackSpeed();
|
|
43
|
-
ReplaySessionState.SeekCompleted += RestorePlaybackAfterSeek;
|
|
67
|
+
if (seekingAllowed) ReplaySessionState.SeekCompleted += RestorePlaybackAfterSeek;
|
|
44
68
|
if (pauseButton != null) pauseButton.onClick.AddListener(PauseReplay);
|
|
45
69
|
if (playbackSpeedButton != null) playbackSpeedButton.onClick.AddListener(CyclePlaybackSpeed);
|
|
46
70
|
}
|
|
@@ -52,13 +76,13 @@ namespace Hyper.Internal
|
|
|
52
76
|
if (replayLabel != null) replayLabel.text = $"Replay of @{playerName}";
|
|
53
77
|
if (progressSlider != null)
|
|
54
78
|
{
|
|
55
|
-
float displayedProgress = ReplaySessionState.IsSeeking
|
|
79
|
+
float displayedProgress = seekingAllowed && ReplaySessionState.IsSeeking
|
|
56
80
|
? ReplaySessionState.SeekTarget01
|
|
57
81
|
: ReplaySessionState.Progress01;
|
|
58
|
-
|
|
82
|
+
SetProgressSliderValue(displayedProgress);
|
|
59
83
|
}
|
|
60
84
|
UpdatePlaybackSpeedLabel(ReplaySessionState.UserPlaybackSpeed);
|
|
61
|
-
SetSeekingPresentation(ReplaySessionState.IsSeeking);
|
|
85
|
+
SetSeekingPresentation(seekingAllowed && ReplaySessionState.IsSeeking);
|
|
62
86
|
|
|
63
87
|
if (playerAvatar != null && ReplaySessionState.AvatarTexture != null)
|
|
64
88
|
{
|
|
@@ -68,7 +92,7 @@ namespace Hyper.Internal
|
|
|
68
92
|
|
|
69
93
|
private void Update()
|
|
70
94
|
{
|
|
71
|
-
bool isSeeking = ReplaySessionState.IsSeeking;
|
|
95
|
+
bool isSeeking = seekingAllowed && ReplaySessionState.IsSeeking;
|
|
72
96
|
if (wasSeeking && !isSeeking && !ReplaySessionState.HasEnded)
|
|
73
97
|
{
|
|
74
98
|
RestorePlaybackAfterSeek(ReplaySessionState.PlaybackSpeed);
|
|
@@ -79,6 +103,11 @@ namespace Hyper.Internal
|
|
|
79
103
|
SetSeekingPresentation(isSeeking);
|
|
80
104
|
}
|
|
81
105
|
|
|
106
|
+
if (isSeeking)
|
|
107
|
+
{
|
|
108
|
+
UpdateSeekProgress(true);
|
|
109
|
+
}
|
|
110
|
+
|
|
82
111
|
if (progressSlider != null && (seekInput == null || !seekInput.IsDragging))
|
|
83
112
|
{
|
|
84
113
|
float displayedProgress = isCapturingSeek
|
|
@@ -86,7 +115,7 @@ namespace Hyper.Internal
|
|
|
86
115
|
: isSeeking
|
|
87
116
|
? ReplaySessionState.SeekTarget01
|
|
88
117
|
: ReplaySessionState.Progress01;
|
|
89
|
-
|
|
118
|
+
SetProgressSliderValue(displayedProgress, !seekingAllowed);
|
|
90
119
|
}
|
|
91
120
|
|
|
92
121
|
wasSeeking = isSeeking;
|
|
@@ -129,7 +158,7 @@ namespace Hyper.Internal
|
|
|
129
158
|
if (progressSlider != null) progressSlider.interactable = false;
|
|
130
159
|
if (seekLoadingOverlay != null) seekLoadingOverlay.SetActive(false);
|
|
131
160
|
ClearSeekSnapshot();
|
|
132
|
-
|
|
161
|
+
SetProgressSliderValue(1f);
|
|
133
162
|
}
|
|
134
163
|
|
|
135
164
|
internal void ResetForReplayRestart()
|
|
@@ -148,10 +177,7 @@ namespace Hyper.Internal
|
|
|
148
177
|
: 0f;
|
|
149
178
|
wasSeeking = ReplaySessionState.IsSeeking;
|
|
150
179
|
|
|
151
|
-
|
|
152
|
-
{
|
|
153
|
-
progressSlider.SetValueWithoutNotify(pendingSeekTarget);
|
|
154
|
-
}
|
|
180
|
+
SetProgressSliderValue(pendingSeekTarget);
|
|
155
181
|
|
|
156
182
|
UpdatePlaybackSpeedLabel(playbackSpeed);
|
|
157
183
|
AlignPlaybackSpeedIndex(playbackSpeed);
|
|
@@ -181,7 +207,7 @@ namespace Hyper.Internal
|
|
|
181
207
|
|
|
182
208
|
private void SeekTo(float targetProgress)
|
|
183
209
|
{
|
|
184
|
-
if (!ReplaySessionState.IsActive || ReplaySessionState.HasEnded ||
|
|
210
|
+
if (!seekingAllowed || !ReplaySessionState.IsActive || ReplaySessionState.HasEnded ||
|
|
185
211
|
ReplaySessionState.IsSeeking || isCapturingSeek)
|
|
186
212
|
{
|
|
187
213
|
return;
|
|
@@ -189,6 +215,7 @@ namespace Hyper.Internal
|
|
|
189
215
|
|
|
190
216
|
float resumeSpeed = SanitizePlaybackSpeed(ReplaySessionState.UserPlaybackSpeed);
|
|
191
217
|
float fastForwardSpeed = SanitizePlaybackSpeed(seekPlaybackSpeed);
|
|
218
|
+
InitializeSeekProgressPresentation();
|
|
192
219
|
StartCoroutine(CaptureAndSeek(targetProgress, resumeSpeed, fastForwardSpeed));
|
|
193
220
|
}
|
|
194
221
|
|
|
@@ -238,7 +265,6 @@ namespace Hyper.Internal
|
|
|
238
265
|
private void RestorePlaybackAfterSeek(float playbackSpeed)
|
|
239
266
|
{
|
|
240
267
|
float speed = SanitizePlaybackSpeed(playbackSpeed);
|
|
241
|
-
Time.timeScale = speed;
|
|
242
268
|
AlignPlaybackSpeedIndex(speed);
|
|
243
269
|
UpdatePlaybackSpeedLabel(speed);
|
|
244
270
|
SetSeekingPresentation(false);
|
|
@@ -259,9 +285,14 @@ namespace Hyper.Internal
|
|
|
259
285
|
|
|
260
286
|
private void SetSeekingPresentation(bool isSeeking)
|
|
261
287
|
{
|
|
288
|
+
isSeeking &= seekingAllowed;
|
|
262
289
|
ReplayPresentationCanvas.SetSeekingLayer(isSeeking);
|
|
290
|
+
UpdateSeekProgress(isSeeking);
|
|
263
291
|
if (seekLoadingOverlay != null) seekLoadingOverlay.SetActive(isSeeking);
|
|
264
|
-
if (progressSlider != null)
|
|
292
|
+
if (progressSlider != null)
|
|
293
|
+
{
|
|
294
|
+
progressSlider.interactable = seekingAllowed && !isSeeking && !ReplaySessionState.HasEnded;
|
|
295
|
+
}
|
|
265
296
|
if (playbackSpeedButton != null)
|
|
266
297
|
{
|
|
267
298
|
playbackSpeedButton.interactable = !isSeeking && !ReplaySessionState.HasEnded;
|
|
@@ -285,7 +316,7 @@ namespace Hyper.Internal
|
|
|
285
316
|
|
|
286
317
|
if (playbackSpeedLabel != null)
|
|
287
318
|
{
|
|
288
|
-
playbackSpeedLabel.text = $"{speed:0
|
|
319
|
+
playbackSpeedLabel.text = $"{speed:0.##}x";
|
|
289
320
|
}
|
|
290
321
|
}
|
|
291
322
|
|
|
@@ -339,8 +370,6 @@ namespace Hyper.Internal
|
|
|
339
370
|
|
|
340
371
|
private void AutoBindReferences()
|
|
341
372
|
{
|
|
342
|
-
if (progressSlider == null) progressSlider = GetComponentInChildren<Slider>(true);
|
|
343
|
-
|
|
344
373
|
Button[] buttons = GetComponentsInChildren<Button>(true);
|
|
345
374
|
for (int i = 0; i < buttons.Length; i++)
|
|
346
375
|
{
|
|
@@ -360,24 +389,69 @@ namespace Hyper.Internal
|
|
|
360
389
|
}
|
|
361
390
|
}
|
|
362
391
|
|
|
363
|
-
private void
|
|
392
|
+
private void ConfigureProgressSliders()
|
|
364
393
|
{
|
|
394
|
+
progressSlider = seekingAllowed ? seekableSlider : readOnlySlider;
|
|
395
|
+
|
|
396
|
+
if (seekableSlider != null)
|
|
397
|
+
{
|
|
398
|
+
seekableSlider.gameObject.SetActive(seekingAllowed);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (readOnlySlider != null)
|
|
402
|
+
{
|
|
403
|
+
readOnlySlider.gameObject.SetActive(!seekingAllowed);
|
|
404
|
+
}
|
|
405
|
+
|
|
365
406
|
if (progressSlider == null) return;
|
|
366
407
|
|
|
367
408
|
progressSlider.minValue = 0f;
|
|
368
409
|
progressSlider.maxValue = 1f;
|
|
369
410
|
progressSlider.wholeNumbers = false;
|
|
370
|
-
progressSlider.interactable =
|
|
371
|
-
float displayedProgress = ReplaySessionState.IsSeeking
|
|
411
|
+
progressSlider.interactable = seekingAllowed;
|
|
412
|
+
float displayedProgress = seekingAllowed && ReplaySessionState.IsSeeking
|
|
372
413
|
? ReplaySessionState.SeekTarget01
|
|
373
414
|
: ReplaySessionState.Progress01;
|
|
374
|
-
|
|
415
|
+
SetProgressSliderValue(displayedProgress);
|
|
416
|
+
|
|
417
|
+
if (!seekingAllowed) return;
|
|
375
418
|
|
|
376
419
|
seekInput = progressSlider.GetComponent<ReplaySeekInput>();
|
|
377
420
|
if (seekInput == null) seekInput = progressSlider.gameObject.AddComponent<ReplaySeekInput>();
|
|
378
421
|
seekInput.Initialize(progressSlider, SeekTo);
|
|
379
422
|
}
|
|
380
423
|
|
|
424
|
+
private void SetProgressSliderValue(float targetProgress, bool smoothReadOnly = false)
|
|
425
|
+
{
|
|
426
|
+
if (progressSlider == null) return;
|
|
427
|
+
|
|
428
|
+
targetProgress = Mathf.Clamp01(targetProgress);
|
|
429
|
+
if (!smoothReadOnly || readOnlyProgressSmoothTime <= 0f ||
|
|
430
|
+
targetProgress < displayedReadOnlyProgress)
|
|
431
|
+
{
|
|
432
|
+
displayedReadOnlyProgress = targetProgress;
|
|
433
|
+
readOnlyProgressVelocity = 0f;
|
|
434
|
+
}
|
|
435
|
+
else
|
|
436
|
+
{
|
|
437
|
+
displayedReadOnlyProgress = Mathf.SmoothDamp(
|
|
438
|
+
displayedReadOnlyProgress,
|
|
439
|
+
targetProgress,
|
|
440
|
+
ref readOnlyProgressVelocity,
|
|
441
|
+
readOnlyProgressSmoothTime,
|
|
442
|
+
Mathf.Infinity,
|
|
443
|
+
Time.unscaledDeltaTime);
|
|
444
|
+
|
|
445
|
+
if (Mathf.Abs(targetProgress - displayedReadOnlyProgress) < 0.0001f)
|
|
446
|
+
{
|
|
447
|
+
displayedReadOnlyProgress = targetProgress;
|
|
448
|
+
readOnlyProgressVelocity = 0f;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
progressSlider.SetValueWithoutNotify(displayedReadOnlyProgress);
|
|
453
|
+
}
|
|
454
|
+
|
|
381
455
|
private void ConfigureSeekOverlay()
|
|
382
456
|
{
|
|
383
457
|
if (seekLoadingOverlay == null)
|
|
@@ -404,10 +478,114 @@ namespace Hyper.Internal
|
|
|
404
478
|
seekLoadingOverlay.transform.SetAsFirstSibling();
|
|
405
479
|
seekOverlayBackground = seekLoadingOverlay.GetComponent<Image>();
|
|
406
480
|
if (seekOverlayBackground != null) seekOverlayBackgroundColor = seekOverlayBackground.color;
|
|
481
|
+
seekOverlay = seekLoadingOverlay.GetComponent<ReplaySeekOverlay>();
|
|
407
482
|
seekLoadingOverlay.SetActive(ReplaySessionState.IsSeeking);
|
|
408
483
|
wasSeeking = ReplaySessionState.IsSeeking;
|
|
409
484
|
}
|
|
410
485
|
|
|
486
|
+
private void UpdateSeekProgress(bool isSeeking)
|
|
487
|
+
{
|
|
488
|
+
if (seekOverlay == null) return;
|
|
489
|
+
|
|
490
|
+
float progress;
|
|
491
|
+
if (!isSeeking)
|
|
492
|
+
{
|
|
493
|
+
progress = 1f;
|
|
494
|
+
}
|
|
495
|
+
else if (isCapturingSeek || ReplaySessionState.IsSeekRestartPending)
|
|
496
|
+
{
|
|
497
|
+
AdvanceSeekPreparationProgress();
|
|
498
|
+
progress = displayedSeekProgress;
|
|
499
|
+
}
|
|
500
|
+
else
|
|
501
|
+
{
|
|
502
|
+
EnsureSeekProgressPresentation();
|
|
503
|
+
float target = ReplaySessionState.SeekTarget01;
|
|
504
|
+
float reconstructionProgress = target <= Mathf.Epsilon
|
|
505
|
+
? 1f
|
|
506
|
+
: ReplaySessionState.Progress01 / target;
|
|
507
|
+
float realProgress = Mathf.Lerp(
|
|
508
|
+
seekPreparationCeiling,
|
|
509
|
+
1f,
|
|
510
|
+
Mathf.Clamp01(reconstructionProgress));
|
|
511
|
+
AdvanceSeekReconstructionProgress(realProgress);
|
|
512
|
+
progress = displayedSeekProgress;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
seekOverlay.SetProgress(progress);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
private void AdvanceSeekPreparationProgress()
|
|
519
|
+
{
|
|
520
|
+
EnsureSeekProgressPresentation();
|
|
521
|
+
float duration = Mathf.Max(0.1f, seekPreparationDuration);
|
|
522
|
+
float deltaTime = Mathf.Min(Time.unscaledDeltaTime, 0.1f);
|
|
523
|
+
|
|
524
|
+
if (displayedSeekProgress < firstSeekPreparationTarget)
|
|
525
|
+
{
|
|
526
|
+
float speed = firstSeekPreparationTarget / (duration * 0.35f);
|
|
527
|
+
displayedSeekProgress = Mathf.MoveTowards(
|
|
528
|
+
displayedSeekProgress,
|
|
529
|
+
firstSeekPreparationTarget,
|
|
530
|
+
speed * deltaTime);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (displayedSeekProgress < secondSeekPreparationTarget)
|
|
535
|
+
{
|
|
536
|
+
float speed = (secondSeekPreparationTarget - firstSeekPreparationTarget) / (duration * 0.2f);
|
|
537
|
+
displayedSeekProgress = Mathf.MoveTowards(
|
|
538
|
+
displayedSeekProgress,
|
|
539
|
+
secondSeekPreparationTarget,
|
|
540
|
+
speed * deltaTime);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
float crawlResponse = 1f - Mathf.Exp(-deltaTime / (duration * 0.65f));
|
|
545
|
+
displayedSeekProgress = Mathf.Lerp(displayedSeekProgress, seekPreparationCeiling, crawlResponse);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
private void InitializeSeekProgressPresentation()
|
|
549
|
+
{
|
|
550
|
+
float center = Mathf.Clamp01(seekPreparationProgress);
|
|
551
|
+
float variation = Mathf.Max(0f, seekPreparationProgressVariation);
|
|
552
|
+
float minimum = Mathf.Clamp01(center - variation);
|
|
553
|
+
float maximum = Mathf.Clamp01(center + variation);
|
|
554
|
+
|
|
555
|
+
displayedSeekProgress = 0f;
|
|
556
|
+
seekPreparationCeiling = RandomRange(minimum, maximum);
|
|
557
|
+
firstSeekPreparationTarget = seekPreparationCeiling * RandomRange(0.25f, 0.35f);
|
|
558
|
+
secondSeekPreparationTarget = seekPreparationCeiling * RandomRange(0.62f, 0.76f);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
private void EnsureSeekProgressPresentation()
|
|
562
|
+
{
|
|
563
|
+
if (seekPreparationCeiling > 0f) return;
|
|
564
|
+
InitializeSeekProgressPresentation();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private static float RandomRange(float minimum, float maximum)
|
|
568
|
+
{
|
|
569
|
+
if (maximum <= minimum) return minimum;
|
|
570
|
+
return minimum + ((float)SeekProgressRandom.NextDouble() * (maximum - minimum));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
private void AdvanceSeekReconstructionProgress(float realProgress)
|
|
574
|
+
{
|
|
575
|
+
float target = Mathf.Max(displayedSeekProgress, realProgress);
|
|
576
|
+
if (target >= 0.999f)
|
|
577
|
+
{
|
|
578
|
+
displayedSeekProgress = 1f;
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
float deltaTime = Mathf.Min(Time.unscaledDeltaTime, 0.1f);
|
|
583
|
+
float gap = target - displayedSeekProgress;
|
|
584
|
+
float responseSpeed = Mathf.Lerp(5f, 14f, Mathf.Clamp01(gap * 4f));
|
|
585
|
+
float response = 1f - Mathf.Exp(-responseSpeed * deltaTime);
|
|
586
|
+
displayedSeekProgress = Mathf.Lerp(displayedSeekProgress, target, response);
|
|
587
|
+
}
|
|
588
|
+
|
|
411
589
|
private void ConfigureSeekSnapshot()
|
|
412
590
|
{
|
|
413
591
|
if (seekSnapshotImage == null)
|
|
@@ -110,7 +110,7 @@ namespace Hyper.Internal
|
|
|
110
110
|
CanvasScaler scaler = GetComponent<CanvasScaler>();
|
|
111
111
|
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
|
112
112
|
scaler.referenceResolution = new Vector2(1080f, 2160f);
|
|
113
|
-
scaler.matchWidthOrHeight = isPortrait ? 1f : 0.593f;
|
|
113
|
+
scaler.matchWidthOrHeight = isPortrait ? 1f : (PlayerDeviceClassification.PlayerDeviceIsPC ? 0.75f : 0.593f);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
private void EnsurePresentationObjects()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
using TMPro;
|
|
2
|
+
using UnityEngine;
|
|
3
|
+
|
|
4
|
+
namespace Hyper.Internal
|
|
5
|
+
{
|
|
6
|
+
internal sealed class ReplaySeekOverlay : MonoBehaviour
|
|
7
|
+
{
|
|
8
|
+
[SerializeField] private TextMeshProUGUI progressText;
|
|
9
|
+
|
|
10
|
+
internal void SetProgress(float progress01)
|
|
11
|
+
{
|
|
12
|
+
if (progressText == null) return;
|
|
13
|
+
|
|
14
|
+
int percentage = Mathf.FloorToInt(Mathf.Clamp01(progress01) * 10f) * 10;
|
|
15
|
+
progressText.text = $"{percentage}%";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
using System.Globalization;
|
|
1
2
|
using TMPro;
|
|
2
3
|
using UnityEngine;
|
|
3
4
|
using UnityEngine.UI;
|
|
@@ -38,7 +39,7 @@ namespace Hyper.Internal
|
|
|
38
39
|
|
|
39
40
|
if (scoreValue != null)
|
|
40
41
|
{
|
|
41
|
-
scoreValue.text = (
|
|
42
|
+
scoreValue.text = FormatFinalScore(ReplaySessionState.FinalScore);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
if (scoreOwnerName != null)
|
|
@@ -99,6 +100,13 @@ namespace Hyper.Internal
|
|
|
99
100
|
: $"@{normalizedName.ToUpperInvariant()}'S SCORE";
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
internal static string FormatFinalScore(int? finalScore)
|
|
104
|
+
{
|
|
105
|
+
return finalScore.HasValue
|
|
106
|
+
? finalScore.Value.ToString("N0", CultureInfo.InvariantCulture)
|
|
107
|
+
: "--";
|
|
108
|
+
}
|
|
109
|
+
|
|
102
110
|
private void UpdateSoundPresentation()
|
|
103
111
|
{
|
|
104
112
|
bool isSoundOn = HyperRuntime.DataManager == null || HyperRuntime.DataManager.GetSound() == 1;
|
|
@@ -14,7 +14,7 @@ namespace Hyper.Internal
|
|
|
14
14
|
[SerializeField] private BlinkingText blinkingText;
|
|
15
15
|
[SerializeField] private RectTransform swipeGif;
|
|
16
16
|
[SerializeField] private RectTransform handSpriteInGif;
|
|
17
|
-
[SerializeField] private float replayAutoStartDelay =
|
|
17
|
+
[SerializeField] private float replayAutoStartDelay = 2f;
|
|
18
18
|
private GameManager _gameManager;
|
|
19
19
|
private StartCTA _currentCTA;
|
|
20
20
|
private SwipeDirection _swipeDirection;
|
|
@@ -29,6 +29,13 @@ namespace Hyper
|
|
|
29
29
|
private double _stoppedElapsed;
|
|
30
30
|
private bool _isSuspended;
|
|
31
31
|
private bool _completionPending;
|
|
32
|
+
private bool _gameplayClockStarted;
|
|
33
|
+
private double _gameplaySourceStartTime;
|
|
34
|
+
private double _gameplayElapsedSeconds;
|
|
35
|
+
private double _gameplayStepSeconds;
|
|
36
|
+
private double _gameplayPauseDurationAtStart;
|
|
37
|
+
private long _gameplayTick;
|
|
38
|
+
private double _currentTickDelta;
|
|
32
39
|
|
|
33
40
|
public event Action<float, float> OnValues2;
|
|
34
41
|
public event Action<float> OnValue;
|
|
@@ -76,6 +83,13 @@ namespace Hyper
|
|
|
76
83
|
_endedRaised = false;
|
|
77
84
|
_completionPending = false;
|
|
78
85
|
_nextActionPrepared = false;
|
|
86
|
+
_gameplayClockStarted = false;
|
|
87
|
+
_gameplaySourceStartTime = 0d;
|
|
88
|
+
_gameplayElapsedSeconds = 0d;
|
|
89
|
+
_gameplayStepSeconds = 0d;
|
|
90
|
+
_gameplayPauseDurationAtStart = 0d;
|
|
91
|
+
_gameplayTick = 0L;
|
|
92
|
+
_currentTickDelta = 0d;
|
|
79
93
|
DurationSeconds = CalculateDuration(_ndjson);
|
|
80
94
|
double gameplayStartSeconds = CalculateGameplayStart(_ndjson, out bool hasGameplayReadyMarker);
|
|
81
95
|
HasGameplayReadyMarker = hasGameplayReadyMarker;
|
|
@@ -122,13 +136,19 @@ namespace Hyper
|
|
|
122
136
|
double.IsInfinity(fixedSimulationDeltaSeconds)
|
|
123
137
|
? 0d
|
|
124
138
|
: Math.Max(0d, fixedSimulationDeltaSeconds);
|
|
139
|
+
_currentTickDelta = delta;
|
|
125
140
|
_elapsedSeconds += delta;
|
|
141
|
+
if (_gameplayClockStarted && delta > 0d)
|
|
142
|
+
{
|
|
143
|
+
if (_gameplayStepSeconds <= 0d) _gameplayStepSeconds = delta;
|
|
144
|
+
_gameplayElapsedSeconds += delta;
|
|
145
|
+
_gameplayTick++;
|
|
146
|
+
}
|
|
126
147
|
|
|
127
148
|
int processed = 0;
|
|
128
149
|
while (IsPlaying && !_completionPending && _hasNext && processed++ < MaxActivitiesPerTick)
|
|
129
150
|
{
|
|
130
|
-
double elapsed
|
|
131
|
-
double dueTime = _next.Time - _sourceStartTime - _removedPauseDuration;
|
|
151
|
+
GetSchedulingTime(_next, out double elapsed, out double dueTime);
|
|
132
152
|
PrepareNextActionIfDue(elapsed, dueTime);
|
|
133
153
|
if (elapsed + 0.000001d < dueTime) return;
|
|
134
154
|
|
|
@@ -154,6 +174,13 @@ namespace Hyper
|
|
|
154
174
|
_isSuspended = false;
|
|
155
175
|
_completionPending = false;
|
|
156
176
|
_nextActionPrepared = false;
|
|
177
|
+
_gameplayClockStarted = false;
|
|
178
|
+
_gameplaySourceStartTime = 0d;
|
|
179
|
+
_gameplayElapsedSeconds = 0d;
|
|
180
|
+
_gameplayStepSeconds = 0d;
|
|
181
|
+
_gameplayPauseDurationAtStart = 0d;
|
|
182
|
+
_gameplayTick = 0L;
|
|
183
|
+
_currentTickDelta = 0d;
|
|
157
184
|
}
|
|
158
185
|
|
|
159
186
|
public void Suspend()
|
|
@@ -224,6 +251,7 @@ namespace Hyper
|
|
|
224
251
|
OnGameStarted?.Invoke();
|
|
225
252
|
break;
|
|
226
253
|
case GameplayReady:
|
|
254
|
+
StartGameplayClock(activity.Time);
|
|
227
255
|
OnGameplayReady?.Invoke();
|
|
228
256
|
break;
|
|
229
257
|
case GamePaused:
|
|
@@ -255,6 +283,47 @@ namespace Hyper
|
|
|
255
283
|
OnActionPreparing?.Invoke(_next.Action);
|
|
256
284
|
}
|
|
257
285
|
|
|
286
|
+
private void StartGameplayClock(double sourceTime)
|
|
287
|
+
{
|
|
288
|
+
_gameplayClockStarted = true;
|
|
289
|
+
_gameplaySourceStartTime = sourceTime;
|
|
290
|
+
_gameplayElapsedSeconds = 0d;
|
|
291
|
+
_gameplayStepSeconds = _currentTickDelta;
|
|
292
|
+
_gameplayPauseDurationAtStart = _removedPauseDuration;
|
|
293
|
+
_gameplayTick = 0L;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private void GetSchedulingTime(ReplayActivity activity, out double elapsed, out double dueTime)
|
|
297
|
+
{
|
|
298
|
+
if (!_gameplayClockStarted)
|
|
299
|
+
{
|
|
300
|
+
elapsed = GetElapsedSeconds();
|
|
301
|
+
dueTime = activity.Time - _sourceStartTime - _removedPauseDuration;
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
double removedGameplayPauses = Math.Max(0d,
|
|
306
|
+
_removedPauseDuration - _gameplayPauseDurationAtStart);
|
|
307
|
+
double rawDueTime = Math.Max(0d,
|
|
308
|
+
activity.Time - _gameplaySourceStartTime - removedGameplayPauses);
|
|
309
|
+
|
|
310
|
+
if (_gameplayStepSeconds <= 0d)
|
|
311
|
+
{
|
|
312
|
+
elapsed = _gameplayElapsedSeconds;
|
|
313
|
+
dueTime = rawDueTime;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Gameplay can only consume an activity on a fixed step. Mapping the
|
|
318
|
+
// timestamp to its nearest GPR-relative tick prevents sub-step JSON
|
|
319
|
+
// precision noise from moving an input to the following physics step.
|
|
320
|
+
long dueTick = Math.Max(0L, (long)Math.Round(
|
|
321
|
+
rawDueTime / _gameplayStepSeconds,
|
|
322
|
+
MidpointRounding.AwayFromZero));
|
|
323
|
+
elapsed = _gameplayTick * _gameplayStepSeconds;
|
|
324
|
+
dueTime = dueTick * _gameplayStepSeconds;
|
|
325
|
+
}
|
|
326
|
+
|
|
258
327
|
private static bool IsLifecycleAction(string action)
|
|
259
328
|
{
|
|
260
329
|
return action == GameStarted || action == GamePaused ||
|