2 extends CharacterBody3D
6 signal foundActionableNode
7 signal noActionableNode
11 @export_category("Movement")
12 @export var movement_enabled := true
13 @export var walk_speed := 5.0
14 @export var acceleration := 30.0
15 @export var gravity := 10.0
17 @export_category("Camera")
18 @export var camera_movement_enabled := true
19 @export_range(0.0, 1.0) var mouse_sensitivity := 0.2
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 var look_at_rotation_speed := 3.0
24 enum {CAMERA_MOUSE_INPUT, CAMERA_JOYSTICK_INPUT}
25 var _camera_input_method := CAMERA_MOUSE_INPUT
26 var _camera_input_direction := Vector2.ZERO
28 const _camera_highest_x_angle := PI / 2
29 const _camera_lowest_x_angle := -PI / 2
31 @onready var _camera: Camera3D = %Camera3D
32 @onready var _phone = %Phone
34 var actionable_node: Actionable = null
35 var look_at_pos: Vector3 = Vector3.ZERO
39 func _physics_process(delta: float) -> void:
40 if camera_movement_enabled:
41 _camera.rotation.y -= _camera_input_direction.x * mouse_sensitivity_x * delta
42 _camera.rotation.x -= _camera_input_direction.y * mouse_sensitivity_y * delta
43 _camera.rotation.x = clamp(
44 _camera.rotation.x, _camera_lowest_x_angle, _camera_highest_x_angle
47 # reset mouse movement vector if mouse input
48 if _camera_input_method == CAMERA_MOUSE_INPUT:
49 _camera_input_direction = Vector2.ZERO
50 elif look_at_pos != Vector3.ZERO:
51 var target_vector: Vector3 = look_at_pos - _camera.global_position
52 var target_angle_y = Vector3.FORWARD.signed_angle_to(target_vector, Vector3.UP)
53 _camera.global_rotation.y = lerp_angle(
54 _camera.global_rotation.y,
56 delta * look_at_rotation_speed
58 var target_angle_x = atan2(target_vector.y, target_vector.length())
59 _camera.rotation.x = lerp_angle(
62 delta * look_at_rotation_speed
67 var move_direction := _get_player_move_direction()
68 velocity = velocity.move_toward(move_direction * walk_speed, acceleration * delta)
70 velocity += Vector3.DOWN * gravity * delta
75 func _unhandled_input(event: InputEvent) -> void:
76 if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
79 if event is InputEventMouseMotion:
80 _camera_input_method = CAMERA_MOUSE_INPUT
81 _camera_input_direction = event.screen_relative * mouse_sensitivity
86 func phone_out(b: bool) -> void:
90 func toggle_phone_out() -> void:
92 _phone.visible = !_phone.visible
95 func camera_look_at(pos: Vector3, snapTo: bool = false) -> void:
97 _camera.look_at(pos, Vector3.UP)
101 func _get_player_move_direction() -> Vector3:
102 var input_dir := Input.get_vector("player-left", "player-right", "player-up", "player-down")
103 var forward := _camera.global_basis.z
104 var right := _camera.global_basis.x
105 var move_direction := (forward * input_dir.y + right * input_dir.x).normalized()
107 return move_direction
110 func _on_talking_area_body_entered(body: Node3D) -> void:
111 if body is Actionable:
112 print("Hello " + str(body) + ": " + body.get("prompt"))
113 actionable_node = body
114 foundActionableNode.emit(body)
117 func _on_talking_area_body_exited(body: Node3D) -> void:
118 if body is Actionable:
119 if actionable_node == body:
120 print("Goodbye " + str(body))
121 actionable_node = null
122 noActionableNode.emit()