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.
- package/CHANGELOG.md +34 -0
- package/Editor/Scripts/Setup/HyperSDKInstaller.cs +42 -0
- package/Editor/Scripts/Tools/HyperGameModeMenu.cs +27 -7
- package/Runtime/Internal/AssemblyInfo.cs +1 -0
- package/Runtime/Internal/Bootstrap/HyperLoader.cs +186 -8
- package/Runtime/Internal/Core/HyperRuntime.cs +82 -0
- package/Runtime/Internal/Core/HyperRuntimeHost.cs +6 -0
- package/Runtime/Internal/Data/URLData.cs +18 -1
- package/Runtime/Internal/Managers/BackendManager.cs +281 -24
- package/Runtime/Internal/Managers/DataManager.cs +207 -21
- package/Runtime/Internal/Managers/GameManager.cs +365 -17
- package/Runtime/Internal/Managers/ReplayManager.cs +117 -0
- package/Runtime/Internal/Managers/ReplayManager.cs.meta +11 -0
- package/Runtime/Internal/Managers/TimeManager.cs +54 -12
- package/Runtime/Internal/Replay/ReplayActivityRecorder.cs +182 -0
- package/Runtime/Internal/Replay/ReplayActivityRecorder.cs.meta +2 -0
- package/Runtime/Internal/Replay/ReplaySessionState.cs +181 -0
- package/Runtime/Internal/Replay/ReplaySessionState.cs.meta +2 -0
- package/Runtime/Internal/Replay.meta +8 -0
- package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +5 -0
- package/Runtime/Internal/UI/Animation/LoadingTextAnimation.cs +15 -1
- package/Runtime/Internal/UI/Components/ControlBar.cs +25 -2
- package/Runtime/Internal/UI/Components/PauseButton.cs +4 -1
- package/Runtime/Internal/UI/Components/ReplayController.cs +527 -0
- package/Runtime/Internal/UI/Components/ReplayController.cs.meta +2 -0
- package/Runtime/Internal/UI/Components/ReplayMarkerPulse.cs +89 -0
- package/Runtime/Internal/UI/Components/ReplayMarkerPulse.cs.meta +2 -0
- package/Runtime/Internal/UI/Components/ReplayPresentationCanvas.cs +183 -0
- package/Runtime/Internal/UI/Components/ReplayPresentationCanvas.cs.meta +2 -0
- package/Runtime/Internal/UI/Modals/LoadingFailedModal.cs +12 -0
- package/Runtime/Internal/UI/Modals/ReplayOverModal.cs +61 -0
- package/Runtime/Internal/UI/Modals/ReplayOverModal.cs.meta +2 -0
- package/Runtime/Internal/UI/Modals/StartingInstructionModal.cs +38 -29
- package/Runtime/Public/AssemblyInfo.cs +3 -0
- package/Runtime/Public/AssemblyInfo.cs.meta +2 -0
- package/Runtime/Public/Core/HyperSDK.cs +305 -109
- package/Runtime/Public/Hyper.asmdef +2 -1
- package/Runtime/Public/Replay/ReplayPlaybackEngine.cs +555 -0
- package/Runtime/Public/Replay/ReplayPlaybackEngine.cs.meta +2 -0
- package/Runtime/Public/Replay.meta +8 -0
- package/Runtime/Resources/Fonts/Nunito/Nunito-Light SDF.asset +528 -521
- package/Runtime/Resources/Materials/HyperReplayScanlines.preset +340 -0
- package/Runtime/Resources/Materials/HyperReplayScanlines.preset.meta +8 -0
- package/Runtime/Resources/Materials/ReplayScanlineOverlay.mat +55 -0
- package/Runtime/Resources/Materials/ReplayScanlineOverlay.mat.meta +8 -0
- package/Runtime/Resources/Shaders/ReplayScanlineOverlay.shader +150 -0
- package/Runtime/Resources/Shaders/ReplayScanlineOverlay.shader.meta +10 -0
- package/Runtime/Resources/Sprites/Replays/Fallback avatar.png +0 -0
- package/Runtime/Resources/Sprites/Replays/Fallback avatar.png.meta +156 -0
- package/Runtime/Resources/Sprites/Replays/Recording.png +0 -0
- package/Runtime/Resources/Sprites/Replays/Recording.png.meta +156 -0
- package/Runtime/Resources/Sprites/Replays/Replay frame.png +0 -0
- package/Runtime/Resources/Sprites/Replays/Replay frame.png.meta +156 -0
- package/Runtime/Resources/Sprites/Replays/Replay modal icon.png +0 -0
- package/Runtime/Resources/Sprites/Replays/Replay modal icon.png.meta +156 -0
- package/Runtime/Resources/Sprites/Replays.meta +8 -0
- package/Runtime/Resources/UIPrefabs/ControlBar.prefab +169 -2
- package/Runtime/Resources/UIPrefabs/GamePausedModal_Battle.prefab +1 -1
- package/Runtime/Resources/UIPrefabs/ReplayController.prefab +1269 -0
- package/Runtime/Resources/UIPrefabs/ReplayController.prefab.meta +7 -0
- package/Runtime/Resources/UIPrefabs/ReplayInputBlocker.prefab +90 -0
- package/Runtime/Resources/UIPrefabs/ReplayInputBlocker.prefab.meta +7 -0
- package/Runtime/Resources/UIPrefabs/ReplayOverModal.prefab +958 -0
- package/Runtime/Resources/UIPrefabs/ReplayOverModal.prefab.meta +7 -0
- package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab +189 -0
- package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab.meta +7 -0
- package/Runtime/Resources/UIPrefabs/SnackBar.prefab +2 -2
- package/Runtime/Shared/AssemblyInfo.cs +5 -0
- package/Runtime/Shared/AssemblyInfo.cs.meta +11 -0
- package/Runtime/Shared/DTOs/Enums.cs +8 -3
- package/Runtime/Shared/DTOs/Score/ScoreSnapshot.cs +13 -1
- package/Runtime/Shared/Replay/IReplayPlaybackEngine.cs +37 -0
- package/Runtime/Shared/Replay/IReplayPlaybackEngine.cs.meta +11 -0
- package/Runtime/Shared/Replay.meta +8 -0
- package/Samples~/LaneRunner/LaneRunnerSample.unity +1603 -0
- package/Samples~/LaneRunner/LaneRunnerSample.unity.meta +7 -0
- package/Samples~/LaneRunner/Materials/DoubleScore.mat +86 -0
- package/Samples~/LaneRunner/Materials/DoubleScore.mat.meta +8 -0
- package/Samples~/LaneRunner/Materials/Ground.mat +84 -0
- package/Samples~/LaneRunner/Materials/Ground.mat.meta +8 -0
- package/Samples~/LaneRunner/Materials/Lane.mat +84 -0
- package/Samples~/LaneRunner/Materials/Lane.mat.meta +8 -0
- package/Samples~/LaneRunner/Materials/LaneMarker.mat +84 -0
- package/Samples~/LaneRunner/Materials/LaneMarker.mat.meta +8 -0
- package/Samples~/LaneRunner/Materials/Obstacle.mat +84 -0
- package/Samples~/LaneRunner/Materials/Obstacle.mat.meta +8 -0
- package/Samples~/LaneRunner/Materials/Player.mat +84 -0
- package/Samples~/LaneRunner/Materials/Player.mat.meta +8 -0
- package/Samples~/LaneRunner/Materials.meta +8 -0
- package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab +152 -0
- package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab.meta +7 -0
- package/Samples~/LaneRunner/Prefabs/Obstacle.prefab +152 -0
- package/Samples~/LaneRunner/Prefabs/Obstacle.prefab.meta +7 -0
- package/Samples~/LaneRunner/Prefabs.meta +8 -0
- package/Samples~/LaneRunner/README.md +35 -0
- package/Samples~/LaneRunner/README.md.meta +7 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerCommand.cs +9 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerCommand.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerGame.cs +93 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerGame.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerPlayer.cs +155 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerPlayer.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs +110 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerTrackItem.cs +41 -0
- package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerTrackItem.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Gameplay.meta +8 -0
- package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerReplayAdapter.cs +62 -0
- package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerReplayAdapter.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs +64 -0
- package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs.meta +2 -0
- package/Samples~/LaneRunner/Scripts/Hyper.meta +8 -0
- package/Samples~/LaneRunner/Scripts.meta +8 -0
- package/Tests.meta +8 -0
- 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
|
+
}
|
|
@@ -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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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,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
|
+
}
|