Add Resume/Exit buttons to pause menu, handle logic for returning to main menu and starting a new game

This commit is contained in:
2025-12-05 20:05:28 -08:00
parent 678916be89
commit 5b9de11e5a
17 changed files with 354 additions and 239 deletions

View File

@@ -24,6 +24,8 @@ public partial class AppLogic
public readonly record struct ShowMainMenu;
public readonly record struct CloseGame;
public readonly record struct ExitGame;
public readonly record struct GameOver;

View File

@@ -9,7 +9,7 @@ public partial class AppLogic
public partial record State
{
[Meta]
public partial record GameStarted : State
public partial record GameStarted : State, IGet<Input.QuitGame>
{
public GameStarted()
{
@@ -26,6 +26,11 @@ public partial class AppLogic
OnDetach(() => Get<IAppRepo>().GameExited -= OnGameExited);
}
public Transition On(in Input.QuitGame input)
{
Output(new Output.CloseGame());
return To<MainMenu>();
}
public void OnGameExited() => Input(new Input.QuitGame());
}
}

View File

@@ -8,10 +8,14 @@ public partial class GameState
public readonly record struct LoadGame;
public readonly record struct ExitGame;
public readonly record struct LoadNextFloor;
public readonly record struct InventoryButtonPressed;
public readonly record struct InteractButtonPressed;
public readonly record struct PauseButtonPressed;
public readonly record struct DebugButtonPressed;

View File

