From 7e998e76dc25e65283d99cc45a3d78e6d2cfaf1a Mon Sep 17 00:00:00 2001 From: Rob Kelly Date: Mon, 29 Sep 2025 13:32:23 -0600 Subject: [PATCH] Basic character movement controls --- project.godot | 49 ++++++++++++++++++ src/player/camera_controller.gd | 4 +- src/player/movement_controller.gd | 61 +++++++++++++++++++++++ src/player/movement_controller.gd.uid | 1 + src/player/player.gd | 9 ++++ src/player/player.gd.uid | 1 + src/player/player.tscn | 27 +++++++++- src/player/player_input_controller.gd | 13 +++++ src/player/player_input_controller.gd.uid | 1 + src/world/world.tscn | 16 +++++- 10 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 src/player/movement_controller.gd create mode 100644 src/player/movement_controller.gd.uid create mode 100644 src/player/player.gd create mode 100644 src/player/player.gd.uid create mode 100644 src/player/player_input_controller.gd create mode 100644 src/player/player_input_controller.gd.uid diff --git a/project.godot b/project.godot index a96e35b..9f40e56 100644 --- a/project.godot +++ b/project.godot @@ -59,8 +59,57 @@ config/input/mouse_sensitivity_y=0.3 config/input/camera_acceleration=40.0 config/input/invert_pitch=false +[input] + +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_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_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_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) +] +} +jump={ +"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) +] +} +sprint={ +"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":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +interact={ +"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) +] +} +fire={ +"deadzone": 0.2, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(147, 21),"global_position":Vector2(156, 67),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null) +] +} +pause={ +"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":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} + [layer_names] 3d_render/layer_1="Global Noise Layer" 3d_physics/layer_1="World Geometry" 3d_physics/layer_2="Player" +3d_physics/layer_3="Layer 3" diff --git a/src/player/camera_controller.gd b/src/player/camera_controller.gd index 4785ea9..e6d28e0 100644 --- a/src/player/camera_controller.gd +++ b/src/player/camera_controller.gd @@ -3,6 +3,8 @@ class_name CameraController extends Node3D const PITCH_LIMIT := deg_to_rad(85.0) +@export var yaw_root: Node3D + @onready var _target := Vector2(rotation.x, rotation.y) @@ -17,5 +19,5 @@ func camera_motion(motion: Vector2) -> void: func _physics_process(delta: float) -> void: var accel: float = ProjectSettings.get_setting("game/config/input/camera_acceleration") var weight := 1.0 - exp(-accel * delta) - rotation.y = lerp_angle(rotation.y, _target.y, weight) + yaw_root.rotation.y = lerp_angle(yaw_root.rotation.y, _target.y, weight) rotation.x = lerp_angle(rotation.x, _target.x, weight) diff --git a/src/player/movement_controller.gd b/src/player/movement_controller.gd new file mode 100644 index 0000000..848b14a --- /dev/null +++ b/src/player/movement_controller.gd @@ -0,0 +1,61 @@ +class_name MovementController extends Node +## Character movement controller + +@export var movement_enabled := true + +@export_group("Speed") +@export var run_speed := 80.0 +@export var sprint_speed := 160.0 +@export var air_speed_factor := 0.1 + +@export_group("Jump") +@export var jump_force := 4.0 + +@export_group("Friction") +@export var ground_friction := 0.3 +@export var air_friction := 0.04 + +var sprinting := false +var movement := Vector2.ZERO + +@onready var character: CharacterBody3D = get_parent() + + +func get_speed() -> float: + var speed := run_speed + if sprinting: + speed = sprint_speed + if not character.is_on_floor(): + speed *= air_speed_factor + return speed + + +func get_friction() -> float: + return ground_friction if character.is_on_floor() else air_friction + + +## Make the character jump. +func jump() -> void: + if movement_enabled and character.is_on_floor(): + character.velocity.y = jump_force + + +func _physics_process(delta: float) -> void: + if not character.is_on_floor(): + # Apply gravity + character.velocity += character.get_gravity() * delta + + if movement_enabled: + # Apply movement input + var rel_movement := movement.rotated(-character.global_rotation.y) + var direction := Vector3(rel_movement.x, 0, rel_movement.y).normalized() + var motion := direction * get_speed() * delta + character.velocity.x += motion.x + character.velocity.z += motion.z + + # Apply friction + var weight := 1.0 - exp(-get_friction() * 60 * delta) + character.velocity.x = lerpf(character.velocity.x, 0, weight) + character.velocity.z = lerpf(character.velocity.z, 0, weight) + + character.move_and_slide() diff --git a/src/player/movement_controller.gd.uid b/src/player/movement_controller.gd.uid new file mode 100644 index 0000000..3878c23 --- /dev/null +++ b/src/player/movement_controller.gd.uid @@ -0,0 +1 @@ +uid://c0nxcunx0fo4r diff --git a/src/player/player.gd b/src/player/player.gd new file mode 100644 index 0000000..2d35185 --- /dev/null +++ b/src/player/player.gd @@ -0,0 +1,9 @@ +class_name Player extends CharacterBody3D +## The instance of the current player in the game world. + +## Global singleton accessor +static var instance: Player + + +func _init() -> void: + Player.instance = self diff --git a/src/player/player.gd.uid b/src/player/player.gd.uid new file mode 100644 index 0000000..50e4e61 --- /dev/null +++ b/src/player/player.gd.uid @@ -0,0 +1 @@ +uid://bihaf1xlwpkdo diff --git a/src/player/player.tscn b/src/player/player.tscn index bece78f..7b376e9 100644 --- a/src/player/player.tscn +++ b/src/player/player.tscn @@ -1,9 +1,12 @@ -[gd_scene load_steps=8 format=3 uid="uid://dtbulshrxetes"] +[gd_scene load_steps=12 format=3 uid="uid://dtbulshrxetes"] +[ext_resource type="Script" uid="uid://bihaf1xlwpkdo" path="res://src/player/player.gd" id="1_5f2sf"] [ext_resource type="Script" uid="uid://f3kssctvn2bo" path="res://src/player/camera_controller.gd" id="1_mf2ua"] [ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="1_rxibo"] [ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="2_mf2ua"] [ext_resource type="Script" uid="uid://032jyhgkb2rv" path="res://src/player/mouselook_controller.gd" id="4_244u8"] +[ext_resource type="Script" uid="uid://c0nxcunx0fo4r" path="res://src/player/movement_controller.gd" id="6_jiejy"] +[ext_resource type="Script" uid="uid://d11erhxna68vd" path="res://src/player/player_input_controller.gd" id="7_o822w"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_qnmj6"] @@ -15,20 +18,27 @@ duration = 0.4 transition = 7 ease = 0 +[sub_resource type="CapsuleMesh" id="CapsuleMesh_o822w"] +radius = 0.1 +height = 1.0 + [node name="Player" type="CharacterBody3D"] collision_layer = 3 +script = ExtResource("1_5f2sf") [node name="CollisionShape3D" type="CollisionShape3D" parent="."] shape = SubResource("CapsuleShape3D_qnmj6") [node name="MeshInstance3D" type="MeshInstance3D" parent="."] +gi_mode = 0 mesh = SubResource("CapsuleMesh_4anbu") [node name="CameraOffset" type="Node3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0) -[node name="CameraController" type="Node3D" parent="CameraOffset"] +[node name="CameraController" type="Node3D" parent="CameraOffset" node_paths=PackedStringArray("yaw_root")] script = ExtResource("1_mf2ua") +yaw_root = NodePath("../..") [node name="PhantomCamera3D" type="Node3D" parent="CameraOffset/CameraController"] script = ExtResource("1_rxibo") @@ -37,6 +47,19 @@ tween_resource = SubResource("Resource_244u8") noise_emitter_layer = 1 metadata/_custom_type_script = "uid://csjccrhj5wnx7" +[node name="MeshInstance3D" type="MeshInstance3D" parent="CameraOffset/CameraController/PhantomCamera3D"] +transform = Transform3D(1, 0, 0, 0, -4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 0, -0.45) +gi_mode = 0 +mesh = SubResource("CapsuleMesh_o822w") + [node name="MouselookController" type="Node" parent="CameraOffset/CameraController"] script = ExtResource("4_244u8") metadata/_custom_type_script = "uid://032jyhgkb2rv" + +[node name="MovementController" type="Node" parent="."] +script = ExtResource("6_jiejy") +metadata/_custom_type_script = "uid://c0nxcunx0fo4r" + +[node name="PlayerInputController" type="Node" parent="MovementController"] +script = ExtResource("7_o822w") +metadata/_custom_type_script = "uid://d11erhxna68vd" diff --git a/src/player/player_input_controller.gd b/src/player/player_input_controller.gd new file mode 100644 index 0000000..5271db2 --- /dev/null +++ b/src/player/player_input_controller.gd @@ -0,0 +1,13 @@ +class_name PlayerInputController extends Node +## Player controller component using project-defined player inputs. + +@onready var movement_controller: MovementController = get_parent() + + +func _physics_process(_delta: float) -> void: + if Input.is_action_just_pressed("jump"): + movement_controller.jump() + movement_controller.movement = Input.get_vector( + "move_left", "move_right", "move_forward", "move_back" + ) + movement_controller.sprinting = Input.is_action_pressed("sprint") diff --git a/src/player/player_input_controller.gd.uid b/src/player/player_input_controller.gd.uid new file mode 100644 index 0000000..b2aed7d --- /dev/null +++ b/src/player/player_input_controller.gd.uid @@ -0,0 +1 @@ +uid://d11erhxna68vd diff --git a/src/world/world.tscn b/src/world/world.tscn index dd982bb..fa04c31 100644 --- a/src/world/world.tscn +++ b/src/world/world.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=9 format=3 uid="uid://daxngklaqlyba"] +[gd_scene load_steps=11 format=3 uid="uid://daxngklaqlyba"] [ext_resource type="PackedScene" uid="uid://dtbulshrxetes" path="res://src/player/player.tscn" id="1_1k4gi"] [ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="2_6fy3g"] @@ -28,6 +28,11 @@ size = Vector2(16, 16) [sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_1k4gi"] +[sub_resource type="BoxMesh" id="BoxMesh_6fy3g"] + +[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_6fy3g"] +data = PackedVector3Array(-0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5) + [node name="World" type="Node3D"] [node name="WorldEnvironment" type="WorldEnvironment" parent="."] @@ -56,3 +61,12 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0) process_priority = 300 process_physics_priority = 300 script = ExtResource("2_6fy3g") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.5, 0.5, 4.5) +mesh = SubResource("BoxMesh_6fy3g") + +[node name="StaticBody3D" type="StaticBody3D" parent="MeshInstance3D"] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="MeshInstance3D/StaticBody3D"] +shape = SubResource("ConcavePolygonShape3D_6fy3g")