diff --git a/project.godot b/project.godot index 71e7e24..0586cf0 100644 --- a/project.godot +++ b/project.godot @@ -61,6 +61,11 @@ toggle_console={ "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":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +move_down={ +"deadzone": 0.2, +"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":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} [physics] diff --git a/src/Features/Players/Contracts/IMovementModeSwitcher.cs b/src/Features/Players/Contracts/IMovementModeSwitcher.cs new file mode 100644 index 0000000..f276c09 --- /dev/null +++ b/src/Features/Players/Contracts/IMovementModeSwitcher.cs @@ -0,0 +1,13 @@ +namespace Game.Features.Players.Contracts; + +public enum MovementMode +{ + Ground, + Noclip +} + +public interface IMovementModeSwitcher +{ + MovementMode ActiveMovementMode { get; } + void SetMovementMode(MovementMode movementMode); +} diff --git a/src/Features/Players/Contracts/IMovementModeSwitcher.cs.uid b/src/Features/Players/Contracts/IMovementModeSwitcher.cs.uid new file mode 100644 index 0000000..504863b --- /dev/null +++ b/src/Features/Players/Contracts/IMovementModeSwitcher.cs.uid @@ -0,0 +1 @@ +uid://c5nmjyb5iw6rh diff --git a/src/Features/Players/Domain/Movement/GroundMovement.cs b/src/Features/Players/Domain/Movement/GroundMovement.cs new file mode 100644 index 0000000..022861a --- /dev/null +++ b/src/Features/Players/Domain/Movement/GroundMovement.cs @@ -0,0 +1,27 @@ +using Godot; + +namespace Game.Features.Players.Domain.Movement; + +public sealed class GroundMovement(float moveSpeed, float jumpVelocity, float gravity) : IMovementStrategy +{ + public Vector3 CalculateVelocity(Vector3 currentVelocity, MovementInput input) + { + Vector3 velocity = currentVelocity; + + if (!input.IsOnFloor) + { + velocity.Y -= gravity * input.Delta; + } + else if (input.JumpPressed) + { + velocity.Y = jumpVelocity; + } + + Vector3 moveDirection = input.Right * input.Direction.X + input.Back * input.Direction.Y; + Vector3 moveDirectionNormalized = moveDirection.Normalized(); + velocity.X = moveDirectionNormalized.X * moveSpeed; + velocity.Z = moveDirectionNormalized.Z * moveSpeed; + + return velocity; + } +} diff --git a/src/Features/Players/Domain/Movement/GroundMovement.cs.uid b/src/Features/Players/Domain/Movement/GroundMovement.cs.uid new file mode 100644 index 0000000..96f7590 --- /dev/null +++ b/src/Features/Players/Domain/Movement/GroundMovement.cs.uid @@ -0,0 +1 @@ +uid://v4pc5k482usm diff --git a/src/Features/Players/Domain/Movement/IMovementStrategy.cs b/src/Features/Players/Domain/Movement/IMovementStrategy.cs new file mode 100644 index 0000000..d3e83ca --- /dev/null +++ b/src/Features/Players/Domain/Movement/IMovementStrategy.cs @@ -0,0 +1,19 @@ +using Godot; + +namespace Game.Features.Players.Domain.Movement; + +public readonly record struct MovementInput( + Vector3 Right, + Vector3 Back, + Vector2 Direction, + bool IsOnFloor, + bool JumpPressed, + bool AscendPressed, + bool DescendPressed, + float Delta +); + +public interface IMovementStrategy +{ + Vector3 CalculateVelocity(Vector3 currentVelocity, MovementInput input); +} diff --git a/src/Features/Players/Domain/Movement/IMovementStrategy.cs.uid b/src/Features/Players/Domain/Movement/IMovementStrategy.cs.uid new file mode 100644 index 0000000..a61019b --- /dev/null +++ b/src/Features/Players/Domain/Movement/IMovementStrategy.cs.uid @@ -0,0 +1 @@ +uid://b3bcwkq278ojo diff --git a/src/Features/Players/Domain/Movement/NoclipMovement.cs b/src/Features/Players/Domain/Movement/NoclipMovement.cs new file mode 100644 index 0000000..ae12204 --- /dev/null +++ b/src/Features/Players/Domain/Movement/NoclipMovement.cs @@ -0,0 +1,23 @@ +using Godot; + +namespace Game.Features.Players.Domain.Movement; + +public sealed class NoclipMovement(float flySpeed) : IMovementStrategy +{ + public Vector3 CalculateVelocity(Vector3 currentVelocity, MovementInput input) + { + Vector3 moveDirection = input.Right * input.Direction.X + input.Back * input.Direction.Y; + + if (input.AscendPressed) + { + moveDirection += Vector3.Up; + } + + if (input.DescendPressed) + { + moveDirection += Vector3.Down; + } + + return moveDirection.Length() > 0f ? moveDirection.Normalized() * flySpeed : Vector3.Zero; + } +} diff --git a/src/Features/Players/Domain/Movement/NoclipMovement.cs.uid b/src/Features/Players/Domain/Movement/NoclipMovement.cs.uid new file mode 100644 index 0000000..6064a3f --- /dev/null +++ b/src/Features/Players/Domain/Movement/NoclipMovement.cs.uid @@ -0,0 +1 @@ +uid://c27csmmeunnld diff --git a/src/Features/Players/Domain/PlayerMovement.cs b/src/Features/Players/Domain/PlayerMovement.cs deleted file mode 100644 index e2f6099..0000000 --- a/src/Features/Players/Domain/PlayerMovement.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Godot; - -namespace Game.Features.Players.Domain; - -/// -/// Per-frame input for movement calculation. -/// -/// World-space right direction of the body. -/// World-space back direction of the body. -/// Movement input, X = left/right, Y = forward/back. -/// Whether the body is currently grounded. -/// Whether jump was just pressed this frame. -/// Physics frame delta time in seconds. -public readonly record struct MovementInput( - Vector3 Right, - Vector3 Back, - Vector2 Direction, - bool IsOnFloor, - bool JumpPressed, - float Delta -); - -public sealed class PlayerMovement(float moveSpeed, float jumpVelocity, float gravity) -{ - public float MoveSpeed { get; } = moveSpeed; - public float JumpVelocity { get; } = jumpVelocity; - public float Gravity { get; } = gravity; - - /// - /// Calculates next-frame velocity from input and ground state. - /// - /// Body's velocity before this frame. - /// Per-frame movement input. - public Vector3 CalculateVelocity(Vector3 currentVelocity, MovementInput input) - { - Vector3 velocity = currentVelocity; - - if (!input.IsOnFloor) - { - velocity.Y -= Gravity * input.Delta; - } - else if (input.JumpPressed) - { - velocity.Y = JumpVelocity; - } - - Vector3 moveDirection = input.Right * input.Direction.X + input.Back * input.Direction.Y; - Vector3 moveDirectionNormalized = moveDirection.Normalized(); - velocity.X = moveDirectionNormalized.X * MoveSpeed; - velocity.Z = moveDirectionNormalized.Z * MoveSpeed; - - return velocity; - } -} diff --git a/src/Features/Players/Domain/PlayerMovement.cs.uid b/src/Features/Players/Domain/PlayerMovement.cs.uid deleted file mode 100644 index ca981bb..0000000 --- a/src/Features/Players/Domain/PlayerMovement.cs.uid +++ /dev/null @@ -1 +0,0 @@ -uid://chnyf8efw3xw2 diff --git a/src/Features/Players/Godot/PlayerController.cs b/src/Features/Players/Godot/PlayerController.cs index ba1d9ba..a3a285e 100644 --- a/src/Features/Players/Godot/PlayerController.cs +++ b/src/Features/Players/Godot/PlayerController.cs @@ -1,5 +1,7 @@ -using System.Linq; +using System.Collections.Generic; +using Game.Features.Players.Contracts; using Game.Features.Players.Domain; +using Game.Features.Players.Domain.Movement; using Game.Godot; using Game.Godot.Extensions; using Game.Godot.Mappings; @@ -7,26 +9,44 @@ using Godot; namespace Game.Features.Players.Godot; -public partial class PlayerController : Node3D +public partial class PlayerController : Node3D, IMovementModeSwitcher { [Export] public CharacterBody3D Body; + [Export] public CollisionShape3D Collision; [Export] public Camera3D Camera; [Export] public float MoveSpeed = 4.0f; [Export] public float JumpVelocity = 4.5f; + [Export] public float NoclipFlySpeed = 8.0f; [Export] public float MouseSensitivity = 0.003f; [Export] public float MaxLookAngleDegrees = 89.0f; - private PlayerMovement _movement; + private Dictionary _movementStrategies; + private IMovementStrategy _currentMovementStrategy; private PlayerLook _look; + public MovementMode ActiveMovementMode { get; private set; } = MovementMode.Ground; + + public void SetMovementMode(MovementMode movementMode) + { + ActiveMovementMode = movementMode; + _currentMovementStrategy = _movementStrategies[movementMode]; + Collision.Disabled = movementMode == MovementMode.Noclip; + } + public override void _Ready() { this.AssertRequiredExports(); - float gravity = (float)ProjectSettings.GetSetting(ProjectSetting.Gravity.SettingPath()); - _movement = new PlayerMovement(MoveSpeed, JumpVelocity, gravity); _look = new PlayerLook(MouseSensitivity, MaxLookAngleDegrees); + + float gravity = (float)ProjectSettings.GetSetting(ProjectSetting.Gravity.SettingPath()); + _movementStrategies = new Dictionary + { + [MovementMode.Ground] = new GroundMovement(MoveSpeed, JumpVelocity, gravity), + [MovementMode.Noclip] = new NoclipMovement(NoclipFlySpeed) + }; + _currentMovementStrategy = _movementStrategies[ActiveMovementMode]; } public override void _UnhandledInput(InputEvent @event) @@ -53,10 +73,12 @@ public partial class PlayerController : Node3D Direction: inputDirection, IsOnFloor: Body.IsOnFloor(), JumpPressed: Input.IsActionJustPressed(InputAction.Jump.ActionName()), + AscendPressed: Input.IsActionPressed(InputAction.Jump.ActionName()), + DescendPressed: Input.IsActionPressed(InputAction.MoveDown.ActionName()), Delta: (float)delta ); - Body.Velocity = _movement.CalculateVelocity(Body.Velocity, input); + Body.Velocity = _currentMovementStrategy.CalculateVelocity(Body.Velocity, input); Body.MoveAndSlide(); } } diff --git a/src/Features/Players/Godot/PlayerNode.tscn b/src/Features/Players/Godot/PlayerNode.tscn index 69e6d5f..0690ea5 100644 --- a/src/Features/Players/Godot/PlayerNode.tscn +++ b/src/Features/Players/Godot/PlayerNode.tscn @@ -290,9 +290,10 @@ _surfaces = [{ }] blend_shape_mode = 0 -[node name="Player" type="Node3D" unique_id=26823676 node_paths=PackedStringArray("Body", "Camera")] +[node name="Player" type="Node3D" unique_id=26823676 node_paths=PackedStringArray("Body", "Collision", "Camera")] script = ExtResource("1_gkqfc") Body = NodePath("CharacterBody3D") +Collision = NodePath("CharacterBody3D/CollisionShape3D") Camera = NodePath("CharacterBody3D/Camera3D") [node name="CharacterBody3D" type="CharacterBody3D" parent="." unique_id=1327672780] diff --git a/src/Godot/Mappings/InputAction.cs b/src/Godot/Mappings/InputAction.cs index f9a2dd4..e4fb564 100644 --- a/src/Godot/Mappings/InputAction.cs +++ b/src/Godot/Mappings/InputAction.cs @@ -8,6 +8,7 @@ public enum InputAction MoveRight, MoveForward, MoveBack, + MoveDown, Jump, Cancel, ToggleConsole @@ -24,6 +25,7 @@ public static class InputActionExtensions InputAction.MoveRight => "move_right", InputAction.MoveForward => "move_forward", InputAction.MoveBack => "move_back", + InputAction.MoveDown => "move_down", InputAction.Jump => "jump", InputAction.Cancel => "ui_cancel", InputAction.ToggleConsole => "toggle_console",