From 2fb0c364caf81b7c99f14bfa6fc4cfa5f3b016ba Mon Sep 17 00:00:00 2001 From: Zenny Date: Fri, 23 Aug 2024 00:39:15 -0700 Subject: [PATCH] Re-introduce prototype code --- .editorconfig | 4 + GameJamDungeon.csproj | 3 + Properties/launchSettings.json | 11 ++ global.json | 8 ++ project.godot | 44 +----- src/app/App.cs | 61 ++++++++ src/app/App.tscn | 103 +++----------- src/app/domain/AppRepo.cs | 68 +++++++++ src/app/state/AppLogic.Input.cs | 18 +++ src/app/state/AppLogic.Output.cs | 30 ++++ src/app/state/AppLogic.State.cs | 11 ++ src/app/state/AppLogic.cs | 14 ++ src/app/state/AppLogic.g.puml | 25 ++++ src/app/state/states/InGame.cs | 37 +++++ src/app/state/states/LeavingMenu.cs | 22 +++ src/app/state/states/MainMenu.cs | 35 +++++ src/app/state/states/SplashScreen.cs | 33 +++++ src/character_controller_3d.gd | 53 ------- src/game/Game.cs | 77 +++++++++++ src/game/Game.tscn | 83 +++++++++++ src/game/GameData.cs | 10 ++ src/game/GameLogic.Input.cs | 20 +++ src/game/GameLogic.Output.cs | 24 ++++ src/game/GameLogic.State.cs | 28 ++++ src/game/GameLogic.cs | 14 ++ src/game/IGameRepo.cs | 98 +++++++++++++ src/game/state/states/GameLogic.State.Quit.cs | 20 +++ src/game/state/states/InventoryOpened.cs | 22 +++ src/game/state/states/MenuBackdrop.cs | 29 ++++ src/game/state/states/MiniMapOpen.cs | 23 ++++ src/game/state/states/Paused.cs | 26 ++++ src/game/state/states/Playing.cs | 34 +++++ src/inventory_menu/InventoryMenu.cs | 126 +++++++++++++++++ src/items/ArmorItem.cs | 8 ++ src/items/InventoryItem.cs | 11 ++ src/items/WeaponItem.cs | 10 ++ src/map/dungeon/corridor/Corridor.tscn | 9 +- src/map/dungeon/door/Door.tscn | 4 +- src/map/dungeon/rooms/DungeonRoom.cs | 44 ++++++ src/map/dungeon/rooms/DungeonRoomLogic.cs | 14 ++ src/map/dungeon/rooms/Room1.tscn | 14 +- .../rooms/state/DungeonRoomLogic.State.cs | 11 ++ .../states/DungeonRoomLogic.State.Idle.cs | 15 ++ src/menu/Menu.cs | 52 +++++++ src/menu/splash/Splash.cs | 30 ++++ src/minimap/MiniMap.cs | 6 + src/player/Player.cs | 130 ++++++++++++++++++ src/player/Player.tscn | 24 ++++ src/player/PlayerData.cs | 22 +++ src/player/state/PlayerLogic.Input.cs | 20 +++ src/player/state/PlayerLogic.Output.cs | 17 +++ src/player/state/PlayerLogic.Settings.cs | 9 ++ src/player/state/PlayerLogic.State.cs | 11 ++ src/player/state/PlayerLogic.cs | 14 ++ src/player/state/PlayerLogic.g.puml | 20 +++ .../PlayerLogic.State.Alive.Attacking.cs | 19 +++ .../states/PlayerLogic.State.Alive.Idle.cs | 23 ++++ .../state/states/PlayerLogic.State.Alive.cs | 41 ++++++ .../states/PlayerLogic.State.Disabled.cs | 24 ++++ src/utils/FpsCounter.cs | 9 ++ src/utils/GameInputs.cs | 7 + src/utils/Instantiator.cs | 40 ++++++ 62 files changed, 1685 insertions(+), 187 deletions(-) create mode 100644 .editorconfig create mode 100644 Properties/launchSettings.json create mode 100644 global.json create mode 100644 src/app/App.cs create mode 100644 src/app/domain/AppRepo.cs create mode 100644 src/app/state/AppLogic.Input.cs create mode 100644 src/app/state/AppLogic.Output.cs create mode 100644 src/app/state/AppLogic.State.cs create mode 100644 src/app/state/AppLogic.cs create mode 100644 src/app/state/AppLogic.g.puml create mode 100644 src/app/state/states/InGame.cs create mode 100644 src/app/state/states/LeavingMenu.cs create mode 100644 src/app/state/states/MainMenu.cs create mode 100644 src/app/state/states/SplashScreen.cs delete mode 100644 src/character_controller_3d.gd create mode 100644 src/game/Game.cs create mode 100644 src/game/Game.tscn create mode 100644 src/game/GameData.cs create mode 100644 src/game/GameLogic.Input.cs create mode 100644 src/game/GameLogic.Output.cs create mode 100644 src/game/GameLogic.State.cs create mode 100644 src/game/GameLogic.cs create mode 100644 src/game/IGameRepo.cs create mode 100644 src/game/state/states/GameLogic.State.Quit.cs create mode 100644 src/game/state/states/InventoryOpened.cs create mode 100644 src/game/state/states/MenuBackdrop.cs create mode 100644 src/game/state/states/MiniMapOpen.cs create mode 100644 src/game/state/states/Paused.cs create mode 100644 src/game/state/states/Playing.cs create mode 100644 src/inventory_menu/InventoryMenu.cs create mode 100644 src/items/ArmorItem.cs create mode 100644 src/items/InventoryItem.cs create mode 100644 src/items/WeaponItem.cs create mode 100644 src/map/dungeon/rooms/DungeonRoom.cs create mode 100644 src/map/dungeon/rooms/DungeonRoomLogic.cs create mode 100644 src/map/dungeon/rooms/state/DungeonRoomLogic.State.cs create mode 100644 src/map/dungeon/rooms/state/states/DungeonRoomLogic.State.Idle.cs create mode 100644 src/menu/Menu.cs create mode 100644 src/menu/splash/Splash.cs create mode 100644 src/minimap/MiniMap.cs create mode 100644 src/player/Player.cs create mode 100644 src/player/Player.tscn create mode 100644 src/player/PlayerData.cs create mode 100644 src/player/state/PlayerLogic.Input.cs create mode 100644 src/player/state/PlayerLogic.Output.cs create mode 100644 src/player/state/PlayerLogic.Settings.cs create mode 100644 src/player/state/PlayerLogic.State.cs create mode 100644 src/player/state/PlayerLogic.cs create mode 100644 src/player/state/PlayerLogic.g.puml create mode 100644 src/player/state/states/PlayerLogic.State.Alive.Attacking.cs create mode 100644 src/player/state/states/PlayerLogic.State.Alive.Idle.cs create mode 100644 src/player/state/states/PlayerLogic.State.Alive.cs create mode 100644 src/player/state/states/PlayerLogic.State.Disabled.cs create mode 100644 src/utils/FpsCounter.cs create mode 100644 src/utils/GameInputs.cs create mode 100644 src/utils/Instantiator.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..59a150c8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CS8632: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. +dotnet_diagnostic.CS8632.severity = silent diff --git a/GameJamDungeon.csproj b/GameJamDungeon.csproj index dbd8b774..1ed1fe8c 100644 --- a/GameJamDungeon.csproj +++ b/GameJamDungeon.csproj @@ -20,4 +20,7 @@ + + + \ No newline at end of file diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 00000000..6e921338 --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "Godot": { + "commandName": "Executable", + "executablePath": "%GODOT%", + "commandLineArgs": "--path . --verbose", + "workingDirectory": ".", + "nativeDebugging": true + } + } +} \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 00000000..df432610 --- /dev/null +++ b/global.json @@ -0,0 +1,8 @@ +{ + "sdk": { + "version": "8.0.400" + }, + "msbuild-sdks": { + "Godot.NET.Sdk": "4.3.0" + } +} \ No newline at end of file diff --git a/project.godot b/project.godot index 4b5ecf46..942a88f1 100644 --- a/project.godot +++ b/project.godot @@ -69,43 +69,7 @@ Inventory={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null) ] } -move_forward={ -"deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) -] -} -move_backward={ -"deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) -] -} -move_right={ -"deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) -] -} -move_left={ -"deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) -] -} -move_sprint={ -"deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) -] -} -move_jump={ -"deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) -] -} -move_crouch={ -"deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) -] -} -move_fly_mode={ -"deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) -] -} + +[physics] + +common/physics_ticks_per_second=144 diff --git a/src/app/App.cs b/src/app/App.cs new file mode 100644 index 00000000..769cf366 --- /dev/null +++ b/src/app/App.cs @@ -0,0 +1,61 @@ +using Chickensoft.AutoInject; +using Chickensoft.GodotNodeInterfaces; +using Chickensoft.Introspection; +using Godot; + +namespace GameJamDungeon +{ + public interface IApp : ICanvasLayer, IProvide; + + [Meta(typeof(IAutoNode))] + public partial class App : CanvasLayer, IApp + { + public override void _Notification(int what) => this.Notify(what); + + public const string GAME_SCENE_PATH = "res://src/game/Game.tscn"; + + public IGame Game { get; set; } = default!; + + public IInstantiator Instantiator { get; set; } = default!; + + IAppRepo IProvide.Value() => AppRepo; + + public IAppRepo AppRepo { get; set; } = default!; + public IAppLogic AppLogic { get; set; } = default!; + public AppLogic.IBinding AppBinding { get; set; } = default!; + + [Node] public ISubViewport GameWindow { get; set; } = default!; + + public void Initialize() + { + Instantiator = new Instantiator(GetTree()); + AppRepo = new AppRepo(); + AppLogic = new AppLogic(); + AppLogic.Set(AppRepo); + + this.Provide(); + } + + public void OnReady() + { + Game = Instantiator.LoadAndInstantiate(GAME_SCENE_PATH); + GameWindow.AddChildEx(Game); + AppBinding = AppLogic.Bind(); + AppLogic.Start(); + + Instantiator.SceneTree.Paused = false; + } + + + public void OnNewGame() => AppLogic.Input(new AppLogic.Input.NewGame()); + + public void OnQuit() => AppLogic.Input(new AppLogic.Input.QuitGame()); + + public void OnExitTree() + { + AppLogic.Stop(); + AppBinding.Dispose(); + AppRepo.Dispose(); + } + } +} diff --git a/src/app/App.tscn b/src/app/App.tscn index e23b2b58..56e215d2 100644 --- a/src/app/App.tscn +++ b/src/app/App.tscn @@ -1,89 +1,22 @@ -[gd_scene load_steps=8 format=3 uid="uid://cagfc5ridmteu"] +[gd_scene load_steps=2 format=3 uid="uid://cagfc5ridmteu"] -[ext_resource type="PackedScene" uid="uid://wg25dg65ksgg" path="res://src/map/dungeon/DungeonGenerator.tscn" id="1_eapuk"] -[ext_resource type="PackedScene" uid="uid://dhpwwqow1ahrc" path="res://src/map/dungeon/rooms/Room1.tscn" id="2_x112h"] -[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="3_614ds"] -[ext_resource type="PackedScene" uid="uid://bn4gslp2gk8ds" path="res://src/map/dungeon/corridor/Corridor.tscn" id="4_tex41"] -[ext_resource type="Script" path="res://src/character_controller_3d.gd" id="4_vtqs3"] +[ext_resource type="Script" path="res://src/app/App.cs" id="1_rt73h"] -[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_ult2r"] -radius = 0.25 -height = 0.5 +[node name="App" type="CanvasLayer"] +script = ExtResource("1_rt73h") -[sub_resource type="Environment" id="Environment_3njg4"] +[node name="SubViewportContainer" type="SubViewportContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +stretch = true -[node name="App" type="Node"] - -[node name="DungeonGenerator3D" parent="." instance=ExtResource("1_eapuk")] - -[node name="RoomsContainer" type="Node3D" parent="DungeonGenerator3D"] - -[node name="DungeonRoom3D_0" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("2_x112h")] -transform = Transform3D(1.19249e-08, 0, -1, 0, 1, 0, 1, 0, 1.19249e-08, 35, 0, 45) -script = ExtResource("3_614ds") - -[node name="DungeonRoom3D_1" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("2_x112h")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -25, 0, 35) -script = ExtResource("3_614ds") - -[node name="Corridor_2" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_tex41")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 25, 0, 45) -script = ExtResource("3_614ds") - -[node name="Corridor_3" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_tex41")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15, 0, 45) -script = ExtResource("3_614ds") - -[node name="Corridor_4" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_tex41")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 45) -script = ExtResource("3_614ds") - -[node name="Corridor_5" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_tex41")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, 45) -script = ExtResource("3_614ds") - -[node name="Corridor_6" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_tex41")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0, 45) -script = ExtResource("3_614ds") - -[node name="Corridor_7" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_tex41")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -25, 0, 45) -script = ExtResource("3_614ds") - -[node name="Corridor_8" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_tex41")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 45, 0, 45) -script = ExtResource("3_614ds") - -[node name="Corridor_9" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_tex41")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -25, 0, 25) -script = ExtResource("3_614ds") - -[node name="Corridor_10" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_tex41")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0, 25) -script = ExtResource("3_614ds") - -[node name="Corridor_11" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_tex41")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, 25) -script = ExtResource("3_614ds") - -[node name="Corridor_12" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_tex41")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 25) -script = ExtResource("3_614ds") - -[node name="Corridor_13" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_tex41")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 35) -script = ExtResource("3_614ds") - -[node name="CharacterBody3D" type="CharacterBody3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14.0223, -3.8587, -16.1332) -script = ExtResource("4_vtqs3") - -[node name="CollisionShape3D" type="CollisionShape3D" parent="CharacterBody3D"] -shape = SubResource("CapsuleShape3D_ult2r") - -[node name="OmniLight3D" type="OmniLight3D" parent="CharacterBody3D"] - -[node name="Camera3D" type="Camera3D" parent="CharacterBody3D"] - -[node name="WorldEnvironment" type="WorldEnvironment" parent="."] -environment = SubResource("Environment_3njg4") +[node name="GameWindow" type="SubViewport" parent="SubViewportContainer"] +unique_name_in_owner = true +transparent_bg = true +handle_input_locally = false +audio_listener_enable_3d = true +size = Vector2i(1920, 1080) +render_target_update_mode = 4 diff --git a/src/app/domain/AppRepo.cs b/src/app/domain/AppRepo.cs new file mode 100644 index 00000000..5bddc463 --- /dev/null +++ b/src/app/domain/AppRepo.cs @@ -0,0 +1,68 @@ +using System; + +namespace GameJamDungeon +{ + public interface IAppRepo : IDisposable + { + event Action? GameEntered; + + event Action? GameExited; + + event Action? SplashScreenSkipped; + + event Action? MainMenuEntered; + + void SkipSplashScreen(); + + void OnMainMenuEntered(); + + void OnEnterGame(); + + void OnExitGame(); + + void OnGameOver(); + } + + public class AppRepo : IAppRepo + { + public event Action? SplashScreenSkipped; + public event Action? MainMenuEntered; + public event Action? GameEntered; + public event Action? GameExited; + + private bool _disposedValue; + + public void SkipSplashScreen() => SplashScreenSkipped?.Invoke(); + + public void OnMainMenuEntered() => MainMenuEntered?.Invoke(); + + public void OnEnterGame() => GameEntered?.Invoke(); + + public void OnExitGame() => GameExited?.Invoke(); + + public void OnGameOver() => GameExited?.Invoke(); + + protected void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + // Dispose managed objects. + SplashScreenSkipped = null; + MainMenuEntered = null; + GameEntered = null; + GameExited = null; + } + + _disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/app/state/AppLogic.Input.cs b/src/app/state/AppLogic.Input.cs new file mode 100644 index 00000000..5d9227a5 --- /dev/null +++ b/src/app/state/AppLogic.Input.cs @@ -0,0 +1,18 @@ +namespace GameJamDungeon +{ + public partial class AppLogic + { + public static class Input + { + public readonly record struct NewGame; + + public readonly record struct FadeInFinished; + + public readonly record struct FadeOutFinished; + + public readonly record struct QuitGame; + + public readonly record struct GameOver; + } + } +} diff --git a/src/app/state/AppLogic.Output.cs b/src/app/state/AppLogic.Output.cs new file mode 100644 index 00000000..67652042 --- /dev/null +++ b/src/app/state/AppLogic.Output.cs @@ -0,0 +1,30 @@ +namespace GameJamDungeon +{ + public partial class AppLogic + { + public static class Output + { + public readonly record struct FadeToBlack; + + public readonly record struct ShowSplashScreen; + + public readonly record struct HideSplashScreen; + + public readonly record struct RemoveExistingGame; + + public readonly record struct PlayGame; + + public readonly record struct ShowGame; + + public readonly record struct HideGame; + + public readonly record struct SetupGameScene(); + + public readonly record struct ShowMainMenu; + + public readonly record struct ExitGame; + + public readonly record struct GameOver; + } + } +} diff --git a/src/app/state/AppLogic.State.cs b/src/app/state/AppLogic.State.cs new file mode 100644 index 00000000..df66a45f --- /dev/null +++ b/src/app/state/AppLogic.State.cs @@ -0,0 +1,11 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class AppLogic + { + [Meta] + public abstract partial record State : StateLogic; + } +} diff --git a/src/app/state/AppLogic.cs b/src/app/state/AppLogic.cs new file mode 100644 index 00000000..bf9fbaee --- /dev/null +++ b/src/app/state/AppLogic.cs @@ -0,0 +1,14 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public interface IAppLogic : ILogicBlock; + + [Meta] + [LogicBlock(typeof(State), Diagram = true)] + public partial class AppLogic : LogicBlock, IAppLogic + { + public override Transition GetInitialState() => To(); + } +} diff --git a/src/app/state/AppLogic.g.puml b/src/app/state/AppLogic.g.puml new file mode 100644 index 00000000..73eb587c --- /dev/null +++ b/src/app/state/AppLogic.g.puml @@ -0,0 +1,25 @@ +@startuml AppLogic +state "AppLogic State" as GameJam2024Practice_AppLogic_State { + state "InGame" as GameJam2024Practice_AppLogic_State_InGame + state "MainMenu" as GameJam2024Practice_AppLogic_State_MainMenu + state "SplashScreen" as GameJam2024Practice_AppLogic_State_SplashScreen + state "LeavingMenu" as GameJam2024Practice_AppLogic_State_LeavingMenu +} + +GameJam2024Practice_AppLogic_State_InGame --> GameJam2024Practice_AppLogic_State_MainMenu : GameOver +GameJam2024Practice_AppLogic_State_LeavingMenu --> GameJam2024Practice_AppLogic_State_InGame : FadeOutFinished +GameJam2024Practice_AppLogic_State_MainMenu --> GameJam2024Practice_AppLogic_State_LeavingMenu : NewGame +GameJam2024Practice_AppLogic_State_MainMenu --> GameJam2024Practice_AppLogic_State_MainMenu : QuitGame +GameJam2024Practice_AppLogic_State_SplashScreen --> GameJam2024Practice_AppLogic_State_MainMenu : FadeOutFinished + +GameJam2024Practice_AppLogic_State_InGame : OnEnter → ShowGame +GameJam2024Practice_AppLogic_State_InGame : OnExit → HideGame +GameJam2024Practice_AppLogic_State_InGame : OnGameOver → RemoveExistingGame +GameJam2024Practice_AppLogic_State_LeavingMenu : OnEnter → FadeToBlack +GameJam2024Practice_AppLogic_State_MainMenu : OnEnter → SetupGameScene, ShowMainMenu +GameJam2024Practice_AppLogic_State_MainMenu : OnQuitGame → ExitGame +GameJam2024Practice_AppLogic_State_SplashScreen : OnEnter → ShowSplashScreen +GameJam2024Practice_AppLogic_State_SplashScreen : OnSplashScreenSkipped() → HideSplashScreen + +[*] --> GameJam2024Practice_AppLogic_State_SplashScreen +@enduml \ No newline at end of file diff --git a/src/app/state/states/InGame.cs b/src/app/state/states/InGame.cs new file mode 100644 index 00000000..320b4976 --- /dev/null +++ b/src/app/state/states/InGame.cs @@ -0,0 +1,37 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class AppLogic + { + public partial record State + { + [Meta] + public partial record InGame : State, IGet + { + public InGame() + { + + this.OnEnter(() => + { + Get().OnEnterGame(); + Output(new Output.ShowGame()); + }); + this.OnExit(() => Output(new Output.HideGame())); + + OnAttach(() => Get().GameExited += OnGameExited); + OnDetach(() => Get().GameExited -= OnGameExited); + } + + public Transition On(in Input.GameOver input) + { + Output(new Output.RemoveExistingGame()); + return To(); + } + + public void OnGameExited() => Input(new Input.GameOver()); + } + } + } +} diff --git a/src/app/state/states/LeavingMenu.cs b/src/app/state/states/LeavingMenu.cs new file mode 100644 index 00000000..e27562a1 --- /dev/null +++ b/src/app/state/states/LeavingMenu.cs @@ -0,0 +1,22 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class AppLogic + { + public partial record State + { + [Meta] + public partial record LeavingMenu : State, IGet + { + public LeavingMenu() + { + this.OnEnter(() => Output(new Output.FadeToBlack())); + } + + public Transition On(in Input.FadeOutFinished input) => To(); + } + } + } +} diff --git a/src/app/state/states/MainMenu.cs b/src/app/state/states/MainMenu.cs new file mode 100644 index 00000000..fc9e9bc5 --- /dev/null +++ b/src/app/state/states/MainMenu.cs @@ -0,0 +1,35 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class AppLogic + { + public partial record State + { + [Meta] + public partial record MainMenu : State, IGet, IGet + { + public MainMenu() + { + this.OnEnter(() => + { + Output(new Output.SetupGameScene()); + + Get().OnMainMenuEntered(); + + Output(new Output.ShowMainMenu()); + }); + } + public Transition On(in Input.NewGame input) => To(); + + public Transition On(in Input.QuitGame input) + { + Output(new Output.ExitGame()); + + return ToSelf(); + } + } + } + } +} diff --git a/src/app/state/states/SplashScreen.cs b/src/app/state/states/SplashScreen.cs new file mode 100644 index 00000000..e00b4f8c --- /dev/null +++ b/src/app/state/states/SplashScreen.cs @@ -0,0 +1,33 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class AppLogic + { + public partial record State + { + [Meta] + public partial record SplashScreen : State, IGet + { + public SplashScreen() + { + this.OnEnter(() => Output(new Output.ShowSplashScreen())); + + OnAttach( + () => Get().SplashScreenSkipped += OnSplashScreenSkipped + ); + + OnDetach( + () => Get().SplashScreenSkipped -= OnSplashScreenSkipped + ); + } + + public Transition On(in Input.FadeOutFinished input) => To(); + + public void OnSplashScreenSkipped() => + Output(new Output.HideSplashScreen()); + } + } + } +} diff --git a/src/character_controller_3d.gd b/src/character_controller_3d.gd deleted file mode 100644 index 854771d2..00000000 --- a/src/character_controller_3d.gd +++ /dev/null @@ -1,53 +0,0 @@ -extends CharacterBody3D - -@export var FORWARD_SPEED = 2.0 -@export var BACK_SPEED = 1.0 -@export var TURN_SPEED = 0.025 - -var Vec3Z = Vector3.ZERO - -#OPTIONAL: These could be used to change sensitivity of either rotating z or y -#var M_LOOK_SENS = 1 -#var V_LOOK_SENS = 1 - -func _physics_process(delta: float) -> void: - if Input.is_action_pressed("move_forward") and Input.is_action_pressed("move_backward"): - velocity.x = 0 - velocity.z = 0 - - elif Input.is_action_pressed("move_forward"): - var forwardVector = -Vector3.FORWARD.rotated(Vector3.UP, rotation.y) - velocity = -forwardVector * FORWARD_SPEED - - elif Input.is_action_pressed("move_backward"): - var backwardVector = Vector3.FORWARD.rotated(Vector3.UP, rotation.y) - velocity = -backwardVector * BACK_SPEED - - #If pressing nothing stop velocity - else: - velocity.x = 0 - velocity.z = 0 - - # IF turn left WHILE moving back, turn right - if Input.is_action_pressed("move_left") and Input.is_action_pressed("move_backward"): - rotation.z -= Vec3Z.y + TURN_SPEED #* V_LOOK_SENS - rotation.z = clamp(rotation.x, -50, 90) - rotation.y -= Vec3Z.y + TURN_SPEED #* M_LOOK_SENS - - elif Input.is_action_pressed("move_left"): - rotation.z += Vec3Z.y - TURN_SPEED #* V_LOOK_SENS - rotation.z = clamp(rotation.x, -50, 90) - rotation.y += Vec3Z.y + TURN_SPEED #* M_LOOK_SENS - - # IF turn right WHILE moving back, turn left - if Input.is_action_pressed("move_right") and Input.is_action_pressed("move_backward"): - rotation.z += Vec3Z.y - TURN_SPEED #* V_LOOK_SENS - rotation.z = clamp(rotation.x, -50, 90) - rotation.y += Vec3Z.y + TURN_SPEED #* M_LOOK_SENS - - elif Input.is_action_pressed("move_right"): - rotation.z -= Vec3Z.y + TURN_SPEED #* V_LOOK_SENS - rotation.z = clamp(rotation.x, -50, 90) - rotation.y -= Vec3Z.y + TURN_SPEED #* M_LOOK_SENS - - move_and_slide() diff --git a/src/game/Game.cs b/src/game/Game.cs new file mode 100644 index 00000000..6fe0d4cd --- /dev/null +++ b/src/game/Game.cs @@ -0,0 +1,77 @@ +namespace GameJamDungeon; + +using Chickensoft.AutoInject; +using Chickensoft.GodotNodeInterfaces; +using Chickensoft.Introspection; +using Godot; + +public interface IGame : IProvide, INode3D +{ +} + +[Meta(typeof(IAutoNode))] +public partial class Game : Node3D, IGame +{ + public override void _Notification(int what) => this.Notify(what); + + IGameRepo IProvide.Value() => GameRepo; + + public IGameLogic GameLogic { get; set; } = default!; + + public IGameRepo GameRepo { get; set; } = default!; + + public GameLogic.IBinding GameBinding { get; set; } = default!; + + [Dependency] public IAppRepo AppRepo => this.DependOn(); + + public void Setup() + { + GameRepo = new GameRepo(); + GameLogic = new GameLogic(); + GameLogic.Set(GameRepo); + GameLogic.Set(AppRepo); + } + + public void OnResolved() + { + GameBinding = GameLogic.Bind(); + GameBinding + .Handle((in GameLogic.Output.StartGame _) => { GameRepo.Resume(); }) + .Handle((in GameLogic.Output.SetPauseMode output) => { CallDeferred(nameof(SetPauseMode), output.IsPaused); }) + .Handle((in GameLogic.Output.GameOver _) => { AppRepo.OnGameOver(); }); + + GameLogic.Input(new GameLogic.Input.Initialize()); + + this.Provide(); + GameLogic.Start(); + } + + public override void _Input(InputEvent @event) + { + if (Input.IsActionJustPressed(GameInputs.Inventory)) + { + GD.Print("Inventory button pressed"); + GameLogic.Input(new GameLogic.Input.InventoryMenuButtonPressed()); + } + + if (Input.IsActionJustPressed(GameInputs.MiniMap)) + { + GD.Print("MiniMap button pressed"); + GameLogic.Input(new GameLogic.Input.MiniMapButtonPressed()); + } + + if (Input.IsActionJustReleased(GameInputs.MiniMap)) + { + GD.Print("MiniMap button released"); + GameLogic.Input(new GameLogic.Input.MiniMapButtonReleased()); + } + } + + private void SetPauseMode(bool isPaused) + { + if (GetTree() != null) + GetTree().Paused = isPaused; + } + + public void OnStart() => GameLogic.Input(new GameLogic.Input.Start()); +} diff --git a/src/game/Game.tscn b/src/game/Game.tscn new file mode 100644 index 00000000..e9b28e0d --- /dev/null +++ b/src/game/Game.tscn @@ -0,0 +1,83 @@ +[gd_scene load_steps=8 format=3 uid="uid://33ek675mfb5n"] + +[ext_resource type="Script" path="res://src/game/Game.cs" id="1_ytcii"] +[ext_resource type="PackedScene" uid="uid://wg25dg65ksgg" path="res://src/map/dungeon/DungeonGenerator.tscn" id="2_cgboj"] +[ext_resource type="PackedScene" uid="uid://cfecvvav8kkp6" path="res://src/player/Player.tscn" id="3_kk6ly"] +[ext_resource type="PackedScene" uid="uid://dhpwwqow1ahrc" path="res://src/map/dungeon/rooms/Room1.tscn" id="4_56rmd"] +[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="5_5i8m5"] +[ext_resource type="PackedScene" uid="uid://bn4gslp2gk8ds" path="res://src/map/dungeon/corridor/Corridor.tscn" id="6_5fcqc"] + +[sub_resource type="Environment" id="Environment_fke5g"] + +[node name="Game" type="Node3D"] +script = ExtResource("1_ytcii") + +[node name="DungeonGenerator3D" parent="." instance=ExtResource("2_cgboj")] + +[node name="WorldEnvironment" type="WorldEnvironment" parent="DungeonGenerator3D"] +environment = SubResource("Environment_fke5g") + +[node name="Player" parent="DungeonGenerator3D" instance=ExtResource("3_kk6ly")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.55927, -4.70883, 25.1515) + +[node name="RoomsContainer" type="Node3D" parent="DungeonGenerator3D"] + +[node name="DungeonRoom3D_0" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_56rmd")] +transform = Transform3D(1.19249e-08, 0, -1, 0, 1, 0, 1, 0, 1.19249e-08, 5, 0, 15) +script = ExtResource("5_5i8m5") + +[node name="DungeonRoom3D_1" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_56rmd")] +transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, -25, 0, -25) +script = ExtResource("5_5i8m5") + +[node name="Corridor_2" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, 15) +script = ExtResource("5_5i8m5") + +[node name="Corridor_3" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, 5) +script = ExtResource("5_5i8m5") + +[node name="Corridor_4" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, -5) +script = ExtResource("5_5i8m5") + +[node name="Corridor_5" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, -15) +script = ExtResource("5_5i8m5") + +[node name="Corridor_6" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0, -15) +script = ExtResource("5_5i8m5") + +[node name="Corridor_7" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0, -25) +script = ExtResource("5_5i8m5") + +[node name="Corridor_8" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15, 0, 15) +script = ExtResource("5_5i8m5") + +[node name="Corridor_9" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15, 0, 5) +script = ExtResource("5_5i8m5") + +[node name="Corridor_10" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 5) +script = ExtResource("5_5i8m5") + +[node name="Corridor_11" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -35, 0, -25) +script = ExtResource("5_5i8m5") + +[node name="Corridor_12" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -35, 0, -15) +script = ExtResource("5_5i8m5") + +[node name="Corridor_13" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -25, 0, -15) +script = ExtResource("5_5i8m5") + +[node name="Corridor_14" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0, -5) +script = ExtResource("5_5i8m5") diff --git a/src/game/GameData.cs b/src/game/GameData.cs new file mode 100644 index 00000000..8bea4b65 --- /dev/null +++ b/src/game/GameData.cs @@ -0,0 +1,10 @@ +using Chickensoft.Introspection; + +namespace GameJamDungeon +{ + [Meta, Id("game_data")] + public partial record GameData + { + public required PlayerData PlayerData { get; init; } + } +} diff --git a/src/game/GameLogic.Input.cs b/src/game/GameLogic.Input.cs new file mode 100644 index 00000000..9b248579 --- /dev/null +++ b/src/game/GameLogic.Input.cs @@ -0,0 +1,20 @@ +namespace GameJamDungeon +{ + public partial class GameLogic + { + public static class Input + { + public readonly record struct Start; + + public readonly record struct Initialize; + + public readonly record struct InventoryMenuButtonPressed; + + public readonly record struct MiniMapButtonPressed; + + public readonly record struct MiniMapButtonReleased; + + public readonly record struct GameOver; + } + } +} diff --git a/src/game/GameLogic.Output.cs b/src/game/GameLogic.Output.cs new file mode 100644 index 00000000..4d7af89a --- /dev/null +++ b/src/game/GameLogic.Output.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace GameJamDungeon +{ + public partial class GameLogic + { + public static class Output + { + public readonly record struct StartGame(); + + public readonly record struct SetInventoryMode(List Inventory); + + public readonly record struct HideInventory(); + + public readonly record struct SetPauseMode(bool IsPaused); + + public readonly record struct ShowMiniMap(); + + public readonly record struct HideMiniMap(); + + public readonly record struct GameOver(); + } + } +} diff --git a/src/game/GameLogic.State.cs b/src/game/GameLogic.State.cs new file mode 100644 index 00000000..20e7acf2 --- /dev/null +++ b/src/game/GameLogic.State.cs @@ -0,0 +1,28 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class GameLogic + { + [Meta] + public abstract partial record State : StateLogic + { + protected State() + { + OnAttach(() => + { + var gameRepo = Get(); + gameRepo.IsPaused.Sync += OnIsPaused; + }); + OnDetach(() => + { + var gameRepo = Get(); + gameRepo.IsPaused.Sync -= OnIsPaused; + }); + } + + public void OnIsPaused(bool isPaused) => Output(new Output.SetPauseMode(isPaused)); + } + } +} diff --git a/src/game/GameLogic.cs b/src/game/GameLogic.cs new file mode 100644 index 00000000..d8d7a6b1 --- /dev/null +++ b/src/game/GameLogic.cs @@ -0,0 +1,14 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public interface IGameLogic : ILogicBlock; + + [Meta] + [LogicBlock(typeof(State), Diagram = true)] + public partial class GameLogic : LogicBlock, IGameLogic + { + public override Transition GetInitialState() => To(); + } +} diff --git a/src/game/IGameRepo.cs b/src/game/IGameRepo.cs new file mode 100644 index 00000000..7105cb87 --- /dev/null +++ b/src/game/IGameRepo.cs @@ -0,0 +1,98 @@ +using Chickensoft.Collections; +using Godot; +using System; +using System.Collections.Generic; + +namespace GameJamDungeon +{ + public interface IGameRepo : IDisposable + { + event Action? Ended; + + IAutoProp> InventoryItems { get; } + + IAutoProp IsInventoryScreenOpened { get; } + + IAutoProp IsPaused { get; } + + void Pause(); + + void Resume(); + + IAutoProp PlayerGlobalPosition { get; } + + void SetPlayerGlobalPosition(Vector3 playerGlobalPosition); + } + + public class GameRepo : IGameRepo + { + public event Action? Ended; + + private readonly AutoProp> _inventoryItems; + private readonly AutoProp _isInventoryScreenOpened; + + public IAutoProp> InventoryItems => _inventoryItems; + + public IAutoProp IsInventoryScreenOpened => _isInventoryScreenOpened; + + public IAutoProp PlayerGlobalPosition => _playerGlobalPosition; + private readonly AutoProp _playerGlobalPosition; + + public IAutoProp IsPaused => _isPaused; + private readonly AutoProp _isPaused; + + private bool _disposedValue; + + public GameRepo() + { + _inventoryItems = new AutoProp>([]); + _isInventoryScreenOpened = new AutoProp(false); + _isPaused = new AutoProp(false); + _playerGlobalPosition = new AutoProp(Vector3.Zero); + } + + public void Pause() + { + _isPaused.OnNext(true); + GD.Print("Paused"); + } + + public void Resume() + { + _isPaused.OnNext(false); + GD.Print("Resume"); + } + + public void SetPlayerGlobalPosition(Vector3 playerGlobalPosition) => _playerGlobalPosition.OnNext(playerGlobalPosition); + + public void OnGameEnded() + { + Pause(); + Ended?.Invoke(); + } + + protected void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _playerGlobalPosition.OnCompleted(); + _playerGlobalPosition.Dispose(); + _inventoryItems.OnCompleted(); + _inventoryItems.Dispose(); + _isInventoryScreenOpened.OnCompleted(); + _isInventoryScreenOpened.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/game/state/states/GameLogic.State.Quit.cs b/src/game/state/states/GameLogic.State.Quit.cs new file mode 100644 index 00000000..098b8339 --- /dev/null +++ b/src/game/state/states/GameLogic.State.Quit.cs @@ -0,0 +1,20 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class GameLogic + { + public partial record State + { + [Meta] + public partial record Quit : State + { + public Quit() + { + this.OnEnter(() => Output(new Output.GameOver())); + } + } + } + } +} \ No newline at end of file diff --git a/src/game/state/states/InventoryOpened.cs b/src/game/state/states/InventoryOpened.cs new file mode 100644 index 00000000..a6c9ac27 --- /dev/null +++ b/src/game/state/states/InventoryOpened.cs @@ -0,0 +1,22 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class GameLogic + { + public partial record State + { + [Meta] + public partial record InventoryOpened : State, IGet + { + public InventoryOpened() + { + this.OnEnter(() => { Get().Pause(); Output(new Output.SetInventoryMode(Get().InventoryItems.Value)); }); + this.OnExit(() => { Output(new Output.HideInventory()); }); + } + public Transition On(in Input.InventoryMenuButtonPressed input) => To(); + } + } + } +} diff --git a/src/game/state/states/MenuBackdrop.cs b/src/game/state/states/MenuBackdrop.cs new file mode 100644 index 00000000..3f840ce3 --- /dev/null +++ b/src/game/state/states/MenuBackdrop.cs @@ -0,0 +1,29 @@ +using Chickensoft.Introspection; + +namespace GameJamDungeon +{ + public partial class GameLogic + { + public partial record State + { + [Meta] + public partial record MenuBackdrop : State, IGet, IGet + { + public MenuBackdrop() + { + OnAttach(() => Get().GameEntered += OnGameEntered); + OnDetach(() => Get().GameEntered -= OnGameEntered); + } + + public void OnGameEntered() => Input(new Input.Start()); + + public Transition On(in Input.Start input) => To(); + + public Transition On(in Input.Initialize input) + { + return ToSelf(); + } + } + } + } +} diff --git a/src/game/state/states/MiniMapOpen.cs b/src/game/state/states/MiniMapOpen.cs new file mode 100644 index 00000000..8bb95971 --- /dev/null +++ b/src/game/state/states/MiniMapOpen.cs @@ -0,0 +1,23 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class GameLogic + { + public partial record State + { + [Meta] + public partial record MinimapOpen : State, IGet + { + public MinimapOpen() + { + this.OnEnter(() => { Get().Pause(); Output(new Output.ShowMiniMap()); }); + this.OnExit(() => { Output(new Output.HideMiniMap()); }); + } + + public Transition On(in Input.MiniMapButtonReleased input) => To(); + } + } + } +} diff --git a/src/game/state/states/Paused.cs b/src/game/state/states/Paused.cs new file mode 100644 index 00000000..989bf669 --- /dev/null +++ b/src/game/state/states/Paused.cs @@ -0,0 +1,26 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class GameLogic + { + public partial record State + { + [Meta] + public partial record Paused : State, IGet, IGet + { + public Paused() + { + this.OnEnter(() => Get().Pause()); + this.OnExit(() => Output(new Output.SetPauseMode(false))); + } + + + public virtual Transition On(in Input.InventoryMenuButtonPressed input) => To(); + + public virtual Transition On(in Input.MiniMapButtonReleased input) => To(); + } + } + } +} diff --git a/src/game/state/states/Playing.cs b/src/game/state/states/Playing.cs new file mode 100644 index 00000000..741e0481 --- /dev/null +++ b/src/game/state/states/Playing.cs @@ -0,0 +1,34 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class GameLogic + { + public partial record State + { + [Meta] + public partial record Playing : State, IGet, IGet, IGet + { + public Playing() + { + this.OnEnter(() => { Output(new Output.StartGame()); Get().Resume(); }); + + OnAttach(() => Get().Ended += OnEnded); + OnDetach(() => Get().Ended -= OnEnded); + } + + public void OnEnded() => Input(new Input.GameOver()); + + public Transition On(in Input.InventoryMenuButtonPressed input) => To(); + + public Transition On(in Input.MiniMapButtonPressed input) => To(); + + public Transition On(in Input.GameOver input) + { + return To(); + } + } + } + } +} diff --git a/src/inventory_menu/InventoryMenu.cs b/src/inventory_menu/InventoryMenu.cs new file mode 100644 index 00000000..3eac8bff --- /dev/null +++ b/src/inventory_menu/InventoryMenu.cs @@ -0,0 +1,126 @@ +using Chickensoft.AutoInject; +using Chickensoft.GodotNodeInterfaces; +using Chickensoft.Introspection; +using GameJamDungeon; +using Godot; +using System.Collections.Generic; + +public interface IInventoryMenu : IControl +{ + public void PopulateItems(List items); + + public void ClearItems(); +} + +[Meta(typeof(IAutoNode))] +public partial class InventoryMenu : Control, IInventoryMenu +{ + public override void _Notification(int what) => this.Notify(what); + + [Node] public IVBoxContainer ItemList { get; set; } = default!; + + [Node] public TextureRect Cursor { get; set; } = default!; + + [Dependency] public IGameRepo GameRepo => this.DependOn(); + + private int _currentSelection = 0; + + public void PopulateItems(List items) + { + foreach (var item in items) + { + var label = new WeaponLabel(item) { Text = item.Name }; + ItemList.AddChild(label); + } + + if (ItemList.GetChildCount() > 0) + CallDeferred(nameof(InitializeInventoryMenu)); + } + + public void InitializeInventoryMenu() + { + if (ItemList.GetChildCount() > 0) + { + var currentItem = ItemList.GetChild(_currentSelection); + SetCursorLocation(currentItem); + } + } + + public void ClearItems() + { + foreach (var item in ItemList.GetChildren()) + ItemList.RemoveChild(item); + } + + public void SetCursorLocation(Control menuItem) + { + var position = menuItem.GlobalPosition; + var size = menuItem.Size; + + Cursor.GlobalPosition = new Vector2(position.X, position.Y + size.Y / 2.0f) - Cursor.Size / 2.0f - new Vector2(15, -5); + } + + public void SetCursorToPrevious() + { + if (ItemList.GetChildCount() == 0) + return; + + if (_currentSelection > 0) + { + _currentSelection -= 1; + var selectedMenuItem = ItemList.GetChild(_currentSelection); + SetCursorLocation(selectedMenuItem); + } + } + + public void SetCursorToNext() + { + if (ItemList.GetChildCount() == 0) + return; + + if (_currentSelection < ItemList.GetChildCount() - 1) + { + _currentSelection += 1; + var selectedMenuItem = ItemList.GetChild(_currentSelection); + SetCursorLocation(selectedMenuItem); + } + } + + private void UnequipItem(WeaponLabel item) + { + item.UnequipItem(); + } + + public override void _Process(double delta) + { + var input = Vector2.Zero; + if (Input.IsActionJustPressed(GameInputs.MoveUp)) + SetCursorToPrevious(); + if (Input.IsActionJustPressed(GameInputs.MoveDown)) + SetCursorToNext(); + } +} + +public partial class WeaponLabel : Label +{ + public WeaponLabel(InventoryItem inventoryItem) + { + InventoryItem = inventoryItem; + LabelSettings = UnequippedItemFont; + } + + public InventoryItem InventoryItem { get; set; } = default!; + + private static LabelSettings UnequippedItemFont => GD.Load("res://src/vfx/Fonts/InventoryLabelSettings.tres"); + private static LabelSettings EquippedItemFont => GD.Load("res://src/vfx/Fonts/EquippedInventoryLabelSettings.tres"); + + public void EquipItem() + { + LabelSettings = EquippedItemFont; + } + + public void UnequipItem() + { + LabelSettings = UnequippedItemFont; + } +} diff --git a/src/items/ArmorItem.cs b/src/items/ArmorItem.cs new file mode 100644 index 00000000..7e837e2f --- /dev/null +++ b/src/items/ArmorItem.cs @@ -0,0 +1,8 @@ +using Godot; + +[GlobalClass] +public partial class ArmorItem : InventoryItem +{ + [Export] + public required int Defense { get; set; } +} diff --git a/src/items/InventoryItem.cs b/src/items/InventoryItem.cs new file mode 100644 index 00000000..99a7ac20 --- /dev/null +++ b/src/items/InventoryItem.cs @@ -0,0 +1,11 @@ +using Godot; + +[GlobalClass] +public partial class InventoryItem : Resource +{ + [Export] + public string Name = string.Empty; + + [Export] + public string Description = string.Empty; +} \ No newline at end of file diff --git a/src/items/WeaponItem.cs b/src/items/WeaponItem.cs new file mode 100644 index 00000000..877b0a08 --- /dev/null +++ b/src/items/WeaponItem.cs @@ -0,0 +1,10 @@ +using Godot; + +[GlobalClass] +public partial class WeaponItem : InventoryItem +{ + [Export] + public required int Damage { get; set; } + + public static WeaponItem Default => new WeaponItem() { Damage = 1 }; +} diff --git a/src/map/dungeon/corridor/Corridor.tscn b/src/map/dungeon/corridor/Corridor.tscn index 91a42a45..928f83ac 100644 --- a/src/map/dungeon/corridor/Corridor.tscn +++ b/src/map/dungeon/corridor/Corridor.tscn @@ -8,6 +8,7 @@ [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_nsah4"] transparency = 1 +albedo_texture = ExtResource("2_6scux") [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_18cgv"] albedo_texture = ExtResource("2_6scux") @@ -40,19 +41,19 @@ size = Vector3(9, 9, 9) material = SubResource("StandardMaterial3D_18cgv") [node name="DOOR?" parent="CSGBox3D" instance=ExtResource("2_vpnlr")] -transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -5, -3.5, 0) +transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -5, -2.58627, 0) material = SubResource("StandardMaterial3D_cquyy") [node name="DOOR?3" parent="CSGBox3D" instance=ExtResource("2_vpnlr")] -transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 5, -3.5, 0) +transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 5, -2.58627, 0) material = SubResource("StandardMaterial3D_67t3u") [node name="DOOR?4" parent="CSGBox3D" instance=ExtResource("2_vpnlr")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -3.5, 5) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.58627, 5) material = SubResource("StandardMaterial3D_ej8w2") [node name="DOOR?2" parent="CSGBox3D" instance=ExtResource("2_vpnlr")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -3.5, -5) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.58627, -5) material = SubResource("StandardMaterial3D_oy7nu") [node name="RemoveUnusedDoors" type="Node" parent="."] diff --git a/src/map/dungeon/door/Door.tscn b/src/map/dungeon/door/Door.tscn index b3edf2fe..26e5429d 100644 --- a/src/map/dungeon/door/Door.tscn +++ b/src/map/dungeon/door/Door.tscn @@ -1,6 +1,6 @@ [gd_scene format=3 uid="uid://ckaw6wjmi0fom"] [node name="DOOR" type="CSGBox3D"] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.01493, 0) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.375916, 1.9271, 0) operation = 2 -size = Vector3(2, 2, 1) +size = Vector3(2.75183, 3.82434, 1) diff --git a/src/map/dungeon/rooms/DungeonRoom.cs b/src/map/dungeon/rooms/DungeonRoom.cs new file mode 100644 index 00000000..99ec9eb5 --- /dev/null +++ b/src/map/dungeon/rooms/DungeonRoom.cs @@ -0,0 +1,44 @@ +using Chickensoft.AutoInject; +using Chickensoft.GodotNodeInterfaces; +using Chickensoft.Introspection; +using GameJamDungeon; +using Godot; + +public interface IDungeonRoom : INode3D +{ + DungeonRoomLogic DungeonRoomLogic { get; } + public Marker3D PlayerSpawn { get; set; } +} + +[Meta(typeof(IAutoNode))] +public partial class DungeonRoom : Node3D, IDungeonRoom, IProvide +{ + public override void _Notification(int what) => this.Notify(what); + + DungeonRoomLogic IProvide.Value() => DungeonRoomLogic; + + [Dependency] public IGameRepo GameRepo => this.DependOn(); + + public DungeonRoomLogic DungeonRoomLogic { get; set; } = default!; + + [Node] public Marker3D PlayerSpawn { get; set; } = default!; + + public DungeonRoomLogic.IBinding DungeonRoomBinding { get; set; } = default!; + + public void Setup() + { + DungeonRoomLogic = new DungeonRoomLogic(); + DungeonRoomLogic.Set(this as IDungeonRoom); + DungeonRoomLogic.Set(GameRepo); + } + + public void OnResolved() + { + DungeonRoomBinding = DungeonRoomLogic.Bind(); + + GameRepo.SetPlayerGlobalPosition(PlayerSpawn.GlobalPosition); + + DungeonRoomLogic.Start(); + this.Provide(); + } +} diff --git a/src/map/dungeon/rooms/DungeonRoomLogic.cs b/src/map/dungeon/rooms/DungeonRoomLogic.cs new file mode 100644 index 00000000..cf5efd36 --- /dev/null +++ b/src/map/dungeon/rooms/DungeonRoomLogic.cs @@ -0,0 +1,14 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public interface IDungeonRoomLogic : ILogicBlock; + + [Meta, Id("dungeon_room_logic")] + [LogicBlock(typeof(State), Diagram = true)] + public partial class DungeonRoomLogic : LogicBlock, IDungeonRoomLogic + { + public override Transition GetInitialState() => To(); + } +} diff --git a/src/map/dungeon/rooms/Room1.tscn b/src/map/dungeon/rooms/Room1.tscn index d05448d8..413886c4 100644 --- a/src/map/dungeon/rooms/Room1.tscn +++ b/src/map/dungeon/rooms/Room1.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=5 format=3 uid="uid://dhpwwqow1ahrc"] +[gd_scene load_steps=6 format=3 uid="uid://dhpwwqow1ahrc"] [ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="1_0tfda"] +[ext_resource type="Script" path="res://src/map/dungeon/rooms/DungeonRoom.cs" id="1_ti7ur"] [ext_resource type="PackedScene" uid="uid://ckaw6wjmi0fom" path="res://src/map/dungeon/door/Door.tscn" id="2_mdawx"] [ext_resource type="Texture2D" uid="uid://bidlc5a6lft6" path="res://src/map/dungeon/textures/map_brickwall.jpg" id="2_rw3uc"] @@ -12,6 +13,9 @@ uv1_triplanar = true [node name="DungeonRoom3D" type="Node3D"] script = ExtResource("1_0tfda") +[node name="DungeonRoom" type="Node3D" parent="."] +script = ExtResource("1_ti7ur") + [node name="CSGBox3D" type="CSGBox3D" parent="."] material_override = SubResource("StandardMaterial3D_gt3ar") use_collision = true @@ -24,7 +28,11 @@ use_collision = true size = Vector3(9, 9, 9) [node name="DOOR" parent="CSGBox3D" instance=ExtResource("2_mdawx")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -3.47376, 4.74571) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.54039, 4.74571) [node name="DOOR2" parent="CSGBox3D" instance=ExtResource("2_mdawx")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -3.51619, -4.73548) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.58282, -4.73548) + +[node name="PlayerSpawn" type="Marker3D" parent="."] +unique_name_in_owner = true +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -4.23461, 0) diff --git a/src/map/dungeon/rooms/state/DungeonRoomLogic.State.cs b/src/map/dungeon/rooms/state/DungeonRoomLogic.State.cs new file mode 100644 index 00000000..87105238 --- /dev/null +++ b/src/map/dungeon/rooms/state/DungeonRoomLogic.State.cs @@ -0,0 +1,11 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class DungeonRoomLogic + { + [Meta] + public abstract partial record State : StateLogic; + } +} diff --git a/src/map/dungeon/rooms/state/states/DungeonRoomLogic.State.Idle.cs b/src/map/dungeon/rooms/state/states/DungeonRoomLogic.State.Idle.cs new file mode 100644 index 00000000..021c23a5 --- /dev/null +++ b/src/map/dungeon/rooms/state/states/DungeonRoomLogic.State.Idle.cs @@ -0,0 +1,15 @@ +using Chickensoft.Introspection; + +namespace GameJamDungeon +{ + public partial class DungeonRoomLogic + { + public partial record State + { + [Meta, Id("dungeon_room_logic_state_idle")] + public partial record Idle : State + { + } + } + } +} diff --git a/src/menu/Menu.cs b/src/menu/Menu.cs new file mode 100644 index 00000000..6a8ccc83 --- /dev/null +++ b/src/menu/Menu.cs @@ -0,0 +1,52 @@ +using Chickensoft.AutoInject; +using Chickensoft.GodotNodeInterfaces; +using Chickensoft.Introspection; +using GameJamDungeon; +using Godot; +using System; + +public interface IMenu : IControl +{ + event Menu.NewGameEventHandler NewGame; + event Menu.QuitEventHandler Quit; +} + +[Meta(typeof(IAutoNode))] +public partial class Menu : Control, IMenu +{ + public override void _Notification(int what) => this.Notify(what); + + [Node] + public IButton NewGameButton { get; set; } = default!; + + [Node] + public IButton QuitButton { get; set; } = default!; + + [Signal] + public delegate void NewGameEventHandler(); + [Signal] + public delegate void QuitEventHandler(); + + public void OnReady() + { + NewGameButton.Pressed += OnNewGamePressed; + QuitButton.Pressed += OnQuitPressed; + NewGameButton.GrabFocus(); + } + + public void OnExitTree() + { + NewGameButton.Pressed -= OnNewGamePressed; + QuitButton.Pressed -= OnQuitPressed; + } + + public void OnNewGamePressed() => EmitSignal(SignalName.NewGame); + + public void OnQuitPressed() => EmitSignal(SignalName.Quit); + + public override void _UnhandledInput(InputEvent @event) + { + if (Input.IsActionJustPressed(GameInputs.Attack)) + OnNewGamePressed(); + } +} diff --git a/src/menu/splash/Splash.cs b/src/menu/splash/Splash.cs new file mode 100644 index 00000000..5ed4e838 --- /dev/null +++ b/src/menu/splash/Splash.cs @@ -0,0 +1,30 @@ +using Chickensoft.AutoInject; +using Chickensoft.GodotNodeInterfaces; +using Chickensoft.Introspection; +using Godot; + +namespace GameJamDungeon +{ + public interface ISplash : IControl; + + [Meta(typeof(IAutoNode))] + public partial class Splash : Control, ISplash + { + public override void _Notification(int what) => this.Notify(what); + + [Dependency] + public IAppRepo AppRepo => this.DependOn(); + + [Node] + public IAnimationPlayer AnimationPlayer { get; set; } = default!; + + public void OnReady() => + AnimationPlayer.AnimationFinished += OnAnimationFinished; + + public void OnExitTree() + => AnimationPlayer.AnimationFinished -= OnAnimationFinished; + + public void OnAnimationFinished(StringName name) + => AppRepo.SkipSplashScreen(); + } +} diff --git a/src/minimap/MiniMap.cs b/src/minimap/MiniMap.cs new file mode 100644 index 00000000..214c8646 --- /dev/null +++ b/src/minimap/MiniMap.cs @@ -0,0 +1,6 @@ +using Godot; +using System; + +public partial class MiniMap : Control +{ +} diff --git a/src/player/Player.cs b/src/player/Player.cs new file mode 100644 index 00000000..d9576817 --- /dev/null +++ b/src/player/Player.cs @@ -0,0 +1,130 @@ +using Chickensoft.AutoInject; +using Chickensoft.GodotNodeInterfaces; +using Chickensoft.Introspection; +using Godot; +using System; + +namespace GameJamDungeon +{ + public interface IPlayer : ICharacterBody3D + { + PlayerLogic PlayerLogic { get; } + + PlayerData PlayerData { get; } + + public Vector3 GetGlobalInputVector(); + } + + [Meta(typeof(IAutoNode))] + public partial class Player : CharacterBody3D, IPlayer, IProvide + { + public override void _Notification(int what) => this.Notify(what); + + PlayerLogic IProvide.Value() => PlayerLogic; + + [Dependency] public IAppRepo AppRepo => this.DependOn(); + [Dependency] public IGameRepo GameRepo => this.DependOn(); + + /// Rotation speed (quaternions?/sec). + [Export(PropertyHint.Range, "0, 100, 0.1")] + public float RotationSpeed { get; set; } = 0.5f; + + /// Player speed (meters/sec). + [Export(PropertyHint.Range, "0, 100, 0.1")] + public float MoveSpeed { get; set; } = 2f; + + /// Player speed (meters^2/sec). + [Export(PropertyHint.Range, "0, 100, 0.1")] + public float Acceleration { get; set; } = 1f; + + public PlayerLogic.Settings Settings { get; set; } = default!; + + public PlayerLogic PlayerLogic { get; set; } = default!; + + public PlayerData PlayerData { get; set; } = default!; + + public PlayerLogic.IBinding PlayerBinding { get; set; } = default!; + + public void Initialize() + { + + } + + public void Setup() + { + Settings = new PlayerLogic.Settings( + RotationSpeed, + MoveSpeed); + + PlayerLogic = new PlayerLogic(); + PlayerLogic.Set(this as IPlayer); + PlayerLogic.Set(Settings); + PlayerLogic.Set(AppRepo); + PlayerLogic.Set(GameRepo); + PlayerLogic.Set(PlayerData); + + GameRepo.PlayerGlobalPosition.Sync += OnPlayerPositionUpdated; + } + + private void OnPlayerPositionUpdated(Vector3 globalPosition) + { + GlobalPosition = globalPosition; + } + + public void OnResolved() + { + PlayerBinding = PlayerLogic.Bind(); + + PlayerBinding + .Handle((in PlayerLogic.Output.MovementComputed output) => + { + Transform = Transform with { Basis = output.Rotation }; + Velocity = output.Velocity; + }) + .Handle((in PlayerLogic.Output.Animations.Attack output) => + { + }); + + this.Provide(); + PlayerLogic.Start(); + } + + public void OnReady() + { + SetPhysicsProcess(true); + } + + public void OnPhysicsProcess(double delta) + { + PlayerLogic.Input(new PlayerLogic.Input.PhysicsTick(delta)); + + var attackIsPressed = Input.IsActionJustPressed(GameInputs.Attack); + if (attackIsPressed) + PlayerLogic.Input(new PlayerLogic.Input.Attack()); + + MoveAndSlide(); + + PlayerLogic.Input(new PlayerLogic.Input.Moved(GlobalPosition)); + } + + public Vector3 GetGlobalInputVector() + { + var rawInput = Input.GetVector(GameInputs.MoveLeft, GameInputs.MoveRight, GameInputs.MoveUp, GameInputs.MoveDown); + + var input = new Vector3 + { + X = rawInput.X, + Z = rawInput.Y + }; + return input with { Y = 0f }; + } + + public void OnExitTree() + { + PlayerLogic.Stop(); + AppRepo.Dispose(); + GameRepo.Dispose(); + PlayerBinding.Dispose(); + } + } +} diff --git a/src/player/Player.tscn b/src/player/Player.tscn new file mode 100644 index 00000000..12fd470d --- /dev/null +++ b/src/player/Player.tscn @@ -0,0 +1,24 @@ +[gd_scene load_steps=3 format=3 uid="uid://cfecvvav8kkp6"] + +[ext_resource type="Script" path="res://src/player/Player.cs" id="1_xcol5"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_dw45s"] +height = 1.2 + +[node name="Player" type="CharacterBody3D"] +motion_mode = 1 +script = ExtResource("1_xcol5") +RotationSpeed = 0.025 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.937567, 0) +shape = SubResource("CapsuleShape3D_dw45s") + +[node name="Camera3D" type="Camera3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.19694, 0) + +[node name="OmniLight3D" type="OmniLight3D" parent="."] +omni_range = 73.156 + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +unique_name_in_owner = true diff --git a/src/player/PlayerData.cs b/src/player/PlayerData.cs new file mode 100644 index 00000000..b920bff5 --- /dev/null +++ b/src/player/PlayerData.cs @@ -0,0 +1,22 @@ +using Chickensoft.Serialization; +using Godot; +using System.Collections; +using System.Collections.Generic; + +namespace GameJamDungeon +{ + public partial record PlayerData + { + [Save("global_transform")] + public required Transform3D GlobalTransform { get; init; } + + [Save("state_machine")] + public required PlayerLogic StateMachine { get; init; } + + [Save("PlayerEquippedSword")] + public required InventoryItem EquippedWeapon { get; set; } + + [Save("PlayerInventory")] + public required IEnumerable Inventory { get; set; } + } +} diff --git a/src/player/state/PlayerLogic.Input.cs b/src/player/state/PlayerLogic.Input.cs new file mode 100644 index 00000000..f003f6a9 --- /dev/null +++ b/src/player/state/PlayerLogic.Input.cs @@ -0,0 +1,20 @@ +using Godot; + +namespace GameJamDungeon +{ + public partial class PlayerLogic + { + public static class Input + { + public readonly record struct PhysicsTick(double Delta); + + public readonly record struct Moved(Vector3 GlobalPosition); + + public readonly record struct Enable; + + public readonly record struct Attack; + + public readonly record struct AttackAnimationFinished; + } + } +} diff --git a/src/player/state/PlayerLogic.Output.cs b/src/player/state/PlayerLogic.Output.cs new file mode 100644 index 00000000..5c561c51 --- /dev/null +++ b/src/player/state/PlayerLogic.Output.cs @@ -0,0 +1,17 @@ +using Godot; + +namespace GameJamDungeon +{ + public partial class PlayerLogic + { + public static class Output + { + public static class Animations + { + public readonly record struct Attack; + } + + public readonly record struct MovementComputed(Basis Rotation, Vector3 Velocity); + } + } +} diff --git a/src/player/state/PlayerLogic.Settings.cs b/src/player/state/PlayerLogic.Settings.cs new file mode 100644 index 00000000..06561015 --- /dev/null +++ b/src/player/state/PlayerLogic.Settings.cs @@ -0,0 +1,9 @@ +namespace GameJamDungeon +{ + public partial class PlayerLogic + { + public record Settings( + float RotationSpeed, + float MoveSpeed); + } +} diff --git a/src/player/state/PlayerLogic.State.cs b/src/player/state/PlayerLogic.State.cs new file mode 100644 index 00000000..091ee2ab --- /dev/null +++ b/src/player/state/PlayerLogic.State.cs @@ -0,0 +1,11 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public partial class PlayerLogic + { + [Meta] + public abstract partial record State : StateLogic; + } +} diff --git a/src/player/state/PlayerLogic.cs b/src/player/state/PlayerLogic.cs new file mode 100644 index 00000000..c8fd7b7e --- /dev/null +++ b/src/player/state/PlayerLogic.cs @@ -0,0 +1,14 @@ +using Chickensoft.Introspection; +using Chickensoft.LogicBlocks; + +namespace GameJamDungeon +{ + public interface IPlayerLogic : ILogicBlock; + + [Meta, Id("player_logic")] + [LogicBlock(typeof(State), Diagram = true)] + public partial class PlayerLogic : LogicBlock, IPlayerLogic + { + public override Transition GetInitialState() => To(); + } +} diff --git a/src/player/state/PlayerLogic.g.puml b/src/player/state/PlayerLogic.g.puml new file mode 100644 index 00000000..47e30ecf --- /dev/null +++ b/src/player/state/PlayerLogic.g.puml @@ -0,0 +1,20 @@ +@startuml PlayerLogic +state "PlayerLogic State" as GameJam2024Practice_PlayerLogic_State { + state "Alive" as GameJam2024Practice_PlayerLogic_State_Alive { + state "Idle" as GameJam2024Practice_PlayerLogic_State_Idle + state "Attacking" as GameJam2024Practice_PlayerLogic_State_Attacking + } + state "Disabled" as GameJam2024Practice_PlayerLogic_State_Disabled +} + +GameJam2024Practice_PlayerLogic_State_Alive --> GameJam2024Practice_PlayerLogic_State_Alive : Moved +GameJam2024Practice_PlayerLogic_State_Alive --> GameJam2024Practice_PlayerLogic_State_Alive : PhysicsTick +GameJam2024Practice_PlayerLogic_State_Attacking --> GameJam2024Practice_PlayerLogic_State_Idle : AttackAnimationFinished +GameJam2024Practice_PlayerLogic_State_Disabled --> GameJam2024Practice_PlayerLogic_State_Idle : Enable +GameJam2024Practice_PlayerLogic_State_Idle --> GameJam2024Practice_PlayerLogic_State_Attacking : Attack + +GameJam2024Practice_PlayerLogic_State_Alive : OnPhysicsTick → MovementComputed +GameJam2024Practice_PlayerLogic_State_Idle : OnAttack → Attack + +[*] --> GameJam2024Practice_PlayerLogic_State_Disabled +@enduml \ No newline at end of file diff --git a/src/player/state/states/PlayerLogic.State.Alive.Attacking.cs b/src/player/state/states/PlayerLogic.State.Alive.Attacking.cs new file mode 100644 index 00000000..311162a1 --- /dev/null +++ b/src/player/state/states/PlayerLogic.State.Alive.Attacking.cs @@ -0,0 +1,19 @@ +using Chickensoft.Introspection; + +namespace GameJamDungeon +{ + public partial class PlayerLogic + { + public partial record State + { + [Meta] + public partial record Attacking : Alive, IGet + { + public Transition On(in Input.AttackAnimationFinished input) + { + return To(); + } + } + } + } +} diff --git a/src/player/state/states/PlayerLogic.State.Alive.Idle.cs b/src/player/state/states/PlayerLogic.State.Alive.Idle.cs new file mode 100644 index 00000000..b77303a5 --- /dev/null +++ b/src/player/state/states/PlayerLogic.State.Alive.Idle.cs @@ -0,0 +1,23 @@ +using Chickensoft.Introspection; +using Godot; + +namespace GameJamDungeon +{ + public partial class PlayerLogic + { + public abstract partial record State + { + [Meta, Id("player_logic_state_alive_idle")] + public partial record Idle : Alive, IGet + { + + public virtual Transition On(in Input.Attack input) + { + GD.Print("Attacking..."); + Output(new Output.Animations.Attack()); + return To(); + } + } + } + } +} diff --git a/src/player/state/states/PlayerLogic.State.Alive.cs b/src/player/state/states/PlayerLogic.State.Alive.cs new file mode 100644 index 00000000..63fc7c05 --- /dev/null +++ b/src/player/state/states/PlayerLogic.State.Alive.cs @@ -0,0 +1,41 @@ +using Chickensoft.Introspection; +using Godot; + +namespace GameJamDungeon +{ + public partial class PlayerLogic + { + public partial record State + { + [Meta, Id("player_logic_alive")] + public abstract partial record Alive : State, IGet, IGet + { + public virtual Transition On(in Input.PhysicsTick input) + { + var player = Get(); + var settings = Get(); + + var rawInput = player.GetGlobalInputVector(); + + var transform = player.Transform; + transform.Basis = new Basis(Vector3.Up, settings.RotationSpeed * -rawInput.X) * transform.Basis; + var velocity = player.Basis * new Vector3(0, 0, rawInput.Z) * settings.MoveSpeed; + + if (Godot.Input.IsActionPressed(GameInputs.Sprint)) + velocity *= 3; + + Output(new Output.MovementComputed(transform.Basis, velocity)); + + return ToSelf(); + } + + public virtual Transition On(in Input.Moved input) + { + var gameRepo = Get(); + gameRepo.SetPlayerGlobalPosition(input.GlobalPosition); + return ToSelf(); + } + } + } + } +} diff --git a/src/player/state/states/PlayerLogic.State.Disabled.cs b/src/player/state/states/PlayerLogic.State.Disabled.cs new file mode 100644 index 00000000..088d7a58 --- /dev/null +++ b/src/player/state/states/PlayerLogic.State.Disabled.cs @@ -0,0 +1,24 @@ +using Chickensoft.Introspection; + +namespace GameJamDungeon +{ + public partial class PlayerLogic + { + public abstract partial record State + { + [Meta, Id("player_logic_state_disabled")] + public partial record Disabled : State, IGet + { + public Disabled() + { + OnAttach(() => Get().GameEntered += OnGameEntered); + OnDetach(() => Get().GameEntered -= OnGameEntered); + } + + public Transition On(in Input.Enable input) => To(); + + public void OnGameEntered() => Input(new Input.Enable()); + } + } + } +} diff --git a/src/utils/FpsCounter.cs b/src/utils/FpsCounter.cs new file mode 100644 index 00000000..83222754 --- /dev/null +++ b/src/utils/FpsCounter.cs @@ -0,0 +1,9 @@ +using Godot; + +public partial class FpsCounter : Label +{ + public override void _Process(double delta) + { + this.Text = Engine.GetFramesPerSecond().ToString(); + } +} diff --git a/src/utils/GameInputs.cs b/src/utils/GameInputs.cs new file mode 100644 index 00000000..a087188a --- /dev/null +++ b/src/utils/GameInputs.cs @@ -0,0 +1,7 @@ +using Godot; + +namespace GameJamDungeon +{ + [InputMap] + public partial class GameInputs; +} diff --git a/src/utils/Instantiator.cs b/src/utils/Instantiator.cs new file mode 100644 index 00000000..d0717c71 --- /dev/null +++ b/src/utils/Instantiator.cs @@ -0,0 +1,40 @@ +using Godot; + +namespace GameJamDungeon +{ + /// + /// Utility class that loads and instantiates scenes. + /// + public interface IInstantiator + { + /// Scene tree. + public SceneTree SceneTree { get; } + + /// + /// Loads and instantiates the given scene. + /// + /// Path to the scene. + /// Type of the scene's root. + /// Instance of the scene. + T LoadAndInstantiate(string path) where T : Node; + } + + /// + /// Utility class that loads and instantiates scenes. + /// + public class Instantiator : IInstantiator + { + public SceneTree SceneTree { get; } + + public Instantiator(SceneTree sceneTree) + { + SceneTree = sceneTree; + } + + public T LoadAndInstantiate(string path) where T : Node + { + var scene = GD.Load(path); + return scene.Instantiate(); + } + } +}