app.hypergames.hypersdk 1.8.20 → 1.8.22

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 (36) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/Runtime/Internal/Bootstrap/HyperLoader.cs +4 -1
  3. package/Runtime/Internal/Managers/BackendManager.cs +126 -19
  4. package/Runtime/Internal/Managers/GameManager.cs +107 -9
  5. package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +1 -1
  6. package/Runtime/Internal/UI/Modals/TutorialAnnouncementModal.cs +57 -0
  7. package/Runtime/Internal/UI/Modals/TutorialAnnouncementModal.cs.meta +2 -0
  8. package/Runtime/Internal/UI/Modals/TutorialLoaderModal.cs +4 -3
  9. package/Runtime/Internal/UI/Modals/TutorialModal.cs +99 -6
  10. package/Runtime/Resources/Materials/RoundedRawImage.mat +3 -3
  11. package/Runtime/Resources/Sprites/Frame 2147224037.png +0 -0
  12. package/Runtime/Resources/Sprites/{TutorialModal/P Container.png.meta → Frame 2147224037.png.meta } +12 -25
  13. package/Runtime/Resources/UIPrefabs/ConnectionStatusBar.prefab +10 -10
  14. package/Runtime/Resources/UIPrefabs/GamePausedModal_Battle.prefab +1 -1
  15. package/Runtime/Resources/UIPrefabs/GamePausedModal_Battle_Landscape.prefab +1 -1
  16. package/Runtime/Resources/UIPrefabs/GamePausedModal_Practice.prefab +12 -0
  17. package/Runtime/Resources/UIPrefabs/LoadingFailedModal.prefab +7 -7
  18. package/Runtime/Resources/UIPrefabs/ScoreSubmissionFailedModal_Portrait.prefab +1 -1
  19. package/Runtime/Resources/UIPrefabs/TutorialAnnouncementModal.prefab +699 -0
  20. package/Runtime/Resources/UIPrefabs/{Content.prefab.meta → TutorialAnnouncementModal.prefab.meta} +1 -1
  21. package/Runtime/Resources/UIPrefabs/TutorialLandscapeModal.prefab +1578 -1218
  22. package/Runtime/Resources/UIPrefabs/TutorialLandscapeModal.prefab.meta +1 -1
  23. package/Runtime/Resources/UIPrefabs/TutorialPortraitModal.prefab +2210 -777
  24. package/package.json +1 -1
  25. package/Runtime/Resources/Sprites/TutorialModal/L Container.png +0 -0
  26. package/Runtime/Resources/Sprites/TutorialModal/L Container.png.meta +0 -169
  27. package/Runtime/Resources/Sprites/TutorialModal/P Container.png +0 -0
  28. package/Runtime/Resources/Sprites/TutorialModal/Time_Seeker_Bar_Empty.png +0 -0
  29. package/Runtime/Resources/Sprites/TutorialModal/Time_Seeker_Bar_Empty.png.meta +0 -156
  30. package/Runtime/Resources/Sprites/TutorialModal/Time_Seeker_Bar_Filled.png +0 -0
  31. package/Runtime/Resources/Sprites/TutorialModal/Time_Seeker_Bar_Filled.png.meta +0 -156
  32. package/Runtime/Resources/Sprites/TutorialModal/Tutorial_Background_Landscape.png +0 -0
  33. package/Runtime/Resources/Sprites/TutorialModal/Tutorial_Background_Landscape.png.meta +0 -169
  34. package/Runtime/Resources/Sprites/TutorialModal/Tutorial_Background_Portrait.png +0 -0
  35. package/Runtime/Resources/Sprites/TutorialModal/Tutorial_Background_Portrait.png.meta +0 -169
  36. package/Runtime/Resources/UIPrefabs/Content.prefab +0 -1672
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [1.8.22](https://github.com/mvmhyper/hyper-sdk/compare/v1.8.21...v1.8.22) (2026-07-08)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add tutorial announcement prompt ([0b5e009](https://github.com/mvmhyper/hyper-sdk/commit/0b5e0099346d144b56d35a39d613e24a776ef11b))
7
+
8
+ ## [1.8.21](https://github.com/mvmhyper/hyper-sdk/compare/v1.8.20...v1.8.21) (2026-07-08)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * smooth tutorial loader and media switching ([b4cfbef](https://github.com/mvmhyper/hyper-sdk/commit/b4cfbef45d5544e7c5dfa37f702acb9f7be67548))
14
+
1
15
  ## [1.8.20](https://github.com/mvmhyper/hyper-sdk/compare/v1.8.19...v1.8.20) (2026-07-07)
2
16
 
3
17
 
@@ -263,6 +263,9 @@ namespace Hyper.Internal
263
263
  _bootstrapProgress = _progressAfterInit;
264
264
  _loadingBarSlider.value = _bootstrapProgress;
265
265
 
266
+ // Start tutorial discovery early, but never wait for it before loading gameplay.
267
+ HyperRuntime.BackendManager.GetTutorial(null);
268
+
266
269
  // Step 2: Handle Backend/PVP or Practice mode initialization
267
270
  if (_gameManager.Config.IsBackendGame && _gameManager.Config.isPVP)
268
271
  {
@@ -475,4 +478,4 @@ namespace Hyper.Internal
475
478
 
476
479
  #endregion
477
480
  }
478
- }
481
+ }
@@ -49,7 +49,8 @@ namespace Hyper.Internal.Managers
49
49
  public bool isSocketConnected { get; private set; }