@@ -6,6 +6,8 @@ public partial class GameState
{
public readonly record struct InitializeGame;
public readonly record struct ExitGame;
public readonly record struct LoadGameFromFile;
public readonly record struct OpenInventoryMenu;

View File

@@ -8,9 +8,9 @@ public partial class GameState
public partial record State
{
[Meta, LogicBlock(typeof(State), Diagram = true)]
public partial record InventoryScreen : State, IGet<Input.InventoryButtonPressed>
public partial record InventoryScreen : State, IGet<Input.InteractButtonPressed>
{
public Transition On(in Input.InventoryButtonPressed input)
public Transition On(in Input.InteractButtonPressed input)
{
Output(new Output.CloseInventoryMenu());
return To<InGame>();

View File

@@ -1,5 +1,6 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
using static Zennysoft.Ma.Adapter.GameState.Output;
namespace Zennysoft.Ma.Adapter;
@@ -8,13 +9,20 @@ public partial class GameState
public partial record State
{
[Meta, LogicBlock(typeof(State), Diagram = true)]
public partial record PauseScreen : State, IGet<Input.PauseButtonPressed>
public partial record PauseScreen : State, IGet<Input.PauseButtonPressed>, IGet<Input.ExitGame>
{
public Transition On(in Input.PauseButtonPressed input)
{
Output(new Output.ClosePauseScreen());
return To<InGame>();
}
public Transition On(in Input.ExitGame input)
{
Output(new Output.ClosePauseScreen());
Output(new Output.ExitGame());
return To<State>();
}
}
}
}

View File

@@ -1,20 +1,14 @@
using Chickensoft.AutoInject;
using Chickensoft.Collections;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
using Godot.Collections;
using NathanHoad;
using SimpleInjector.Lifestyles;
using System;
using System.IO.Abstractions;
using System.Linq;
using System.Threading.Tasks;
using Zennysoft.Game.Abstractions;
using Zennysoft.Game.Implementation;
using Zennysoft.Ma.Adapter;
using static Zennysoft.Game.Ma.SceneLoader;
using static Zennysoft.Ma.Adapter.AppLogic.State;
namespace Zennysoft.Game.Ma;
@@ -37,8 +31,6 @@ public partial class App : Node, IApp
[Node] private GalleryMenu GalleryMenu { get; set; }
public IInstantiator Instantiator { get; set; } = default!;
IAppRepo IProvide<IAppRepo>.Value() => AppRepo;
public IAppRepo AppRepo { get; set; } = default!;
@@ -57,9 +49,6 @@ public partial class App : Node, IApp
private IGame _game;
private IDataViewer _enemyViewer;
private event Action OnGameLoaded;
private event Action OnEnemyViewerLoaded;
private double _reportedProgress = 0;
public void Initialize()
@@ -110,6 +99,11 @@ public partial class App : Node, IApp
this.Provide();
}
private void GameExitRequested()
{
AppLogic.Input(new AppLogic.Input.QuitGame());
}
private void DeleteSaveData()
{
var saveFileManager = _container.GetInstance<ISaveFileManager>();
@@ -150,27 +144,31 @@ public partial class App : Node, IApp
})
.Handle((in AppLogic.Output.SetupGameScene _) =>
{
LoadingScreen.Show();
LoadGame(GAME_SCENE_PATH);
MainMenu.ReleaseFocus();
MainMenu.Hide();
})
.Handle((in AppLogic.Output.ShowMainMenu _) =>
{
})
.Handle((in AppLogic.Output.ShowGame _) =>
.Handle((in AppLogic.Output.CloseGame _) =>
{
LoadingScreen.Hide();
_game.GameExitRequested -= GameExitRequested;
MainMenu.StartGameButton.GrabFocus();
_game.CallDeferred(MethodName.QueueFree, []);
GetTree().Paused = false;
})
.Handle((in AppLogic.Output.StartLoadingSaveFile _) =>
{
})
.Handle((in AppLogic.Output.EnemyViewerOpened _) =>
{
LoadingScreen.Show();
LoadEnemyViewer(ENEMY_VIEWER_PATH);
MainMenu.ReleaseFocus();
MainMenu.Hide();
})
.Handle((in AppLogic.Output.EnemyViewerExited _) =>
{
LoadingScreen.Hide();
if (_enemyViewer != null && _enemyViewer is DataViewer enemyViewer)
enemyViewer.CallDeferred(MethodName.QueueFree);
MainMenu.Show();
@@ -202,6 +200,7 @@ public partial class App : Node, IApp
{
var scene = await LoadSceneInternal(sceneName);
_game = scene as IGame;
_game.GameExitRequested += GameExitRequested;
await ToSignal(GetTree().CreateTimer(0.8f), "timeout");
CallDeferred(MethodName.AddChild, scene);
}
@@ -216,6 +215,7 @@ public partial class App : Node, IApp
private async Task<Node> LoadSceneInternal(string sceneName)
{
LoadingScreen.Show();
LoadingScreen.ProgressBar.Value = 0;
var sceneLoader = new SceneLoader();
CallDeferred(MethodName.AddChild, sceneLoader);

View File

@@ -10,12 +10,13 @@
process_mode = 3
script = ExtResource("1_rt73h")
[node name="LoadingScreen" parent="." instance=ExtResource("3_3st5l")]
unique_name_in_owner = true
[node name="MainMenu" parent="." instance=ExtResource("2_1uiag")]
unique_name_in_owner = true
[node name="LoadingScreen" parent="." instance=ExtResource("3_3st5l")]
unique_name_in_owner = true
visible = false
[node name="OptionsMenu" parent="." instance=ExtResource("2_v0mgf")]
unique_name_in_owner = true
visible = false

View File

@@ -56,6 +56,8 @@ public partial class Game : Node3D, IGame
[Signal]
public delegate void SaveFileLoadedEventHandler();
public event Action GameExitRequested;
#endregion
public RescuedItemDatabase RescuedItems { get; set; } = default!;
@@ -174,6 +176,8 @@ public partial class Game : Node3D, IGame
GameOverMenu.NewGame += OnNewGame;
GameOverMenu.QuitGame += OnQuit;
PauseMenu.ExitGamePressed += OnQuit;
GameRepo.IsPaused.Sync += IsPaused_Sync;
InGameUI.PlayerInfoUI.Activate();
}
@@ -252,6 +256,12 @@ public partial class Game : Node3D, IGame
{
if (@event.IsActionPressed(GameInputs.Debug))
GameState.Input(new GameState.Input.DebugButtonPressed());
if (@event.IsActionPressed(GameInputs.Pause))
GameState.Input(new GameState.Input.PauseButtonPressed());
if (Input.IsActionJustPressed(GameInputs.Inventory))
GameState.Input(new GameState.Input.InventoryButtonPressed());
if (Input.IsActionJustPressed(GameInputs.Interact))
GameState.Input(new GameState.Input.InteractButtonPressed());
}
private void HandleGameLogic()
@@ -280,12 +290,13 @@ public partial class Game : Node3D, IGame
})
.Handle((in GameState.Output.OpenInventoryMenu _) =>
{
//InGameUI.InventoryMenu.RefreshInventoryScreen();
GameRepo.Pause();
InGameUI.InventoryMenu.Show();
InGameUI.InventoryMenu.SetProcessInput(true);
})
.Handle((in GameState.Output.CloseInventoryMenu _) =>
{
GameRepo.Resume();
InGameUI.InventoryMenu.Hide();
InGameUI.InventoryMenu.SetProcessInput(false);
})
@@ -337,6 +348,10 @@ public partial class Game : Node3D, IGame
}
LoadNextLevel.FadeOut();
})
.Handle((in GameState.Output.ExitGame _) =>
{
OnQuit();
})
.Handle((in GameState.Output.GameOver _) =>
{
//GameRepo.Pause();
@@ -507,7 +522,7 @@ public partial class Game : Node3D, IGame
LoadNextLevel.Hide();
}
private void OnQuit() => GetTree().Root.QueueFree();
private void OnQuit() => GameExitRequested?.Invoke();
public void OnExitTree()
{

View File

@@ -4,6 +4,7 @@ namespace Zennysoft.Game.Ma;
using Chickensoft.AutoInject;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.SaveFileBuilder;
using System;
using System.Threading.Tasks;
using Zennysoft.Ma.Adapter;
@@ -30,4 +31,6 @@ public interface IGame : IProvide<IGame>, IProvide<IGameRepo>, IProvide<IPlayer>
public Task Save();
public QuestData QuestData { get; }
public event Action GameExitRequested;
}

View File

@@ -7,6 +7,7 @@
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1ctjd"]
[node name="Control" type="Control"]
process_mode = 3
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0

View File

@@ -60,5 +60,14 @@ public partial class GalleryMenu : Control
BackButton.Pressed += BackButton_Pressed;
}
public override void _Input(InputEvent @event)
{
if (!Visible)
return;
if (@event.IsActionPressed(GameInputs.Interact))
BackButton.GrabFocus();
}
private void BackButton_Pressed() => EmitSignal(SignalName.GalleryExited);
}

