app.hypergames.hypersdk 1.10.0 → 1.11.0-beta.2

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 (115) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/Editor/Scripts/Setup/HyperSDKInstaller.cs +42 -0
  3. package/Editor/Scripts/Tools/HyperGameModeMenu.cs +27 -7
  4. package/Runtime/Internal/AssemblyInfo.cs +1 -0
  5. package/Runtime/Internal/Bootstrap/HyperLoader.cs +186 -8
  6. package/Runtime/Internal/Core/HyperRuntime.cs +82 -0
  7. package/Runtime/Internal/Core/HyperRuntimeHost.cs +6 -0
  8. package/Runtime/Internal/Data/URLData.cs +18 -1
  9. package/Runtime/Internal/Managers/BackendManager.cs +281 -24
  10. package/Runtime/Internal/Managers/DataManager.cs +207 -21
  11. package/Runtime/Internal/Managers/GameManager.cs +365 -17
  12. package/Runtime/Internal/Managers/ReplayManager.cs +117 -0
  13. package/Runtime/Internal/Managers/ReplayManager.cs.meta +11 -0
  14. package/Runtime/Internal/Managers/TimeManager.cs +54 -12
  15. package/Runtime/Internal/Replay/ReplayActivityRecorder.cs +182 -0
  16. package/Runtime/Internal/Replay/ReplayActivityRecorder.cs.meta +2 -0
  17. package/Runtime/Internal/Replay/ReplaySessionState.cs +181 -0
  18. package/Runtime/Internal/Replay/ReplaySessionState.cs.meta +2 -0
  19. package/Runtime/Internal/Replay.meta +8 -0
  20. package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +5 -0
  21. package/Runtime/Internal/UI/Animation/LoadingTextAnimation.cs +15 -1
  22. package/Runtime/Internal/UI/Components/ControlBar.cs +25 -2
  23. package/Runtime/Internal/UI/Components/PauseButton.cs +4 -1
  24. package/Runtime/Internal/UI/Components/ReplayController.cs +527 -0
  25. package/Runtime/Internal/UI/Components/ReplayController.cs.meta +2 -0
  26. package/Runtime/Internal/UI/Components/ReplayMarkerPulse.cs +89 -0
  27. package/Runtime/Internal/UI/Components/ReplayMarkerPulse.cs.meta +2 -0
  28. package/Runtime/Internal/UI/Components/ReplayPresentationCanvas.cs +183 -0
  29. package/Runtime/Internal/UI/Components/ReplayPresentationCanvas.cs.meta +2 -0
  30. package/Runtime/Internal/UI/Modals/LoadingFailedModal.cs +12 -0
  31. package/Runtime/Internal/UI/Modals/ReplayOverModal.cs +61 -0
  32. package/Runtime/Internal/UI/Modals/ReplayOverModal.cs.meta +2 -0
  33. package/Runtime/Internal/UI/Modals/StartingInstructionModal.cs +38 -29
  34. package/Runtime/Public/AssemblyInfo.cs +3 -0
  35. package/Runtime/Public/AssemblyInfo.cs.meta +2 -0
  36. package/Runtime/Public/Core/HyperSDK.cs +305 -109
  37. package/Runtime/Public/Hyper.asmdef +2 -1
  38. package/Runtime/Public/Replay/ReplayPlaybackEngine.cs +555 -0
  39. package/Runtime/Public/Replay/ReplayPlaybackEngine.cs.meta +2 -0
  40. package/Runtime/Public/Replay.meta +8 -0
  41. package/Runtime/Resources/Fonts/Nunito/Nunito-Light SDF.asset +528 -521
  42. package/Runtime/Resources/Materials/HyperReplayScanlines.preset +340 -0
  43. package/Runtime/Resources/Materials/HyperReplayScanlines.preset.meta +8 -0
  44. package/Runtime/Resources/Materials/ReplayScanlineOverlay.mat +55 -0
  45. package/Runtime/Resources/Materials/ReplayScanlineOverlay.mat.meta +8 -0
  46. package/Runtime/Resources/Shaders/ReplayScanlineOverlay.shader +150 -0
  47. package/Runtime/Resources/Shaders/ReplayScanlineOverlay.shader.meta +10 -0
  48. package/Runtime/Resources/Sprites/Replays/Fallback avatar.png +0 -0
  49. package/Runtime/Resources/Sprites/Replays/Fallback avatar.png.meta +156 -0
  50. package/Runtime/Resources/Sprites/Replays/Recording.png +0 -0
  51. package/Runtime/Resources/Sprites/Replays/Recording.png.meta +156 -0
  52. package/Runtime/Resources/Sprites/Replays/Replay frame.png +0 -0
  53. package/Runtime/Resources/Sprites/Replays/Replay frame.png.meta +156 -0
  54. package/Runtime/Resources/Sprites/Replays/Replay modal icon.png +0 -0
  55. package/Runtime/Resources/Sprites/Replays/Replay modal icon.png.meta +156 -0
  56. package/Runtime/Resources/Sprites/Replays.meta +8 -0
  57. package/Runtime/Resources/UIPrefabs/ControlBar.prefab +169 -2
  58. package/Runtime/Resources/UIPrefabs/GamePausedModal_Battle.prefab +1 -1
  59. package/Runtime/Resources/UIPrefabs/ReplayController.prefab +1269 -0
  60. package/Runtime/Resources/UIPrefabs/ReplayController.prefab.meta +7 -0
  61. package/Runtime/Resources/UIPrefabs/ReplayInputBlocker.prefab +90 -0
  62. package/Runtime/Resources/UIPrefabs/ReplayInputBlocker.prefab.meta +7 -0
  63. package/Runtime/Resources/UIPrefabs/ReplayOverModal.prefab +958 -0
  64. package/Runtime/Resources/UIPrefabs/ReplayOverModal.prefab.meta +7 -0
  65. package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab +189 -0
  66. package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab.meta +7 -0
  67. package/Runtime/Resources/UIPrefabs/SnackBar.prefab +2 -2
  68. package/Runtime/Shared/AssemblyInfo.cs +5 -0
  69. package/Runtime/Shared/AssemblyInfo.cs.meta +11 -0
  70. package/Runtime/Shared/DTOs/Enums.cs +8 -3
  71. package/Runtime/Shared/DTOs/Score/ScoreSnapshot.cs +13 -1
  72. package/Runtime/Shared/Replay/IReplayPlaybackEngine.cs +37 -0
  73. package/Runtime/Shared/Replay/IReplayPlaybackEngine.cs.meta +11 -0
  74. package/Runtime/Shared/Replay.meta +8 -0
  75. package/Samples~/LaneRunner/LaneRunnerSample.unity +1603 -0
  76. package/Samples~/LaneRunner/LaneRunnerSample.unity.meta +7 -0
  77. package/Samples~/LaneRunner/Materials/DoubleScore.mat +86 -0
  78. package/Samples~/LaneRunner/Materials/DoubleScore.mat.meta +8 -0
  79. package/Samples~/LaneRunner/Materials/Ground.mat +84 -0
  80. package/Samples~/LaneRunner/Materials/Ground.mat.meta +8 -0
  81. package/Samples~/LaneRunner/Materials/Lane.mat +84 -0
  82. package/Samples~/LaneRunner/Materials/Lane.mat.meta +8 -0
  83. package/Samples~/LaneRunner/Materials/LaneMarker.mat +84 -0
  84. package/Samples~/LaneRunner/Materials/LaneMarker.mat.meta +8 -0
  85. package/Samples~/LaneRunner/Materials/Obstacle.mat +84 -0
  86. package/Samples~/LaneRunner/Materials/Obstacle.mat.meta +8 -0
  87. package/Samples~/LaneRunner/Materials/Player.mat +84 -0
  88. package/Samples~/LaneRunner/Materials/Player.mat.meta +8 -0
  89. package/Samples~/LaneRunner/Materials.meta +8 -0
  90. package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab +152 -0
  91. package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab.meta +7 -0
  92. package/Samples~/LaneRunner/Prefabs/Obstacle.prefab +152 -0
  93. package/Samples~/LaneRunner/Prefabs/Obstacle.prefab.meta +7 -0
  94. package/Samples~/LaneRunner/Prefabs.meta +8 -0
  95. package/Samples~/LaneRunner/README.md +35 -0
  96. package/Samples~/LaneRunner/README.md.meta +7 -0
  97. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerCommand.cs +9 -0
  98. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerCommand.cs.meta +2 -0
  99. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerGame.cs +93 -0
  100. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerGame.cs.meta +2 -0
  101. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerPlayer.cs +155 -0
  102. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerPlayer.cs.meta +2 -0
  103. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs +110 -0
  104. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs.meta +2 -0
  105. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerTrackItem.cs +41 -0
  106. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerTrackItem.cs.meta +2 -0
  107. package/Samples~/LaneRunner/Scripts/Gameplay.meta +8 -0
  108. package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerReplayAdapter.cs +62 -0
  109. package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerReplayAdapter.cs.meta +2 -0
  110. package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs +64 -0
  111. package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs.meta +2 -0
  112. package/Samples~/LaneRunner/Scripts/Hyper.meta +8 -0
  113. package/Samples~/LaneRunner/Scripts.meta +8 -0
  114. package/Tests.meta +8 -0
  115. package/package.json +8 -1