50
50
  public bool tutorialFetchSuccess { get; private set; }
51
51
  public bool tutorialExistsButFailed { get; private set; }
52
- public bool tutorialDoesNotExist => !tutorialExistsButFailed && !tutorialFetchSuccess && !_isFetchingTutorial;
52
+ public bool tutorialDoesNotExist => TutorialAvailability == TutorialAvailabilityState.Unavailable;
53
+ public TutorialAvailabilityState TutorialAvailability { get; private set; } = TutorialAvailabilityState.Unknown;
53
54
 
54
55
  /// <summary>
55
56
  /// Type of error that occurred during score submission.
@@ -127,7 +128,7 @@ namespace Hyper.Internal.Managers
127
128
  private void FetchAndCacheTutorials(string serverBaseUrl, string gameSlug, string gameObject)
128
129
  {
129
130
  _isFetchingTutorial = false;
130
- _onTutorialSuccess?.Invoke();
131
+ OnTutorialCacheComplete("success");
131
132
  }
132
133
 
133
134
  private void InitWebSocket(string serverUrl) { }
@@ -1587,27 +1588,41 @@ namespace Hyper.Internal.Managers
1587
1588
  NetworkError
1588
1589
  }
1589
1590
 
1590
- private Action _onFetchComplete;
1591
- private Action _onTutorialSuccess;
1592
- private Action<TutorialFetchStatus> _onTutorialFailure;
1591
+ public enum TutorialAvailabilityState
1592
+ {
1593
+ Unknown,
1594
+ InProgress,
1595
+ Available,
1596
+ Unavailable,
1597
+ ExistsButFailed
1598
+ }
1599
+
1600
+ private readonly List<Action> _fetchCompleteCallbacks = new List<Action>();
1601
+ private readonly List<Action> _tutorialSuccessCallbacks = new List<Action>();
1602
+ private readonly List<Action<TutorialFetchStatus>> _tutorialFailureCallbacks = new List<Action<TutorialFetchStatus>>();
1593
1603
  private bool _isFetchingTutorial;
1594
1604
 
1595
1605
  public void GetTutorial(Action fetchComplete, Action onSuccess = null, Action<TutorialFetchStatus> onFailure = null)
1596
1606
  {
1597
1607
  if (_gameManager == null) _gameManager = HyperRuntime.GameManager;
1598
1608
 
1609
+ if (IsTutorialFetchComplete())
1610
+ {
1611
+ InvokeTutorialCallbacksForCurrentState(fetchComplete, onSuccess, onFailure);
1612
+ return;
1613
+ }
1614
+
1615
+ AddTutorialCallbacks(fetchComplete, onSuccess, onFailure);
1616
+
1599
1617
  if (_isFetchingTutorial)
1600
1618
  {
1601
- HyperDebug.LogWarning("Tutorial fetch already in progress");
1602
1619
  return;
1603
1620
  }
1604
1621
 
1605
- _onFetchComplete = fetchComplete;
1606
- _onTutorialSuccess = onSuccess;
1607
- _onTutorialFailure = onFailure;
1608
1622
  _currentRetryCount = 0;
1609
1623
  tutorialExistsButFailed = false;
1610
1624
  tutorialFetchSuccess = false;
1625
+ TutorialAvailability = TutorialAvailabilityState.InProgress;
1611
1626
  TryFetchTutorial();
1612
1627
  }
