app.hypergames.hypersdk 1.11.0-beta.2 → 1.11.0-beta.21
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 +146 -0
- package/Editor/Scripts/Inspectors/HyperSDKSettingsEditor.cs +6 -0
- package/HyperLoader.unity +2 -2
- package/Runtime/Internal/Core/HyperRuntime.cs +104 -9
- package/Runtime/Internal/Managers/DataManager.cs +2 -1
- package/Runtime/Internal/Managers/EventManager.cs +7 -1
- package/Runtime/Internal/Managers/GameManager.cs +88 -51
- package/Runtime/Internal/Managers/ReplayManager.cs +5 -1
- package/Runtime/Internal/Managers/TimeManager.cs +25 -8
- package/Runtime/Internal/Replay/ReplaySessionState.cs +66 -6
- package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +4 -0
- package/Runtime/Internal/UI/Components/ReplayController.cs +134 -11
- 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 +145 -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 +16 -0
- package/Runtime/Public/Replay/ReplayPlaybackEngine.cs +127 -3
- package/Runtime/Resources/Fonts/Nunito/Nunito-Bold SDF.asset +500 -457
- package/Runtime/Resources/Fonts/Wonder Boys/Wonder Boys.asset +394 -379
- package/Runtime/Resources/HyperSDKSettings.asset +1 -0
- package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Normal.png +0 -0
- package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Normal.png.meta +156 -0
- package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Pressed.png +0 -0
- package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Pressed.png.meta +156 -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/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 +305 -183
- package/Runtime/Resources/UIPrefabs/ReplayPausedModal.prefab +2194 -0
- package/Runtime/Resources/UIPrefabs/ReplayPausedModal.prefab.meta +7 -0
- package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab +291 -6
- package/Runtime/Shared/Replay/IReplayPlaybackEngine.cs +3 -0
- package/Samples~/LaneRunner/LaneRunnerSample.unity +4 -3
- package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab +1 -1
- package/Samples~/LaneRunner/Prefabs/Obstacle.prefab +1 -1
- package/Samples~/LaneRunner/README.md +8 -1
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerPlayer.cs +18 -4
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs +3 -1
- package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs +8 -0
- package/package.json +1 -1
- package/Runtime/Resources/Sprites/Replays/Recording.png +0 -0
|
@@ -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.
|
|
@@ -480,10 +486,15 @@ namespace Hyper
|
|
|
480
486
|
/// <summary>Fired when all replay activities have been dispatched.</summary>
|
|
481
487
|
event Action OnEnded;
|
|
482
488
|
|
|
489
|
+
/// <summary>Fired at the recorded boundary where game-specific playable gameplay began.</summary>
|
|
490
|
+
event Action OnGameplayReady;
|
|
491
|
+
|
|
483
492
|
bool IsPlaying { get; }
|
|
484
493
|
double DurationSeconds { get; }
|
|
485
494
|
double ElapsedSeconds { get; }
|
|
486
495
|
float Progress01 { get; }
|
|
496
|
+
bool IsSeeking { get; }
|
|
497
|
+
bool HasGameplayReadyMarker { get; }
|
|
487
498
|
|
|
488
499
|
/// <summary>
|
|
489
500
|
/// Optional visual lead time. Gameplay actions still fire at their recorded time.
|
|
@@ -616,6 +627,8 @@ namespace Hyper
|
|
|
616
627
|
|
|
617
628
|
public void ExitPracticeMenu() => Manager?.ExitPracticeMenu();
|
|
618
629
|
|
|
630
|
+
public void CompleteCustomRestart() => HyperRuntime.CompleteCustomRestart();
|
|
631
|
+
|
|
619
632
|
public void InitializeLives(int maxLives, int initialLives = -1) => Manager?.InitializeLives(maxLives, initialLives);
|
|
620
633
|
public void SetLives(int lives) => Manager?.SetLives(lives);
|
|
621
634
|
public void AddLife() => Manager?.AddLife();
|
|
@@ -677,11 +690,14 @@ namespace Hyper
|
|
|
677
690
|
public event Action<float, string> OnValueAction { add => _playback.OnValueAction += value; remove => _playback.OnValueAction -= value; }
|
|
678
691
|
public event Action<string> OnActionPreparing { add => _playback.OnActionPreparing += value; remove => _playback.OnActionPreparing -= value; }
|
|
679
692
|
public event Action OnEnded { add => _playback.OnGameEnded += value; remove => _playback.OnGameEnded -= value; }
|
|
693
|
+
public event Action OnGameplayReady { add => _playback.OnGameplayReady += value; remove => _playback.OnGameplayReady -= value; }
|
|
680
694
|
|
|
681
695
|
public bool IsPlaying => _playback.IsPlaying;
|
|
682
696
|
public double DurationSeconds => _playback.DurationSeconds;
|
|
683
697
|
public double ElapsedSeconds => _playback.ElapsedSeconds;
|
|
684
698
|
public float Progress01 => _playback.Progress01;
|
|
699
|
+
public bool IsSeeking => ReplaySessionState.IsSeeking;
|
|
700
|
+
public bool HasGameplayReadyMarker => _playback.HasGameplayReadyMarker;
|
|
685
701
|
public double ActionPreparationLeadSeconds
|
|
686
702
|
{
|
|
687
703
|
get => _playback.ActionPreparationLeadSeconds;
|
|
@@ -11,6 +11,7 @@ namespace Hyper
|
|
|
11
11
|
internal sealed class ReplayPlaybackEngine : IReplayPlaybackEngine
|
|
12
12
|
{
|
|
13
13
|
private const string GameStarted = "GS";
|
|
14
|
+
private const string GameplayReady = "GPR";
|
|
14
15
|
private const string GamePaused = "GP";
|
|
15
16
|
private const string GameResumed = "GR";
|
|
16
17
|
private const string GameEnded = "GE";
|
|
@@ -28,6 +29,13 @@ namespace Hyper
|
|
|
28
29
|
private double _stoppedElapsed;
|
|
29
30
|
private bool _isSuspended;
|
|
30
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;
|
|
31
39
|
|
|
32
40
|
public event Action<float, float> OnValues2;
|
|
33
41
|
public event Action<float> OnValue;
|
|
@@ -45,6 +53,7 @@ namespace Hyper
|
|
|
45
53
|
/// </summary>
|
|
46
54
|
public event Action<string> OnActionPreparing;
|
|
47
55
|
public event Action OnGameStarted;
|
|
56
|
+
public event Action OnGameplayReady;
|
|
48
57
|
public event Action OnGameEnded;
|
|
49
58
|
|
|
50
59
|
public bool IsPlaying { get; private set; }
|
|
@@ -55,6 +64,8 @@ namespace Hyper
|
|
|
55
64
|
/// The action itself is still dispatched at its recorded timestamp.
|
|
56
65
|
/// </summary>
|
|
57
66
|
public double ActionPreparationLeadSeconds { get; set; }
|
|
67
|
+
public bool HasGameplayReadyMarker { get; private set; }
|
|
68
|
+
public float GameplayStartProgress01 { get; private set; }
|
|
58
69
|
public float Progress01 => DurationSeconds <= 0d
|
|
59
70
|
? (IsPlaying ? 0f : 1f)
|
|
60
71
|
: Mathf.Clamp01((float)(ElapsedSeconds / DurationSeconds));
|
|
@@ -72,7 +83,19 @@ namespace Hyper
|
|
|
72
83
|
_endedRaised = false;
|
|
73
84
|
_completionPending = false;
|
|
74
85
|
_nextActionPrepared = false;
|
|
86
|
+
_gameplayClockStarted = false;
|
|
87
|
+
_gameplaySourceStartTime = 0d;
|
|
88
|
+
_gameplayElapsedSeconds = 0d;
|
|
89
|
+
_gameplayStepSeconds = 0d;
|
|
90
|
+
_gameplayPauseDurationAtStart = 0d;
|
|
91
|
+
_gameplayTick = 0L;
|
|
92
|
+
_currentTickDelta = 0d;
|
|
75
93
|
DurationSeconds = CalculateDuration(_ndjson);
|
|
94
|
+
double gameplayStartSeconds = CalculateGameplayStart(_ndjson, out bool hasGameplayReadyMarker);
|
|
95
|
+
HasGameplayReadyMarker = hasGameplayReadyMarker;
|
|
96
|
+
GameplayStartProgress01 = hasGameplayReadyMarker && DurationSeconds > 0d
|
|
97
|
+
? Mathf.Clamp01((float)(gameplayStartSeconds / DurationSeconds))
|
|
98
|
+
: 0f;
|
|
76
99
|
_hasNext = TryReadNext(out _next);
|
|
77
100
|
|
|
78
101
|
if (!_hasNext)
|
|
@@ -113,13 +136,19 @@ namespace Hyper
|
|
|
113
136
|
double.IsInfinity(fixedSimulationDeltaSeconds)
|
|
114
137
|
? 0d
|
|
115
138
|
: Math.Max(0d, fixedSimulationDeltaSeconds);
|
|
139
|
+
_currentTickDelta = delta;
|
|
116
140
|
_elapsedSeconds += delta;
|
|
141
|
+
if (_gameplayClockStarted && delta > 0d)
|
|
142
|
+
{
|
|
143
|
+
if (_gameplayStepSeconds <= 0d) _gameplayStepSeconds = delta;
|
|
144
|
+
_gameplayElapsedSeconds += delta;
|
|
145
|
+
_gameplayTick++;
|
|
146
|
+
}
|
|
117
147
|
|
|
118
148
|
int processed = 0;
|
|
119
149
|
while (IsPlaying && !_completionPending && _hasNext && processed++ < MaxActivitiesPerTick)
|
|
120
150
|
{
|
|
121
|
-
double elapsed
|
|
122
|
-
double dueTime = _next.Time - _sourceStartTime - _removedPauseDuration;
|
|
151
|
+
GetSchedulingTime(_next, out double elapsed, out double dueTime);
|
|
123
152
|
PrepareNextActionIfDue(elapsed, dueTime);
|
|
124
153
|
if (elapsed + 0.000001d < dueTime) return;
|
|
125
154
|
|
|
@@ -145,6 +174,13 @@ namespace Hyper
|
|
|
145
174
|
_isSuspended = false;
|
|
146
175
|
_completionPending = false;
|
|
147
176
|
_nextActionPrepared = false;
|
|
177
|
+
_gameplayClockStarted = false;
|
|
178
|
+
_gameplaySourceStartTime = 0d;
|
|
179
|
+
_gameplayElapsedSeconds = 0d;
|
|
180
|
+
_gameplayStepSeconds = 0d;
|
|
181
|
+
_gameplayPauseDurationAtStart = 0d;
|
|
182
|
+
_gameplayTick = 0L;
|
|
183
|
+
_currentTickDelta = 0d;
|
|
148
184
|
}
|
|
149
185
|
|
|
150
186
|
public void Suspend()
|
|
@@ -214,6 +250,10 @@ namespace Hyper
|
|
|
214
250
|
case GameStarted:
|
|
215
251
|
OnGameStarted?.Invoke();
|
|
216
252
|
break;
|
|
253
|
+
case GameplayReady:
|
|
254
|
+
StartGameplayClock(activity.Time);
|
|
255
|
+
OnGameplayReady?.Invoke();
|
|
256
|
+
break;
|
|
217
257
|
case GamePaused:
|
|
218
258
|
SkipRecordedPause(activity.Time);
|
|
219
259
|
break;
|
|
@@ -243,10 +283,94 @@ namespace Hyper
|
|
|
243
283
|
OnActionPreparing?.Invoke(_next.Action);
|
|
244
284
|
}
|
|
245
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
|
+
|
|
246
327
|
private static bool IsLifecycleAction(string action)
|
|
247
328
|
{
|
|
248
329
|
return action == GameStarted || action == GamePaused ||
|
|
249
|
-
action == GameResumed || action == GameEnded;
|
|
330
|
+
action == GameResumed || action == GameEnded || action == GameplayReady;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private static double CalculateGameplayStart(string ndjson, out bool found)
|
|
334
|
+
{
|
|
335
|
+
found = false;
|
|
336
|
+
if (string.IsNullOrWhiteSpace(ndjson)) return 0d;
|
|
337
|
+
|
|
338
|
+
int cursor = 0;
|
|
339
|
+
bool hasFirst = false;
|
|
340
|
+
double firstTime = 0d;
|
|
341
|
+
double removedPauses = 0d;
|
|
342
|
+
double? pauseStartedAt = null;
|
|
343
|
+
|
|
344
|
+
while (TryReadLine(ndjson, ref cursor, out string line))
|
|
345
|
+
{
|
|
346
|
+
if (!TryParse(line, out ReplayActivity activity)) continue;
|
|
347
|
+
if (!hasFirst)
|
|
348
|
+
{
|
|
349
|
+
hasFirst = true;
|
|
350
|
+
firstTime = activity.Time;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (activity.Kind != ReplayActivityKind.Action) continue;
|
|
354
|
+
if (activity.Action == GamePaused)
|
|
355
|
+
{
|
|
356
|
+
pauseStartedAt ??= activity.Time;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (activity.Action == GameResumed && pauseStartedAt.HasValue)
|
|
361
|
+
{
|
|
362
|
+
removedPauses += Math.Max(0d, activity.Time - pauseStartedAt.Value);
|
|
363
|
+
pauseStartedAt = null;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (activity.Action != GameplayReady) continue;
|
|
368
|
+
|
|
369
|
+
found = true;
|
|
370
|
+
return Math.Max(0d, activity.Time - firstTime - removedPauses);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return 0d;
|
|
250
374
|
}
|
|
251
375
|
|
|
252
376
|
private void SkipRecordedPause(double pauseStartedAt)
|