app.hypergames.hypersdk 1.6.0 → 1.7.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.
- package/CHANGELOG.md +14 -0
- package/Editor/Scripts/Windows/HyperGameContentWindow.cs +10 -0
- package/Editor/Scripts/Windows/HyperSettingsWindow.cs +10 -0
- package/HyperLoader.unity +3 -3
- package/Runtime/Internal/Bootstrap/HyperLoader.cs +57 -10
- package/Runtime/Internal/Core/HyperRuntime.cs +17 -8
- package/Runtime/Internal/Managers/BackendManager.cs +110 -82
- package/Runtime/Internal/Managers/DataManager.cs +0 -11
- package/Runtime/Internal/Managers/EventManager.cs +1 -1
- package/Runtime/Internal/Managers/GameManager.cs +27 -21
- package/Runtime/Internal/Managers/TimeManager.cs +5 -0
- package/Runtime/Internal/ScriptableObjects/HyperGameContentConfig.cs +8 -0
- package/Runtime/Internal/UI/Modals/GamePausedModal.cs +1 -8
- package/Runtime/Plugins/WebGL/HyperCorePlugin.jslib +26 -0
- package/Runtime/Public/Core/HyperSDK.cs +191 -0
- package/Runtime/Public/PlayerDeviceClassification.cs +41 -0
- package/Runtime/Public/PlayerDeviceClassification.cs.meta +2 -0
- package/Runtime/Resources/HyperGameContentConfig.asset +1 -0
- package/Runtime/Shared/DTOs/Enums.cs +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.7.1](https://github.com/mvmhyper/hyper-sdk/compare/v1.7.0...v1.7.1) (2026-04-02)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* reset Time.timeScale to 1 on RestartSession ([9dfb2bf](https://github.com/mvmhyper/hyper-sdk/commit/9dfb2bfce336c6a569889d2482b9f8aaa4f6e4fa))
|
|
7
|
+
|
|
8
|
+
# [1.7.0](https://github.com/mvmhyper/hyper-sdk/compare/v1.6.0...v1.7.0) (2026-04-02)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add loader API, device classification, and landscape canvas scaling fixes ([780ac39](https://github.com/mvmhyper/hyper-sdk/commit/780ac39b2d4f4c73286614a8e6b960fa947276f2))
|
|
14
|
+
|
|
1
15
|
# [1.6.0](https://github.com/mvmhyper/hyper-sdk/compare/v1.5.10...v1.6.0) (2026-03-28)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -126,6 +126,16 @@ namespace Hyper.Editor
|
|
|
126
126
|
"• TRANSITION → optional loading/transition scene.",
|
|
127
127
|
MessageType.Info);
|
|
128
128
|
|
|
129
|
+
if (_contentConfig.EnableCustomLoading)
|
|
130
|
+
{
|
|
131
|
+
EditorGUILayout.Space(4);
|
|
132
|
+
EditorGUILayout.HelpBox(
|
|
133
|
+
"Custom Pre-Loading is enabled. The HyperLoader bootstrap will pause after SDK " +
|
|
134
|
+
"initialization and wait for HyperSDK.Loader.CompleteCustomLoading() to be called " +
|
|
135
|
+
"before loading the game scene.",
|
|
136
|
+
MessageType.Info);
|
|
137
|
+
}
|
|
138
|
+
|
|
129
139
|
EditorGUILayout.Space(10);
|
|
130
140
|
|
|
131
141
|
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
|
|
@@ -253,6 +253,16 @@ namespace Hyper.Editor
|
|
|
253
253
|
"• TRANSITION → optional loading/transition scene.",
|
|
254
254
|
MessageType.Info);
|
|
255
255
|
|
|
256
|
+
if (_contentConfig.EnableCustomLoading)
|
|
257
|
+
{
|
|
258
|
+
EditorGUILayout.Space(4);
|
|
259
|
+
EditorGUILayout.HelpBox(
|
|
260
|
+
"Custom Pre-Loading is enabled. The HyperLoader bootstrap will pause after SDK " +
|
|
261
|
+
"initialization and wait for HyperSDK.Loader.CompleteCustomLoading() to be called " +
|
|
262
|
+
"before loading the game scene.",
|
|
263
|
+
MessageType.Info);
|
|
264
|
+
}
|
|
265
|
+
|
|
256
266
|
EditorGUILayout.Space(8);
|
|
257
267
|
|
|
258
268
|
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
|
package/HyperLoader.unity
CHANGED
|
@@ -304,7 +304,7 @@ RectTransform:
|
|
|
304
304
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
305
305
|
m_AnchorMin: {x: 0, y: 1}
|
|
306
306
|
m_AnchorMax: {x: 0, y: 1}
|
|
307
|
-
m_AnchoredPosition: {x: 489.
|
|
307
|
+
m_AnchoredPosition: {x: 489.45853, y: -280.1698}
|
|
308
308
|
m_SizeDelta: {x: 1044.9, y: 239.347}
|
|
309
309
|
m_Pivot: {x: 0.5, y: 0.5}
|
|
310
310
|
--- !u!114 &751153944
|
|
@@ -840,7 +840,7 @@ GameObject:
|
|
|
840
840
|
m_Component:
|
|
841
841
|
- component: {fileID: 1064166296}
|
|
842
842
|
m_Layer: 0
|
|
843
|
-
m_Name:
|
|
843
|
+
m_Name: __HyperSDK_BuildMarker_639104165398939431
|
|
844
844
|
m_TagString: Untagged
|
|
845
845
|
m_Icon: {fileID: 0}
|
|
846
846
|
m_NavMeshLayer: 0
|
|
@@ -1092,7 +1092,7 @@ RectTransform:
|
|
|
1092
1092
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
1093
1093
|
m_AnchorMin: {x: 0, y: 1}
|
|
1094
1094
|
m_AnchorMax: {x: 0, y: 1}
|
|
1095
|
-
m_AnchoredPosition: {x: 489.
|
|
1095
|
+
m_AnchoredPosition: {x: 489.45853, y: -133.2274}
|
|
1096
1096
|
m_SizeDelta: {x: 875, y: 54.5378}
|
|
1097
1097
|
m_Pivot: {x: 0.5, y: 0.5}
|
|
1098
1098
|
--- !u!1 &2874543288936963705
|
|
@@ -42,6 +42,10 @@ namespace Hyper.Internal
|
|
|
42
42
|
private GameManager _gameManager;
|
|
43
43
|
private BackendManager _backendManager;
|
|
44
44
|
|
|
45
|
+
// Progress budget milestones (adjusted based on custom pre-loading)
|
|
46
|
+
private float _progressAfterInit = 0.20f;
|
|
47
|
+
private float _progressAfterBackend = 0.50f;
|
|
48
|
+
private float _progressAfterCustomLoad = 0.50f;
|
|
45
49
|
|
|
46
50
|
#endregion
|
|
47
51
|
|
|
@@ -231,6 +235,12 @@ namespace Hyper.Internal
|
|
|
231
235
|
_bootstrapProgress = 0f;
|
|
232
236
|
_loadingBarSlider.value = _bootstrapProgress;
|
|
233
237
|
|
|
238
|
+
// Configure progress budget based on whether custom pre-loading is enabled
|
|
239
|
+
bool hasCustomPreLoading = _gameContentConfig != null && _gameContentConfig.EnableCustomLoading;
|
|
240
|
+
_progressAfterInit = 0.20f;
|
|
241
|
+
_progressAfterBackend = hasCustomPreLoading ? 0.45f : 0.50f;
|
|
242
|
+
_progressAfterCustomLoad = hasCustomPreLoading ? 0.75f : _progressAfterBackend;
|
|
243
|
+
|
|
234
244
|
// Step 1: Initialize HyperSDK
|
|
235
245
|
bool sdkInitialized = false;
|
|
236
246
|
HyperRuntime.Initialize(() =>
|
|
@@ -245,12 +255,12 @@ namespace Hyper.Internal
|
|
|
245
255
|
|
|
246
256
|
while (!sdkInitialized)
|
|
247
257
|
{
|
|
248
|
-
_bootstrapProgress = Mathf.MoveTowards(_bootstrapProgress,
|
|
258
|
+
_bootstrapProgress = Mathf.MoveTowards(_bootstrapProgress, _progressAfterInit, Time.deltaTime * 2f);
|
|
249
259
|
_loadingBarSlider.value = _bootstrapProgress;
|
|
250
260
|
yield return null;
|
|
251
261
|
}
|
|
252
262
|
|
|
253
|
-
_bootstrapProgress =
|
|
263
|
+
_bootstrapProgress = _progressAfterInit;
|
|
254
264
|
_loadingBarSlider.value = _bootstrapProgress;
|
|
255
265
|
|
|
256
266
|
// Step 2: Handle Backend/PVP or Practice mode initialization
|
|
@@ -267,6 +277,12 @@ namespace Hyper.Internal
|
|
|
267
277
|
InitializePracticeMode();
|
|
268
278
|
}
|
|
269
279
|
|
|
280
|
+
// Step 2.5: Custom Pre-Loading (if enabled)
|
|
281
|
+
if (hasCustomPreLoading)
|
|
282
|
+
{
|
|
283
|
+
yield return WaitForCustomPreLoading();
|
|
284
|
+
}
|
|
285
|
+
|
|
270
286
|
// Step 3: Load target scene asynchronously
|
|
271
287
|
yield return LoadTargetScene();
|
|
272
288
|
}
|
|
@@ -275,17 +291,20 @@ namespace Hyper.Internal
|
|
|
275
291
|
{
|
|
276
292
|
_targetSceneName = _gameContentConfig.GameScene?.SceneName;
|
|
277
293
|
|
|
294
|
+
// WebSocket milestone is midpoint between init and backend completion
|
|
295
|
+
float webSocketMilestone = Mathf.Lerp(_progressAfterInit, _progressAfterBackend, 0.5f);
|
|
296
|
+
|
|
278
297
|
// Initialize WebSocket
|
|
279
298
|
HyperRuntime.BackendManager.InitializeWebSocket();
|
|
280
299
|
|
|
281
300
|
while (!HyperRuntime.BackendManager.isSocketConnected)
|
|
282
301
|
{
|
|
283
|
-
_bootstrapProgress = Mathf.MoveTowards(_bootstrapProgress,
|
|
302
|
+
_bootstrapProgress = Mathf.MoveTowards(_bootstrapProgress, webSocketMilestone, Time.deltaTime * 0.5f);
|
|
284
303
|
_loadingBarSlider.value = _bootstrapProgress;
|
|
285
304
|
yield return null;
|
|
286
305
|
}
|
|
287
306
|
|
|
288
|
-
_bootstrapProgress =
|
|
307
|
+
_bootstrapProgress = webSocketMilestone;
|
|
289
308
|
_loadingBarSlider.value = _bootstrapProgress;
|
|
290
309
|
|
|
291
310
|
// Create session
|
|
@@ -297,12 +316,12 @@ namespace Hyper.Internal
|
|
|
297
316
|
|
|
298
317
|
while (!sessionCreated)
|
|
299
318
|
{
|
|
300
|
-
_bootstrapProgress = Mathf.MoveTowards(_bootstrapProgress,
|
|
319
|
+
_bootstrapProgress = Mathf.MoveTowards(_bootstrapProgress, _progressAfterBackend, Time.deltaTime * 0.5f);
|
|
301
320
|
_loadingBarSlider.value = _bootstrapProgress;
|
|
302
321
|
yield return null;
|
|
303
322
|
}
|
|
304
323
|
|
|
305
|
-
_bootstrapProgress =
|
|
324
|
+
_bootstrapProgress = _progressAfterBackend;
|
|
306
325
|
_loadingBarSlider.value = _bootstrapProgress;
|
|
307
326
|
}
|
|
308
327
|
|
|
@@ -322,6 +341,38 @@ namespace Hyper.Internal
|
|
|
322
341
|
: _gameContentConfig.GameScene?.SceneName;
|
|
323
342
|
}
|
|
324
343
|
|
|
344
|
+
private IEnumerator WaitForCustomPreLoading()
|
|
345
|
+
{
|
|
346
|
+
HyperRuntime.CustomLoadingComplete = false;
|
|
347
|
+
HyperRuntime.CustomLoadingProgress = 0f;
|
|
348
|
+
|
|
349
|
+
float simulatedProgress = 0f;
|
|
350
|
+
const float simulatedSpeed = 0.15f;
|
|
351
|
+
const float simulatedCap = 0.70f;
|
|
352
|
+
|
|
353
|
+
HyperDebug.Log("Custom pre-loading enabled. Waiting for HyperSDK.Loader.CompleteCustomLoading()");
|
|
354
|
+
|
|
355
|
+
while (!HyperRuntime.CustomLoadingComplete)
|
|
356
|
+
{
|
|
357
|
+
float devProgress = HyperRuntime.CustomLoadingProgress;
|
|
358
|
+
|
|
359
|
+
// Simulate progress if dev isn't reporting (auto-animate so bar doesn't look stuck)
|
|
360
|
+
simulatedProgress = Mathf.MoveTowards(simulatedProgress, simulatedCap, Time.unscaledDeltaTime * simulatedSpeed);
|
|
361
|
+
|
|
362
|
+
// Use whichever is higher: dev-reported or simulated
|
|
363
|
+
float effectiveProgress = Mathf.Max(devProgress, simulatedProgress);
|
|
364
|
+
|
|
365
|
+
_bootstrapProgress = Mathf.Lerp(_progressAfterBackend, _progressAfterCustomLoad, effectiveProgress);
|
|
366
|
+
_loadingBarSlider.value = _bootstrapProgress;
|
|
367
|
+
|
|
368
|
+
yield return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Snap to end of custom loading range
|
|
372
|
+
_bootstrapProgress = _progressAfterCustomLoad;
|
|
373
|
+
_loadingBarSlider.value = _bootstrapProgress;
|
|
374
|
+
}
|
|
375
|
+
|
|
325
376
|
private long GetRandomSeed()
|
|
326
377
|
{
|
|
327
378
|
#if UNITY_EDITOR
|
|
@@ -424,9 +475,5 @@ namespace Hyper.Internal
|
|
|
424
475
|
}
|
|
425
476
|
|
|
426
477
|
#endregion
|
|
427
|
-
|
|
428
|
-
#region Utility Methods
|
|
429
|
-
|
|
430
|
-
#endregion
|
|
431
478
|
}
|
|
432
479
|
}
|
|
@@ -19,6 +19,8 @@ namespace Hyper.Internal
|
|
|
19
19
|
|
|
20
20
|
private static bool _isInitialized;
|
|
21
21
|
private static bool _isSoftRestarting;
|
|
22
|
+
private static bool _customLoadingComplete;
|
|
23
|
+
private static float _customLoadingProgress;
|
|
22
24
|
private static HyperSDKSettings _settings;
|
|
23
25
|
private static HyperGameContentConfig _gameContent;
|
|
24
26
|
private static GameManager _gameManager;
|
|
@@ -33,6 +35,16 @@ namespace Hyper.Internal
|
|
|
33
35
|
get => _isSoftRestarting;
|
|
34
36
|
set => _isSoftRestarting = value;
|
|
35
37
|
}
|
|
38
|
+
internal static bool CustomLoadingComplete
|
|
39
|
+
{
|
|
40
|
+
get => _customLoadingComplete;
|
|
41
|
+
set => _customLoadingComplete = value;
|
|
42
|
+
}
|
|
43
|
+
internal static float CustomLoadingProgress
|
|
44
|
+
{
|
|
45
|
+
get => _customLoadingProgress;
|
|
46
|
+
set => _customLoadingProgress = value;
|
|
47
|
+
}
|
|
36
48
|
internal static HyperSDKSettings Settings => _settings;
|
|
37
49
|
internal static HyperGameContentConfig GameContent => _gameContent;
|
|
38
50
|
internal static GameManager GameManager => _gameManager;
|
|
@@ -54,28 +66,21 @@ namespace Hyper.Internal
|
|
|
54
66
|
internal static void RestartSession()
|
|
55
67
|
{
|
|
56
68
|
_isSoftRestarting = true;
|
|
69
|
+
Time.timeScale = 1f; // Always reset time scale before restart — PauseGame() sets it to 0 and it persists across scene loads
|
|
57
70
|
|
|
58
71
|
if (_settings != null && _settings.useCustomRestartFlow)
|
|
59
72
|
{
|
|
60
|
-
// Fire custom restart event - developers handle restart themselves
|
|
61
73
|
_eventManager?.InvokeCustomRestartEvent();
|
|
62
|
-
|
|
63
|
-
// Reset SDK UI state for custom restart
|
|
64
|
-
// This prepares the SDK UI (hides game over, shows start modal, resets timer, etc.)
|
|
65
|
-
// so the game appears fresh when the developer's restart logic runs
|
|
66
74
|
_gameManager?.ResetUIStateForCustomRestart();
|
|
67
|
-
|
|
68
75
|
return;
|
|
69
76
|
}
|
|
70
77
|
|
|
71
|
-
// Use improved default restart flow
|
|
72
78
|
if (_host != null)
|
|
73
79
|
{
|
|
74
80
|
_host.StartCoroutine(RestartSessionCoroutine());
|
|
75
81
|
}
|
|
76
82
|
else
|
|
77
83
|
{
|
|
78
|
-
// Fallback to immediate restart if host doesn't exist
|
|
79
84
|
ClearAllDontDestroyOnLoadObjects();
|
|
80
85
|
SceneManager.LoadScene("HyperLoader");
|
|
81
86
|
}
|
|
@@ -287,6 +292,8 @@ namespace Hyper.Internal
|
|
|
287
292
|
|
|
288
293
|
HyperRandom.Clear();
|
|
289
294
|
_gameContent = null;
|
|
295
|
+
_customLoadingComplete = false;
|
|
296
|
+
_customLoadingProgress = 0f;
|
|
290
297
|
|
|
291
298
|
if (_singletonObject != null)
|
|
292
299
|
{
|
|
@@ -335,6 +342,8 @@ namespace Hyper.Internal
|
|
|
335
342
|
_dataManager = null;
|
|
336
343
|
_gameContent = null;
|
|
337
344
|
_isInitialized = false;
|
|
345
|
+
_customLoadingComplete = false;
|
|
346
|
+
_customLoadingProgress = 0f;
|
|
338
347
|
}
|
|
339
348
|
}
|
|
340
349
|
}
|
|
@@ -498,7 +498,11 @@ namespace Hyper.Internal.Managers
|
|
|
498
498
|
if (_gameManager.HasGameStarted)
|
|
499
499
|
{
|
|
500
500
|
_gameManager?.ShowConnectionRestored();
|
|
501
|
-
|
|
501
|
+
|
|
502
|
+
if (!_gameManager.IsPaused)
|
|
503
|
+
{
|
|
504
|
+
SendGameResumed();
|
|
505
|
+
}
|
|
502
506
|
}
|
|
503
507
|
else
|
|
504
508
|
{
|
|
@@ -723,10 +727,6 @@ namespace Hyper.Internal.Managers
|
|
|
723
727
|
LastSubmissionError = SubmissionErrorType.None;
|
|
724
728
|
|
|
725
729
|
// Keep connection alive during game end sequence
|
|
726
|
-
// WebSocket connections can be closed by proxies/firewalls if idle for too long (typically 30-60 seconds)
|
|
727
|
-
// During gameplay, scores are sent every second, keeping the connection active
|
|
728
|
-
// But at game end, we stop sending scores, so the connection might go idle
|
|
729
|
-
// We ensure the connection is active right before submission by checking and reconnecting if needed
|
|
730
730
|
if (!isSocketConnected)
|
|
731
731
|
{
|
|
732
732
|
HyperDebug.LogWarning("WebSocket disconnected at game end - attempting to reconnect before submission");
|
|
@@ -748,8 +748,7 @@ namespace Hyper.Internal.Managers
|
|
|
748
748
|
}
|
|
749
749
|
else
|
|
750
750
|
{
|
|
751
|
-
|
|
752
|
-
// The act of checking and the upcoming gameResumed call will keep it active
|
|
751
|
+
|
|
753
752
|
HyperDebug.Log("Connection active - maintaining during game end sequence");
|
|
754
753
|
}
|
|
755
754
|
|
|
@@ -777,7 +776,10 @@ namespace Hyper.Internal.Managers
|
|
|
777
776
|
var finalScoreData = _dataManager?.CurrentScoreData;
|
|
778
777
|
finalScoreData.isFinal = true;
|
|
779
778
|
|
|
780
|
-
|
|
779
|
+
// For consistency with per-second and backlog submissions, always send
|
|
780
|
+
// the final score as an array of ScoreData.
|
|
781
|
+
var finalScoreList = new List<ScoreData>(1) { finalScoreData };
|
|
782
|
+
string rawScoreJson = JsonConvert.SerializeObject(finalScoreList);
|
|
781
783
|
var compressedScoreData = JsonCompressor.CompressJson(rawScoreJson);
|
|
782
784
|
string scoreJson = compressedScoreData.MinifiedJson;
|
|
783
785
|
string encryptedScoreData = EncryptScore(scoreJson);
|
|
@@ -835,6 +837,18 @@ namespace Hyper.Internal.Managers
|
|
|
835
837
|
continue;
|
|
836
838
|
}
|
|
837
839
|
|
|
840
|
+
// Flush any offline backlog that accumulated while disconnected.
|
|
841
|
+
if (_dataManager?.PendingScores != null && _dataManager.PendingScores.Count > 0)
|
|
842
|
+
{
|
|
843
|
+
var backlog = new List<ScoreData>(_dataManager.PendingScores);
|
|
844
|
+
_dataManager.PendingScores.Clear();
|
|
845
|
+
int backlogCount = backlog.Count;
|
|
846
|
+
HyperDebug.Log($"[Score] Flushing {backlogCount} offline backlog score(s) before final submission.");
|
|
847
|
+
SendScoresOverWebSocket(backlog);
|
|
848
|
+
|
|
849
|
+
yield return new WaitForSecondsRealtime(1f);
|
|
850
|
+
}
|
|
851
|
+
|
|
838
852
|
// Sending final score via WebSocket
|
|
839
853
|
_finalScoreSubmitted = false;
|
|
840
854
|
_isWaitingForFinalScoreResponse = true;
|
|
@@ -986,15 +1000,30 @@ namespace Hyper.Internal.Managers
|
|
|
986
1000
|
{
|
|
987
1001
|
while (true)
|
|
988
1002
|
{
|
|
1003
|
+
// Don't drain PendingScores while offline - let them accumulate as an offline batch.
|
|
1004
|
+
// Scores will be flushed when the connection is restored.
|
|
1005
|
+
if (!isSocketConnected)
|
|
1006
|
+
{
|
|
1007
|
+
int pendingCount = _dataManager?.PendingScores?.Count ?? 0;
|
|
1008
|
+
HyperDebug.Log($"[Score] Socket disconnected - accumulating scores for offline batch. PendingCount={pendingCount}");
|
|
1009
|
+
yield return _scoreSendInterval;
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
989
1013
|
var scoresToSend = _scoreSendBuffer;
|
|
990
1014
|
scoresToSend.Clear();
|
|
991
1015
|
|
|
992
1016
|
if (_dataManager?.PendingScores.Count > 0)
|
|
993
1017
|
{
|
|
994
|
-
// Copy pending scores into our reusable buffer, then clear the source list
|
|
1018
|
+
// Copy pending scores into our reusable buffer, then clear the source list.
|
|
1019
|
+
// This may be 1 score (normal) or many (offline backlog after reconnection).
|
|
995
1020
|
scoresToSend.AddRange(_dataManager?.PendingScores);
|
|
996
1021
|
_dataManager?.PendingScores.Clear();
|
|
997
|
-
|
|
1022
|
+
int count = scoresToSend.Count;
|
|
1023
|
+
HyperDebug.Log($"[Score] Preparing to send {count} score sample(s) over WebSocket. LastPendingCumulativeScore={scoresToSend[count - 1].cumulativeScore}");
|
|
1024
|
+
|
|
1025
|
+
// Send the entire batch as an array payload.
|
|
1026
|
+
// Normal ticks send [1 score], reconnection flushes send [N scores].
|
|
998
1027
|
SendScoresOverWebSocket(scoresToSend);
|
|
999
1028
|
}
|
|
1000
1029
|
else
|
|
@@ -1021,77 +1050,36 @@ namespace Hyper.Internal.Managers
|
|
|
1021
1050
|
|
|
1022
1051
|
try
|
|
1023
1052
|
{
|
|
1024
|
-
// Clone the
|
|
1053
|
+
// Clone the batch into standalone ScoreData instances
|
|
1025
1054
|
// so that pooling/reset in DataManager does not zero out the values we log/use here.
|
|
1026
1055
|
_lastSentBatch.Clear();
|
|
1027
1056
|
|
|
1028
|
-
|
|
1029
|
-
if (scores.Count > 0)
|
|
1057
|
+
for (int i = 0; i < scores.Count; i++)
|
|
1030
1058
|
{
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
{
|
|
1035
|
-
var source = scores[i];
|
|
1036
|
-
if (source == null) continue;
|
|
1037
|
-
|
|
1038
|
-
var cloned = new ScoreData
|
|
1039
|
-
{
|
|
1040
|
-
cumulativeScore = source.cumulativeScore,
|
|
1041
|
-
isFinal = source.isFinal,
|
|
1042
|
-
timestampUtc = source.timestampUtc
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
|
-
if (source.scoreSnapshot != null)
|
|
1046
|
-
{
|
|
1047
|
-
var snapshot = source.scoreSnapshot;
|
|
1048
|
-
var clonedSnapshot = new ScoreSnapshot
|
|
1049
|
-
{
|
|
1050
|
-
scoreTime = snapshot.scoreTime,
|
|
1051
|
-
scoreDelta = snapshot.scoreDelta
|
|
1052
|
-
};
|
|
1053
|
-
|
|
1054
|
-
if (snapshot.gameplayModifiers != null)
|
|
1055
|
-
{
|
|
1056
|
-
var mods = snapshot.gameplayModifiers;
|
|
1057
|
-
var clonedMods = new GameplayModifiers
|
|
1058
|
-
{
|
|
1059
|
-
comboModifiers = mods.comboModifiers,
|
|
1060
|
-
globalModifiers = mods.globalModifiers,
|
|
1061
|
-
activePowerUps = mods.activePowerUps != null
|
|
1062
|
-
? new List<string>(mods.activePowerUps)
|
|
1063
|
-
: null
|
|
1064
|
-
};
|
|
1065
|
-
|
|
1066
|
-
clonedSnapshot.gameplayModifiers = clonedMods;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
cloned.scoreSnapshot = clonedSnapshot;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
_lastSentBatch.Add(cloned);
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
scoreData = scores[scores.Count - 1];
|
|
1059
|
+
var source = scores[i];
|
|
1060
|
+
if (source == null) continue;
|
|
1061
|
+
_lastSentBatch.Add(CloneScoreData(source));
|
|
1076
1062
|
}
|
|
1077
1063
|
|
|
1078
|
-
|
|
1064
|
+
// Serialize the full array of scores.
|
|
1065
|
+
// Normal ticks: array of 1. Offline backlog flush: array of N.
|
|
1066
|
+
string rawScoreJson = JsonConvert.SerializeObject(scores);
|
|
1079
1067
|
var compressedScoreData = JsonCompressor.CompressJson(rawScoreJson);
|
|
1080
1068
|
string scoreJson = compressedScoreData.MinifiedJson;
|
|
1081
1069
|
string encryptedData = EncryptScore(scoreJson);
|
|
1082
1070
|
|
|
1083
1071
|
if (string.IsNullOrEmpty(encryptedData))
|
|
1084
1072
|
{
|
|
1085
|
-
int lastScore =
|
|
1086
|
-
HyperDebug.LogError($"[Score] Encryption failed for
|
|
1073
|
+
int lastScore = scores[scores.Count - 1].cumulativeScore;
|
|
1074
|
+
HyperDebug.LogError($"[Score] Encryption failed for score submission. LastCumulativeScore={lastScore} [ErrorType=EncryptionError]");
|
|
1087
1075
|
return;
|
|
1088
1076
|
}
|
|
1089
1077
|
|
|
1090
1078
|
var message = new WebSocketScoreMessage(_cachedSessionId, _csrfToken, encryptedData);
|
|
1091
1079
|
string json = JsonConvert.SerializeObject(message);
|
|
1092
1080
|
int csrfLength = string.IsNullOrEmpty(_csrfToken) ? 0 : _csrfToken.Length;
|
|
1093
|
-
int
|
|
1094
|
-
HyperDebug.Log($"[Score] Sending
|
|
1081
|
+
int lastCumulativeScore = scores[scores.Count - 1].cumulativeScore;
|
|
1082
|
+
HyperDebug.Log($"[Score] Sending score batch. LastCumulativeScore={lastCumulativeScore}, BatchSize={scores.Count}, SessionId={_cachedSessionId}, CsrfLength={csrfLength}");
|
|
1095
1083
|
SendWebSocketMessage(json);
|
|
1096
1084
|
}
|
|
1097
1085
|
catch (Exception ex)
|
|
@@ -1107,6 +1095,51 @@ namespace Hyper.Internal.Managers
|
|
|
1107
1095
|
}
|
|
1108
1096
|
}
|
|
1109
1097
|
|
|
1098
|
+
/// <summary>
|
|
1099
|
+
/// Creates a deep clone of a ScoreData instance.
|
|
1100
|
+
/// Used to create standalone copies that are not affected by the DataManager's object pooling.
|
|
1101
|
+
/// </summary>
|
|
1102
|
+
private static ScoreData CloneScoreData(ScoreData source)
|
|
1103
|
+
{
|
|
1104
|
+
if (source == null) return null;
|
|
1105
|
+
|
|
1106
|
+
var cloned = new ScoreData
|
|
1107
|
+
{
|
|
1108
|
+
cumulativeScore = source.cumulativeScore,
|
|
1109
|
+
isFinal = source.isFinal,
|
|
1110
|
+
timestampUtc = source.timestampUtc
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
if (source.scoreSnapshot != null)
|
|
1114
|
+
{
|
|
1115
|
+
var snapshot = source.scoreSnapshot;
|
|
1116
|
+
var clonedSnapshot = new ScoreSnapshot
|
|
1117
|
+
{
|
|
1118
|
+
scoreTime = snapshot.scoreTime,
|
|
1119
|
+
scoreDelta = snapshot.scoreDelta
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
if (snapshot.gameplayModifiers != null)
|
|
1123
|
+
{
|
|
1124
|
+
var mods = snapshot.gameplayModifiers;
|
|
1125
|
+
var clonedMods = new GameplayModifiers
|
|
1126
|
+
{
|
|
1127
|
+
comboModifiers = mods.comboModifiers,
|
|
1128
|
+
globalModifiers = mods.globalModifiers,
|
|
1129
|
+
activePowerUps = mods.activePowerUps != null
|
|
1130
|
+
? new List<string>(mods.activePowerUps)
|
|
1131
|
+
: null
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
clonedSnapshot.gameplayModifiers = clonedMods;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
cloned.scoreSnapshot = clonedSnapshot;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
return cloned;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1110
1143
|
[Serializable]
|
|
1111
1144
|
private class WebSocketScoreMessage
|
|
1112
1145
|
{
|
|
@@ -1244,7 +1277,7 @@ namespace Hyper.Internal.Managers
|
|
|
1244
1277
|
|
|
1245
1278
|
string eventName = eventType switch
|
|
1246
1279
|
{
|
|
1247
|
-
ConsoleEventType.
|
|
1280
|
+
ConsoleEventType.GameStarted => "GameStarted",
|
|
1248
1281
|
ConsoleEventType.GameResumed => "GameResumed",
|
|
1249
1282
|
ConsoleEventType.GamePaused => "GamePaused",
|
|
1250
1283
|
ConsoleEventType.GameEnd => "GameEnd",
|
|
@@ -1255,27 +1288,22 @@ namespace Hyper.Internal.Managers
|
|
|
1255
1288
|
_ => string.Empty
|
|
1256
1289
|
};
|
|
1257
1290
|
|
|
1258
|
-
bool
|
|
1259
|
-
eventType == ConsoleEventType.PracticeGameEnd;
|
|
1260
|
-
|
|
1261
|
-
ConsoleEvent logEntry;
|
|
1291
|
+
bool stopGameOnLog = true;
|
|
1262
1292
|
|
|
1263
|
-
|
|
1293
|
+
switch (eventType)
|
|
1264
1294
|
{
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
else
|
|
1275
|
-
{
|
|
1276
|
-
logEntry = new ConsoleEvent { name = eventName };
|
|
1295
|
+
case ConsoleEventType.GameStarted:
|
|
1296
|
+
stopGameOnLog = false;
|
|
1297
|
+
break;
|
|
1298
|
+
case ConsoleEventType.GameResumed:
|
|
1299
|
+
stopGameOnLog = false;
|
|
1300
|
+
break;
|
|
1301
|
+
case ConsoleEventType.GamePaused:
|
|
1302
|
+
stopGameOnLog = false;
|
|
1303
|
+
break;
|
|
1277
1304
|
}
|
|
1278
1305
|
|
|
1306
|
+
ConsoleEvent logEntry = new ConsoleEvent { name = eventName };
|
|
1279
1307
|
string jsonLog = JsonUtility.ToJson(logEntry);
|
|
1280
1308
|
|
|
1281
1309
|
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
@@ -1283,7 +1311,7 @@ namespace Hyper.Internal.Managers
|
|
|
1283
1311
|
#endif
|
|
1284
1312
|
|
|
1285
1313
|
#if UNITY_EDITOR
|
|
1286
|
-
UnityEditor.EditorApplication.isPlaying = false;
|
|
1314
|
+
if (stopGameOnLog) UnityEditor.EditorApplication.isPlaying = false;
|
|
1287
1315
|
#endif
|
|
1288
1316
|
}
|
|
1289
1317
|
|
|
@@ -429,21 +429,10 @@ namespace Hyper
|
|
|
429
429
|
{
|
|
430
430
|
#region Public Data Structures
|
|
431
431
|
|
|
432
|
-
[Serializable]
|
|
433
|
-
public class ConsoleData
|
|
434
|
-
{
|
|
435
|
-
public int score;
|
|
436
|
-
public double time;
|
|
437
|
-
public string gameId;
|
|
438
|
-
public string currentPlatform;
|
|
439
|
-
public string recommendedPlatform;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
432
|
[Serializable]
|
|
443
433
|
public class ConsoleEvent
|
|
444
434
|
{
|
|
445
435
|
public string name;
|
|
446
|
-
public ConsoleData data;
|
|
447
436
|
}
|
|
448
437
|
|
|
449
438
|
#endregion
|
|
@@ -67,7 +67,7 @@ namespace Hyper.Internal.Managers
|
|
|
67
67
|
internal void InvokeGameStartEvent()
|
|
68
68
|
{
|
|
69
69
|
OnGameStart?.Invoke();
|
|
70
|
-
HyperRuntime.BackendManager.LogEvent(ConsoleEventType.
|
|
70
|
+
HyperRuntime.BackendManager.LogEvent(ConsoleEventType.GameStarted);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
internal void InvokeSoundChangedEvent(bool sound) => OnSoundChanged?.Invoke(sound);
|
|
@@ -59,32 +59,14 @@ namespace Hyper.Internal.Managers
|
|
|
59
59
|
{
|
|
60
60
|
if (!IsInGameScene || _hasGameEnded || !_hasGameStarted) return;
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
// Use appropriate method based on game mode
|
|
64
|
-
if (IsBattleMode)
|
|
65
|
-
{
|
|
66
|
-
EndBattleGame();
|
|
67
|
-
}
|
|
68
|
-
else
|
|
69
|
-
{
|
|
70
|
-
EndPracticeGame();
|
|
71
|
-
}
|
|
62
|
+
EndGame();
|
|
72
63
|
}
|
|
73
64
|
|
|
74
65
|
private void HandleHealthExhausted()
|
|
75
66
|
{
|
|
76
67
|
if (!IsInGameScene || _hasGameEnded || !_hasGameStarted) return;
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
// Use appropriate method based on game mode
|
|
80
|
-
if (IsBattleMode)
|
|
81
|
-
{
|
|
82
|
-
EndBattleGame();
|
|
83
|
-
}
|
|
84
|
-
else
|
|
85
|
-
{
|
|
86
|
-
EndPracticeGame();
|
|
87
|
-
}
|
|
69
|
+
EndGame();
|
|
88
70
|
}
|
|
89
71
|
|
|
90
72
|
internal void Cleanup()
|
|
@@ -107,6 +89,22 @@ namespace Hyper.Internal.Managers
|
|
|
107
89
|
public bool HasGameStarted => _hasGameStarted;
|
|
108
90
|
public bool HasGameEnded => _hasGameEnded;
|
|
109
91
|
|
|
92
|
+
internal int BattleTimeRemaining =>
|
|
93
|
+
IsBattleMode && HyperRuntime.Timer != null
|
|
94
|
+
? HyperRuntime.Timer.BattleTimeRemaining
|
|
95
|
+
: 0;
|
|
96
|
+
|
|
97
|
+
/// <summary>
|
|
98
|
+
/// Ends the current session for whichever mode is active (practice or battle).
|
|
99
|
+
/// </summary>
|
|
100
|
+
public void EndGame()
|
|
101
|
+
{
|
|
102
|
+
if (IsBattleMode)
|
|
103
|
+
EndBattleGame();
|
|
104
|
+
else
|
|
105
|
+
EndPracticeGame();
|
|
106
|
+
}
|
|
107
|
+
|
|
110
108
|
public void EndBattleGame()
|
|
111
109
|
{
|
|
112
110
|
if (!IsBattleMode) return;
|
|
@@ -222,6 +220,14 @@ namespace Hyper.Internal.Managers
|
|
|
222
220
|
_eventManager.InvokeTimeScaleResumedEvent();
|
|
223
221
|
HyperDebug.Log("Time scale resumed event invoked");
|
|
224
222
|
|
|
223
|
+
// Always restart capturing when game resumes, even if offline,
|
|
224
|
+
// so scores accumulate for the offline batch and are flushed on reconnect.
|
|
225
|
+
if (!_hasGameEnded && _hasGameStarted)
|
|
226
|
+
{
|
|
227
|
+
_dataManager.StartCapturingScores();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Only resume server session if connected
|
|
225
231
|
if (_backendManager.isSocketConnected && !_hasGameEnded && _hasGameStarted)
|
|
226
232
|
{
|
|
227
233
|
_backendManager.SendGameResumed();
|
|
@@ -993,7 +999,7 @@ namespace Hyper.Internal.Managers
|
|
|
993
999
|
_canvasScaler = _canvas.GetComponent<CanvasScaler>();
|
|
994
1000
|
_canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
|
995
1001
|
_canvasScaler.referenceResolution = new Vector2(1080, 1920);
|
|
996
|
-
_canvasScaler.matchWidthOrHeight = 0.
|
|
1002
|
+
_canvasScaler.matchWidthOrHeight = IsPortrait() ? 0.43f : 0.73f;
|
|
997
1003
|
|
|
998
1004
|
CreateSafeArea();
|
|
999
1005
|
CreateBlockerUI();
|
|
@@ -68,6 +68,11 @@ namespace Hyper.Internal.Managers
|
|
|
68
68
|
? (int)_battleTimer
|
|
69
69
|
: (int)_practiceTimer;
|
|
70
70
|
|
|
71
|
+
/// <summary>
|
|
72
|
+
/// Match countdown seconds (floored, clamped at zero). Same source as battle branch of <see cref="CurrentTime"/>.
|
|
73
|
+
/// </summary>
|
|
74
|
+
internal int BattleTimeRemaining => Mathf.Max(0, (int)_battleTimer);
|
|
75
|
+
|
|
71
76
|
public int SessionTimeRemaining => (int)_sessionTimer;
|
|
72
77
|
|
|
73
78
|
private GameManager _gameManager;
|
|
@@ -49,6 +49,14 @@ namespace Hyper.Internal
|
|
|
49
49
|
|
|
50
50
|
#endregion
|
|
51
51
|
|
|
52
|
+
#region Custom Pre-Loading
|
|
53
|
+
|
|
54
|
+
[Header("Custom Pre-Loading")]
|
|
55
|
+
[Tooltip("Enable if your game needs to perform custom work (e.g. downloading assets, fetching remote config) after SDK initialization but before the game scene loads. When enabled, the bootstrap will pause and wait for HyperSDK.Loader.CompleteCustomLoading() to be called.")]
|
|
56
|
+
public bool EnableCustomLoading = false;
|
|
57
|
+
|
|
58
|
+
#endregion
|
|
59
|
+
|
|
52
60
|
#region Game Posters & Hints
|
|
53
61
|
|
|
54
62
|
[Space]
|
|
@@ -96,16 +96,9 @@ namespace Hyper.Internal
|
|
|
96
96
|
|
|
97
97
|
public void ForfeitGame()
|
|
98
98
|
{
|
|
99
|
+
HyperRuntime.GameManager.EndGame();
|
|
99
100
|
if (!IsPVPMode)
|
|
100
|
-
{
|
|
101
|
-
HyperRuntime.GameManager.EndPracticeGame();
|
|
102
101
|
_gameManager.LoadPracticeMenuScene();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
else
|
|
106
|
-
{
|
|
107
|
-
HyperRuntime.GameManager.EndBattleGame();
|
|
108
|
-
}
|
|
109
102
|
}
|
|
110
103
|
|
|
111
104
|
public void ToggleSound()
|
|
@@ -56,6 +56,32 @@ mergeInto(LibraryManager.library, {
|
|
|
56
56
|
window.location.reload();
|
|
57
57
|
},
|
|
58
58
|
|
|
59
|
+
HyperIsMobileUserAgent: function () {
|
|
60
|
+
var ua = navigator.userAgent || '';
|
|
61
|
+
if (/Android|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop|Mobile/i.test(ua)) {
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// Large screen.* vs small inner viewport: typical of desktop Chrome DevTools device toolbar (not a guarantee).
|
|
68
|
+
HyperIsLikelyDesktopDevtoolsMobileViewport: function () {
|
|
69
|
+
try {
|
|
70
|
+
var sw = window.screen.width;
|
|
71
|
+
var sh = window.screen.height;
|
|
72
|
+
var screenLong = Math.max(sw, sh);
|
|
73
|
+
var iw = window.innerWidth;
|
|
74
|
+
var ih = window.innerHeight;
|
|
75
|
+
var innerLong = Math.max(iw, ih);
|
|
76
|
+
if (screenLong >= 1280 && innerLong <= 900 && screenLong > innerLong * 1.35) {
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
return 0;
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
|
|
59
85
|
getDeviceScreenResolution: function () {
|
|
60
86
|
// Get the device screen width and height
|
|
61
87
|
const width = window.screen.width;
|
|
@@ -15,6 +15,7 @@ namespace Hyper
|
|
|
15
15
|
private static GameAPIAdapter _gameAdapter;
|
|
16
16
|
private static DataAPIAdapter _dataAdapter;
|
|
17
17
|
private static EventAPIAdapter _eventAdapter;
|
|
18
|
+
private static LoaderAPIAdapter _loaderAdapter;
|
|
18
19
|
|
|
19
20
|
/// <summary>
|
|
20
21
|
/// Returns true if the SDK runtime has been initialized.
|
|
@@ -89,11 +90,33 @@ namespace Hyper
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
/// <summary>
|
|
94
|
+
/// Access loader control for custom pre-loading workflows.
|
|
95
|
+
/// Use when EnableCustomLoading is enabled in HyperGameContentConfig.
|
|
96
|
+
/// </summary>
|
|
97
|
+
public static ILoaderAPI Loader
|
|
98
|
+
{
|
|
99
|
+
get
|
|
100
|
+
{
|
|
101
|
+
if (_loaderAdapter == null)
|
|
102
|
+
{
|
|
103
|
+
_loaderAdapter = new LoaderAPIAdapter();
|
|
104
|
+
}
|
|
105
|
+
return _loaderAdapter;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
92
109
|
/// <summary>
|
|
93
110
|
/// Current Hyper SDK version string.
|
|
94
111
|
/// </summary>
|
|
95
112
|
public static string Version => HyperSDKVersion.Version;
|
|
96
113
|
|
|
114
|
+
/// <summary>True if the player is treated as mobile (heuristic). In the Unity Editor, follows the Device Simulator when active.</summary>
|
|
115
|
+
public static bool PlayerDeviceIsMobile => PlayerDeviceClassification.PlayerDeviceIsMobile;
|
|
116
|
+
|
|
117
|
+
/// <summary>True if the player is treated as PC/desktop (opposite of <see cref="PlayerDeviceIsMobile"/>).</summary>
|
|
118
|
+
public static bool PlayerDeviceIsPC => PlayerDeviceClassification.PlayerDeviceIsPC;
|
|
119
|
+
|
|
97
120
|
private static void EnsureAdaptersReady()
|
|
98
121
|
{
|
|
99
122
|
if (_gameAdapter == null)
|
|
@@ -121,32 +144,106 @@ namespace Hyper
|
|
|
121
144
|
/// </summary>
|
|
122
145
|
public interface IGameAPI
|
|
123
146
|
{
|
|
147
|
+
/// <summary>
|
|
148
|
+
/// True when the current mode is Battle (PvP or timed battle).
|
|
149
|
+
/// </summary>
|
|
124
150
|
bool IsBattleMode { get; }
|
|
151
|
+
/// <summary>
|
|
152
|
+
/// True when the current mode is Practice.
|
|
153
|
+
/// </summary>
|
|
125
154
|
bool IsPracticeMode { get; }
|
|
155
|
+
/// <summary>
|
|
156
|
+
/// True if the game is currently paused by the SDK.
|
|
157
|
+
/// </summary>
|
|
126
158
|
bool IsPaused { get; }
|
|
159
|
+
/// <summary>
|
|
160
|
+
/// True once gameplay has begun (post countdown/start).
|
|
161
|
+
/// </summary>
|
|
127
162
|
bool HasGameStarted { get; }
|
|
163
|
+
/// <summary>
|
|
164
|
+
/// True after the current game session has ended.
|
|
165
|
+
/// </summary>
|
|
128
166
|
bool HasGameEnded { get; }
|
|
129
167
|
|
|
168
|
+
/// <summary>
|
|
169
|
+
/// Seconds remaining on the PvP match clock. Zero in practice mode or when not in a timed battle.
|
|
170
|
+
/// </summary>
|
|
171
|
+
int BattleTimeRemaining { get; }
|
|
172
|
+
|
|
173
|
+
/// <summary>
|
|
174
|
+
/// Ends the current session for whichever mode is active (practice or battle).
|
|
175
|
+
/// </summary>
|
|
176
|
+
void EndGame();
|
|
177
|
+
|
|
178
|
+
[Obsolete("Use EndGame() instead; it ends practice or battle based on the current mode.")]
|
|
130
179
|
void EndBattleGame();
|
|
180
|
+
|
|
181
|
+
[Obsolete("Use EndGame() instead; it ends practice or battle based on the current mode.")]
|
|
131
182
|
void EndPracticeGame();
|
|
183
|
+
|
|
184
|
+
/// <summary>
|
|
185
|
+
/// Closes the practice menu and enters gameplay (only relevant when practice scene is a menu).
|
|
186
|
+
/// </summary>
|
|
132
187
|
void ExitPracticeMenu();
|
|
133
188
|
|
|
134
189
|
// Lives Management (only available when ControlBarLayout is HeartsLeft)
|
|
190
|
+
/// <summary>
|
|
191
|
+
/// Initialize the lives system with a maximum and optional initial value.
|
|
192
|
+
/// </summary>
|
|
135
193
|
void InitializeLives(int maxLives, int initialLives = -1);
|
|
194
|
+
/// <summary>
|
|
195
|
+
/// Set the current number of lives.
|
|
196
|
+
/// </summary>
|
|
136
197
|
void SetLives(int lives);
|
|
198
|
+
/// <summary>
|
|
199
|
+
/// Increment lives by one.
|
|
200
|
+
/// </summary>
|
|
137
201
|
void AddLife();
|
|
202
|
+
/// <summary>
|
|
203
|
+
/// Decrement lives by one.
|
|
204
|
+
/// </summary>
|
|
138
205
|
void RemoveLife();
|
|
206
|
+
/// <summary>
|
|
207
|
+
/// Get the current number of lives.
|
|
208
|
+
/// </summary>
|
|
139
209
|
int GetCurrentLives();
|
|
210
|
+
/// <summary>
|
|
211
|
+
/// Get the configured maximum number of lives.
|
|
212
|
+
/// </summary>
|
|
140
213
|
int GetMaxLives();
|
|
141
214
|
|
|
142
215
|
// Health Management (only available when ControlBarLayout is HealthBarGreenLeft)
|
|
216
|
+
/// <summary>
|
|
217
|
+
/// Initialize the health system with a maximum and optional initial value.
|
|
218
|
+
/// </summary>
|
|
143
219
|
void InitializeHealth(float maxHealth, float initialHealth = -1f, bool isNormalized = false);
|
|
220
|
+
/// <summary>
|
|
221
|
+
/// Set the current health (absolute units).
|
|
222
|
+
/// </summary>
|
|
144
223
|
void SetHealth(float health);
|
|
224
|
+
/// <summary>
|
|
225
|
+
/// Set the current health as a normalized value in [0,1].
|
|
226
|
+
/// </summary>
|
|
145
227
|
void SetHealthNormalized(float normalizedHealth);
|
|
228
|
+
/// <summary>
|
|
229
|
+
/// Add to the current health by an absolute amount.
|
|
230
|
+
/// </summary>
|
|
146
231
|
void AddHealth(float amount);
|
|
232
|
+
/// <summary>
|
|
233
|
+
/// Remove from the current health by an absolute amount.
|
|
234
|
+
/// </summary>
|
|
147
235
|
void RemoveHealth(float amount);
|
|
236
|
+
/// <summary>
|
|
237
|
+
/// Get the current health (absolute units).
|
|
238
|
+
/// </summary>
|
|
148
239
|
float GetCurrentHealth();
|
|
240
|
+
/// <summary>
|
|
241
|
+
/// Get the maximum health (absolute units).
|
|
242
|
+
/// </summary>
|
|
149
243
|
float GetMaxHealth();
|
|
244
|
+
/// <summary>
|
|
245
|
+
/// Get the current health normalized to [0,1].
|
|
246
|
+
/// </summary>
|
|
150
247
|
float GetHealthNormalized();
|
|
151
248
|
}
|
|
152
249
|
|
|
@@ -155,34 +252,119 @@ namespace Hyper
|
|
|
155
252
|
/// </summary>
|
|
156
253
|
public interface IDataAPI
|
|
157
254
|
{
|
|
255
|
+
/// <summary>
|
|
256
|
+
/// Current in-session score value.
|
|
257
|
+
/// </summary>
|
|
158
258
|
int CurrentScore { get; }
|
|
159
259
|
|
|
260
|
+
/// <summary>
|
|
261
|
+
/// Overwrite the current score with a new value.
|
|
262
|
+
/// </summary>
|
|
160
263
|
void UpdateScore(int score);
|
|
264
|
+
/// <summary>
|
|
265
|
+
/// Set the combo multiplier applied to scoring.
|
|
266
|
+
/// </summary>
|
|
161
267
|
void SetComboMultiplier(float value);
|
|
268
|
+
/// <summary>
|
|
269
|
+
/// Set a global multiplier applied to scoring.
|
|
270
|
+
/// </summary>
|
|
162
271
|
void SetGlobalMultiplier(float value);
|
|
272
|
+
/// <summary>
|
|
273
|
+
/// Register that a powerup is active by type identifier.
|
|
274
|
+
/// </summary>
|
|
163
275
|
void AddPowerup(string powerupType);
|
|
276
|
+
/// <summary>
|
|
277
|
+
/// Remove an active powerup by type identifier.
|
|
278
|
+
/// </summary>
|
|
164
279
|
void RemovePowerup(string powerupType);
|
|
280
|
+
/// <summary>
|
|
281
|
+
/// Retrieve the best/high score recorded for the player.
|
|
282
|
+
/// </summary>
|
|
165
283
|
int GetBestScore();
|
|
166
284
|
}
|
|
167
285
|
|
|
286
|
+
/// <summary>
|
|
287
|
+
/// Public API for custom pre-loading control during the HyperLoader bootstrap.
|
|
288
|
+
/// </summary>
|
|
289
|
+
public interface ILoaderAPI
|
|
290
|
+
{
|
|
291
|
+
/// <summary>
|
|
292
|
+
/// Report progress of your custom loading (0.0 to 1.0).
|
|
293
|
+
/// This feeds into the loading bar so players see smooth progress during your custom work.
|
|
294
|
+
/// Optional — if not called, the loader will simulate progress automatically.
|
|
295
|
+
/// </summary>
|
|
296
|
+
void ReportCustomLoadingProgress(float progress);
|
|
297
|
+
|
|
298
|
+
/// <summary>
|
|
299
|
+
/// Signal that your custom pre-loading is complete.
|
|
300
|
+
/// The HyperLoader bootstrap will resume and load the game scene.
|
|
301
|
+
/// Must be called when EnableCustomLoading is true in HyperGameContentConfig.
|
|
302
|
+
/// </summary>
|
|
303
|
+
void CompleteCustomLoading();
|
|
304
|
+
}
|
|
305
|
+
|
|
168
306
|
/// <summary>
|
|
169
307
|
/// Public API for SDK events.
|
|
170
308
|
/// </summary>
|
|
171
309
|
public interface IEventAPI
|
|
172
310
|
{
|
|
311
|
+
/// <summary>
|
|
312
|
+
/// Fired when gameplay begins.
|
|
313
|
+
/// </summary>
|
|
173
314
|
event Action OnGameStart;
|
|
315
|
+
/// <summary>
|
|
316
|
+
/// Fired when the match timer reaches zero (battle mode).
|
|
317
|
+
/// </summary>
|
|
174
318
|
event Action OnTimeUp;
|
|
319
|
+
/// <summary>
|
|
320
|
+
/// Fired when the game is paused by the SDK.
|
|
321
|
+
/// </summary>
|
|
175
322
|
event Action OnGamePaused;
|
|
323
|
+
/// <summary>
|
|
324
|
+
/// Fired when the game resumes from a paused state.
|
|
325
|
+
/// </summary>
|
|
176
326
|
event Action OnGameResumed;
|
|
327
|
+
/// <summary>
|
|
328
|
+
/// Fired when the current game session ends.
|
|
329
|
+
/// </summary>
|
|
177
330
|
event Action OnGameEnded;
|
|
331
|
+
/// <summary>
|
|
332
|
+
/// Fired when the SDK sound setting changes (true = enabled).
|
|
333
|
+
/// </summary>
|
|
178
334
|
event Action<bool> OnSoundChanged;
|
|
335
|
+
/// <summary>
|
|
336
|
+
/// Fired when lives reach zero (when lives UI is in use).
|
|
337
|
+
/// </summary>
|
|
179
338
|
event Action OnLivesExhausted;
|
|
339
|
+
/// <summary>
|
|
340
|
+
/// Fired when health reaches zero (when health UI is in use).
|
|
341
|
+
/// </summary>
|
|
180
342
|
event Action OnHealthExhausted;
|
|
343
|
+
/// <summary>
|
|
344
|
+
/// Fired when a custom restart flow is requested by the SDK.
|
|
345
|
+
/// </summary>
|
|
181
346
|
event Action OnCustomRestart;
|
|
182
347
|
}
|
|
183
348
|
|
|
184
349
|
#region Adapter Implementations
|
|
185
350
|
|
|
351
|
+
/// <summary>
|
|
352
|
+
/// Bridges the public loader API to the internal HyperRuntime custom loading state.
|
|
353
|
+
/// </summary>
|
|
354
|
+
internal sealed class LoaderAPIAdapter : ILoaderAPI
|
|
355
|
+
{
|
|
356
|
+
public void ReportCustomLoadingProgress(float progress)
|
|
357
|
+
{
|
|
358
|
+
HyperRuntime.CustomLoadingProgress = Mathf.Clamp01(progress);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
public void CompleteCustomLoading()
|
|
362
|
+
{
|
|
363
|
+
HyperRuntime.CustomLoadingComplete = true;
|
|
364
|
+
HyperDebug.Log("Custom pre-loading complete. Bootstrap resuming");
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
186
368
|
/// <summary>
|
|
187
369
|
/// Bridges the public game API to the internal GameManager without exposing it to consumers.
|
|
188
370
|
/// </summary>
|
|
@@ -196,8 +378,17 @@ namespace Hyper
|
|
|
196
378
|
public bool HasGameStarted => Manager != null && Manager.HasGameStarted;
|
|
197
379
|
public bool HasGameEnded => Manager != null && Manager.HasGameEnded;
|
|
198
380
|
|
|
381
|
+
public int BattleTimeRemaining => Manager?.BattleTimeRemaining ?? 0;
|
|
382
|
+
|
|
383
|
+
/// <inheritdoc/>
|
|
384
|
+
public void EndGame() => Manager?.EndGame();
|
|
385
|
+
|
|
386
|
+
[Obsolete("Use EndGame() instead; it ends practice or battle based on the current mode.")]
|
|
199
387
|
public void EndBattleGame() => Manager?.EndBattleGame();
|
|
388
|
+
|
|
389
|
+
[Obsolete("Use EndGame() instead; it ends practice or battle based on the current mode.")]
|
|
200
390
|
public void EndPracticeGame() => Manager?.EndPracticeGame();
|
|
391
|
+
|
|
201
392
|
public void ExitPracticeMenu() => Manager?.ExitPracticeMenu();
|
|
202
393
|
|
|
203
394
|
public void InitializeLives(int maxLives, int initialLives = -1) => Manager?.InitializeLives(maxLives, initialLives);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
using System.Runtime.InteropServices;
|
|
2
|
+
using Hyper.Shared;
|
|
3
|
+
using UnityEngine;
|
|
4
|
+
|
|
5
|
+
namespace Hyper
|
|
6
|
+
{
|
|
7
|
+
internal static class PlayerDeviceClassification
|
|
8
|
+
{
|
|
9
|
+
internal static bool PlayerDeviceIsMobile => Resolve() == DevicePlatform.Mobile;
|
|
10
|
+
|
|
11
|
+
internal static bool PlayerDeviceIsPC => !PlayerDeviceIsMobile;
|
|
12
|
+
|
|
13
|
+
private static DevicePlatform Resolve()
|
|
14
|
+
{
|
|
15
|
+
#if UNITY_EDITOR
|
|
16
|
+
// Unity Device Simulator: Device.Application matches the simulated device; Application does not.
|
|
17
|
+
return UnityEngine.Device.Application.isMobilePlatform ? DevicePlatform.Mobile : DevicePlatform.PC;
|
|
18
|
+
#elif UNITY_WEBGL && !UNITY_EDITOR
|
|
19
|
+
if (HyperIsLikelyDesktopDevtoolsMobileViewport() != 0)
|
|
20
|
+
return DevicePlatform.PC;
|
|
21
|
+
if (Application.isMobilePlatform || HyperIsMobileUserAgent() != 0)
|
|
22
|
+
return DevicePlatform.Mobile;
|
|
23
|
+
return DevicePlatform.PC;
|
|
24
|
+
#else
|
|
25
|
+
return Application.isMobilePlatform ? DevicePlatform.Mobile : DevicePlatform.PC;
|
|
26
|
+
#endif
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
30
|
+
[DllImport("__Internal")]
|
|
31
|
+
private static extern int HyperIsMobileUserAgent();
|
|
32
|
+
|
|
33
|
+
[DllImport("__Internal")]
|
|
34
|
+
private static extern int HyperIsLikelyDesktopDevtoolsMobileViewport();
|
|
35
|
+
#else
|
|
36
|
+
private static int HyperIsMobileUserAgent() => 0;
|
|
37
|
+
|
|
38
|
+
private static int HyperIsLikelyDesktopDevtoolsMobileViewport() => 0;
|
|
39
|
+
#endif
|
|
40
|
+
}
|
|
41
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "app.hypergames.hypersdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"displayName": "HyperSDK",
|
|
5
5
|
"description": "Official Unity SDK for Hyper: multiplayer battles, leaderboards, deterministic RNG, session management, tutorials, and WebGL optimizations for production-ready games.",
|
|
6
6
|
"unity": "6000.0",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"email": "info@mvm.gg",
|
|
25
25
|
"url": "https://mvm.gg/"
|
|
26
26
|
},
|
|
27
|
-
"licensesUrl": "https://
|
|
28
|
-
"changelogUrl": "https://
|
|
29
|
-
"documentationUrl": "https://
|
|
27
|
+
"licensesUrl": "https://github.com/mvmhyper/hyper-sdk/blob/releaseTests/LICENSE.md",
|
|
28
|
+
"changelogUrl": "https://github.com/mvmhyper/hyper-sdk/blob/releaseTests/CHANGELOG.md",
|
|
29
|
+
"documentationUrl": "https://docs-getting-started-updates.hyper-sdk-documentation.pages.dev/",
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
32
32
|
"url": "https://github.com/mvmhyper/hyper-sdk.git",
|