1613
1628
 
@@ -1618,30 +1633,32 @@ namespace Hyper.Internal.Managers
1618
1633
 
1619
1634
  #if UNITY_WEBGL && !UNITY_EDITOR
1620
1635
  FetchAndCacheTutorials(GetServerUrl(), gameSlug, gameObject.name);
1621
- #else
1636
+ #elif UNITY_EDITOR
1622
1637
  StartCoroutine(SimulateTutorialFetch());
1638
+ #else
1639
+ OnTutorialCacheComplete("fetch_failed");
1623
1640
  #endif
1624
1641
  }
1625
1642
 
1626
1643
  public void OnTutorialCacheComplete(string result)
1627
1644
  {
1628
- _onFetchComplete?.Invoke();
1629
-
1630
1645
  switch (result)
1631
1646
  {
1632
1647
  case "success":
1633
- _eventManager?.InvokeTutorialFetchCompleteEvent();
1634
1648
  _isFetchingTutorial = false;
1635
1649
  tutorialExistsButFailed = false;
1636
1650
  tutorialFetchSuccess = true;
1637
- _onTutorialSuccess?.Invoke();
1651
+ TutorialAvailability = TutorialAvailabilityState.Available;
1652
+ _eventManager?.InvokeTutorialFetchCompleteEvent();
1653
+ InvokeAndClearTutorialCallbacks(TutorialFetchStatus.Success);
1638
1654
  break;
1639
1655
 
1640
1656
  case "no_tutorial":
1641
1657
  _isFetchingTutorial = false;
1642
1658
  tutorialExistsButFailed = false;
1643
1659
  tutorialFetchSuccess = false;
1644
- _onTutorialFailure?.Invoke(TutorialFetchStatus.NoTutorialExists);
1660
+ TutorialAvailability = TutorialAvailabilityState.Unavailable;
1661
+ InvokeAndClearTutorialCallbacks(TutorialFetchStatus.NoTutorialExists);
1645
1662
  _eventManager?.InvokeTutorialNotExistsEvent();
1646
1663
  break;
1647
1664
 
@@ -1659,16 +1676,106 @@ namespace Hyper.Internal.Managers
1659
1676
 
1660
1677
  private void HandleRetryOrFail(TutorialFetchStatus status)
1661
1678
  {
1662
- _onTutorialFailure?.Invoke(status);
1663
- _eventManager?.InvokeTutorialFetchFailedEvent();
1664
1679
  _isFetchingTutorial = false;
1680
+
1681
+ if (status == TutorialFetchStatus.DownloadFailed)
1682
+ {
1683
+ tutorialExistsButFailed = true;
1684
+ tutorialFetchSuccess = false;
1685
+ TutorialAvailability = TutorialAvailabilityState.ExistsButFailed;
1686
+ }
1687
+ else
1688
+ {
1689
+ tutorialExistsButFailed = false;
1690
+ tutorialFetchSuccess = false;
1691
+ TutorialAvailability = TutorialAvailabilityState.Unknown;
1692
+ }
1693
+
1694
+ InvokeAndClearTutorialCallbacks(status);
1695
+ _eventManager?.InvokeTutorialFetchFailedEvent();
1696
+ }
1697
+
1698
+ private bool IsTutorialFetchComplete()
1699
+ {
1700
+ return TutorialAvailability == TutorialAvailabilityState.Available ||
1701
+ TutorialAvailability == TutorialAvailabilityState.Unavailable;
1702
+ }
1703
+
1704
+ private void AddTutorialCallbacks(Action fetchComplete, Action onSuccess, Action<TutorialFetchStatus> onFailure)
1705
+ {
1706
+ if (fetchComplete != null) _fetchCompleteCallbacks.Add(fetchComplete);
1707
+ if (onSuccess != null) _tutorialSuccessCallbacks.Add(onSuccess);
1708
+ if (onFailure != null) _tutorialFailureCallbacks.Add(onFailure);
1709
+ }
1710
+
1711
+ private void InvokeTutorialCallbacksForCurrentState(Action fetchComplete, Action onSuccess, Action<TutorialFetchStatus> onFailure)
1712
+ {
1713
+ switch (TutorialAvailability)
1714
+ {
1715
+ case TutorialAvailabilityState.Available:
1716
+ onSuccess?.Invoke();
1717
+ break;
1718
+ case TutorialAvailabilityState.Unavailable:
1719
+ onFailure?.Invoke(TutorialFetchStatus.NoTutorialExists);
1720
+ break;
1721
+ case TutorialAvailabilityState.ExistsButFailed:
1722
+ onFailure?.Invoke(TutorialFetchStatus.DownloadFailed);
1723
+ break;
1724
+ }
1725
+
1726
+ fetchComplete?.Invoke();
1727
+ }
1728
+
1729
+ private void InvokeAndClearTutorialCallbacks(TutorialFetchStatus status)
1730
+ {
1731
+ if (status == TutorialFetchStatus.Success)
1732
+ {
1733
+ for (int i = 0; i < _tutorialSuccessCallbacks.Count; i++)
1734
+ {
1735
+ _tutorialSuccessCallbacks[i]?.Invoke();
1736
+ }
1737
+ }
1738
+ else
1739
+ {
1740
+ for (int i = 0; i < _tutorialFailureCallbacks.Count; i++)
1741
+ {
1742
+ _tutorialFailureCallbacks[i]?.Invoke(status);
1743
+ }
1744
+ }
1745
+
1746
+ for (int i = 0; i < _fetchCompleteCallbacks.Count; i++)
1747
+ {
1748
+ _fetchCompleteCallbacks[i]?.Invoke();
1749
+ }
1750
+
1751
+ _fetchCompleteCallbacks.Clear();
1752
+ _tutorialSuccessCallbacks.Clear();
1753
+ _tutorialFailureCallbacks.Clear();
1665
1754
  }
1666
1755
 
1667
1756
  #if UNITY_EDITOR
1668
1757
  private IEnumerator SimulateTutorialFetch()
1669
1758
  {
1670
- yield return new WaitForSecondsRealtime(1.5f);
1671
- OnTutorialCacheComplete(UnityEngine.Random.value > 0.5f ? "no_tutorial" : "download_failed");
1759
+ var value = UnityEngine.Random.Range(0, 4);
1760
+ Debug.Log($"[Tutorial] Simulating tutorial fetch result: {value}");
1761
+
1762
+ switch (value)
1763
+ {
1764
+ case 0:
1765
+ OnTutorialCacheComplete("success");
1766
+ break;
1767
+ case 1:
1768
+ OnTutorialCacheComplete("download_failed");
1769
+ break;
1770
+ case 2:
1771
+ OnTutorialCacheComplete("fetch_failed");
1772
+ break;
1773
+ default:
1774
+ OnTutorialCacheComplete("no_tutorial");
1775
+ break;
1776
+ }
1777
+
1778
+ yield break;
1672
1779
  }
1673
1780
  #endif
1674
1781
 
@@ -805,20 +805,13 @@ namespace Hyper.Internal.Managers
805
805
 
806
806
  ShowControlBarElements();
807
807
  AddTutorialModal();
808
- ShowStartingInstructionModal();
808
+ ShowTutorialAnnouncementOrStartingInstruction();
809
809
  }
810
810
 
811
811
  private IEnumerator BootstrapAndStart()
812
812
  {
813
813
  HyperRuntime.EventManager.OnGameStart += HandleGameStarted;
814
-
815
- bool tutorialDone = false;
816
- HyperRuntime.BackendManager.GetTutorial(() => tutorialDone = true);
817
-
818
- while (!tutorialDone)
819
- {
820
- yield return null;
821
- }
814
+ yield break;
822
815
  }
823
816
 
824
817
  private void HandleGameStarted()
@@ -978,6 +971,7 @@ namespace Hyper.Internal.Managers
978
971
  private GameObject _battleGameOverModal;
979
972
  private GameObject _practiceGameOverModal;
980
973
  private GameObject _startingInstructionModal;
974
+ private GameObject _tutorialAnnouncementModal;
981
975
  private GameObject _tutorialPortraitModal;
982
976
  private GameObject _tutorialLandscapeModal;
983
977
  private GameObject _controlBarElements;
@@ -987,6 +981,7 @@ namespace Hyper.Internal.Managers
987
981
  private GameOverModal _gameOverModalComponent;
988
982
  private ControlBar _controlBarModal;
989
983
  private TutorialModal _tutorialModalComponent;
984
+ private TutorialAnnouncementModal _tutorialAnnouncementModalComponent;
990
985
  private ScoreSubmissionFailedModal _scoreSubmissionFailedModalComponent;
991
986
  private StartingInstructionModal _startingInstructionModalComponent;
992
987
 
@@ -1495,6 +1490,109 @@ namespace Hyper.Internal.Managers
1495
1490
  SetBlockerActive(false);
1496
1491
  }
