Re-introduce prototype code

This commit is contained in:
2024-08-23 00:39:15 -07:00
parent 8b3d1bed8a
commit 2fb0c364ca
62 changed files with 1685 additions and 187 deletions

4
.editorconfig Normal file
View File

@@ -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

View File

@@ -20,4 +20,7 @@
<ItemGroup> <ItemGroup>
<Folder Include="src\map\dungeon\corridor\" /> <Folder Include="src\map\dungeon\corridor\" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include=".editorconfig" />
</ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,11 @@
{
"profiles": {
"Godot": {
"commandName": "Executable",
"executablePath": "%GODOT%",
"commandLineArgs": "--path . --verbose",
"workingDirectory": ".",
"nativeDebugging": true
}
}
}

8
global.json Normal file
View File

@@ -0,0 +1,8 @@
{
"sdk": {
"version": "8.0.400"
},
"msbuild-sdks": {
"Godot.NET.Sdk": "4.3.0"
}
}

View File

@@ -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) "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, [physics]
"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)
] common/physics_ticks_per_second=144
}
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)
]
}

61
src/app/App.cs Normal file
View File

@@ -0,0 +1,61 @@
using Chickensoft.AutoInject;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
namespace GameJamDungeon
{
public interface IApp : ICanvasLayer, IProvide<IAppRepo>;
[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<IAppRepo>.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>(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();
}
}
}

View File

@@ -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="Script" path="res://src/app/App.cs" id="1_rt73h"]
[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"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_ult2r"] [node name="App" type="CanvasLayer"]
radius = 0.25 script = ExtResource("1_rt73h")
height = 0.5
[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="GameWindow" type="SubViewport" parent="SubViewportContainer"]
unique_name_in_owner = true
[node name="DungeonGenerator3D" parent="." instance=ExtResource("1_eapuk")] transparent_bg = true
handle_input_locally = false
[node name="RoomsContainer" type="Node3D" parent="DungeonGenerator3D"] audio_listener_enable_3d = true
size = Vector2i(1920, 1080)
[node name="DungeonRoom3D_0" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("2_x112h")] render_target_update_mode = 4
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")

68
src/app/domain/AppRepo.cs Normal file
View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
namespace GameJamDungeon
{
public partial class AppLogic
{
[Meta]
public abstract partial record State : StateLogic<State>;
}
}

14
src/app/state/AppLogic.cs Normal file
View File

@@ -0,0 +1,14 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
namespace GameJamDungeon
{
public interface IAppLogic : ILogicBlock<AppLogic.State>;
[Meta]
[LogicBlock(typeof(State), Diagram = true)]
public partial class AppLogic : LogicBlock<AppLogic.State>, IAppLogic
{
public override Transition GetInitialState() => To<State.SplashScreen>();
}
}

View File

@@ -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

View File

@@ -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<Input.GameOver>
{
public InGame()
{
this.OnEnter(() =>
{
Get<IAppRepo>().OnEnterGame();
Output(new Output.ShowGame());
});
this.OnExit(() => Output(new Output.HideGame()));
OnAttach(() => Get<IAppRepo>().GameExited += OnGameExited);
OnDetach(() => Get<IAppRepo>().GameExited -= OnGameExited);
}
public Transition On(in Input.GameOver input)
{
Output(new Output.RemoveExistingGame());
return To<MainMenu>();
}
public void OnGameExited() => Input(new Input.GameOver());
}
}
}
}

View File

@@ -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<Input.FadeOutFinished>
{
public LeavingMenu()
{
this.OnEnter(() => Output(new Output.FadeToBlack()));
}
public Transition On(in Input.FadeOutFinished input) => To<InGame>();
}
}
}
}

View File

@@ -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<Input.NewGame>, IGet<Input.QuitGame>
{
public MainMenu()
{
this.OnEnter(() =>
{
Output(new Output.SetupGameScene());
Get<IAppRepo>().OnMainMenuEntered();
Output(new Output.ShowMainMenu());
});
}
public Transition On(in Input.NewGame input) => To<LeavingMenu>();
public Transition On(in Input.QuitGame input)
{
Output(new Output.ExitGame());
return ToSelf();
}
}
}
}
}

View File

