]> Untitled Git - frog-ninja.git/blob - player/player.gd
11e41ac8eb165d6fbb4b83508675b14c99a40e8c
[frog-ninja.git] / player / player.gd
1 extends CharacterBody3D
2 class_name Player
3
4 ##########
5 # settings
6 ##########
7
8 @export_group("Movement")
9 @export var walk_speed := 8.0
10 @export var dash_length := 10.0
11
12 @export var air_speed := 3.0
13 @export var acceleration := 30.0
14 @export var rotation_speed := 10.0
15 @export var idle_timeout := 5.0
16
17 @export_group("Camera")
18 @export_range(1.0, 10.0) var camera_distance := 2.0
19 @export_range(0.0, 1.0) var mouse_sensitivity := 0.15
20 @export_range(0.0, 1.0) var mouse_sensitivity_x := 1.0
21 @export_range(0.0, 1.0) var mouse_sensitivity_y := 0.5
22 @export_range(0.0, 10.0) var joystick_sensitivity_x := 4.0
23 @export_range(0.0, 10.0) var joystick_sensitivity_y := 2.0
24
25 ######
26 # init
27 ######
28
29 @onready var _camera_pivot: Node3D = %camera_pivot
30 @onready var _camera: Camera3D = %camera
31 @onready var _camera_spring: SpringArm3D = %spring
32 @onready var _skin: AnimatedSkin = %skin
33
34 var _last_movement_direction := rotation
35 var _floor_normal := Vector3.ONE
36 var _ground_slope_input := 0.0
37 var _camera_input_direction := Vector2.ZERO
38 var _idle_time: float = 0.0
39 var _player_speed: float = walk_speed
40
41 enum CameraInput {MOUSE, JOYSTICK}
42 var _camera_input_method := CameraInput.MOUSE
43
44
45 func _ready() -> void:
46         _camera_spring.spring_length = camera_distance
47         
48         
49
50 #######
51 # input
52 #######
53
54 func _unhandled_input(event: InputEvent) -> void:
55         # If user clicks on the window, capture the mouse and direct the camera with it
56         if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
57                 return
58
59                         #_camera_input_direction *= mouse_sensitivity
60         if event is InputEventMouseMotion:
61                 _camera_input_method = CameraInput.MOUSE
62                 _camera_input_direction = event.screen_relative * mouse_sensitivity
63         elif event is InputEventJoypadMotion:
64                 # TODO: add these settings!
65                 _camera_input_method = CameraInput.JOYSTICK
66                 _camera_input_direction = Input.get_vector("camera-left", "camera-right", "camera-up", "camera-down")
67                 _camera_input_direction *= Vector2(joystick_sensitivity_x, -joystick_sensitivity_y)
68
69
70 func _input(event: InputEvent):
71         if event.is_action_pressed("player-dash"):
72                 _skin.transition_dash()
73                 _player_dash()
74         elif event.is_action_pressed("player-slash"):
75                 _skin.sword_visible()
76                 _skin.transition_slash()
77         elif event.is_action_pressed("player-shoot"):
78                 _skin.gun_visible()
79                 _skin.transition_gunfire()
80                 
81
82 ##########
83 # movement
84 ##########
85
86 func _physics_process(delta: float) -> void:
87         _process_camera(delta)
88         _process_player(delta)
89
90
91 # Get the XZ input direction based on player's input relative to the camera
92 func _get_player_move_direction() -> Vector3:
93         var input_dir := Input.get_vector("player-left", "player-right", "player-forward", "player-backward")
94         var forward := _camera.global_basis.z
95         var right := _camera.global_basis.x
96         var move_direction := (forward * input_dir.y + right * input_dir.x).normalized()
97         move_direction.y = 0
98         return move_direction
99
100
101 func _process_camera(delta: float) -> void:
102         # vertical camera rotation
103         _camera_pivot.rotation.x += _camera_input_direction.y * mouse_sensitivity_y * delta
104         _camera_pivot.rotation.x = clamp(_camera_pivot.rotation.x, -PI / 6, PI / 3)
105
106         # horizontal camera rotation
107         _camera_pivot.rotation.y -= _camera_input_direction.x * mouse_sensitivity_x * delta
108
109         # reset mouse movement vector if mouse input
110         if _camera_input_method == CameraInput.MOUSE:
111                 _camera_input_direction = Vector2.ZERO
112                 
113         # change spring length depending on player speed
114         _camera_spring.spring_length = lerp(
115                 _camera_spring.spring_length, camera_distance + velocity.length() / 4, delta 
116         ) 
117
118
119 func _process_player_on_floor(delta: float):
120         var move_direction := _get_player_move_direction()
121         
122         # if we're not stuck, then it's okay to set the velocity
123         _floor_normal = get_floor_normal()
124         _ground_slope_input = (PI / 2) - velocity.angle_to(_floor_normal)
125         velocity = velocity.move_toward(
126                 move_direction * (_player_speed + _ground_slope_input * _player_speed),
127                 acceleration * delta
128                 )
129         var movement_speed := Vector3(velocity.x, 0, velocity.z).length()
130         
131         # also, if we're moving, we're not idle
132         if move_direction.length() < 0.2:
133                 if velocity == Vector3.ZERO:
134                         _idle_time += delta
135         else:
136                 _last_movement_direction = move_direction
137                 _idle_time = 0.0
138
139         # if camera is unlocked, rotate whole skin to face movement direction
140         var skin_target_angle := Vector3.BACK.signed_angle_to(_last_movement_direction, Vector3.UP)
141         _skin.global_rotation.y = lerp_angle(
142                 _skin.global_rotation.y,
143                 skin_target_angle,
144                 rotation_speed * delta
145                 )
146                 
147         # lean into player momentum just a little bit
148         _skin.rotation.z = lerp_angle(
149                 _skin.rotation.z,
150                 clamp(_last_movement_direction.signed_angle_to(velocity, Vector3.UP) * movement_speed * 0.08, -PI/4, PI/ 4),
151                 rotation_speed * delta * 0.25
152                 )
153         
154         # let skin know how fast player is moving along the ground
155         _skin.set_walking_speed(movement_speed)
156
157
158 func _process_player(delta: float) -> void:
159         if is_on_floor():
160                 _process_player_on_floor(delta)
161                 
162                 if _idle_time > idle_timeout:
163                         _skin.transition_move()
164                 else:
165                         _skin.transition_move()
166         else:
167                 _skin.transition_falling()
168                 velocity += get_gravity() * air_speed * delta
169
170         move_and_slide()
171
172 #########
173 # actions
174 #########
175
176 func _player_dash():
177         if _last_movement_direction != Vector3.ZERO:
178                 var dash_local_pos = _last_movement_direction * dash_length
179                 # TODO: check if valid position, crop the vector to last valid position
180                 global_position += dash_local_pos