1497
1492
 
1493
+ private void ShowTutorialAnnouncementOrStartingInstruction()
1494
+ {
1495
+ if (ShouldShowTutorialAnnouncement())
1496
+ {
1497
+ ShowTutorialAnnouncementModal();
1498
+ return;
1499
+ }
1500
+
1501
+ ShowStartingInstructionModal();
1502
+ }
1503
+
1504
+ private bool ShouldShowTutorialAnnouncement()
1505
+ {
1506
+ var availability = HyperRuntime.BackendManager.TutorialAvailability;
1507
+ bool tutorialKnownToExist =
1508
+ availability == BackendManager.TutorialAvailabilityState.Available ||
1509
+ availability == BackendManager.TutorialAvailabilityState.ExistsButFailed;
1510
+
1511
+ return tutorialKnownToExist && !HasSeenTutorialAnnouncement();
1512
+ }
1513
+
1514
+ private bool HasSeenTutorialAnnouncement()
1515
+ {
1516
+ string key = GetTutorialAnnouncementSeenKey();
1517
+ return !string.IsNullOrEmpty(key) && PlayerPrefs.GetInt(key, 0) == 1;
1518
+ }
1519
+
1520
+ private void MarkTutorialAnnouncementSeen()
1521
+ {
1522
+ string key = GetTutorialAnnouncementSeenKey();
1523
+ if (string.IsNullOrEmpty(key)) return;
1524
+
1525
+ PlayerPrefs.SetInt(key, 1);
1526
+ PlayerPrefs.Save();
1527
+ }
1528
+
1529
+ private string GetTutorialAnnouncementSeenKey()
1530
+ {
1531
+ string gameIdentifier = Config != null ? Config.GameSlug : null;
1532
+
1533
+ #if UNITY_EDITOR
1534
+ if (string.IsNullOrWhiteSpace(gameIdentifier))
1535
+ {
1536
+ gameIdentifier = Application.productName;
1537
+ }
1538
+ #endif
1539
+
1540
+ if (string.IsNullOrWhiteSpace(gameIdentifier))
1541
+ {
1542
+ gameIdentifier = HyperRuntime.GameContent != null
1543
+ ? HyperRuntime.GameContent.GameScene?.SceneName
1544
+ : null;
1545
+ }
1546
+
1547
+ return string.IsNullOrWhiteSpace(gameIdentifier)
1548
+ ? null
1549
+ : $"{gameIdentifier}_sdkTutorialPromptSeen";
1550
+ }
1551
+
1552
+ private void ShowTutorialAnnouncementModal()
1553
+ {
1554
+ EnsureTutorialAnnouncementModal();
1555
+
1556
+ if (_tutorialAnnouncementModalComponent == null)
1557
+ {
1558
+ ShowStartingInstructionModal();
1559
+ return;
1560
+ }
1561
+
1562
+ _tutorialAnnouncementModalComponent.Show(() =>
1563
+ {
1564
+ MarkTutorialAnnouncementSeen();
1565
+ SetBlockerActive(false);
1566
+ ShowStartingInstructionModal();
1567
+ });
1568
+
1569
+ SetBlockerActive(true);
1570
+ }
1571
+
1572
+ private void EnsureTutorialAnnouncementModal()
1573
+ {
1574
+ if (_tutorialAnnouncementModal != null)
1575
+ {
1576
+ _tutorialAnnouncementModal.SetActive(true);
1577
+ return;
1578
+ }
1579
+
1580
+ if (_canvas == null) CreateCanvas();
1581
+
1582
+ GameObject prefab = Resources.Load<GameObject>("UIPrefabs/TutorialAnnouncementModal");
1583
+ if (prefab == null)
1584
+ {
1585
+ HyperDebug.LogWarning("[GameManager] TutorialAnnouncementModal prefab not found.");
1586
+ return;
1587
+ }
1588
+
1589
+ GameObject instance = Instantiate(prefab);
1590
+ instance.transform.SetParent(_safeArea.transform, false);
1591
+
1592
+ _tutorialAnnouncementModal = instance;
1593
+ _tutorialAnnouncementModalComponent = instance.GetComponent<TutorialAnnouncementModal>();
1594
+ }
1595
+
1498
1596
  private void EnsureStartingInstructionModal()
