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

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 +25 -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 +58 -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 +350 -15
  12. package/Runtime/Internal/Managers/ReplayManager.cs +111 -0
  13. package/Runtime/Internal/Managers/ReplayManager.cs.meta +11 -0
  14. package/Runtime/Internal/Managers/TimeManager.cs +56 -14
  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 +501 -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 +54 -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,501 @@
1
+ using System;
2
+ using System.Collections;
3
+ using Hyper.Shared;
4
+ using TMPro;
5
+ using UnityEngine;
6
+ using UnityEngine.EventSystems;
7
+ using UnityEngine.UI;
8
+
9
+ namespace Hyper.Internal
10
+ {
11
+ internal sealed class ReplayController : MonoBehaviour
12
+ {
13
+ internal const int AvatarTextureSize = 128;
14
+
15
+ [SerializeField] private Slider progressSlider;
16
+ [SerializeField] private TextMeshProUGUI replayLabel;
17
+ [SerializeField] private RawImage playerAvatar;
18
+ [SerializeField] private Button exitButton;
19
+ [SerializeField] private Button playbackSpeedButton;
20
+ [SerializeField] private TextMeshProUGUI playbackSpeedLabel;
21
+ [SerializeField] private float[] playbackSpeeds = { 1.5f, 2f, 3f, 4f, 5f, 1f };
22
+ [SerializeField, Min(1f)] private float seekPlaybackSpeed = 30f;
23
+ [SerializeField, Range(0.25f, 1f)] private float seekSnapshotScale = 0.55f;
24
+ [SerializeField] private GameObject seekLoadingOverlay;
25
+ [SerializeField] private RawImage seekSnapshotImage;
26
+
27
+ private int playbackSpeedIndex;
28
+ private ReplaySeekInput seekInput;
29
+ private bool wasSeeking;
30
+ private bool isCapturingSeek;
31
+ private float pendingSeekTarget;
32
+ private Texture2D seekSnapshotTexture;
33
+ private Image seekOverlayBackground;
34
+ private Color seekOverlayBackgroundColor;
35
+
36
+ private void Awake()
37
+ {
38
+ AutoBindReferences();
39
+ ConfigureProgressSlider();
40
+ ConfigureSeekOverlay();
41
+ ConfigureSeekSnapshot();
42
+ ApplyInitialPlaybackSpeed();
43
+ ReplaySessionState.SeekCompleted += RestorePlaybackAfterSeek;
44
+ if (exitButton != null) exitButton.onClick.AddListener(ExitReplay);
45
+ if (playbackSpeedButton != null) playbackSpeedButton.onClick.AddListener(CyclePlaybackSpeed);
46
+ }
47
+
48
+ private void Start()
49
+ {
50
+ string playerName = ReplaySessionState.PlayerName ?? string.Empty;
51
+ playerName = playerName.TrimStart('@');
52
+ if (replayLabel != null) replayLabel.text = $"Replay of @{playerName}";
53
+ if (progressSlider != null)
54
+ {
55
+ float displayedProgress = ReplaySessionState.IsSeeking
56
+ ? ReplaySessionState.SeekTarget01
57
+ : ReplaySessionState.Progress01;
58
+ progressSlider.SetValueWithoutNotify(displayedProgress);
59
+ }
60
+ UpdatePlaybackSpeedLabel(ReplaySessionState.UserPlaybackSpeed);
61
+ SetSeekingPresentation(ReplaySessionState.IsSeeking);
62
+
63
+ if (playerAvatar != null && ReplaySessionState.AvatarTexture != null)
64
+ {
65
+ playerAvatar.texture = ReplaySessionState.AvatarTexture;
66
+ }
67
+ }
68
+
69
+ private void Update()
70
+ {
71
+ bool isSeeking = ReplaySessionState.IsSeeking;
72
+ if (wasSeeking && !isSeeking && !ReplaySessionState.HasEnded)
73
+ {
74
+ RestorePlaybackAfterSeek(ReplaySessionState.PlaybackSpeed);
75
+ }
76
+
77
+ if (wasSeeking != isSeeking)
78
+ {
79
+ SetSeekingPresentation(isSeeking);
80
+ }
81
+
82
+ if (progressSlider != null && (seekInput == null || !seekInput.IsDragging))
83
+ {
84
+ float displayedProgress = isCapturingSeek
85
+ ? pendingSeekTarget
86
+ : isSeeking
87
+ ? ReplaySessionState.SeekTarget01
88
+ : ReplaySessionState.Progress01;
89
+ progressSlider.SetValueWithoutNotify(displayedProgress);
90
+ }
91
+
92
+ wasSeeking = isSeeking;
93
+ }
94
+
95
+ private void OnDestroy()
96
+ {
97
+ if (exitButton != null) exitButton.onClick.RemoveListener(ExitReplay);
98
+ if (playbackSpeedButton != null) playbackSpeedButton.onClick.RemoveListener(CyclePlaybackSpeed);
99
+ ReplaySessionState.SeekCompleted -= RestorePlaybackAfterSeek;
100
+ seekInput?.Clear();
101
+ ClearSeekSnapshot();
102
+ }
103
+
104
+ private void ApplyInitialPlaybackSpeed()
105
+ {
106
+ if (!ReplaySessionState.IsActive || playbackSpeeds == null || playbackSpeeds.Length == 0)
107
+ {
108
+ return;
109
+ }
110
+
111
+ float speed = ReplaySessionState.IsSeeking
112
+ ? ReplaySessionState.UserPlaybackSpeed
113
+ : SanitizePlaybackSpeed(playbackSpeeds[0]);
114
+ if (!ReplaySessionState.IsSeeking)
115
+ {
116
+ ReplaySessionState.SetPlaybackSpeed(speed);
117
+ Time.timeScale = speed;
118
+ }
119
+
120
+ playbackSpeedIndex = playbackSpeeds.Length > 1 ? 1 : 0;
121
+ AlignPlaybackSpeedIndex(speed);
122
+ UpdatePlaybackSpeedLabel(speed);
123
+ }
124
+
125
+ internal void SetReplayEnded()
126
+ {
127
+ if (exitButton != null) exitButton.interactable = false;
128
+ if (playbackSpeedButton != null) playbackSpeedButton.interactable = false;
129
+ if (progressSlider != null) progressSlider.interactable = false;
130
+ if (seekLoadingOverlay != null) seekLoadingOverlay.SetActive(false);
131
+ ClearSeekSnapshot();
132
+ if (progressSlider != null) progressSlider.SetValueWithoutNotify(1f);
133
+ }
134
+
135
+ private void ExitReplay()
136
+ {
137
+ if (exitButton != null) exitButton.interactable = false;
138
+ HyperRuntime.GameManager?.ExitReplayEarly();
139
+ }
140
+
141
+ private void CyclePlaybackSpeed()
142
+ {
143
+ if (!ReplaySessionState.IsActive || ReplaySessionState.HasEnded ||
144
+ playbackSpeeds == null || playbackSpeeds.Length == 0)
145
+ {
146
+ return;
147
+ }
148
+
149
+ float speed = SanitizePlaybackSpeed(playbackSpeeds[playbackSpeedIndex]);
150
+ playbackSpeedIndex = (playbackSpeedIndex + 1) % playbackSpeeds.Length;
151
+
152
+ ReplaySessionState.SetPlaybackSpeed(speed);
153
+ Time.timeScale = speed;
154
+ UpdatePlaybackSpeedLabel(speed);
155
+ }
156
+
157
+ private void SeekTo(float targetProgress)
158
+ {
159
+ if (!ReplaySessionState.IsActive || ReplaySessionState.HasEnded ||
160
+ ReplaySessionState.IsSeeking || isCapturingSeek)
161
+ {
162
+ return;
163
+ }
164
+
165
+ float resumeSpeed = SanitizePlaybackSpeed(ReplaySessionState.UserPlaybackSpeed);
166
+ float fastForwardSpeed = SanitizePlaybackSpeed(seekPlaybackSpeed);
167
+ StartCoroutine(CaptureAndSeek(targetProgress, resumeSpeed, fastForwardSpeed));
168
+ }
169
+
170
+ private IEnumerator CaptureAndSeek(float targetProgress, float resumeSpeed, float fastForwardSpeed)
171
+ {
172
+ isCapturingSeek = true;
173
+ pendingSeekTarget = Mathf.Clamp01(targetProgress);
174
+
175
+ yield return new WaitForEndOfFrame();
176
+
177
+ Texture2D capturedFrame = null;
178
+ try
179
+ {
180
+ capturedFrame = ScreenCapture.CaptureScreenshotAsTexture();
181
+ if (capturedFrame != null)
182
+ {
183
+ seekSnapshotTexture = CreateScaledSnapshot(capturedFrame, seekSnapshotScale);
184
+ }
185
+ }
186
+ catch (Exception exception)
187
+ {
188
+ HyperDebug.LogWarning($"[Replay] Unable to capture seek snapshot: {exception.Message}");
189
+ }
190
+ finally
191
+ {
192
+ if (capturedFrame != null) Destroy(capturedFrame);
193
+ }
194
+
195
+ if (seekSnapshotImage != null && seekSnapshotTexture != null)
196
+ {
197
+ seekSnapshotImage.texture = seekSnapshotTexture;
198
+ seekSnapshotImage.gameObject.SetActive(true);
199
+ SetSeekOverlayBackgroundVisible(false);
200
+ }
201
+
202
+ try
203
+ {
204
+ SetSeekingPresentation(true);
205
+ HyperRuntime.SeekReplaySession(pendingSeekTarget, resumeSpeed, fastForwardSpeed);
206
+ }
207
+ finally
208
+ {
209
+ isCapturingSeek = false;
210
+ }
211
+ }
212
+
213
+ private void RestorePlaybackAfterSeek(float playbackSpeed)
214
+ {
215
+ float speed = SanitizePlaybackSpeed(playbackSpeed);
216
+ Time.timeScale = speed;
217
+ AlignPlaybackSpeedIndex(speed);
218
+ UpdatePlaybackSpeedLabel(speed);
219
+ SetSeekingPresentation(false);
220
+ wasSeeking = false;
221
+ }
222
+
223
+ private void AlignPlaybackSpeedIndex(float speed)
224
+ {
225
+ if (playbackSpeeds == null || playbackSpeeds.Length == 0) return;
226
+
227
+ for (int i = 0; i < playbackSpeeds.Length; i++)
228
+ {
229
+ if (!Mathf.Approximately(SanitizePlaybackSpeed(playbackSpeeds[i]), speed)) continue;
230
+ playbackSpeedIndex = (i + 1) % playbackSpeeds.Length;
231
+ return;
232
+ }
233
+ }
234
+
235
+ private void SetSeekingPresentation(bool isSeeking)
236
+ {
237
+ ReplayPresentationCanvas.SetSeekingLayer(isSeeking);
238
+ if (seekLoadingOverlay != null) seekLoadingOverlay.SetActive(isSeeking);
239
+ if (progressSlider != null) progressSlider.interactable = !isSeeking && !ReplaySessionState.HasEnded;
240
+ if (playbackSpeedButton != null)
241
+ {
242
+ playbackSpeedButton.interactable = !isSeeking && !ReplaySessionState.HasEnded;
243
+ }
244
+
245
+ if (exitButton != null) exitButton.interactable = !isSeeking && !ReplaySessionState.HasEnded;
246
+ if (!isSeeking) ClearSeekSnapshot();
247
+ }
248
+
249
+ private static float SanitizePlaybackSpeed(float speed)
250
+ {
251
+ return float.IsNaN(speed) || float.IsInfinity(speed) || speed <= 0f ? 1f : speed;
252
+ }
253
+
254
+ private void UpdatePlaybackSpeedLabel(float speed)
255
+ {
256
+ if (playbackSpeedLabel == null && playbackSpeedButton != null)
257
+ {
258
+ playbackSpeedLabel = playbackSpeedButton.GetComponentInChildren<TextMeshProUGUI>(true);
259
+ }
260
+
261
+ if (playbackSpeedLabel != null)
262
+ {
263
+ playbackSpeedLabel.text = $"{speed:0.#}x";
264
+ }
265
+ }
266
+
267
+ internal static Texture2D CreateCircularAvatar(Texture2D source, int size)
268
+ {
269
+ var result = new Texture2D(size, size, TextureFormat.RGBA32, false)
270
+ {
271
+ name = "Replay Avatar (Circular)",
272
+ filterMode = FilterMode.Bilinear,
273
+ wrapMode = TextureWrapMode.Clamp
274
+ };
275
+
276
+ Color[] pixels = new Color[size * size];
277
+ float sourceAspect = source.width / (float)source.height;
278
+ float edgeFeather = 2.5f / size;
279
+
280
+ for (int y = 0; y < size; y++)
281
+ {
282
+ float v = (y + 0.5f) / size;
283
+ for (int x = 0; x < size; x++)
284
+ {
285
+ float u = (x + 0.5f) / size;
286
+ float centeredX = u - 0.5f;
287
+ float centeredY = v - 0.5f;
288
+ float distanceFromCenter = Mathf.Sqrt(centeredX * centeredX + centeredY * centeredY);
289
+ float edgeCoverage = Mathf.Clamp01((0.5f - distanceFromCenter) / edgeFeather);
290
+ edgeCoverage = edgeCoverage * edgeCoverage * (3f - 2f * edgeCoverage);
291
+
292
+ if (edgeCoverage <= 0f)
293
+ {
294
+ pixels[y * size + x] = Color.clear;
295
+ continue;
296
+ }
297
+
298
+ float sampleU = sourceAspect > 1f
299
+ ? 0.5f + centeredX / sourceAspect
300
+ : u;
301
+ float sampleV = sourceAspect < 1f
302
+ ? 0.5f + centeredY * sourceAspect
303
+ : v;
304
+ Color color = source.GetPixelBilinear(sampleU, sampleV);
305
+ color.a *= edgeCoverage;
306
+ pixels[y * size + x] = color;
307
+ }
308
+ }
309
+
310
+ result.SetPixels(pixels);
311
+ result.Apply(false, true);
312
+ return result;
313
+ }
314
+
315
+ private void AutoBindReferences()
316
+ {
317
+ if (progressSlider == null) progressSlider = GetComponentInChildren<Slider>(true);
318
+ if (exitButton == null) exitButton = GetComponentInChildren<Button>(true);
319
+
320
+ TextMeshProUGUI[] labels = GetComponentsInChildren<TextMeshProUGUI>(true);
321
+ for (int i = 0; replayLabel == null && i < labels.Length; i++)
322
+ {
323
+ if (labels[i].name == "Subtext") replayLabel = labels[i];
324
+ }
325
+
326
+ RawImage[] images = GetComponentsInChildren<RawImage>(true);
327
+ for (int i = 0; playerAvatar == null && i < images.Length; i++)
328
+ {
329
+ if (images[i].name == "Player Avatar") playerAvatar = images[i];
330
+ }
331
+ }
332
+
333
+ private void ConfigureProgressSlider()
334
+ {
335
+ if (progressSlider == null) return;
336
+
337
+ progressSlider.minValue = 0f;
338
+ progressSlider.maxValue = 1f;
339
+ progressSlider.wholeNumbers = false;
340
+ progressSlider.interactable = true;
341
+ float displayedProgress = ReplaySessionState.IsSeeking
342
+ ? ReplaySessionState.SeekTarget01
343
+ : ReplaySessionState.Progress01;
344
+ progressSlider.SetValueWithoutNotify(displayedProgress);
345
+
346
+ seekInput = progressSlider.GetComponent<ReplaySeekInput>();
347
+ if (seekInput == null) seekInput = progressSlider.gameObject.AddComponent<ReplaySeekInput>();
348
+ seekInput.Initialize(progressSlider, SeekTo);
349
+ }
350
+
351
+ private void ConfigureSeekOverlay()
352
+ {
353
+ if (seekLoadingOverlay == null)
354
+ {
355
+ Transform existing = transform.Find("ReplaySeekOverlay");
356
+ if (existing != null) seekLoadingOverlay = existing.gameObject;
357
+ }
358
+
359
+ if (seekLoadingOverlay == null)
360
+ {
361
+ GameObject prefab = Resources.Load<GameObject>("UIPrefabs/ReplaySeekOverlay");
362
+ seekLoadingOverlay = Instantiate(prefab, transform, false);
363
+ }
364
+
365
+ RectTransform rect = seekLoadingOverlay.GetComponent<RectTransform>();
366
+ if (rect != null)
367
+ {
368
+ rect.anchorMin = Vector2.zero;
369
+ rect.anchorMax = Vector2.one;
370
+ rect.offsetMin = Vector2.zero;
371
+ rect.offsetMax = Vector2.zero;
372
+ }
373
+
374
+ seekLoadingOverlay.transform.SetAsFirstSibling();
375
+ seekOverlayBackground = seekLoadingOverlay.GetComponent<Image>();
376
+ if (seekOverlayBackground != null) seekOverlayBackgroundColor = seekOverlayBackground.color;
377
+ seekLoadingOverlay.SetActive(ReplaySessionState.IsSeeking);
378
+ wasSeeking = ReplaySessionState.IsSeeking;
379
+ }
380
+
381
+ private void ConfigureSeekSnapshot()
382
+ {
383
+ if (seekSnapshotImage == null)
384
+ {
385
+ Transform existing = transform.Find("ReplaySeekSnapshot");
386
+ if (existing != null) seekSnapshotImage = existing.GetComponent<RawImage>();
387
+ }
388
+
389
+ if (seekSnapshotImage == null)
390
+ {
391
+ var snapshot = new GameObject(
392
+ "ReplaySeekSnapshot",
393
+ typeof(RectTransform),
394
+ typeof(CanvasRenderer),
395
+ typeof(RawImage));
396
+ snapshot.transform.SetParent(transform, false);
397
+ seekSnapshotImage = snapshot.GetComponent<RawImage>();
398
+ }
399
+
400
+ RectTransform rect = seekSnapshotImage.rectTransform;
401
+ rect.anchorMin = Vector2.zero;
402
+ rect.anchorMax = Vector2.one;
403
+ rect.offsetMin = Vector2.zero;
404
+ rect.offsetMax = Vector2.zero;
405
+ seekSnapshotImage.raycastTarget = false;
406
+ seekSnapshotImage.transform.SetAsFirstSibling();
407
+ if (seekLoadingOverlay != null) seekLoadingOverlay.transform.SetSiblingIndex(1);
408
+ seekSnapshotImage.gameObject.SetActive(false);
409
+ }
410
+
411
+ private static Texture2D CreateScaledSnapshot(Texture2D source, float scale)
412
+ {
413
+ float sanitizedScale = Mathf.Clamp(scale, 0.25f, 1f);
414
+ int width = Mathf.Max(1, Mathf.RoundToInt(source.width * sanitizedScale));
415
+ int height = Mathf.Max(1, Mathf.RoundToInt(source.height * sanitizedScale));
416
+ RenderTexture temporary = RenderTexture.GetTemporary(width, height, 0, RenderTextureFormat.ARGB32);
417
+ RenderTexture previous = RenderTexture.active;
418
+ Texture2D result = null;
419
+
420
+ try
421
+ {
422
+ Graphics.Blit(source, temporary);
423
+ RenderTexture.active = temporary;
424
+ result = new Texture2D(width, height, TextureFormat.RGB24, false)
425
+ {
426
+ name = "Replay Seek Snapshot",
427
+ filterMode = FilterMode.Bilinear,
428
+ wrapMode = TextureWrapMode.Clamp
429
+ };
430
+ result.ReadPixels(new Rect(0f, 0f, width, height), 0, 0, false);
431
+ result.Apply(false, true);
432
+ return result;
433
+ }
434
+ catch
435
+ {
436
+ if (result != null) Destroy(result);
437
+ throw;
438
+ }
439
+ finally
440
+ {
441
+ RenderTexture.active = previous;
442
+ RenderTexture.ReleaseTemporary(temporary);
443
+ }
444
+ }
445
+
446
+ private void ClearSeekSnapshot()
447
+ {
448
+ SetSeekOverlayBackgroundVisible(true);
449
+ if (seekSnapshotImage != null)
450
+ {
451
+ seekSnapshotImage.texture = null;
452
+ seekSnapshotImage.gameObject.SetActive(false);
453
+ }
454
+
455
+ if (seekSnapshotTexture == null) return;
456
+ Destroy(seekSnapshotTexture);
457
+ seekSnapshotTexture = null;
458
+ }
459
+
460
+ private void SetSeekOverlayBackgroundVisible(bool isVisible)
461
+ {
462
+ if (seekOverlayBackground == null) return;
463
+ Color color = seekOverlayBackgroundColor;
464
+ if (!isVisible) color.a = 0.5f;
465
+ seekOverlayBackground.color = color;
466
+ }
467
+ }
468
+
469
+ internal sealed class ReplaySeekInput : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
470
+ {
471
+ private Slider slider;
472
+ private Action<float> onSeekRequested;
473
+
474
+ internal bool IsDragging { get; private set; }
475
+
476
+ internal void Initialize(Slider targetSlider, Action<float> callback)
477
+ {
478
+ slider = targetSlider;
479
+ onSeekRequested = callback;
480
+ }
481
+
482
+ internal void Clear()
483
+ {
484
+ IsDragging = false;
485
+ onSeekRequested = null;
486
+ }
487
+
488
+ public void OnPointerDown(PointerEventData eventData)
489
+ {
490
+ if (slider == null || !slider.interactable) return;
491
+ IsDragging = true;
492
+ }
493
+
494
+ public void OnPointerUp(PointerEventData eventData)
495
+ {
496
+ if (!IsDragging) return;
497
+ IsDragging = false;
498
+ onSeekRequested?.Invoke(slider.normalizedValue);
499
+ }
500
+ }
501
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 488ba0c343178684c9e74294492a1fa1
@@ -0,0 +1,89 @@
1
+ using System.Collections;
2
+ using UnityEngine;
3
+ using UnityEngine.UI;
4
+
5
+ namespace Hyper.Internal
6
+ {
7
+ [RequireComponent(typeof(Graphic))]
8
+ internal sealed class ReplayMarkerPulse : MonoBehaviour
9
+ {
10
+ [SerializeField, Range(0f, 1f)] private float minimumAlpha = 0.3f;
11
+ [SerializeField] private float fadeDuration = 0.8f;
12
+
13
+ private Graphic _graphic;
14
+ private Coroutine _pulseCoroutine;
15
+
16
+ private void Awake()
17
+ {
18
+ _graphic = GetComponent<Graphic>();
19
+ }
20
+
21
+ private void OnEnable()
22
+ {
23
+ HyperRuntime.EventManager.OnGameStart += StartPulsing;
24
+ HyperRuntime.EventManager.OnGameEnded += StopPulsing;
25
+ SetAlpha(minimumAlpha);
26
+
27
+ if (HyperRuntime.GameManager != null && HyperRuntime.GameManager.HasGameStarted)
28
+ {
29
+ StartPulsing();
30
+ }
31
+ }
32
+
33
+ private void OnDisable()
34
+ {
35
+ if (HyperRuntime.EventManager != null)
36
+ {
37
+ HyperRuntime.EventManager.OnGameStart -= StartPulsing;
38
+ HyperRuntime.EventManager.OnGameEnded -= StopPulsing;
39
+ }
40
+ StopPulsing();
41
+ }
42
+
43
+ private void StartPulsing()
44
+ {
45
+ if (_pulseCoroutine != null) StopCoroutine(_pulseCoroutine);
46
+ _pulseCoroutine = StartCoroutine(Pulse());
47
+ }
48
+
49
+ private void StopPulsing()
50
+ {
51
+ if (_pulseCoroutine != null)
52
+ {
53
+ StopCoroutine(_pulseCoroutine);
54
+ _pulseCoroutine = null;
55
+ }
56
+ SetAlpha(1f);
57
+ }
58
+
59
+ private IEnumerator Pulse()
60
+ {
61
+ float duration = Mathf.Max(0.05f, fadeDuration);
62
+ while (true)
63
+ {
64
+ yield return Fade(minimumAlpha, 1f, duration);
65
+ yield return Fade(1f, minimumAlpha, duration);
66
+ }
67
+ }
68
+
69
+ private IEnumerator Fade(float from, float to, float duration)
70
+ {
71
+ float elapsed = 0f;
72
+ while (elapsed < duration)
73
+ {
74
+ elapsed += Time.unscaledDeltaTime;
75
+ SetAlpha(Mathf.Lerp(from, to, elapsed / duration));
76
+ yield return null;
77
+ }
78
+ SetAlpha(to);
79
+ }
80
+
81
+ private void SetAlpha(float alpha)
82
+ {
83
+ if (_graphic == null) return;
84
+ Color color = _graphic.color;
85
+ color.a = alpha;
86
+ _graphic.color = color;
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 361528b20842ffc4ead65b0e23d7cc78