@@ -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<Input.FadeOutFinished>
{
public SplashScreen()
{
this.OnEnter(() => Output(new Output.ShowSplashScreen()));
OnAttach(
() => Get<IAppRepo>().SplashScreenSkipped += OnSplashScreenSkipped
);
OnDetach(
() => Get<IAppRepo>().SplashScreenSkipped -= OnSplashScreenSkipped
);
}
public Transition On(in Input.FadeOutFinished input) => To<MainMenu>();
public void OnSplashScreenSkipped() =>
Output(new Output.HideSplashScreen());
}
}
}
}

View File

@@ -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()

77
src/game/Game.cs Normal file
View File

@@ -0,0 +1,77 @@
namespace GameJamDungeon;
using Chickensoft.AutoInject;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
public interface IGame : IProvide<IGameRepo>, INode3D
{
}
[Meta(typeof(IAutoNode))]
public partial class Game : Node3D, IGame
{
public override void _Notification(int what) => this.Notify(what);
IGameRepo IProvide<IGameRepo>.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<IAppRepo>();
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());
}

83
src/game/Game.tscn Normal file
View File

@@ -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")

10
src/game/GameData.cs Normal file
View File

@@ -0,0 +1,10 @@
using Chickensoft.Introspection;
namespace GameJamDungeon
{
[Meta, Id("game_data")]
public partial record GameData
{
public required PlayerData PlayerData { get; init; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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<InventoryItem> 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();
}
}
}

View File

@@ -0,0 +1,28 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
namespace GameJamDungeon
{
public partial class GameLogic
{
[Meta]
public abstract partial record State : StateLogic<State>
{
protected State()
{
OnAttach(() =>
{
var gameRepo = Get<IGameRepo>();
gameRepo.IsPaused.Sync += OnIsPaused;
});
OnDetach(() =>
{
var gameRepo = Get<IGameRepo>();
gameRepo.IsPaused.Sync -= OnIsPaused;
});
}
public void OnIsPaused(bool isPaused) => Output(new Output.SetPauseMode(isPaused));
}
}
}

14
src/game/GameLogic.cs Normal file
View File

@@ -0,0 +1,14 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
namespace GameJamDungeon
{
public interface IGameLogic : ILogicBlock<GameLogic.State>;
[Meta]
[LogicBlock(typeof(State), Diagram = true)]
public partial class GameLogic : LogicBlock<GameLogic.State>, IGameLogic
{
public override Transition GetInitialState() => To<State.MenuBackdrop>();
}
}

98
src/game/IGameRepo.cs Normal file
View File

@@ -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<List<InventoryItem>> InventoryItems { get; }
IAutoProp<bool> IsInventoryScreenOpened { get; }
IAutoProp<bool> IsPaused { get; }
void Pause();
void Resume();
IAutoProp<Vector3> PlayerGlobalPosition { get; }
void SetPlayerGlobalPosition(Vector3 playerGlobalPosition);
}
public class GameRepo : IGameRepo
{
public event Action? Ended;
private readonly AutoProp<List<InventoryItem>> _inventoryItems;
private readonly AutoProp<bool> _isInventoryScreenOpened;
public IAutoProp<List<InventoryItem>> InventoryItems => _inventoryItems;
public IAutoProp<bool> IsInventoryScreenOpened => _isInventoryScreenOpened;
public IAutoProp<Vector3> PlayerGlobalPosition => _playerGlobalPosition;
private readonly AutoProp<Vector3> _playerGlobalPosition;
public IAutoProp<bool> IsPaused => _isPaused;
private readonly AutoProp<bool> _isPaused;
private bool _disposedValue;
public GameRepo()
{
_inventoryItems = new AutoProp<List<InventoryItem>>([]);
_isInventoryScreenOpened = new AutoProp<bool>(false);
_isPaused = new AutoProp<bool>(false);
_playerGlobalPosition = new AutoProp<Vector3>(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);
}
}
}

View File

@@ -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()));
}
}
}
}
}

View File

@@ -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<Input.InventoryMenuButtonPressed>
{
public InventoryOpened()
{
this.OnEnter(() => { Get<IGameRepo>().Pause(); Output(new Output.SetInventoryMode(Get<IGameRepo>().InventoryItems.Value)); });
this.OnExit(() => { Output(new Output.HideInventory()); });
}
public Transition On(in Input.InventoryMenuButtonPressed input) => To<Playing>();
}
}
}
}