1499
1597
  {
1500
1598
  if (_startingInstructionModal != null)
@@ -95,4 +95,4 @@ namespace Hyper.Internal
95
95
 
96
96
  #endregion
97
97
  }
98
- }
98
+ }
@@ -0,0 +1,57 @@
1
+ using UnityEngine;
2
+ using UnityEngine.UI;
3
+
4
+ namespace Hyper.Internal
5
+ {
6
+ internal class TutorialAnnouncementModal : MonoBehaviour
7
+ {
8
+ [SerializeField] private Button closeButton;
9
+
10
+ private System.Action _onClosed;
11
+ private bool _hasClosed;
12
+
13
+ private void Awake()
14
+ {
15
+ if (closeButton == null)
16
+ {
17
+ closeButton = GetComponentInChildren<Button>(true);
18
+ }
19
+
20
+ if (closeButton != null)
21
+ {
22
+ closeButton.onClick.AddListener(HandleCloseClicked);
23
+ }
24
+ else
25
+ {
26
+ HyperDebug.LogWarning("[TutorialAnnouncementModal] Close button is not assigned.");
27
+ }
28
+ }
29
+
30
+ private void OnDestroy()
31
+ {
32
+ if (closeButton != null)
33
+ {
34
+ closeButton.onClick.RemoveListener(HandleCloseClicked);
35
+ }
36
+ }
37
+
38
+ internal void Show(System.Action onClosed)
39
+ {
40
+ _onClosed = onClosed;
41
+ _hasClosed = false;
42
+ gameObject.SetActive(true);
43
+ }
44
+
45
+ private void HandleCloseClicked()
46
+ {
47
+ if (_hasClosed) return;
48
+
49
+ _hasClosed = true;
50
+ gameObject.SetActive(false);
51
+
52
+ System.Action onClosed = _onClosed;
53
+ _onClosed = null;
54
+ onClosed?.Invoke();
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: ebbcf84b3f3cc354b8072846a794168a
@@ -36,10 +36,10 @@ namespace Hyper.Internal
36
36
  KillTweens();
37
37
  tutorialLoaderModalCG.alpha = 0f;
38
38
 
39
- // Fade in and enable
40
- tutorialLoaderModalCG.DOFade(1, 0.3f).SetUpdate(true);
41
-
42
39
  HandleLoadingTutorial();
40
+
41
+ // Fade in after loading state has reset its own tweens.
42
+ tutorialLoaderModalCG.DOFade(1, 0.3f).SetUpdate(true);
43
43
  }
44
44
 
45
45
  void OnDestroy()
@@ -124,6 +124,7 @@ namespace Hyper.Internal
124
124
  void HandleTutorialFailed()
125
125
  {
126
126
  KillTweens();
127
+ tutorialLoaderModalCG.alpha = 1f;
127
128
 
128
129
  loaderTextTween.SetActive(false);
129
130
  loaderText.SetActive(true);
@@ -21,6 +21,8 @@ namespace Hyper.Internal
21
21
  private const float SlideInDuration = 0.33f;
22
22
  private const float ContentFadeInDuration = 0.28f;
23
23
  private const float ContentFadeOutDuration = 0.18f;
24
+ private const float MediaFadeInDuration = 0.18f;
25
+ private const float VideoRevealDelay = 0.05f;
24
26
  private const float HiddenPreloadPlayDuration = 0.75f;
25
27
 
26
28
  #if UNITY_WEBGL && !UNITY_EDITOR
@@ -69,6 +71,7 @@ namespace Hyper.Internal
69
71
  private Coroutine updateVideoTimeCo = null;
70
72
  private Coroutine editorTutorialLoadCo = null;
71
73
  private Coroutine hiddenPreloadCloseCo = null;
74
+ private Coroutine mediaDisplayFadeCo = null;
72
75
  private TutorialData tutorialData;
73
76
  private bool isClosing;
74
77
  private bool hasInvokedTutorialLoadComplete;
@@ -621,6 +624,90 @@ namespace Hyper.Internal
621
624
  }
622
625
  }
623
626
 
627
+ private void ResetCardMediaState()
628
+ {
629
+ StopMediaDisplayFade();
630
+ ResetVideoProgressState();
631
+ HideMediaDisplay(tutorialDisplayP);
632
+ HideMediaDisplay(tutorialDisplayL);
633
+ HideMediaDisplay(tutorialImageDisplay);
634
+ }
635
+
636
+ private void ResetVideoProgressState()
637
+ {
638
+ cachedVideoLength = 0f;
639
+ isDraggingSlider = false;
640
+
641
+ if (timeSlider == null) return;
642
+
643
+ timeSlider.minValue = 0f;
644
+ timeSlider.maxValue = 1f;
645
+ timeSlider.value = 0f;
646
+ }
647
+
648
+ private void HideMediaDisplay(RawImage display)
649
+ {
650
+ if (display == null) return;
651
+
652
+ SetMediaDisplayAlpha(display, 0f);
653
+ display.gameObject.SetActive(false);
654
+ }
655
+
656
+ private void SetMediaDisplayAlpha(RawImage display, float alpha)
657
+ {
658
+ if (display == null) return;
659
+
660
+ Color color = display.color;
661
+ color.a = alpha;
662
+ display.color = color;
663
+ }
664
+
665
+ private void StopMediaDisplayFade()
666
+ {
667
+ if (mediaDisplayFadeCo == null) return;
668
+
669
+ StopCoroutine(mediaDisplayFadeCo);
670
+ mediaDisplayFadeCo = null;
671
+ }
672
+
673
+ private void FadeInMediaDisplay(RawImage display, float delay = 0f)
674
+ {
675
+ if (display == null) return;
676
+
677
+ StopMediaDisplayFade();
678
+ mediaDisplayFadeCo = StartCoroutine(FadeInMediaDisplayCoroutine(display, delay));
679
+ }
680
+
681
+ private IEnumerator FadeInMediaDisplayCoroutine(RawImage display, float delay)
682
+ {
683
+ display.gameObject.SetActive(true);
684
+ SetMediaDisplayAlpha(display, 0f);
685
+
686
+ if (delay > 0f)
687
+ {
688
+ yield return new WaitForSecondsRealtime(delay);
689
+ }
690
+
691
+ float elapsed = 0f;
692
+
693
+ while (elapsed < MediaFadeInDuration)
694
+ {
695
+ if (display == null) yield break;
696
+
697
+ elapsed += Time.unscaledDeltaTime;
698
+ float t = Mathf.Clamp01(elapsed / MediaFadeInDuration);
699
+ SetMediaDisplayAlpha(display, Mathf.SmoothStep(0f, 1f, t));
700
+ yield return null;
701
+ }
702
+
703
+ if (display != null)
704
+ {
705
+ SetMediaDisplayAlpha(display, 1f);
706
+ }
707
+
708
+ mediaDisplayFadeCo = null;
709
+ }
710
+
624
711
 
625
712
 
626
713
  private void DisplayCurrentCard(bool isInitialDisplay)
@@ -628,6 +715,7 @@ namespace Hyper.Internal
628
715
  if (currentCardIndex < 0 || currentCardIndex >= tutorialCards.Count) return;
629
716
 
630
717
  StopVideoPlayback();
718
+ ResetCardMediaState();
631
719
  TutorialCardData card = tutorialCards[currentCardIndex];
632
720
 
633
721
  if (descriptionText != null)
@@ -656,12 +744,7 @@ namespace Hyper.Internal
656
744
  return;
657
745
  }