View File

@@ -57,13 +57,4 @@ public partial class InGameUI : Control, IInGameUI
InventoryMenu.Hide();
InventoryMenu.SetProcessInput(false);
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed(GameInputs.Inventory) && !InventoryMenu.Visible)
{
GD.Print("Inventory button pressed");
InGameUILogic.Input(new InGameUILogic.Input.ShowInventory());
}
}
}

View File

@@ -100,7 +100,7 @@ public partial class InventoryMenu : Control, IInventoryMenu
SfxDatabase.Instance.Play(SoundEffect.MoveUI);
}
public override void _Input(InputEvent @event)
public override void _UnhandledInput(InputEvent @event)
{
if (!Visible)
return;
@@ -108,12 +108,6 @@ public partial class InventoryMenu : Control, IInventoryMenu
if ((!Input.IsActionJustPressed(GameInputs.UiUp) && Input.IsActionPressed(GameInputs.UiUp)) || (!Input.IsActionJustPressed(GameInputs.UiDown) && Input.IsActionPressed(GameInputs.UiDown)))
AcceptEvent();
if (Input.IsActionJustPressed(GameInputs.Inventory) && !(UseButton.HasFocus() || DropButton.HasFocus() || ThrowButton.HasFocus()))
{
SfxDatabase.Instance.Play(SoundEffect.CancelUI);
AcceptEvent();
_gameRepo.CloseInventory();
}
if (Input.IsActionJustPressed(GameInputs.UiCancel) && (UseButton.HasFocus() || DropButton.HasFocus() || ThrowButton.HasFocus()))
{
SfxDatabase.Instance.Play(SoundEffect.CancelUI);
@@ -153,6 +147,7 @@ public partial class InventoryMenu : Control, IInventoryMenu
}
else
{
SfxDatabase.Instance.Play(SoundEffect.CancelUI);
_enableMenuSound = false;
}
}

View File