View File

@@ -0,0 +1,29 @@
using Chickensoft.Introspection;
namespace GameJamDungeon
{
public partial class GameLogic
{
public partial record State
{
[Meta]
public partial record MenuBackdrop : State, IGet<Input.Start>, IGet<Input.Initialize>
{
public MenuBackdrop()
{
OnAttach(() => Get<IAppRepo>().GameEntered += OnGameEntered);
OnDetach(() => Get<IAppRepo>().GameEntered -= OnGameEntered);
}
public void OnGameEntered() => Input(new Input.Start());
public Transition On(in Input.Start input) => To<Playing>();
public Transition On(in Input.Initialize input)
{
return ToSelf();
}
}
}
}
}

View File

@@ -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<Input.MiniMapButtonReleased>
{
public MinimapOpen()
{
this.OnEnter(() => { Get<IGameRepo>().Pause(); Output(new Output.ShowMiniMap()); });
this.OnExit(() => { Output(new Output.HideMiniMap()); });
}
public Transition On(in Input.MiniMapButtonReleased input) => To<Playing>();
}
}
}
}

View File

@@ -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<Input.InventoryMenuButtonPressed>, IGet<Input.MiniMapButtonReleased>
{
public Paused()
{
this.OnEnter(() => Get<IGameRepo>().Pause());
this.OnExit(() => Output(new Output.SetPauseMode(false)));
}
public virtual Transition On(in Input.InventoryMenuButtonPressed input) => To<Playing>();
public virtual Transition On(in Input.MiniMapButtonReleased input) => To<Playing>();
}
}
}
}

View File

@@ -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<Input.InventoryMenuButtonPressed>, IGet<Input.MiniMapButtonPressed>, IGet<Input.GameOver>
{
public Playing()
{
this.OnEnter(() => { Output(new Output.StartGame()); Get<IGameRepo>().Resume(); });
OnAttach(() => Get<IGameRepo>().Ended += OnEnded);
OnDetach(() => Get<IGameRepo>().Ended -= OnEnded);
}
public void OnEnded() => Input(new Input.GameOver());
public Transition On(in Input.InventoryMenuButtonPressed input) => To<InventoryOpened>();
public Transition On(in Input.MiniMapButtonPressed input) => To<MinimapOpen>();
public Transition On(in Input.GameOver input)
{
return To<Quit>();
}
}
}
}
}

View File

@@ -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<InventoryItem> 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<IGameRepo>();
private int _currentSelection = 0;
public void PopulateItems(List<InventoryItem> 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<Control>(_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<Control>(_currentSelection);
SetCursorLocation(selectedMenuItem);
}
}
public void SetCursorToNext()
{
if (ItemList.GetChildCount() == 0)
return;
if (_currentSelection < ItemList.GetChildCount() - 1)
{
_currentSelection += 1;
var selectedMenuItem = ItemList.GetChild<Control>(_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<LabelSettings>("res://src/vfx/Fonts/InventoryLabelSettings.tres");
private static LabelSettings EquippedItemFont => GD.Load<LabelSettings>("res://src/vfx/Fonts/EquippedInventoryLabelSettings.tres");
public void EquipItem()
{
LabelSettings = EquippedItemFont;
}
public void UnequipItem()
{
LabelSettings = UnequippedItemFont;
}
}

8
src/items/ArmorItem.cs Normal file
View File

@@ -0,0 +1,8 @@
using Godot;
[GlobalClass]
public partial class ArmorItem : InventoryItem
{
[Export]
public required int Defense { get; set; }
}

View File

@@ -0,0 +1,11 @@
using Godot;
[GlobalClass]
public partial class InventoryItem : Resource
{
[Export]
public string Name = string.Empty;
[Export]
public string Description = string.Empty;
}

10
src/items/WeaponItem.cs Normal file
View File

@@ -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 };
}

View File