658
746
 
659
- if (tutorialImageDisplay != null)
660
- tutorialImageDisplay.gameObject.SetActive(false);
661
-
662
- // Start with portrait display as default; will switch to landscape in OnCardVideoPrepared if needed
663
- tutorialDisplayP.gameObject.SetActive(true);
664
- tutorialDisplayL.gameObject.SetActive(false);
747
+ // Keep the media surface hidden while VideoPlayer prepares the next source.
665
748
  currentDisplay = tutorialDisplayP;
666
749
 
667
750
  videoPlayer.enabled = true;
@@ -714,6 +797,7 @@ namespace Hyper.Internal
714
797
  tutorialDisplayP.gameObject.SetActive(targetDisplay == tutorialDisplayP);
715
798
  tutorialDisplayL.gameObject.SetActive(targetDisplay == tutorialDisplayL);
716
799
  currentDisplay = targetDisplay;
800
+ SetMediaDisplayAlpha(currentDisplay, 0f);
717
801
 
718
802
  if (targetTexture != null)
719
803
  {
@@ -733,7 +817,9 @@ namespace Hyper.Internal
733
817
  updateVideoTimeCo = StartCoroutine(UpdateVideoTime());
734
818
 
735
819
  InvokeTutorialLoadingCompleteOnce();
820
+ videoPlayer.time = 0;
736
821
  videoPlayer.Play();
822
+ FadeInMediaDisplay(currentDisplay, VideoRevealDelay);
737
823
  }
