diff --git a/Assets/Scenes/Startup.unity b/Assets/Scenes/Startup.unity index 208e4e9..ff9f072 100644 --- a/Assets/Scenes/Startup.unity +++ b/Assets/Scenes/Startup.unity @@ -160,6 +160,7 @@ MonoBehaviour: _scenePath: Assets/Scenes/Match.unity _transition: {fileID: 686083401} _inputManager: {fileID: 1379932018} + _transitionDuration: 0.5 --- !u!4 &437329616 Transform: m_ObjectHideFlags: 0 @@ -353,6 +354,11 @@ PrefabInstance: propertyPath: m_Name value: Transition Canvas objectReference: {fileID: 0} + - target: {fileID: 7593389556534962451, guid: 8c44d27ccf79c4f37a77e8dc1c3e8e2e, + type: 3} + propertyPath: m_Enabled + value: 0 + objectReference: {fileID: 0} - target: {fileID: 7593389556534962460, guid: 8c44d27ccf79c4f37a77e8dc1c3e8e2e, type: 3} propertyPath: m_Pivot.x diff --git a/Assets/Scripts/Managers/GameManager.cs b/Assets/Scripts/Managers/GameManager.cs index 520e8fe..b84fbb6 100644 --- a/Assets/Scripts/Managers/GameManager.cs +++ b/Assets/Scripts/Managers/GameManager.cs @@ -39,6 +39,10 @@ private enum GamePart [SerializeField] private SceneReference _matchScene; [SerializeField] private CanvasFader _transition; [SerializeField] private InputManager _inputManager; + /// + /// The duration of <see cref="UI.Screen"/> transitions in the UI. + /// + [SerializeField, Range(0.1f, 5f)] private float _transitionDuration; #endregion @@ -47,11 +51,6 @@ private enum GamePart private MenuManager _menuManager; private MatchManager _matchManager; - /// - /// The duration of transitions in the UI. - /// - private const float TransitionDuration = 1f; - /// /// The currently loaded scene. /// @@ -82,7 +81,8 @@ private async void Start() { try { - await LoadMenuAsync(); + Input.backButtonLeavesApp = true; + await LoadMenuAsync(false); _inputManager.OnReturn += HandleReturn; } catch (OperationCanceledException) @@ -96,6 +96,11 @@ private void OnDestroy() _cancellationTokenSource.Cancel(); _cancellationTokenSource.Dispose(); _inputManager.OnReturn -= HandleReturn; + if (_menuManager) + { + _menuManager.OnReturnToMainMenu -= HandleReturnToMainMenu; + _menuManager.OnEnterMenu -= HandleEnterSubmenu; + } } #endregion @@ -125,9 +130,12 @@ private async void HandleReturn() await _menuManager.ReturnAsync(token); break; case GamePart.Match: - var matchEnd = _matchManager.StopMatchAsync(TransitionDuration * 0.9f, token); - var loadMenu = LoadMenuAsync(); + var matchEnd = _matchManager.StopMatchAsync(_transitionDuration * 0.9f, token); + var loadMenu = LoadMenuAsync(true); await UniTask.WhenAll(matchEnd, loadMenu); + // Wait for the loading to set this to true, otherwise the event system might pick up the + // back button press right away (within the same frame), effectively quitting the application. + Input.backButtonLeavesApp = true; break; default: throw new NotImplementedException($"Game part not implemented: {_part}"); @@ -175,19 +183,42 @@ private async void HandleRestartMatch() } } + /// + /// Handles the event of entering s submenu. + /// + private void HandleEnterSubmenu() + { + Input.backButtonLeavesApp = false; + } + + /// + /// Handles the event of going back to the main menu, from a submenu. + /// + private void HandleReturnToMainMenu() + { + Input.backButtonLeavesApp = true; + } + #endregion #region Private - /// + /// /// Loads the menu scene. /// + /// Whether the transition animation should be played when loading the menu. /// A task to be awaited which represents the loading. - private async UniTask LoadMenuAsync() + private async UniTask LoadMenuAsync(bool transition) { + if (transition) + await StartTransitionAsync(); _menuManager = await LoadManagedSceneAsync(_menuScene); _menuManager.OnStartMatch += HandleStartMatch; + _menuManager.OnReturnToMainMenu += HandleReturnToMainMenu; + _menuManager.OnEnterMenu += HandleEnterSubmenu; _part = GamePart.Menu; + if (transition) + await EndTransitionAsync(); } /// @@ -196,26 +227,43 @@ private async UniTask LoadMenuAsync() /// The settings of the match to be started. private async UniTask StartMatch(MatchSettings settings) { + await StartTransitionAsync(); _matchManager = await LoadManagedSceneAsync(_matchScene); _matchManager.OnLeaveRequest += HandleReturn; _matchManager.OnRestartRequest += HandleRestartMatch; _part = GamePart.Match; + await EndTransitionAsync(); await _matchManager.StartMatchAsync(settings, _cancellationTokenSource.Token); } + /// + /// Starts a scene transition. + /// + private async UniTask StartTransitionAsync() + { + _loading = true; + await _transition.FadeInAsync(_transitionDuration, _cancellationTokenSource.Token); + } + + /// + /// End a scene transition. + /// + private async UniTask EndTransitionAsync() + { + await _transition.FadeOutAsync(_transitionDuration, _cancellationTokenSource.Token); + _loading = false; + } + /// /// Loads a scene that contains a manager asynchronously. /// /// The scene to be loaded. /// The type of the manager to be fetched in the scene. /// A task to be awaited which represents the loading. Its value is the scene's manager. - /// Thrown if the given does not contain a manager of type + /// Thrown if the given wasn't loaded. /// The type of the manager in the scene. private async UniTask LoadManagedSceneAsync(SceneReference scene) where T : MonoBehaviour { - _loading = true; - var token = _cancellationTokenSource.Token; - await _transition.FadeInAsync(TransitionDuration, token); if (_scene != null) // In some cases (e.g. leading menu), there is nothing to unload. await SceneManager.UnloadSceneAsync(_scene.Value); await SceneManager.LoadSceneAsync(scene, LoadSceneMode.Additive); @@ -224,11 +272,7 @@ private async UniTask LoadManagedSceneAsync(SceneReference scene) where T throw new Exception($"Managed scene wasn't loaded ({typeof(T)})."); SceneManager.SetActiveScene(_scene.Value); - var manager = FindAnyObjectByType(); - await _transition.FadeOutAsync(TransitionDuration, token); - _loading = false; - - return manager; + return FindAnyObjectByType(); } #endregion diff --git a/Assets/Scripts/Menu/MenuManager.cs b/Assets/Scripts/Menu/MenuManager.cs index 9d9962d..e9e49a5 100644 --- a/Assets/Scripts/Menu/MenuManager.cs +++ b/Assets/Scripts/Menu/MenuManager.cs @@ -22,6 +22,16 @@ internal class MenuManager : MonoBehaviour /// internal event Action OnStartMatch; + /// + /// Invoked whenever the application leaves a submenu and goes back to the main menu. + /// + internal event Action OnEnterMenu; + + /// + /// Invoked whenever the application goes to a submenu. + /// + internal event Action OnReturnToMainMenu; + #endregion #region Serialized fields @@ -38,6 +48,7 @@ internal class MenuManager : MonoBehaviour #endregion private readonly CancellationTokenSource _cancellationTokenSource = new(); + #region Fields private Screen _currentScreen; @@ -78,6 +89,7 @@ private void OnDestroy() private async void HandleSelectNewMatch() { + OnEnterMenu?.Invoke(); try { await TransitionToAsync(_playScreen); @@ -90,6 +102,7 @@ private async void HandleSelectNewMatch() private async void HandleSelectSettings() { + OnEnterMenu?.Invoke(); try { await TransitionToAsync(_settingsScreen); @@ -102,6 +115,7 @@ private async void HandleSelectSettings() private async void HandleSelectCredits() { + OnEnterMenu?.Invoke(); try { await TransitionToAsync(_creditsScreen); @@ -110,7 +124,6 @@ private async void HandleSelectCredits() { Debug.Log("Credits selection handling stopped because the operation was cancelled."); } - } private void StartMatch(MatchSettings settings) @@ -147,14 +160,14 @@ internal async UniTask ReturnAsync(CancellationToken token) private async UniTask ReturnMenuAsync(CancellationToken token) { + if (_currentScreen == null) + return; + await _transition.FadeInAsync(_transitionDuration / 2f, token); - - if (_currentScreen != null) - _currentScreen.Hide(); - + _currentScreen.Hide(); _currentScreen = null; - await _transition.FadeOutAsync(_transitionDuration / 2f, token); + OnReturnToMainMenu?.Invoke(); } /// diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index f3f3fb7..296935f 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -135,7 +135,7 @@ PlayerSettings: vulkanEnableLateAcquireNextImage: 0 vulkanEnableCommandBufferRecycling: 1 loadStoreDebugModeEnabled: 0 - bundleVersion: 1.1.0 + bundleVersion: 1.1.1 preloadedAssets: [] metroInputSource: 0 wsaTransparentSwapchain: 0 @@ -163,7 +163,7 @@ PlayerSettings: iPhone: 0 tvOS: 0 overrideDefaultApplicationIdentifier: 1 - AndroidBundleVersionCode: 8 + AndroidBundleVersionCode: 9 AndroidMinSdkVersion: 22 AndroidTargetSdkVersion: 33 AndroidPreferredInstallLocation: 1