@@ -8,6 +8,7 @@
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_nsah4"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_nsah4"]
transparency = 1 transparency = 1
albedo_texture = ExtResource("2_6scux")
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_18cgv"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_18cgv"]
albedo_texture = ExtResource("2_6scux") albedo_texture = ExtResource("2_6scux")
@@ -40,19 +41,19 @@ size = Vector3(9, 9, 9)
material = SubResource("StandardMaterial3D_18cgv") material = SubResource("StandardMaterial3D_18cgv")
[node name="DOOR?" parent="CSGBox3D" instance=ExtResource("2_vpnlr")] [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") material = SubResource("StandardMaterial3D_cquyy")
[node name="DOOR?3" parent="CSGBox3D" instance=ExtResource("2_vpnlr")] [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") material = SubResource("StandardMaterial3D_67t3u")
[node name="DOOR?4" parent="CSGBox3D" instance=ExtResource("2_vpnlr")] [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") material = SubResource("StandardMaterial3D_ej8w2")
[node name="DOOR?2" parent="CSGBox3D" instance=ExtResource("2_vpnlr")] [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") material = SubResource("StandardMaterial3D_oy7nu")
[node name="RemoveUnusedDoors" type="Node" parent="."] [node name="RemoveUnusedDoors" type="Node" parent="."]

View File

@@ -1,6 +1,6 @@
[gd_scene format=3 uid="uid://ckaw6wjmi0fom"] [gd_scene format=3 uid="uid://ckaw6wjmi0fom"]
[node name="DOOR" type="CSGBox3D"] [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 operation = 2
size = Vector3(2, 2, 1) size = Vector3(2.75183, 3.82434, 1)

View File

@@ -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<DungeonRoomLogic>
{
public override void _Notification(int what) => this.Notify(what);
DungeonRoomLogic IProvide<DungeonRoomLogic>.Value() => DungeonRoomLogic;
[Dependency] public IGameRepo GameRepo => this.DependOn<IGameRepo>();
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();
}
}

View File

@@ -0,0 +1,14 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
namespace GameJamDungeon
{
public interface IDungeonRoomLogic : ILogicBlock<DungeonRoomLogic.State>;
[Meta, Id("dungeon_room_logic")]
[LogicBlock(typeof(State), Diagram = true)]
public partial class DungeonRoomLogic : LogicBlock<DungeonRoomLogic.State>, IDungeonRoomLogic
{
public override Transition GetInitialState() => To<State.Idle>();
}
}

View File

@@ -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://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="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"] [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"] [node name="DungeonRoom3D" type="Node3D"]
script = ExtResource("1_0tfda") script = ExtResource("1_0tfda")
[node name="DungeonRoom" type="Node3D" parent="."]
script = ExtResource("1_ti7ur")
[node name="CSGBox3D" type="CSGBox3D" parent="."] [node name="CSGBox3D" type="CSGBox3D" parent="."]
material_override = SubResource("StandardMaterial3D_gt3ar") material_override = SubResource("StandardMaterial3D_gt3ar")
use_collision = true use_collision = true
@@ -24,7 +28,11 @@ use_collision = true
size = Vector3(9, 9, 9) size = Vector3(9, 9, 9)
[node name="DOOR" parent="CSGBox3D" instance=ExtResource("2_mdawx")] [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")] [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)

View File

@@ -0,0 +1,11 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
namespace GameJamDungeon
{
public partial class DungeonRoomLogic
{
[Meta]
public abstract partial record State : StateLogic<State>;
}
}

View File

@@ -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
{
}
}
}
}

52
src/menu/Menu.cs Normal file
View File

@@ -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();
}
}

30
src/menu/splash/Splash.cs Normal file
View File

@@ -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<IAppRepo>();
[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();
}
}

6
src/minimap/MiniMap.cs Normal file
View File

@@ -0,0 +1,6 @@
using Godot;
using System;
public partial class MiniMap : Control
{
}

130
src/player/Player.cs Normal file
View File

@@ -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<PlayerLogic>
{
public override void _Notification(int what) => this.Notify(what);
PlayerLogic IProvide<PlayerLogic>.Value() => PlayerLogic;
[Dependency] public IAppRepo AppRepo => this.DependOn<IAppRepo>();
[Dependency] public IGameRepo GameRepo => this.DependOn<IGameRepo>();
/// <summary>Rotation speed (quaternions?/sec).</summary>
[Export(PropertyHint.Range, "0, 100, 0.1")]
public float RotationSpeed { get; set; } = 0.5f;
/// <summary>Player speed (meters/sec).</summary>
[Export(PropertyHint.Range, "0, 100, 0.1")]
public float MoveSpeed { get; set; } = 2f;
/// <summary>Player speed (meters^2/sec).</summary>
[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();
}
}
}