738
824
 
739
825
  private void DisplayImageCard(TutorialCardData card, bool isInitialDisplay)
@@ -754,6 +840,7 @@ namespace Hyper.Internal
754
840
 
755
841
  tutorialImageDisplay.gameObject.SetActive(true);
756
842
  tutorialImageDisplay.texture = card.image.texture;
843
+ FadeInMediaDisplay(tutorialImageDisplay);
757
844
 
758
845
  if (isInitialDisplay)
759
846
  {
@@ -781,6 +868,11 @@ namespace Hyper.Internal
781
868
  if (videoPlayer == null) return;
782
869
 
783
870
  videoPlayer.prepareCompleted -= OnCardVideoPrepared;
871
+ videoPlayer.loopPointReached -= OnLoopPointReached;
872
+ if (videoPlayer.isPrepared)
873
+ {
874
+ videoPlayer.time = 0;
875
+ }
784
876
  videoPlayer.Stop();
785
877
  videoPlayer.targetTexture = null;
786
878
  }
@@ -1023,6 +1115,7 @@ namespace Hyper.Internal
1023
1115
  contentRevealScheduled = false;
1024
1116
  isClosing = false;
1025
1117
  CancelHiddenPreloadClose();
1118
+ StopMediaDisplayFade();
1026
1119
  HideTutorialContent();