@@ -0,0 +1,117 @@
1
+ using Hyper.Shared;
2
+
3
+ namespace Hyper.Internal.Managers
4
+ {
5
+ /// <summary>
6
+ /// Owns replay playback for the active SDK session. Games only subscribe to
7
+ /// typed activity callbacks; loading, timing, progress, suspension and ending
8
+ /// are handled here.
9
+ /// </summary>
10
+ internal sealed class ReplayManager
11
+ {
12
+ private IReplayPlaybackEngine _playback;
13
+ private EventManager _eventManager;
14
+ private bool _endingGame;
15
+
16
+ internal void Initialize(EventManager eventManager)
17
+ {
18
+ _eventManager = eventManager;
19
+ _eventManager.OnGameStart += HandleGameStart;
20
+ _eventManager.OnGameEnded += HandleGameEnded;
21
+ _eventManager.OnTimeScaleFrozen += HandleTimeScaleFrozen;
22
+ _eventManager.OnTimeScaleResumed += HandleTimeScaleResumed;
23
+ }
24
+
25
+ internal void AttachPlayback(IReplayPlaybackEngine playback)
26
+ {
27
+ if (ReferenceEquals(_playback, playback)) return;
28
+ DetachPlayback();
29
+ _playback = playback;
30
+ if (_playback == null) return;
31
+
32
+ _playback.OnGameEnded += HandlePlaybackEnded;
33
+ }
34
+
35
+ internal void FixedTick()
36
+ {
37
+ if (_playback == null || !_playback.IsPlaying) return;
38
+ _playback.Tick();
39
+ ReplaySessionState.SetProgress(_playback.Progress01);
40
+ }
41
+
42
+ internal void PrepareForRestart()
43
+ {
44
+ _endingGame = false;
45
+ _playback?.Stop();
46
+ }
47
+
48
+ internal void Cleanup()
49
+ {
50
+ if (_eventManager != null)
51
+ {
52
+ _eventManager.OnGameStart -= HandleGameStart;
53
+ _eventManager.OnGameEnded -= HandleGameEnded;
54
+ _eventManager.OnTimeScaleFrozen -= HandleTimeScaleFrozen;
55
+ _eventManager.OnTimeScaleResumed -= HandleTimeScaleResumed;
56
+ }
57
+
58
+ DetachPlayback();
59
+ _eventManager = null;
60
+ }
61
+
62
+ private void HandleGameStart()
63
+ {
64
+ if (HyperRuntime.GameManager == null || !HyperRuntime.GameManager.IsReplayMode ||
65
+ !ReplaySessionState.HasReplayData || _playback == null)
66
+ {
67
+ return;
68
+ }
69
+
70
+ if (ReplaySessionState.IsSeeking)
71
+ {
72
+ ReplaySessionState.NotifySeekPlaybackStarted();
73
+ UnityEngine.Time.timeScale = ReplaySessionState.PlaybackSpeed;
74
+ }
75
+
76
+ _playback.Begin(ReplaySessionState.ReplayNdjson);
77
+ }
78
+
79
+ private void HandleGameEnded()
80
+ {
81
+ if (_endingGame) return;
82
+ _playback?.Stop();
83
+ }
84
+
85
+ private void HandleTimeScaleFrozen()
86
+ {
87
+ if (HyperRuntime.GameManager != null && HyperRuntime.GameManager.IsReplayMode)
88
+ _playback?.Suspend();
89
+ }
90
+
91
+ private void HandleTimeScaleResumed()
92
+ {
93
+ if (HyperRuntime.GameManager != null && HyperRuntime.GameManager.IsReplayMode)
94
+ _playback?.Resume();
95
+ }
96
+
97
+ private void HandlePlaybackEnded()
98
+ {
99
+ ReplaySessionState.MarkEnded();
100
+
101
+ GameManager gameManager = HyperRuntime.GameManager;
102
+ if (gameManager == null || gameManager.HasGameEnded) return;
103
+
104
+ _endingGame = true;
105
+ gameManager.EndGame();
106
+ _endingGame = false;
107
+ }
108
+
109
+ private void DetachPlayback()
110
+ {
111
+ if (_playback == null) return;
112
+ _playback.Stop();
113
+ _playback.OnGameEnded -= HandlePlaybackEnded;
114
+ _playback = null;
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 26c94db513ed4191b3f0d4040f8a7d67
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -43,6 +43,7 @@ namespace Hyper.Internal.Managers
43
43
  private bool _isSessionRunning;
44
44
  private bool _sessionWarningTriggered;
45
45
  private bool _timeUpTriggered;
46
+ private bool _timeUpPending;
46
47
 
47
48
  private float _lastUnscaledTime;
48
49
  private bool _hasGameEnded;
@@ -64,13 +65,14 @@ namespace Hyper.Internal.Managers
64
65
  _hasBackendSessionDuration = false;
65
66
  _lastUnscaledTime = Time.unscaledTime;
66
67
  _timeUpTriggered = false;
68
+ _timeUpPending = false;
67
69
  }
68
70
 
69
71
  #endregion
70
72
 
71
73
  #region Public API Implementation (ITimerAPI)
72
74
 
73
- public int CurrentTime => _gameManager.Config.GameMode == GameMode.Battle
75
+ public int CurrentTime => _gameManager.IsBattleMode
74
76
  ? (int)_battleTimer
75
77
  : (int)_practiceTimer;
76
78
 
@@ -89,11 +91,21 @@ namespace Hyper.Internal.Managers
89
91
  float unscaledDelta = Time.unscaledTime - _lastUnscaledTime;
90
92
  _lastUnscaledTime = Time.unscaledTime;
91
93
 
92
- float gameTimerDelta = Mathf.Approximately(Time.timeScale, 0f) ? 0f : unscaledDelta;
94
+ UpdateSessionTimer(unscaledDelta);
95
+ DispatchPendingTimeUp();
96
+ }
97
+
98
+ internal void FixedTick()
99
+ {
100
+ if (Mathf.Approximately(Time.timeScale, 0f))
101
+ return;
102
+
103
+ float gameTimerDelta = _gameManager != null && _gameManager.IsReplayMode
104
+ ? Time.fixedDeltaTime
105
+ : Time.fixedUnscaledDeltaTime;
93
106
 
94
107
  UpdatePracticeTimer(gameTimerDelta);
95
108
  UpdateBattleTimer(gameTimerDelta);
96
- UpdateSessionTimer(unscaledDelta);
97
109
  }
98
110
 
99
111
  public void Reset()
@@ -102,9 +114,22 @@ namespace Hyper.Internal.Managers
102
114
  _lastUnscaledTime = Time.unscaledTime;
103
115
  }
