1 extends CharacterBody3D
6 @export_group("Movement")
7 @export var walk_speed := 3.0
8 @export var jog_speed := 6.0
9 @export var charge_speed := 20
11 @export var air_speed := 3.0
12 @export var acceleration := 20.0
13 @export var rotation_speed := 10.0
14 @export var idle_timeout := 5.0
15 @export var hard_landing_limit := 10.0
17 @export_group("Physics")
18 @export var push_force := 10
20 @export_group("Camera")
21 @export_range(1.0, 10.0) var camera_distance := 2.5
22 @export_range(0.0, 1.0) var mouse_sensitivity := 0.15
23 @export_range(0.0, 1.0) var mouse_sensitivity_x := 1.0
24 @export_range(0.0, 1.0) var mouse_sensitivity_y := 0.5
25 @export_range(0.0, 10.0) var joystick_sensitivity_x := 4.0
26 @export_range(0.0, 10.0) var joystick_sensitivity_y := 2.0
29 @onready var _debug: CanvasLayer = %debug
30 @onready var _camera_pivot: Node3D = %camera_pivot
31 @onready var _camera: Camera3D = %camera
32 @onready var _skin: AnimatedSkin = %skin
34 var _last_movement_direction := rotation
35 var _camera_input_direction := Vector2.ZERO
37 enum {CAMERA_MOUSE_INPUT, CAMERA_JOYSTICK_INPUT}
38 var _camera_input_method := CAMERA_MOUSE_INPUT
40 var _idle_time: float = 0.0
41 var _player_speed: float = walk_speed
44 func _ready() -> void:
45 $camera_pivot/SpringArm3D.spring_length = camera_distance
46 _debug.draw.add_vector(self, "velocity", 1, 1, Color(0,1,0,1))
47 _debug.draw.add_vector(self, "_last_movement_direction", 1, 1, Color(1,0,0,1))
48 _debug.stats.add_property(self, "velocity", "length")
49 _debug.stats.add_property(self, "_idle_time", "round")
52 func _physics_process(delta: float) -> void:
53 _process_camera(delta)
54 _process_player(delta)
57 func _unhandled_input(event: InputEvent) -> void:
58 # If user clicks on the window, capture the mouse and direct the camera with it
59 if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
62 #_camera_input_direction *= mouse_sensitivity
63 if event is InputEventMouseMotion:
64 _camera_input_method = CAMERA_MOUSE_INPUT
65 _camera_input_direction = event.screen_relative * mouse_sensitivity
66 elif event is InputEventJoypadMotion:
67 _camera_input_method = CAMERA_JOYSTICK_INPUT
68 _camera_input_direction = Input.get_vector("camera-left", "camera-right", "camera-up", "camera-down")
69 _camera_input_direction *= Vector2(joystick_sensitivity_x, -joystick_sensitivity_y)
72 func _input(event: InputEvent):
73 if event.is_action_pressed("player_run"):
74 _player_speed = jog_speed
75 elif event.is_action_released("player_run"):
76 _player_speed = walk_speed
78 if event.is_action_pressed("player_attack") and velocity.length() > jog_speed * .75:
79 _player_speed = charge_speed
82 # Get the XZ input direction based on player's input relative to the camera
83 func _get_player_move_direction() -> Vector3:
84 var input_dir := Input.get_vector("player_left", "player_right", "player_forward", "player_backward")
85 var forward := _camera.global_basis.z
86 var right := _camera.global_basis.x
87 var move_direction := (forward * input_dir.y + right * input_dir.x).normalized()
92 func _process_camera(delta: float) -> void:
93 # vertical camera rotation
94 _camera_pivot.rotation.x += _camera_input_direction.y * mouse_sensitivity_y * delta
95 _camera_pivot.rotation.x = clamp(_camera_pivot.rotation.x, -PI / 6, PI / 3)
97 # horizontal camera rotation
98 _camera_pivot.rotation.y -= _camera_input_direction.x * mouse_sensitivity_x * delta
100 # reset mouse movement vector if mouse input
101 if _camera_input_method == CAMERA_MOUSE_INPUT:
102 _camera_input_direction = Vector2.ZERO
105 func _process_player_on_floor(delta: float):
106 var move_direction := _get_player_move_direction()
108 # if we're not stuck, then it's okay to set the velocity
109 var floor_normal := move_direction.angle_to(get_floor_normal() * Vector3(1, 0, 1)) + 1
110 velocity = velocity.move_toward(move_direction * (_player_speed + 1 / floor_normal * 20), acceleration * delta)
111 var movement_speed := Vector3(velocity.x, 0, velocity.z).length()
113 # also, if we're moving, we're not idle
114 if move_direction.length() < 0.2:
115 if velocity == Vector3.ZERO:
118 _last_movement_direction = move_direction
121 # if camera is unlocked, rotate whole skin to face movement direction
122 # else, rotate to face camera pivot global Y direction
123 var skin_target_angle := Vector3.BACK.signed_angle_to(_last_movement_direction, Vector3.UP)
124 _skin.global_rotation.y = lerp_angle(
125 _skin.global_rotation.y,
127 rotation_speed * delta
129 _skin.set_grounded_speed(movement_speed)
131 # timescale tweaking for fun effect!
132 if movement_speed >= charge_speed * 0.75:
133 _skin.set_timescale(2.0)
135 # ignore objects when I'm going fast!
136 set_collision_mask_value(3, false)
138 _skin.set_timescale(1.0)
139 set_collision_mask_value(3, true)
142 func _process_player(delta: float) -> void:
145 _process_player_on_floor(delta)
148 velocity += get_gravity() * air_speed * delta
151 var movement_speed := velocity.length()
155 for i in get_slide_collision_count():
156 var c := get_slide_collision(i)
157 if c.get_collider() is RigidBody3D:
158 var col: RigidBody3D = c.get_collider()
159 col.apply_central_impulse(-c.get_normal() * push_force * movement_speed / col.mass)