1027
1120
 
1028
1121
  #if UNITY_EDITOR
@@ -32,7 +32,7 @@ Material:
32
32
  m_Floats:
33
33
  - _ColorMask: 15
34
34
  - _InvertedRadius: 0
35
- - _Radius: 60
35
+ - _Radius: 75
36
36
  - _Softness: 1.75
37
37
  - _Stencil: 0
38
38
  - _StencilComp: 8
@@ -44,10 +44,10 @@ Material:
44
44
  m_Colors:
45
45
  - _ClipRect: {r: -32767, g: -32767, b: 32767, a: 32767}
46
46
  - _Color: {r: 1, g: 1, b: 1, a: 1}
47
- - _CornerRadii: {r: 60, g: 60, b: 0, a: 0}
47
+ - _CornerRadii: {r: 75, g: 75, b: 0, a: 0}
48
48
  - _InvertedCornerRadii: {r: 0, g: 0, b: 962.5, a: 1481.2}
49
49
  - _Pivot: {r: 0.5, g: 0.556, b: 0, a: 0}
50
- - _RectSize: {r: 998, g: 2160, b: 0, a: 0}
50
+ - _RectSize: {r: 2532, g: 1216, b: 0, a: 0}
51
51
  m_BuildTextureStacks: []
52
52
  m_AllowLocking: 1
53
53
  --- !u!114 &4644396538228076653