24
src/player/Player.tscn Normal file
View File

@@ -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

22
src/player/PlayerData.cs Normal file
View File

@@ -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<InventoryItem> Inventory { get; set; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace GameJamDungeon
{
public partial class PlayerLogic
{
public record Settings(
float RotationSpeed,
float MoveSpeed);
}
}

View File

@@ -0,0 +1,11 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
namespace GameJamDungeon
{
public partial class PlayerLogic
{
[Meta]
public abstract partial record State : StateLogic<State>;
}
}

View File

@@ -0,0 +1,14 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
namespace GameJamDungeon
{
public interface IPlayerLogic : ILogicBlock<PlayerLogic.State>;
[Meta, Id("player_logic")]
[LogicBlock(typeof(State), Diagram = true)]
public partial class PlayerLogic : LogicBlock<PlayerLogic.State>, IPlayerLogic
{
public override Transition GetInitialState() => To<State.Idle>();
}
}

View File

@@ -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

View File

@@ -0,0 +1,19 @@
using Chickensoft.Introspection;
namespace GameJamDungeon
{
public partial class PlayerLogic
{
public partial record State
{
[Meta]
public partial record Attacking : Alive, IGet<Input.AttackAnimationFinished>
{
public Transition On(in Input.AttackAnimationFinished input)
{
return To<Idle>();
}
}
}
}
}

View File

@@ -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<Input.Attack>
{
public virtual Transition On(in Input.Attack input)
{
GD.Print("Attacking...");
Output(new Output.Animations.Attack());
return To<Attacking>();
}
}
}
}
}

View File

@@ -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<Input.PhysicsTick>, IGet<Input.Moved>
{
public virtual Transition On(in Input.PhysicsTick input)
{
var player = Get<IPlayer>();
var settings = Get<Settings>();
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<IGameRepo>();
gameRepo.SetPlayerGlobalPosition(input.GlobalPosition);
return ToSelf();
}
}
}
}
}

View File

@@ -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<Input.Enable>
{
public Disabled()
{
OnAttach(() => Get<IAppRepo>().GameEntered += OnGameEntered);
OnDetach(() => Get<IAppRepo>().GameEntered -= OnGameEntered);
}
public Transition On(in Input.Enable input) => To<Idle>();
public void OnGameEntered() => Input(new Input.Enable());
}
}
}
}

9
src/utils/FpsCounter.cs Normal file
View File

@@ -0,0 +1,9 @@
using Godot;
public partial class FpsCounter : Label
{
public override void _Process(double delta)
{
this.Text = Engine.GetFramesPerSecond().ToString();
}
}

7
src/utils/GameInputs.cs Normal file
View File

@@ -0,0 +1,7 @@
using Godot;
namespace GameJamDungeon
{
[InputMap]
public partial class GameInputs;
}

40
src/utils/Instantiator.cs Normal file
View File

@@ -0,0 +1,40 @@
using Godot;
namespace GameJamDungeon
{
/// <summary>
/// Utility class that loads and instantiates scenes.
/// </summary>
public interface IInstantiator
{
/// <summary>Scene tree.</summary>
public SceneTree SceneTree { get; }
/// <summary>
/// Loads and instantiates the given scene.
/// </summary>
/// <param name="path">Path to the scene.</param>
/// <typeparam name="T">Type of the scene's root.</typeparam>
/// <returns>Instance of the scene.</returns>
T LoadAndInstantiate<T>(string path) where T : Node;
}
/// <summary>
/// Utility class that loads and instantiates scenes.
/// </summary>
public class Instantiator : IInstantiator
{
public SceneTree SceneTree { get; }
public Instantiator(SceneTree sceneTree)
{
SceneTree = sceneTree;
}
public T LoadAndInstantiate<T>(string path) where T : Node
{
var scene = GD.Load<PackedScene>(path);
return scene.Instantiate<T>();
}
}
}