diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..1617678
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,4 @@
+root = true
+
+[*]
+charset = utf-8
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..83264fb
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Normalize EOL for all files that Git considers text files.
+* text=auto eol=lf
diff --git a/.gitignore b/.gitignore
index 4dbc812..79d5dce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
# ---> Godot
# Godot 4+ specific ignores
.godot/
+/android/
# Godot-specific ignores
.import/
diff --git a/Imperfect Space.csproj b/Imperfect Space.csproj
new file mode 100644
index 0000000..d7ef75c
--- /dev/null
+++ b/Imperfect Space.csproj
@@ -0,0 +1,11 @@
+
+
+ net8.0
+ net9.0
+ true
+ ImperfectSpace
+ true
+
+
diff --git a/Imperfect Space.sln b/Imperfect Space.sln
new file mode 100644
index 0000000..b32fe55
--- /dev/null
+++ b/Imperfect Space.sln
@@ -0,0 +1,19 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Imperfect Space", "Imperfect Space.csproj", "{93B3F7D2-13DE-425C-89B4-C80E0B48EAB7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ ExportDebug|Any CPU = ExportDebug|Any CPU
+ ExportRelease|Any CPU = ExportRelease|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {93B3F7D2-13DE-425C-89B4-C80E0B48EAB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {93B3F7D2-13DE-425C-89B4-C80E0B48EAB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {93B3F7D2-13DE-425C-89B4-C80E0B48EAB7}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
+ {93B3F7D2-13DE-425C-89B4-C80E0B48EAB7}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
+ {93B3F7D2-13DE-425C-89B4-C80E0B48EAB7}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
+ {93B3F7D2-13DE-425C-89B4-C80E0B48EAB7}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/README.md b/README.md
index ee42f15..22eb597 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
# imperfect-space
-Space RPG Simulation Game
\ No newline at end of file
+Space RPG Simulation Game
diff --git a/icon.svg b/icon.svg
new file mode 100644
index 0000000..1345199
--- /dev/null
+++ b/icon.svg
@@ -0,0 +1 @@
+
diff --git a/icon.svg.import b/icon.svg.import
new file mode 100644
index 0000000..01cc6c0
--- /dev/null
+++ b/icon.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d1qrx53nygt1v"
+path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.svg"
+dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/project.godot b/project.godot
new file mode 100644
index 0000000..19b0a67
--- /dev/null
+++ b/project.godot
@@ -0,0 +1,76 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+config_version=5
+
+[application]
+
+config/name="Imperfect Space"
+run/main_scene="uid://bnmh1mebldx4l"
+config/features=PackedStringArray("4.6", "C#", "Forward Plus")
+config/icon="res://icon.svg"
+
+[dotnet]
+
+project/assembly_name="Imperfect Space"
+
+[input]
+
+jump={
+"deadzone": 0.2,
+"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_left={
+"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":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
+]
+}
+move_right={
+"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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
+]
+}
+move_forward={
+"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":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
+]
+}
+move_back={
+"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":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
+]
+}
+move_up={
+"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":32,"key_label":0,"unicode":32,"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)
+]
+}
+rotate_left={
+"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":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
+]
+}
+rotate_right={
+"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":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
+]
+}
+
+[physics]
+
+3d/physics_engine="Jolt Physics"
+
+[rendering]
+
+rendering_device/driver.windows="d3d12"
diff --git a/scenes/game.tscn b/scenes/game.tscn
new file mode 100644
index 0000000..b47df55
--- /dev/null
+++ b/scenes/game.tscn
@@ -0,0 +1,122 @@
+[gd_scene format=3 uid="uid://b3yh6h7fdt160"]
+
+[ext_resource type="Script" uid="uid://cue3c56axvyja" path="res://scripts/Player.cs" id="1_uwrxv"]
+[ext_resource type="Script" uid="uid://cy8nuarxbnd" path="res://scripts/GravityZone.cs" id="1_yqjtg"]
+[ext_resource type="Script" uid="uid://bwpdtkgmwjs7g" path="res://scripts/GravityReceiver.cs" id="3_lnu2h"]
+[ext_resource type="Script" uid="uid://n557xfrv0i6x" path="res://scripts/GeneralControlManager.cs" id="4_lbhrr"]
+[ext_resource type="Script" uid="uid://betypbypf6bf2" path="res://scripts/GameMenuController.cs" id="5_iywne"]
+
+[sub_resource type="PlaneMesh" id="PlaneMesh_8cj0n"]
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_8cj0n"]
+size = Vector3(10, 0.5, 10)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_uwrxv"]
+size = Vector3(10, 5, 10)
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_8cj0n"]
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_uwrxv"]
+
+[node name="Game" type="Node3D" unique_id=1201210338]
+
+[node name="Plane2" type="StaticBody3D" parent="." unique_id=289682669]
+transform = Transform3D(1, 0, 0, 0, 0.99999994, 0, 0, 0, 0.99999994, -20.320108, 0, 0)
+
+[node name="PlaneMesh" type="MeshInstance3D" parent="Plane2" unique_id=1859317875]
+transform = Transform3D(5, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0)
+mesh = SubResource("PlaneMesh_8cj0n")
+
+[node name="PlaneCollider" type="CollisionShape3D" parent="Plane2" unique_id=907638578]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.2, 0)
+shape = SubResource("BoxShape3D_8cj0n")
+
+[node name="GravityZone" type="Area3D" parent="Plane2" unique_id=600317513]
+gravity_space_override = 2
+script = ExtResource("1_yqjtg")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Plane2/GravityZone" unique_id=991265750]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0)
+shape = SubResource("BoxShape3D_uwrxv")
+
+[node name="Plane" type="StaticBody3D" parent="." unique_id=1260154250]
+transform = Transform3D(1, 0, 0, 0, 0.70710677, 0.70710677, 0, -0.70710677, 0.70710677, 0, 0, 0)
+
+[node name="PlaneMesh" type="MeshInstance3D" parent="Plane" unique_id=107049489]
+transform = Transform3D(5, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0)
+mesh = SubResource("PlaneMesh_8cj0n")
+
+[node name="PlaneCollider" type="CollisionShape3D" parent="Plane" unique_id=970373853]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.2, 0)
+shape = SubResource("BoxShape3D_8cj0n")
+
+[node name="GravityZone" type="Area3D" parent="Plane" unique_id=1326543003]
+gravity_space_override = 2
+script = ExtResource("1_yqjtg")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Plane/GravityZone" unique_id=772916098]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0)
+shape = SubResource("BoxShape3D_uwrxv")
+
+[node name="Player" type="CharacterBody3D" parent="." unique_id=612572257 node_paths=PackedStringArray("GameMenu")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5.1320534, 0.7854848)
+script = ExtResource("1_uwrxv")
+GameMenu = NodePath("../GameMenu")
+
+[node name="Camera" type="Camera3D" parent="Player" unique_id=1097983892]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
+fov = 90.0
+
+[node name="PlayerMesh" type="MeshInstance3D" parent="Player" unique_id=1440613415]
+mesh = SubResource("CapsuleMesh_8cj0n")
+
+[node name="PlayerCollider" type="CollisionShape3D" parent="Player" unique_id=886478863]
+shape = SubResource("CapsuleShape3D_uwrxv")
+
+[node name="GravityReceiver" type="Node3D" parent="Player" unique_id=1963556576]
+script = ExtResource("3_lnu2h")
+
+[node name="OmniLight" type="OmniLight3D" parent="." unique_id=1887866205]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6497431, -4.41529)
+omni_range = 95.383
+omni_attenuation = 0.254
+
+[node name="GeneralControlManager" type="Node" parent="." unique_id=1751385863 node_paths=PackedStringArray("GameMenu")]
+script = ExtResource("4_lbhrr")
+GameMenu = NodePath("../GameMenu")
+
+[node name="GameMenu" type="Control" parent="." unique_id=223510406]
+visible = false
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("5_iywne")
+
+[node name="Overlay" type="ColorRect" parent="GameMenu" unique_id=255916820]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+color = Color(0, 0, 0, 0.2509804)
+
+[node name="VBoxContainer" type="VBoxContainer" parent="GameMenu" unique_id=502136213]
+layout_mode = 0
+offset_left = 32.0
+offset_top = 32.0
+offset_right = 128.0
+offset_bottom = 63.0
+
+[node name="MainMenuButton" type="Button" parent="GameMenu/VBoxContainer" unique_id=1125420773]
+layout_mode = 2
+text = "Main Menu"
+
+[connection signal="body_entered" from="Plane2/GravityZone" to="Plane2/GravityZone" method="OnBodyEntered"]
+[connection signal="body_exited" from="Plane2/GravityZone" to="Plane2/GravityZone" method="OnBodyExited"]
+[connection signal="body_entered" from="Plane/GravityZone" to="Plane/GravityZone" method="OnBodyEntered"]
+[connection signal="body_exited" from="Plane/GravityZone" to="Plane/GravityZone" method="OnBodyExited"]
+[connection signal="pressed" from="GameMenu/VBoxContainer/MainMenuButton" to="GameMenu" method="OnMainMenu"]
diff --git a/scenes/main_menu.tscn b/scenes/main_menu.tscn
new file mode 100644
index 0000000..31a8acf
--- /dev/null
+++ b/scenes/main_menu.tscn
@@ -0,0 +1,35 @@
+[gd_scene format=3 uid="uid://bnmh1mebldx4l"]
+
+[ext_resource type="Script" uid="uid://b7inoxterktk3" path="res://scripts/MainMenu/MainMenuController.cs" id="1_l6cm7"]
+
+[node name="MainMenu" type="Control" unique_id=1620629749]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_l6cm7")
+
+[node name="CenterContainer" type="CenterContainer" parent="." unique_id=601944177]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer" unique_id=180209254]
+layout_mode = 2
+alignment = 1
+
+[node name="StartGameButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=364461644]
+layout_mode = 2
+text = "Start Game"
+
+[node name="ExitGameButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=1050417063]
+layout_mode = 2
+text = "Exit Game"
+
+[connection signal="pressed" from="CenterContainer/VBoxContainer/StartGameButton" to="." method="OnStartGame"]
+[connection signal="pressed" from="CenterContainer/VBoxContainer/ExitGameButton" to="." method="OnExitGame"]
diff --git a/scripts/Constants.cs b/scripts/Constants.cs
new file mode 100644
index 0000000..d2ebdd9
--- /dev/null
+++ b/scripts/Constants.cs
@@ -0,0 +1,3 @@
+public static partial class Constants
+{
+}
diff --git a/scripts/Constants.cs.uid b/scripts/Constants.cs.uid
new file mode 100644
index 0000000..e87393f
--- /dev/null
+++ b/scripts/Constants.cs.uid
@@ -0,0 +1 @@
+uid://dxtvl7k4e3nn4
diff --git a/scripts/GameMenuController.cs b/scripts/GameMenuController.cs
new file mode 100644
index 0000000..d5320c1
--- /dev/null
+++ b/scripts/GameMenuController.cs
@@ -0,0 +1,9 @@
+using Godot;
+
+public partial class GameMenuController : Control
+{
+ public void OnMainMenu()
+ {
+ GetTree().ChangeSceneToFile("res://scenes/main_menu.tscn");
+ }
+}
diff --git a/scripts/GameMenuController.cs.uid b/scripts/GameMenuController.cs.uid
new file mode 100644
index 0000000..c736551
--- /dev/null
+++ b/scripts/GameMenuController.cs.uid
@@ -0,0 +1 @@
+uid://betypbypf6bf2
diff --git a/scripts/GeneralControlManager.cs b/scripts/GeneralControlManager.cs
new file mode 100644
index 0000000..19bf5aa
--- /dev/null
+++ b/scripts/GeneralControlManager.cs
@@ -0,0 +1,41 @@
+using Godot;
+
+public partial class GeneralControlManager : Node
+{
+ [Export] Control GameMenu;
+
+ public override void _Ready()
+ {
+ Input.MouseMode = Input.MouseModeEnum.Captured;
+ }
+
+ public override void _Input(InputEvent @event)
+ {
+ if (@event.IsActionPressed("ui_cancel"))
+ {
+ if (GameMenu.Visible)
+ {
+ HideMenu();
+ }
+ else
+ {
+ ShowMenu();
+ }
+ return;
+ }
+ }
+
+ public void ShowMenu()
+ {
+ GameMenu.Show();
+
+ Input.MouseMode = Input.MouseModeEnum.Visible;
+ }
+
+ public void HideMenu()
+ {
+ GameMenu.Hide();
+
+ Input.MouseMode = Input.MouseModeEnum.Captured;
+ }
+}
diff --git a/scripts/GeneralControlManager.cs.uid b/scripts/GeneralControlManager.cs.uid
new file mode 100644
index 0000000..7694a93
--- /dev/null
+++ b/scripts/GeneralControlManager.cs.uid
@@ -0,0 +1 @@
+uid://n557xfrv0i6x
diff --git a/scripts/GravityReceiver.cs b/scripts/GravityReceiver.cs
new file mode 100644
index 0000000..94eab20
--- /dev/null
+++ b/scripts/GravityReceiver.cs
@@ -0,0 +1,22 @@
+using Godot;
+
+public partial class GravityReceiver : Node3D
+{
+ public bool InGravityZone = false;
+ public float GravityStrength = 0f;
+ public Vector3 GravityDirection = Vector3.Down;
+
+ public void EnterGravityZone(float gravityStrength, Vector3 gravityDirection)
+ {
+ InGravityZone = true;
+ GravityStrength = gravityStrength;
+ GravityDirection = gravityDirection;
+ }
+
+ public void ExitGravityZone()
+ {
+ InGravityZone = false;
+ GravityStrength = 0f;
+ GravityDirection = Vector3.Down;
+ }
+}
diff --git a/scripts/GravityReceiver.cs.uid b/scripts/GravityReceiver.cs.uid
new file mode 100644
index 0000000..34b16fb
--- /dev/null
+++ b/scripts/GravityReceiver.cs.uid
@@ -0,0 +1 @@
+uid://bwpdtkgmwjs7g
diff --git a/scripts/GravityZone.cs b/scripts/GravityZone.cs
new file mode 100644
index 0000000..37d5d7d
--- /dev/null
+++ b/scripts/GravityZone.cs
@@ -0,0 +1,16 @@
+using Godot;
+
+public partial class GravityZone : Area3D
+{
+ public void OnBodyEntered(Node3D body)
+ {
+ GravityReceiver receiver = body.GetNodeOrNull("GravityReceiver");
+ receiver?.EnterGravityZone(Gravity, GlobalTransform.Basis.Y);
+ }
+
+ public void OnBodyExited(Node3D body)
+ {
+ GravityReceiver receiver = body.GetNodeOrNull("GravityReceiver");
+ receiver?.ExitGravityZone();
+ }
+}
diff --git a/scripts/GravityZone.cs.uid b/scripts/GravityZone.cs.uid
new file mode 100644
index 0000000..73ef3d3
--- /dev/null
+++ b/scripts/GravityZone.cs.uid
@@ -0,0 +1 @@
+uid://cy8nuarxbnd
diff --git a/scripts/Helpers.cs b/scripts/Helpers.cs
new file mode 100644
index 0000000..c1cfad6
--- /dev/null
+++ b/scripts/Helpers.cs
@@ -0,0 +1,3 @@
+public static partial class Helpers
+{
+}
diff --git a/scripts/Helpers.cs.uid b/scripts/Helpers.cs.uid
new file mode 100644
index 0000000..faf8c81
--- /dev/null
+++ b/scripts/Helpers.cs.uid
@@ -0,0 +1 @@
+uid://de2nkadeybqn0
diff --git a/scripts/MainMenu/MainMenuController.cs b/scripts/MainMenu/MainMenuController.cs
new file mode 100644
index 0000000..970fe6a
--- /dev/null
+++ b/scripts/MainMenu/MainMenuController.cs
@@ -0,0 +1,14 @@
+using Godot;
+
+public partial class MainMenuController : Node
+{
+ public void OnStartGame()
+ {
+ GetTree().ChangeSceneToFile("res://scenes/game.tscn");
+ }
+
+ public void OnExitGame()
+ {
+ GetTree().Quit();
+ }
+}
diff --git a/scripts/MainMenu/MainMenuController.cs.uid b/scripts/MainMenu/MainMenuController.cs.uid
new file mode 100644
index 0000000..65a044c
--- /dev/null
+++ b/scripts/MainMenu/MainMenuController.cs.uid
@@ -0,0 +1 @@
+uid://b7inoxterktk3
diff --git a/scripts/Player.cs b/scripts/Player.cs
new file mode 100644
index 0000000..b00bd12
--- /dev/null
+++ b/scripts/Player.cs
@@ -0,0 +1,200 @@
+using Godot;
+
+public partial class Player : CharacterBody3D
+{
+ [Export] Control GameMenu;
+
+ [Export] public float Speed = 5f;
+ [Export] public float JumpForce = 5f;
+ [Export] public float MouseSensitivity = 0.2f;
+
+ private Vector3 gravityVelocity = Vector3.Zero;
+ private Vector3 movementVelocity = Vector3.Zero;
+ private float cameraPitch = 0f;
+ private Camera3D camera;
+ private GravityReceiver gravityReceiver;
+
+ public override void _Ready()
+ {
+ camera = GetNode("Camera");
+ gravityReceiver = GetNode("GravityReceiver");
+ }
+
+ public override void _PhysicsProcess(double delta)
+ {
+ Vector3 newGravityVelocity = gravityVelocity;
+ Vector3 newMovementVelocity = movementVelocity;
+
+ if (IsInGravity())
+ {
+ ProcessGravity(delta, ref newGravityVelocity);
+ if (!GameMenu.Visible)
+ {
+ ProcessControlsGravity(ref newGravityVelocity, ref newMovementVelocity);
+ }
+ }
+ else
+ {
+ newGravityVelocity = Vector3.Zero;
+
+ ProcessCamera(delta);
+ if (!GameMenu.Visible)
+ {
+ ProcessControls(ref newMovementVelocity);
+ }
+ }
+
+ gravityVelocity = newGravityVelocity;
+ movementVelocity = newMovementVelocity;
+ Velocity = newGravityVelocity + newMovementVelocity;
+
+ MoveAndSlide();
+ }
+
+ public override void _Input(InputEvent @event)
+ {
+ if (GameMenu.Visible)
+ {
+ return;
+ }
+
+ if (@event is InputEventMouseMotion motion)
+ {
+ float yawDelta = -motion.Relative.X * MouseSensitivity;
+ float yawDeltaRad = Mathf.DegToRad(yawDelta);
+ float pitchDelta = -motion.Relative.Y * MouseSensitivity;
+ float pitchDeltaRad = Mathf.DegToRad(pitchDelta);
+
+ RotateObjectLocal(Vector3.Up, yawDeltaRad);
+
+ if (IsInGravity())
+ {
+ cameraPitch = Mathf.Clamp(cameraPitch + pitchDelta, -90f, 90f);
+ }
+ else
+ {
+ RotateObjectLocal(Vector3.Right, pitchDeltaRad);
+ }
+
+ camera.RotationDegrees = new Vector3(cameraPitch, 0, 0);
+ }
+ }
+
+ public bool IsInGravity()
+ {
+ return gravityReceiver.InGravityZone && gravityReceiver.GravityStrength > 0f;
+ }
+
+ public bool IsOnGravityFloor()
+ {
+ for (int i = 0; i < GetSlideCollisionCount(); i++)
+ {
+ KinematicCollision3D collision = GetSlideCollision(i);
+ Vector3 collisionNormal = collision.GetNormal();
+
+ float alignment = collisionNormal.Dot(gravityReceiver.GravityDirection);
+
+ if (alignment > 0.7f)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void ProcessGravity(double delta, ref Vector3 newGravityVelocity)
+ {
+ if (!IsOnGravityFloor())
+ {
+ newGravityVelocity -= gravityReceiver.GravityDirection * gravityReceiver.GravityStrength * (float)delta;
+ }
+ else
+ {
+ newGravityVelocity = -gravityReceiver.GravityDirection;
+ }
+
+ Vector3 currentUp = GlobalTransform.Basis.Y;
+ Vector3 targetUp = gravityReceiver.GravityDirection;
+
+ Vector3 axis = currentUp.Cross(targetUp);
+ if (axis.Length() < 0.00001f)
+ {
+ return;
+ }
+ float angle = currentUp.AngleTo(targetUp);
+
+ Rotate(axis.Normalized(), angle * (float)delta * 10f);
+ }
+
+ private void ProcessCamera(double delta)
+ {
+ if (cameraPitch > 0.01f)
+ {
+ cameraPitch -= 90f * (float)delta;
+ }
+ else if (cameraPitch < -0.001f)
+ {
+ cameraPitch += 90f * (float)delta;
+ }
+ if (Mathf.Abs(cameraPitch) < 1f)
+ {
+ cameraPitch = 0f;
+ }
+ camera.RotationDegrees = new Vector3(cameraPitch, 0, 0);
+ }
+
+ private void ProcessControlsGravity(ref Vector3 newGravityVelocity, ref Vector3 newMovementVelocity)
+ {
+ float inputX = Input.GetAxis("move_left", "move_right");
+ float inputZ = Input.GetAxis("move_forward", "move_back");
+
+ Vector3 direction = Transform.Basis.X * inputX + Transform.Basis.Z * inputZ;
+
+ if (direction != Vector3.Zero)
+ {
+ newMovementVelocity.X = direction.X * Speed;
+ newMovementVelocity.Y = direction.Y * Speed;
+ newMovementVelocity.Z = direction.Z * Speed;
+ }
+ else
+ {
+ newMovementVelocity.X = 0f;
+ newMovementVelocity.Y = 0f;
+ newMovementVelocity.Z = 0f;
+ }
+
+ if (Input.IsActionJustPressed("jump") && IsOnGravityFloor())
+ {
+ newGravityVelocity = gravityReceiver.GravityDirection * JumpForce;
+ }
+ }
+
+ private void ProcessControls(ref Vector3 newMovementVelocity)
+ {
+ float inputX = Input.GetAxis("move_left", "move_right");
+ float inputY = -Input.GetAxis("move_up", "move_down");
+ float inputZ = Input.GetAxis("move_forward", "move_back");
+
+ Vector3 direction = Transform.Basis.X * inputX + Transform.Basis.Y * inputY + Transform.Basis.Z * inputZ;
+
+ if (direction != Vector3.Zero)
+ {
+ newMovementVelocity.X = direction.X * Speed;
+ newMovementVelocity.Y = direction.Y * Speed;
+ newMovementVelocity.Z = direction.Z * Speed;
+ }
+ else
+ {
+ newMovementVelocity.X = 0f;
+ newMovementVelocity.Y = 0f;
+ newMovementVelocity.Z = 0f;
+ }
+
+ float inputRotateZ = Input.GetAxis("rotate_left", "rotate_right");
+
+ float rotateAmountZ = Mathf.DegToRad(inputRotateZ);
+
+ RotateObjectLocal(Vector3.Forward, rotateAmountZ);
+ }
+}
diff --git a/scripts/Player.cs.uid b/scripts/Player.cs.uid
new file mode 100644
index 0000000..1d81a8c
--- /dev/null
+++ b/scripts/Player.cs.uid
@@ -0,0 +1 @@
+uid://cue3c56axvyja