104
116
 
117
+ internal void ResetForGameRestart()
118
+ {
119
+ _practiceTimer = 0f;
120
+ _battleTimer = _gameManager != null ? _gameManager.Config.TimeLimit : 0f;
121
+ _isPracticeRunning = false;
122
+ _isBattleRunning = false;
123
+ _timeUpTriggered = false;
124
+ _timeUpPending = false;
125
+ _hasGameEnded = false;
126
+ _hasGameStarted = false;
127
+ _lastUnscaledTime = Time.unscaledTime;
128
+ }
129
+
105
130
  public void ResumeGameTimersAfterPause()
106
131
  {
107
- if (_gameManager.Config.GameMode == GameMode.Battle)
132
+ if (_gameManager.IsBattleMode)
108
133
  {
109
134
  StartBattle();
110
135
  }
@@ -116,7 +141,7 @@ namespace Hyper.Internal.Managers
116
141
 
117
142
  public void StartActiveGameTimer()
118
143
  {
119
- if (_gameManager.Config.GameMode == GameMode.Battle)
144
+ if (_gameManager.IsBattleMode)
120
145
  {
121
146
  StartSessionTimer();
122
147
  StartBattle();
@@ -132,6 +157,7 @@ namespace Hyper.Internal.Managers
132
157
  public void StopAllTimers()
133
158
  {
134
159
  _hasGameEnded = true;
160
+ _timeUpPending = false;
135
161
  StopBattle();
136
162
  StopPractice();
137
163
  }
@@ -247,7 +273,7 @@ namespace Hyper.Internal.Managers
247
273
  {
248
274
  if (_isBattleRunning)
249
275
  {
250
- if (_gameManager.Config.GameMode == GameMode.Battle &&
276
+ if (_gameManager.IsBattleMode &&
251
277
  _hasGameStarted &&
252
278
  _isPreSessionRunning &&
253
279
  _sessionTimer <= _battleTimer)
@@ -261,8 +287,7 @@ namespace Hyper.Internal.Managers
261
287
  _battleTimer = 0;
262
288
  if (!_timeUpTriggered)
263
289
  {
264
- _timeUpTriggered = true;
265
- _eventManager?.InvokeTimeUpEvent();
290
+ QueueTimeUp();
266
291
  }
267
292
  }
268
293
  }
@@ -274,13 +299,30 @@ namespace Hyper.Internal.Managers
274
299
 
275
300
  if (_battleTimer <= 0 && !_timeUpTriggered)
276
301
  {
277
- _timeUpTriggered = true;
278
- _eventManager?.InvokeTimeUpEvent();
302
+ QueueTimeUp();
279
303
  }
280
304
  }
281
305
  }
282
306
  }
283
307
 
308
+ private void QueueTimeUp()
309
+ {
310
+ _timeUpPending = true;
311
+ }
312
+
313
+ private void DispatchPendingTimeUp()
314
+ {
315
+ if (!_timeUpPending)
316
+ return;
317
+
318
+ _timeUpPending = false;
319
+ if (_hasGameEnded || _timeUpTriggered)
320
+ return;
321
+
322
+ _timeUpTriggered = true;
323
+ _eventManager?.InvokeTimeUpEvent();
324
+ }
325
+
284
326
  private void UpdateSessionTimer(float unscaledDelta)
285
327
  {
286
328
  if (!_isPreSessionRunning) return;
@@ -311,7 +353,7 @@ namespace Hyper.Internal.Managers
311
353
  _isSessionRunning = false;
312
354
  _isPreSessionRunning = false;
313
355
 
314
- if (_hasGameStarted && _gameManager.Config.GameMode == GameMode.Battle)
356
+ if (_hasGameStarted && _gameManager.IsBattleMode)
315
357
  {
316
358
  _battleTimer = 0;
317
359
  if (!_timeUpTriggered)
@@ -341,7 +383,7 @@ namespace Hyper.Internal.Managers
341
383
  }
342
384
  };
343
385
 
344
- if (_gameManager.Config.GameMode == GameMode.Battle)
386
+ if (_gameManager.IsBattleMode)
345
387
  {
346
388
  _eventManager.OnStartDeadlineReached += () =>
347
389
  {
@@ -0,0 +1,182 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+
4
+ namespace Hyper.Internal.Replay
5
+ {
6
+ internal sealed class ReplayActivityRecorder
7
+ {
8
+ internal const string GameStarted = "GS";
9
+ internal const string GamePaused = "GP";
10
+ internal const string GameResumed = "GR";
11
+ internal const string GameEnded = "GE";
12
+
13
+ private readonly Func<double> _utcNow;
14
+ private readonly Func<double> _simulationNow;
15
+ private List<object[]> _activities;
16
+ private bool _hasStarted;
17
+ private bool _isPaused;
18
+ private bool _hasEnded;
19
+ private double _sessionStartUtc;
20
+ private double _sessionStartSimulation;
21
+ private double _pauseStartedUtc;
22
+ private double _pauseStartedReplayTime;
23
+ private double _recordedPauseDuration;
24
+
25
+ internal event Action<object[]> ActivityRecorded;
26
+
27
+ internal ReplayActivityRecorder(Func<double> utcNow) : this(utcNow, utcNow)
28
+ {
29
+ }
30
+
31
+ internal ReplayActivityRecorder(Func<double> utcNow, Func<double> simulationNow)
32
+ {
33
+ _utcNow = utcNow ?? throw new ArgumentNullException(nameof(utcNow));
34
+ _simulationNow = simulationNow ?? throw new ArgumentNullException(nameof(simulationNow));
35
+ }
36
+
37
+ internal void StartSession()
38
+ {
39
+ if (_hasStarted && !_hasEnded) return;
40
+
41
+ _activities?.Clear();
42
+ _hasStarted = true;
43
+ _isPaused = false;
44
+ _hasEnded = false;
45
+ _recordedPauseDuration = 0d;
46
+ _sessionStartUtc = _utcNow();
47
+ _sessionStartSimulation = _simulationNow();
48
+ AddAction(GameStarted, _sessionStartUtc);
49
+ }
50
+
51
+ internal void PauseSession()
52
+ {
53
+ if (!_hasStarted || _hasEnded || _isPaused) return;
54
+
55
+ _pauseStartedReplayTime = GetReplayTime();
56
+ _pauseStartedUtc = _utcNow();
57
+ _isPaused = true;
58
+ AddAction(GamePaused, _pauseStartedReplayTime);
59
+ }
60
+
61
+ internal void ResumeSession()
62
+ {
63
+ if (!_hasStarted || _hasEnded || !_isPaused) return;
64
+
65
+ double pauseDuration = Math.Max(0d, _utcNow() - _pauseStartedUtc);
66
+ _recordedPauseDuration += pauseDuration;
67
+ _isPaused = false;
68
+ AddAction(GameResumed, _pauseStartedReplayTime + pauseDuration);
69
+ }
70
+
71
+ internal void EndSession()
72
+ {
73
+ if (!_hasStarted || _hasEnded) return;
74
+
75
+ _hasEnded = true;
76
+ AddAction(GameEnded);
77
+ }
78
+
79
+ internal void RecordValues(float first, float second)
80
+ {
81
+ if (!CanRecordGameplay()) return;
82
+ AddActivity(new object[] { GetReplayTime(), first, second });
83
+ }
84
+
85
+ internal void RecordValue(float value)
86
+ {
87
+ if (!CanRecordGameplay()) return;
88
+ AddActivity(new object[] { GetReplayTime(), value });
89
+ }
90
+
91
+ internal void RecordValues(float first, float second, float third)
92
+ {
93
+ if (!CanRecordGameplay()) return;
94
+ AddActivity(new object[] { GetReplayTime(), first, second, third });
95
+ }
96
+
97
+ internal void RecordAction(string actionName)
98
+ {
99
+ if (!CanRecordGameplay() || string.IsNullOrWhiteSpace(actionName) || IsLifecycleAction(actionName)) return;
100
+ AddAction(actionName);
101
+ }
102
+
103
+ internal void RecordActions(string firstAction, string secondAction)
104
+ {
105
+ if (!CanRecordGameplay() || !IsValidAction(firstAction) || !IsValidAction(secondAction)) return;
106
+ AddActivity(new object[] { GetReplayTime(), firstAction, secondAction });
107
+ }
108
+
109
+ internal void RecordActionValues(string actionName, float first, float second)
110
+ {
111
+ if (!CanRecordGameplay() || !IsValidAction(actionName)) return;
112
+ AddActivity(new object[] { GetReplayTime(), actionName, first, second });
113
+ }
114
+
115
+ internal void RecordActionValue(string actionName, float value)
116
+ {
117
+ if (!CanRecordGameplay() || !IsValidAction(actionName)) return;
118
+ AddActivity(new object[] { GetReplayTime(), actionName, value });
119
+ }
120
+
121
+ internal void RecordValuesAction(float first, float second, string actionName)
122
+ {
123
+ if (!CanRecordGameplay() || !IsValidAction(actionName)) return;
124
+ AddActivity(new object[] { GetReplayTime(), first, second, actionName });
125
+ }
126
+
127
+ internal void RecordValueAction(float value, string actionName)
128
+ {
129
+ if (!CanRecordGameplay() || !IsValidAction(actionName)) return;
130
+ AddActivity(new object[] { GetReplayTime(), value, actionName });
131
+ }
132
+
133
+ internal List<object[]> Drain()
134
+ {
135
+ if (_activities == null || _activities.Count == 0) return null;
136
+
137
+ var drained = _activities;
138
+ _activities = null;
139
+ return drained;
140
+ }
141
+
142
+ private List<object[]> Activities => _activities ??= new List<object[]>(32);
143
+
144
+ private bool CanRecordGameplay()
145
+ {
146
+ return _hasStarted && !_isPaused && !_hasEnded;
147
+ }
148
+
149
+ private void AddAction(string actionName)
150
+ {
151
+ AddAction(actionName, GetReplayTime());
152
+ }
153
+
154
+ private void AddAction(string actionName, double replayTime)
155
+ {
156
+ AddActivity(new object[] { replayTime, actionName });
157
+ }
158
+
159
+ private void AddActivity(object[] activity)
160
+ {
161
+ Activities.Add(activity);
162
+ ActivityRecorded?.Invoke(activity);
163
+ }
164
+
165
+ private double GetReplayTime()
166
+ {
167
+ double simulationElapsed = Math.Max(0d, _simulationNow() - _sessionStartSimulation);
168
+ return _sessionStartUtc + simulationElapsed + _recordedPauseDuration;
169
+ }
170
+
171
+ private static bool IsValidAction(string actionName)
172
+ {
173
+ return !string.IsNullOrWhiteSpace(actionName) && !IsLifecycleAction(actionName);
174
+ }
175
+
176
+ internal static bool IsLifecycleAction(string actionName)
177
+ {
178
+ return actionName == GameStarted || actionName == GamePaused ||
179
+ actionName == GameResumed || actionName == GameEnded;
180
+ }
181
+ }
182
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: b9b36e61a5cd881439809d4248abfb9c
@@ -0,0 +1,181 @@
1
+ using System;
2
+ using System.IO;
3
+ using UnityEngine;
4
+
5
+ namespace Hyper.Internal
6
+ {
7
+ /// <summary>
8
+ /// Holds replay metadata and presentation state across scene reloads and seeks.
9
+ /// </summary>
10
+ internal static class ReplaySessionState
11
+ {
12
+ public static bool IsActive { get; private set; }
13
+ public static string PlayerName { get; private set; }
14
+ public static string AvatarUrl { get; private set; }
15
+ public static string EntryId { get; private set; }
16
+ public static string ReplayNdjson { get; private set; }
17
+ public static Texture2D AvatarTexture { get; private set; }
18
+ public static bool HasReplayData => !string.IsNullOrWhiteSpace(ReplayNdjson);
19
+ public static long OriginalRandomSeed { get; private set; }
20
+ public static float Progress01 { get; private set; }
21
+ public static float PlaybackSpeed { get; private set; } = 1f;
22
+ public static float UserPlaybackSpeed { get; private set; } = 1f;
23
+ public static bool IsSeeking { get; private set; }
24
+ public static bool IsSeekRestartPending => IsSeeking && !_seekPlaybackReady;
25
+ public static float SeekTarget01 { get; private set; }
26
+ public static bool HasEnded { get; private set; }
27
+
28
+ internal static string EditorReplayFilePath =>
29
+ Path.Combine(Application.persistentDataPath, "hyper-replay.ndjson");
30
+
31
+ public static event Action<float> SeekCompleted;
32
+
33
+ private static bool _seekPlaybackReady;
34
+
35
+ public static void Configure(
36
+ string playerName,
37
+ string avatarUrl,
38
+ long originalRandomSeed,
39
+ string entryId = null)
40
+ {
41
+ IsActive = true;
42
+ PlayerName = playerName ?? string.Empty;
43
+ AvatarUrl = avatarUrl ?? string.Empty;
44
+ EntryId = entryId ?? string.Empty;
45
+ ReplayNdjson = string.Empty;
46
+ ReleaseAvatarTexture();
47
+ OriginalRandomSeed = originalRandomSeed;
48
+ Progress01 = 0f;
49
+ PlaybackSpeed = 1f;
50
+ UserPlaybackSpeed = 1f;
51
+ IsSeeking = false;
52
+ SeekTarget01 = 0f;
53
+ _seekPlaybackReady = false;
54
+ HasEnded = false;
55
+ }
56
+
57
+ public static void SetMetadata(string playerName, string avatarUrl, long originalRandomSeed)
58
+ {
59
+ if (!IsActive) return;
60
+
61
+ PlayerName = playerName ?? string.Empty;
62
+ AvatarUrl = avatarUrl ?? string.Empty;
63
+ OriginalRandomSeed = originalRandomSeed;
64
+ }
65
+
66
+ public static void SetReplayData(string ndjson)
67
+ {
68
+ if (!IsActive) return;
69
+ ReplayNdjson = ndjson ?? string.Empty;
70
+ }
71
+
72
+ public static void SetAvatarTexture(Texture2D texture)
73
+ {
74
+ if (!IsActive || AvatarTexture == texture) return;
75
+ ReleaseAvatarTexture();
76
+ AvatarTexture = texture;
77
+ }
78
+
79
+ public static void SetProgress(float progress)
80
+ {
81
+ if (!IsActive) return;
82
+ if (IsSeeking && !_seekPlaybackReady) return;
83
+ Progress01 = Math.Max(0f, Math.Min(1f, progress));
84
+
85
+ if (IsSeeking && Progress01 >= SeekTarget01)
86
+ {
87
+ IsSeeking = false;
88
+ _seekPlaybackReady = false;
89
+ PlaybackSpeed = UserPlaybackSpeed;
90
+ SeekCompleted?.Invoke(PlaybackSpeed);
91
+ }
92
+ }
93
+
94
+ public static void SetPlaybackSpeed(float speed)
95
+ {
96
+ if (!IsActive) return;
97
+ PlaybackSpeed = SanitizeSpeed(speed);
98
+ if (!IsSeeking) UserPlaybackSpeed = PlaybackSpeed;
99
+ }
100
+
101
+ public static void MarkEnded()
102
+ {
103
+ if (!IsActive) return;
104
+ if (IsSeeking && !_seekPlaybackReady) return;
105
+ Progress01 = 1f;
106
+ if (IsSeeking) PlaybackSpeed = UserPlaybackSpeed;
107
+ IsSeeking = false;
108
+ HasEnded = true;
109
+ }
110
+
111
+ public static void RequestSeek(float targetProgress, float resumeSpeed, float seekSpeed)
112
+ {
113
+ if (!IsActive || HasEnded) return;
114
+
115
+ SeekTarget01 = Math.Max(0f, Math.Min(1f, targetProgress));
116
+ UserPlaybackSpeed = SanitizeSpeed(resumeSpeed);
117
+ PlaybackSpeed = SanitizeSpeed(seekSpeed);
118
+ IsSeeking = true;
119
+ _seekPlaybackReady = false;
120
+ }
121
+
122
+ public static void ResetForSeekRestart()
123
+ {
124
+ if (!IsActive || !IsSeeking) return;
125
+ Progress01 = 0f;
126
+ _seekPlaybackReady = false;
127
+ HasEnded = false;
128
+ }
129
+
130
+ public static void NotifySeekPlaybackStarted()
131
+ {
132
+ if (!IsActive || !IsSeeking) return;
133
+ _seekPlaybackReady = true;
134
+ }
135
+
136
+ public static void ResetForWatchAgain()
137
+ {
138
+ if (!IsActive) return;
139
+ Progress01 = 0f;
140
+ PlaybackSpeed = 1f;
141
+ UserPlaybackSpeed = 1f;
142
+ IsSeeking = false;
143
+ SeekTarget01 = 0f;
144
+ _seekPlaybackReady = false;
145
+ HasEnded = false;
146
+ }
147
+
148
+ public static void Clear()
149
+ {
150
+ IsActive = false;
151
+ PlayerName = string.Empty;
152
+ AvatarUrl = string.Empty;
153
+ EntryId = string.Empty;
154
+ ReplayNdjson = string.Empty;
155
+ ReleaseAvatarTexture();
156
+ OriginalRandomSeed = 0;
157
+ Progress01 = 0f;
158
+ PlaybackSpeed = 1f;
159
+ UserPlaybackSpeed = 1f;
160
+ IsSeeking = false;
161
+ SeekTarget01 = 0f;
162
+ _seekPlaybackReady = false;
163
+ HasEnded = false;
164
+ SeekCompleted = null;
165
+ }
166
+
167
+ private static float SanitizeSpeed(float speed)
168
+ {
169
+ return float.IsNaN(speed) || float.IsInfinity(speed) || speed <= 0f ? 1f : speed;
170
+ }
171
+
172
+ private static void ReleaseAvatarTexture()
173
+ {
174
+ if (AvatarTexture != null)
175
+ {
176
+ UnityEngine.Object.Destroy(AvatarTexture);
177
+ AvatarTexture = null;
178
+ }
179
+ }
180
+ }
181
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: e2073f1e0560836419268d91a01396eb
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: e33926a8ee756c34e92345e72c0c8e17
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant:
@@ -93,6 +93,11 @@ namespace Hyper.Internal
93
93
  _settings.SetModeAsPractice();
94
94
  }
95
95
 
96
+ public void SetModeAsReplay()
97
+ {
98
+ _settings.SetModeAsReplay();
99
+ }
100
+
96
101
  #endregion
97
102
  }
98
103
  }