diff --git a/app/IGameScenes.cs b/app/IGameScenes.cs new file mode 100644 index 0000000..6b27a50 --- /dev/null +++ b/app/IGameScenes.cs @@ -0,0 +1,10 @@ +using Godot; + +namespace Mr.BrickAdventures.app; + +public interface IGameScenes +{ + void Load(PackedScene scene); + void Restart(); + void ReturnToMain(PackedScene mainMenu); +} \ No newline at end of file diff --git a/app/IGameScenes.cs.uid b/app/IGameScenes.cs.uid new file mode 100644 index 0000000..324624e --- /dev/null +++ b/app/IGameScenes.cs.uid @@ -0,0 +1 @@ +uid://b7d1ospa5p4nx diff --git a/app/ILevelCatalog.cs b/app/ILevelCatalog.cs new file mode 100644 index 0000000..cc43f3d --- /dev/null +++ b/app/ILevelCatalog.cs @@ -0,0 +1,11 @@ +using Godot; + +namespace Mr.BrickAdventures.app; + +public interface ILevelCatalog +{ + int Count { get; } + PackedScene? Get(int index); + PackedScene First { get; } + PackedScene MainMenu { get; } +} \ No newline at end of file diff --git a/app/ILevelCatalog.cs.uid b/app/ILevelCatalog.cs.uid new file mode 100644 index 0000000..28dd3de --- /dev/null +++ b/app/ILevelCatalog.cs.uid @@ -0,0 +1 @@ +uid://b2sxttgvk6nuq diff --git a/common/AppRoot.cs b/common/AppRoot.cs index 8a059b3..2e8431b 100644 --- a/common/AppRoot.cs +++ b/common/AppRoot.cs @@ -1,6 +1,8 @@ using Chickensoft.AutoInject; using Chickensoft.Introspection; using Godot; +using Godot.Collections; +using Mr.BrickAdventures.app; using Mr.BrickAdventures.data; using Mr.BrickAdventures.game.repositories; using Mr.BrickAdventures.game.services; @@ -8,27 +10,61 @@ using Mr.BrickAdventures.game.services; namespace Mr.BrickAdventures.common; [Meta(typeof(IAutoNode))] -public partial class AppRoot : Node2D, IProvide, IProvide, IProvide, IProvide +public partial class AppRoot : Node2D, + IProvide, + IProvide, + IProvide, + IProvide, + IProvide, + IProvide, + IProvide, + IGameScenes { public override void _Notification(int what) => this.Notify(what); + + [Export] private Array _levels = []; + [Export] private PackedScene _mainMenu = null!; private readonly SaveClient _save = new("user://savegame.save", version: 2); private readonly PlayerRepository _players = new(); - private readonly LevelRepository _levels = new(); + private readonly LevelRepository _levelsRepo = new(); private SaveService _saveService = null!; + private LevelService _levelService = null!; + private ILevelCatalog _catalog = null!; PlayerRepository IProvide.Value() => _players; - LevelRepository IProvide.Value() => _levels; + LevelRepository IProvide.Value() => _levelsRepo; SaveClient IProvide.Value() => _save; SaveService IProvide.Value() => _saveService; + LevelService IProvide.Value() => _levelService; + ILevelCatalog IProvide.Value() => _catalog; + IGameScenes IProvide.Value() => this; public void OnReady() { - _saveService = new SaveService(_players, _levels, _save); + _saveService = new SaveService(_players, _levelsRepo, _save); + _levelService = new LevelService(_levelsRepo); + + _catalog = new ExportedLevelCatalog(_levels, _mainMenu); _saveService.TryLoad(); - this.Provide(); } + + public void Load(PackedScene scene) => GetTree().ChangeSceneToPacked(scene); + public void Restart() => GetTree().ReloadCurrentScene(); + public void ReturnToMain(PackedScene mainMenu) => GetTree().ChangeSceneToPacked(mainMenu); + + private sealed class ExportedLevelCatalog : ILevelCatalog + { + private readonly Array _levels; + public PackedScene MainMenu { get; } + public ExportedLevelCatalog(Array levels, PackedScene mainMenu) { + _levels = levels; MainMenu = mainMenu; + } + public int Count => _levels.Count; + public PackedScene? Get(int index) => (index >= 0 && index < _levels.Count) ? _levels[index] : null; + public PackedScene First => _levels.Count > 0 ? _levels[0] : MainMenu; + } } \ No newline at end of file diff --git a/features/level/ExitDoorComponent.cs b/features/level/ExitDoorComponent.cs new file mode 100644 index 0000000..49aea5e --- /dev/null +++ b/features/level/ExitDoorComponent.cs @@ -0,0 +1,40 @@ +using Chickensoft.AutoInject; +using Chickensoft.Introspection; +using Godot; +using Mr.BrickAdventures.app; +using Mr.BrickAdventures.game.services; + +namespace Mr.BrickAdventures.features.level; + +[Meta(typeof(IAutoNode))] +public partial class ExitDoorComponent : Area2D +{ + public override void _Notification(int what) => this.Notify(what); + + [Export] public CollisionShape2D UnlockIndicator { get; set; } = null!; + [Export] public bool Unlocked { get; set; } = false; + + [Dependency] public LevelService Levels => this.DependOn(); + [Dependency] public ILevelCatalog Catalog => this.DependOn(); + [Dependency] public IGameScenes Scenes => this.DependOn(); + + public void OnReady() { + BodyEntered += OnBodyEntered; + UpdateVisuals(); + } + + private void OnBodyEntered(Node body) { + if (!Unlocked) return; + if (body is not CharacterBody2D) return; + + var nextIdx = Levels.CompleteAndAdvance(); + var next = Catalog.Get(nextIdx); + if (next != null) Scenes.Load(next); else Scenes.ReturnToMain(Catalog.MainMenu); + } + + private void UpdateVisuals() { + if (UnlockIndicator != null) UnlockIndicator.Disabled = !Unlocked; + } + + public void SetUnlocked(bool value) { Unlocked = value; UpdateVisuals(); } +} \ No newline at end of file diff --git a/features/level/ExitDoorComponent.cs.uid b/features/level/ExitDoorComponent.cs.uid new file mode 100644 index 0000000..829f85b --- /dev/null +++ b/features/level/ExitDoorComponent.cs.uid @@ -0,0 +1 @@ +uid://ddh0jm0heqtc3 diff --git a/features/ui/menus/GameOverScreen.cs b/features/ui/menus/GameOverScreen.cs new file mode 100644 index 0000000..85c439c --- /dev/null +++ b/features/ui/menus/GameOverScreen.cs @@ -0,0 +1,26 @@ +using Chickensoft.AutoInject; +using Chickensoft.Introspection; +using Godot; +using Mr.BrickAdventures.app; + +namespace Mr.BrickAdventures.features.ui.menus; + +[Meta(typeof(IAutoNode))] +public partial class GameOverScreen : Control +{ + public override void _Notification(int what) => this.Notify(what); + + [Export] public Button RestartButton { get; set; } = null!; + [Export] public Button MainMenuButton { get; set; } = null!; + [Export] public PackedScene MainMenuScene { get; set; } = null!; + + [Dependency] public IGameScenes Scenes => this.DependOn(); + + public void OnReady() { + Visible = false; + RestartButton.Pressed += () => Scenes.Restart(); + MainMenuButton.Pressed += () => Scenes.ReturnToMain(MainMenuScene); + } + + public void ShowGameOver() => Show(); +} \ No newline at end of file diff --git a/features/ui/menus/GameOverScreen.cs.uid b/features/ui/menus/GameOverScreen.cs.uid new file mode 100644 index 0000000..111d033 --- /dev/null +++ b/features/ui/menus/GameOverScreen.cs.uid @@ -0,0 +1 @@ +uid://b36jv878751ta diff --git a/features/ui/menus/MainMenu.cs b/features/ui/menus/MainMenu.cs index d3bff9e..9e77e2c 100644 --- a/features/ui/menus/MainMenu.cs +++ b/features/ui/menus/MainMenu.cs @@ -1,6 +1,7 @@ using Chickensoft.AutoInject; using Chickensoft.Introspection; using Godot; +using Mr.BrickAdventures.app; using Mr.BrickAdventures.game.repositories; using Mr.BrickAdventures.game.services; @@ -20,10 +21,11 @@ public partial class MainMenu : Control [Export] public Label VersionLabel { get; set; } = null!; [Export] public Control SettingsControl { get; set; } = null!; [Export] public Control CreditsControl { get; set; } = null!; - [Export] public PackedScene FirstLevelScene { get; set; } = null!; [Dependency] public SaveService Save => this.DependOn(); [Dependency] public LevelRepository Levels => this.DependOn(); + [Dependency] public IGameScenes Scenes => this.DependOn(); + [Dependency] public ILevelCatalog Catalog => this.DependOn(); public void OnReady() { VersionLabel.Text = $"v. {ProjectSettings.GetSetting("application/config/version")}"; @@ -44,15 +46,13 @@ public partial class MainMenu : Control private void OnNewGamePressed() { Save.NewGame(); Levels.SetCurrent(0); - GetTree().ChangeSceneToPacked(FirstLevelScene); + var first = Catalog.First; + Scenes.Load(first); } private void OnContinuePressed() { - if (!Save.TryLoad()) { - // Fallback: start new game - OnNewGamePressed(); - return; - } - GetTree().ChangeSceneToPacked(FirstLevelScene); + if (!Save.TryLoad()) { OnNewGamePressed(); return; } + var scene = Catalog.Get(Levels.Current) ?? Catalog.First; + Scenes.Load(scene); } } \ No newline at end of file diff --git a/features/ui/menus/PauseMenu.cs b/features/ui/menus/PauseMenu.cs index 85f8e84..8e35433 100644 --- a/features/ui/menus/PauseMenu.cs +++ b/features/ui/menus/PauseMenu.cs @@ -1,6 +1,33 @@ +using Chickensoft.AutoInject; +using Chickensoft.Introspection; +using Godot; +using Mr.BrickAdventures.app; +using Mr.BrickAdventures.game.services; + namespace Mr.BrickAdventures.features.ui.menus; -public class PauseMenu +[Meta(typeof(IAutoNode))] +public partial class PauseMenu : Control { + public override void _Notification(int what) => this.Notify(what); + + [Export] public Button ResumeButton { get; set; } = null!; + [Export] public Button RestartButton { get; set; } = null!; + [Export] public Button MainMenuButton { get; set; } = null!; + + [Dependency] public IGameScenes Scenes => this.DependOn(); + [Dependency] public ILevelCatalog Catalog => this.DependOn(); + [Dependency] public LevelService Levels => this.DependOn(); + public void OnReady() { + Visible = false; + ResumeButton.Pressed += () => { GetTree().Paused = false; Hide(); }; + RestartButton.Pressed += () => { GetTree().Paused = false; Scenes.Restart(); }; + MainMenuButton.Pressed += () => { GetTree().Paused = false; Scenes.ReturnToMain(Catalog.MainMenu); }; + } + + public void Toggle() { + if (Visible) { GetTree().Paused = false; Hide(); } + else { Show(); GetTree().Paused = true; } + } } \ No newline at end of file diff --git a/game/services/LevelService.cs b/game/services/LevelService.cs index 4b14d2d..5f0be26 100644 --- a/game/services/LevelService.cs +++ b/game/services/LevelService.cs @@ -1,6 +1,21 @@ +using Mr.BrickAdventures.game.repositories; + namespace Mr.BrickAdventures.game.services; public sealed class LevelService { + private readonly LevelRepository _levels; + public LevelService(LevelRepository levels) => _levels = levels; + public int CompleteAndAdvance() { + var cur = _levels.Current; + _levels.Complete(cur); + var next = cur + 1; + _levels.SetCurrent(next); + return next; + } + + public void StartNew() { + _levels.Load(new LevelState { Current = 0, Unlocked = new [] { 0 }, Completed = [] }); + } } \ No newline at end of file diff --git a/objects/ui/pause_menu.tscn b/objects/ui/pause_menu.tscn index 14d62bf..e5f96c5 100644 --- a/objects/ui/pause_menu.tscn +++ b/objects/ui/pause_menu.tscn @@ -1,11 +1,11 @@ [gd_scene load_steps=3 format=3 uid="uid://i6mnjbjcoqe5"] -[ext_resource type="Script" uid="uid://cakgxndurgfa3" path="res://scripts/UI/PauseMenu.cs" id="1_aktha"] +[ext_resource type="Script" uid="uid://bwgs02wcfnm8u" path="res://features/ui/menus/PauseMenu.cs" id="1_ljtns"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g4ivv"] bg_color = Color(0, 0, 0, 1) -[node name="Pause menu" type="Control" node_paths=PackedStringArray("ResumeButton", "MainMenuButton", "QuitButton", "SettingsButton")] +[node name="Pause menu" type="Control" node_paths=PackedStringArray("ResumeButton", "RestartButton", "MainMenuButton")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -14,11 +14,10 @@ grow_horizontal = 2 grow_vertical = 2 size_flags_horizontal = 6 size_flags_vertical = 6 -script = ExtResource("1_aktha") +script = ExtResource("1_ljtns") ResumeButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/Resume Button") +RestartButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/Settings Button") MainMenuButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/Exit to menu Button") -QuitButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/Quit game Button") -SettingsButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/Settings Button") [node name="PanelContainer" type="PanelContainer" parent="."] layout_mode = 1 diff --git a/scenes/main_menu.tscn b/scenes/main_menu.tscn index 5f20dd9..1052391 100644 --- a/scenes/main_menu.tscn +++ b/scenes/main_menu.tscn @@ -1,22 +1,27 @@ -[gd_scene load_steps=8 format=3 uid="uid://cl00e2ocomk3m"] +[gd_scene load_steps=12 format=3 uid="uid://cl00e2ocomk3m"] [ext_resource type="PackedScene" uid="uid://8b6ol5sssbgo" path="res://features/ui/menus/main_menu.tscn" id="1_ekxnf"] [ext_resource type="Script" uid="uid://dg2l7cw6da4vb" path="res://common/AppRoot.cs" id="1_rtw2f"] [ext_resource type="PackedScene" uid="uid://y0ae6e7t70fj" path="res://objects/ui/settings_menu.tscn" id="2_bqqt6"] [ext_resource type="PackedScene" uid="uid://bwgmrcyj4mvu" path="res://objects/ui/credits.tscn" id="3_bqqt6"] +[ext_resource type="PackedScene" uid="uid://chqb11pfoqmeb" path="res://scenes/level_village_2.tscn" id="3_lgwnu"] [ext_resource type="PackedScene" uid="uid://bol7g83v2accs" path="res://scenes/level_village_1.tscn" id="3_oa1go"] [ext_resource type="PackedScene" uid="uid://b5fx1vdfky307" path="res://objects/ui/audio_settings.tscn" id="4_8ln24"] +[ext_resource type="PackedScene" uid="uid://h60obxmju6mo" path="res://scenes/level_village_3.tscn" id="4_flqon"] +[ext_resource type="PackedScene" uid="uid://bhad760x3vvco" path="res://scenes/level_village_4.tscn" id="5_rcqid"] [ext_resource type="PackedScene" uid="uid://cvfsbiy5ggrpg" path="res://objects/ui/input_settings.tscn" id="5_rtw2f"] +[ext_resource type="PackedScene" uid="uid://dagpmlgvr262d" path="res://scenes/level_forest_5.tscn" id="6_1ajci"] [node name="AppRoot" type="Node2D"] script = ExtResource("1_rtw2f") +_levels = Array[PackedScene]([ExtResource("3_oa1go"), ExtResource("3_lgwnu"), ExtResource("4_flqon"), ExtResource("5_rcqid"), ExtResource("6_1ajci")]) +_mainMenu = null [node name="Main menu" type="CanvasLayer" parent="."] [node name="MainMenu" parent="Main menu" node_paths=PackedStringArray("SettingsControl", "CreditsControl") instance=ExtResource("1_ekxnf")] SettingsControl = NodePath("../Settings menu") CreditsControl = NodePath("../Credits") -FirstLevelScene = ExtResource("3_oa1go") [node name="Settings menu" parent="Main menu" node_paths=PackedStringArray("input_settings", "audio_settings") instance=ExtResource("2_bqqt6")] visible = false