@@ -2,6 +2,7 @@ using Chickensoft.AutoInject;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
using System;
namespace Zennysoft.Game.Ma;
public interface IPauseMenu : IControl
@@ -10,6 +11,8 @@ public interface IPauseMenu : IControl
void FadeOut();
event PauseMenu.UnpauseButtonPressedEventHandler UnpauseButtonPressed;
event PauseMenu.TransitionCompletedEventHandler TransitionCompleted;
public event Action ExitGamePressed;
}
[Meta(typeof(IAutoNode))]
@@ -24,14 +27,32 @@ public partial class PauseMenu : Control, IPauseMenu
[Node] public IAnimationPlayer AnimationPlayer { get; set; } = default!;
[Node] public Button ResumeButton { get; set; } = default!;
[Node] public Button ExitButton { get; set; } = default!;
public event Action ExitGamePressed;
public void OnResolved()
{
AnimationPlayer.AnimationFinished += OnAnimationFinished;
ResumeButton.Pressed += ResumeButton_Pressed;
ExitButton.Pressed += ExitButton_Pressed;
}
public void FadeIn() => AnimationPlayer.Play("fade_in");
private void ExitButton_Pressed() => ExitGamePressed?.Invoke();
private void ResumeButton_Pressed() => FadeOut();
public void FadeIn()
{
ResumeButton.GrabFocus();
AnimationPlayer.Play("fade_in");
}
public void FadeOut() => AnimationPlayer.Play("fade_out");
public void FadeOut()
{
ResumeButton.ReleaseFocus();
ExitButton.ReleaseFocus();
AnimationPlayer.Play("fade_out");
}
public override void _UnhandledInput(InputEvent @event)
{
@@ -41,5 +62,7 @@ public partial class PauseMenu : Control, IPauseMenu
public void OnAnimationFinished(StringName name)
{
if (name == "fade_out")
Hide();
}
}

View File

@@ -1,4 +1,9 @@
[gd_scene load_steps=5 format=3 uid="uid://blbqgw3wosc1w"]
[gd_scene load_steps=9 format=3 uid="uid://blbqgw3wosc1w"]
[ext_resource type="Script" uid="uid://cbal5oeaha4nx" path="res://src/ui/pause_menu/PauseMenu.cs" id="1_1mfd6"]
[ext_resource type="FontFile" uid="uid://beh6d5lo5ihq0" path="res://src/ui/fonts/georgiai.ttf" id="2_o1qdr"]
[ext_resource type="StyleBox" uid="uid://bxuy4tnftibfq" path="res://src/options/SelectedOptionsBox.tres" id="2_xhp56"]
[ext_resource type="StyleBox" uid="uid://bl15q835s4ene" path="res://src/options/UnselectedOptionsBox.tres" id="3_o1qdr"]
[sub_resource type="Animation" id="Animation_f1eqn"]
length = 0.001
@@ -62,6 +67,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_1mfd6")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
unique_name_in_owner = true
@@ -77,3 +83,53 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 1)
[node name="CenterContainer" type="CenterContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="ResumeButton" type="Button" parent="CenterContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_fonts/font = ExtResource("2_o1qdr")
theme_override_font_sizes/font_size = 38
theme_override_styles/focus = ExtResource("2_xhp56")
theme_override_styles/disabled_mirrored = ExtResource("3_o1qdr")
theme_override_styles/disabled = ExtResource("3_o1qdr")
theme_override_styles/hover_pressed_mirrored = ExtResource("3_o1qdr")
theme_override_styles/hover_pressed = ExtResource("3_o1qdr")
theme_override_styles/hover_mirrored = ExtResource("3_o1qdr")
theme_override_styles/hover = ExtResource("3_o1qdr")
theme_override_styles/pressed_mirrored = ExtResource("3_o1qdr")
theme_override_styles/pressed = ExtResource("3_o1qdr")
theme_override_styles/normal_mirrored = ExtResource("3_o1qdr")
theme_override_styles/normal = ExtResource("3_o1qdr")
text = "Resume"
flat = true
[node name="ExitButton" type="Button" parent="CenterContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_fonts/font = ExtResource("2_o1qdr")
theme_override_font_sizes/font_size = 38
theme_override_styles/focus = ExtResource("2_xhp56")
theme_override_styles/disabled_mirrored = ExtResource("3_o1qdr")
theme_override_styles/disabled = ExtResource("3_o1qdr")
theme_override_styles/hover_pressed_mirrored = ExtResource("3_o1qdr")
theme_override_styles/hover_pressed = ExtResource("3_o1qdr")
theme_override_styles/hover_mirrored = ExtResource("3_o1qdr")
theme_override_styles/hover = ExtResource("3_o1qdr")
theme_override_styles/pressed_mirrored = ExtResource("3_o1qdr")
theme_override_styles/pressed = ExtResource("3_o1qdr")
theme_override_styles/normal_mirrored = ExtResource("3_o1qdr")
theme_override_styles/normal = ExtResource("3_o1qdr")
text = "Exit Game"
flat = true