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.
Files changed (48) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/Editor/Scripts/Inspectors/HyperSDKSettingsEditor.cs +6 -0
  3. package/HyperLoader.unity +2 -2
  4. package/Runtime/Internal/Core/HyperRuntime.cs +104 -9
  5. package/Runtime/Internal/Managers/DataManager.cs +2 -1
  6. package/Runtime/Internal/Managers/EventManager.cs +7 -1
  7. package/Runtime/Internal/Managers/GameManager.cs +88 -51
  8. package/Runtime/Internal/Managers/ReplayManager.cs +5 -1
  9. package/Runtime/Internal/Managers/TimeManager.cs +25 -8
  10. package/Runtime/Internal/Replay/ReplaySessionState.cs +66 -6
  11. package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +4 -0
  12. package/Runtime/Internal/UI/Components/ReplayController.cs +134 -11
  13. package/Runtime/Internal/UI/Components/ReplaySeekOverlay.cs +18 -0
  14. package/Runtime/Internal/UI/Components/ReplaySeekOverlay.cs.meta +2 -0
  15. package/Runtime/Internal/UI/Modals/ReplayPausedModal.cs +145 -0
  16. package/Runtime/Internal/UI/Modals/ReplayPausedModal.cs.meta +2 -0
  17. package/Runtime/Internal/UI/Modals/StartingInstructionModal.cs +1 -0
  18. package/Runtime/Public/Core/HyperSDK.cs +16 -0
  19. package/Runtime/Public/Replay/ReplayPlaybackEngine.cs +127 -3
  20. package/Runtime/Resources/Fonts/Nunito/Nunito-Bold SDF.asset +500 -457
  21. package/Runtime/Resources/Fonts/Wonder Boys/Wonder Boys.asset +394 -379
  22. package/Runtime/Resources/HyperSDKSettings.asset +1 -0
  23. package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Normal.png +0 -0
  24. package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Normal.png.meta +156 -0
  25. package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Pressed.png +0 -0
  26. package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Pressed.png.meta +156 -0
  27. package/Runtime/Resources/Sprites/Circle Loader/Property 1=1.png.meta +7 -7
  28. package/Runtime/Resources/Sprites/Circle Loader/Property 1=2.png.meta +7 -7
  29. package/Runtime/Resources/Sprites/Circle Loader/Property 1=3.png.meta +7 -7
  30. package/Runtime/Resources/Sprites/Circle Loader/Property 1=4.png.meta +7 -7
  31. package/Runtime/Resources/Sprites/Circle Loader/Property 1=5.png.meta +7 -7
  32. package/Runtime/Resources/Sprites/Replays/Replay.png +0 -0
  33. package/Runtime/Resources/Sprites/Replays/{Recording.png.meta → Replay.png.meta} +9 -9
  34. package/Runtime/Resources/UIPrefabs/ControlBar.prefab +5 -5
  35. package/Runtime/Resources/UIPrefabs/ReplayController.prefab +305 -183
  36. package/Runtime/Resources/UIPrefabs/ReplayPausedModal.prefab +2194 -0
  37. package/Runtime/Resources/UIPrefabs/ReplayPausedModal.prefab.meta +7 -0
  38. package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab +291 -6
  39. package/Runtime/Shared/Replay/IReplayPlaybackEngine.cs +3 -0
  40. package/Samples~/LaneRunner/LaneRunnerSample.unity +4 -3
  41. package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab +1 -1
  42. package/Samples~/LaneRunner/Prefabs/Obstacle.prefab +1 -1
  43. package/Samples~/LaneRunner/README.md +8 -1
  44. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerPlayer.cs +18 -4
  45. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs +3 -1
  46. package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs +8 -0
  47. package/package.json +1 -1
  48. 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 = GetElapsedSeconds();
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)