2025-12-13 12:49:08 +00:00
|
|
|
class_name PlayerController extends RigidBody3D
|
2023-03-06 14:06:36 +00:00
|
|
|
|
2026-01-12 17:39:34 +00:00
|
|
|
@export var enabled: bool = false:
|
|
|
|
|
set(value):
|
2026-01-19 18:36:05 +00:00
|
|
|
if enabled == value: return
|
2026-01-12 17:39:34 +00:00
|
|
|
enabled = value
|
2026-01-19 15:41:57 +00:00
|
|
|
_apply_enabled_state()
|
2026-01-12 17:39:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func _apply_enabled_state() -> void:
|
|
|
|
|
# Kill any existing jitter tween to prevent stacking
|
2026-01-19 18:36:05 +00:00
|
|
|
if jitter_tween: jitter_tween.kill()
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2026-01-12 17:39:34 +00:00
|
|
|
if enabled:
|
|
|
|
|
camera.make_current()
|
|
|
|
|
get_viewport().gui_release_focus()
|
|
|
|
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
|
|
|
|
jitter_tween = create_tween()
|
|
|
|
|
jitter_tween.tween_property(self, "jitter_strength", 1.0, 1.0)
|
2026-01-19 18:36:05 +00:00
|
|
|
|
2026-01-12 17:39:34 +00:00
|
|
|
if has_entered:
|
|
|
|
|
ui_exited.emit()
|
2026-01-19 18:36:05 +00:00
|
|
|
|
2026-01-16 17:11:22 +00:00
|
|
|
# Show hand cursor when player is enabled
|
|
|
|
|
if hand_cursor:
|
|
|
|
|
hand_cursor.visible = true
|
2026-01-12 17:39:34 +00:00
|
|
|
else:
|
|
|
|
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
|
|
|
|
jitter_tween = create_tween()
|
|
|
|
|
jitter_tween.tween_property(self, "jitter_strength", 0.0, 0.5)
|
|
|
|
|
if has_entered:
|
|
|
|
|
ui_exited.emit()
|
2026-01-16 17:11:22 +00:00
|
|
|
# Hide hand cursor when player is disabled
|
|
|
|
|
if hand_cursor:
|
|
|
|
|
hand_cursor.visible = false
|
2026-01-12 17:39:34 +00:00
|
|
|
sleeping = not enabled
|
2025-02-06 18:15:39 +00:00
|
|
|
|
|
|
|
|
@export var mouse_sensitivity: Vector2 = Vector2(6, 5)
|
|
|
|
|
|
2025-10-29 21:35:33 +00:00
|
|
|
@export var initial_pitch: float = 50
|
|
|
|
|
|
2026-01-16 17:11:22 +00:00
|
|
|
@export_range (0.0, 10.0) var max_speed: float = 3
|
|
|
|
|
@export_range (0.0, 10.0) var max_acceleration: float = 5
|
|
|
|
|
@export_range (0.0, 20.0) var damp: float = 10
|
|
|
|
|
@export_range (0.1, 1.0) var mouse_jerk:float = 0.5
|
|
|
|
|
@export_range (10.0, 100.0) var gamepad_response:float = 50.0
|
|
|
|
|
@export_range (50.0, 500.0) var mouse_jerk_rejection:float = 200.0
|
|
|
|
|
@export var max_angle:float = 75.0
|
2023-03-06 14:06:36 +00:00
|
|
|
|
|
|
|
|
@export var camera_jitter_speed:float = 3
|
|
|
|
|
@export var angular_jitter:Vector3 = Vector3(0.1, 0, 0.05)
|
|
|
|
|
@export var angular_jitter_speed:Vector3 = Vector3(2, 1, 1)
|
2023-07-08 20:05:18 +00:00
|
|
|
@export var location_jitter:Vector3 = Vector3(0.05, 0.05, 0.05)
|
|
|
|
|
@export var location_jitter_speed:Vector3 = Vector3(2, 0.3, 1)
|
2026-01-12 18:42:14 +00:00
|
|
|
|
2023-03-26 22:30:27 +00:00
|
|
|
var jitter_strength: float = 0
|
2026-01-12 17:39:34 +00:00
|
|
|
var jitter_tween: Tween = null
|
2023-03-06 14:06:36 +00:00
|
|
|
|
|
|
|
|
var loc_noise_spot: Vector3 = Vector3.ZERO
|
|
|
|
|
var rot_noise_spot: Vector3 = Vector3.ZERO
|
|
|
|
|
|
|
|
|
|
var rotation_speed: Vector2 = Vector2.ZERO
|
|
|
|
|
var current_mouse_rotation: Vector2 = Vector2.ZERO
|
2025-02-06 18:15:39 +00:00
|
|
|
|
2023-03-06 14:06:36 +00:00
|
|
|
var noise: Noise = FastNoiseLite.new()
|
2025-08-17 22:13:20 +00:00
|
|
|
var crouched:bool = false:
|
|
|
|
|
set(set_crouching):
|
2026-01-12 18:42:14 +00:00
|
|
|
if !is_node_ready(): return
|
|
|
|
|
if set_crouching and not crouched:
|
|
|
|
|
if State.reduce_motion:
|
|
|
|
|
$PlayerAnimationPlayer.play("reduced_crouch")
|
|
|
|
|
elif trigger_slow_crouch:
|
|
|
|
|
$PlayerAnimationPlayer.play("crouch")
|
|
|
|
|
trigger_slow_crouch = false
|
|
|
|
|
else:
|
|
|
|
|
$PlayerAnimationPlayer.play("fast_crouch")
|
|
|
|
|
crouched = set_crouching
|
|
|
|
|
elif (not set_crouching and crouched) and not crouch_held:
|
|
|
|
|
if can_stand_up():
|
2026-01-12 17:39:34 +00:00
|
|
|
if State.reduce_motion:
|
2026-01-12 18:42:14 +00:00
|
|
|
$PlayerAnimationPlayer.play("reduced_stand_up")
|
2026-01-12 17:39:34 +00:00
|
|
|
elif trigger_slow_crouch:
|
2026-01-12 18:42:14 +00:00
|
|
|
$PlayerAnimationPlayer.play("stand_up")
|
2025-09-04 23:50:38 +00:00
|
|
|
trigger_slow_crouch = false
|
|
|
|
|
else:
|
2026-01-12 18:42:14 +00:00
|
|
|
$PlayerAnimationPlayer.play("fast_stand_up")
|
2025-08-17 22:13:20 +00:00
|
|
|
crouched = set_crouching
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2025-06-03 21:18:58 +00:00
|
|
|
@onready var yaw: Node3D = $Yaw
|
|
|
|
|
@onready var pitch: Node3D = $Yaw/Pitch
|
|
|
|
|
@onready var mount: Node3D = $Yaw/Pitch/Mount
|
|
|
|
|
@onready var camera: Camera3D = $Yaw/Pitch/Mount/Camera3D
|
2023-04-22 13:11:10 +00:00
|
|
|
@onready var focus_ray: RayCast3D = $Yaw/Pitch/Mount/Camera3D/RayCast3D
|
2025-06-10 19:09:47 +00:00
|
|
|
@onready var ui_prober: Area3D = $Yaw/Pitch/Mount/Camera3D/UiProber
|
2026-01-16 17:11:22 +00:00
|
|
|
@onready var hand_cursor: TextureRect = %Cursor
|
|
|
|
|
|
|
|
|
|
# Cursor textures (preloaded for performance)
|
|
|
|
|
const cursor_default: Texture2D = preload("res://import/interface-elements/cursor_point.png")
|
|
|
|
|
const cursor_point: Texture2D = preload("res://import/interface-elements/cursor_grab.png")
|
2023-03-06 14:06:36 +00:00
|
|
|
|
2025-12-13 12:41:18 +00:00
|
|
|
@onready var base_fov := camera.fov
|
2025-02-06 18:15:39 +00:00
|
|
|
var zoomed:bool = false:
|
2024-09-15 09:30:31 +00:00
|
|
|
set(zoom):
|
|
|
|
|
if zoomed != zoom:
|
|
|
|
|
if zoom:
|
2025-12-13 12:41:18 +00:00
|
|
|
var zoom_tween := create_tween()
|
2025-08-17 22:14:22 +00:00
|
|
|
zoom_tween.tween_property(camera, "fov", base_fov*0.5, 0.5)
|
2024-09-15 09:30:31 +00:00
|
|
|
else:
|
2025-12-13 12:41:18 +00:00
|
|
|
var zoom_tween := create_tween()
|
2025-08-17 22:14:22 +00:00
|
|
|
zoom_tween.tween_property(camera, "fov", base_fov, 0.5)
|
2026-01-19 14:52:26 +00:00
|
|
|
zoomed = zoom
|
2023-04-22 13:11:10 +00:00
|
|
|
signal ui_entered
|
|
|
|
|
signal ui_exited
|
2023-03-06 14:06:36 +00:00
|
|
|
|
|
|
|
|
func _ready():
|
2026-01-15 12:04:52 +00:00
|
|
|
State.player = self
|
|
|
|
|
State.player_view = %Camera3D
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2026-01-19 18:36:05 +00:00
|
|
|
# Connect to central player enable signal.
|
|
|
|
|
Scenes.player_enable.connect(_on_player_enable)
|
|
|
|
|
|
2024-09-15 09:30:31 +00:00
|
|
|
_handle_jitter(0)
|
2026-01-12 18:21:21 +00:00
|
|
|
|
|
|
|
|
pitch.rotation_degrees.x = initial_pitch
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2025-06-10 19:09:47 +00:00
|
|
|
ui_prober.area_entered.connect(_on_ray_entered)
|
|
|
|
|
ui_prober.area_exited.connect(_on_ray_exited)
|
2026-01-12 18:21:21 +00:00
|
|
|
|
|
|
|
|
$CrouchDetector.area_entered.connect(enter_crouch)
|
2025-08-17 22:13:20 +00:00
|
|
|
$CrouchDetector.area_exited.connect(exit_crouch)
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2026-01-16 17:11:22 +00:00
|
|
|
# Setup hand cursor
|
|
|
|
|
_setup_hand_cursor()
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2026-01-12 17:39:34 +00:00
|
|
|
# Apply exported enabled state now that nodes are ready
|
|
|
|
|
_apply_enabled_state()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_player_enable(enable: bool) -> void:
|
|
|
|
|
enabled = enable
|
|
|
|
|
|
2026-01-16 17:11:22 +00:00
|
|
|
## Setup the hand cursor in the center of the screen
|
|
|
|
|
func _setup_hand_cursor() -> void:
|
|
|
|
|
# Configure the existing TextureRect for cursor display
|
|
|
|
|
hand_cursor.texture = cursor_default # Start with default cursor
|
|
|
|
|
hand_cursor.visible = false
|
|
|
|
|
|
2026-01-16 12:03:39 +00:00
|
|
|
## Restores player position and camera rotation from save game
|
|
|
|
|
func restore_from_save(save: SaveGame) -> void:
|
|
|
|
|
if save.player_position != Vector3.ZERO:
|
|
|
|
|
global_position = save.player_position
|
2026-01-19 14:52:26 +00:00
|
|
|
if save.player_yaw != 0:
|
2026-01-16 12:03:39 +00:00
|
|
|
yaw.rotation.y = save.player_yaw
|
2026-01-19 14:52:26 +00:00
|
|
|
if save.player_pitch != 0:
|
2026-01-16 12:03:39 +00:00
|
|
|
pitch.rotation.x = save.player_pitch
|
|
|
|
|
|
2026-01-12 18:42:14 +00:00
|
|
|
func _process(_delta) -> void:
|
2026-01-12 17:39:34 +00:00
|
|
|
if not enabled:
|
|
|
|
|
return
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2026-01-12 17:39:34 +00:00
|
|
|
if not has_entered:
|
2026-01-12 17:09:56 +00:00
|
|
|
camera.fov = base_fov / (1 + Input.get_action_raw_strength("zoom_in_controller"))
|
2025-06-10 19:09:47 +00:00
|
|
|
|
|
|
|
|
var has_entered:bool = false:
|
|
|
|
|
set(val):
|
|
|
|
|
if val != has_entered:
|
|
|
|
|
if val:
|
|
|
|
|
ui_entered.emit()
|
|
|
|
|
else:
|
|
|
|
|
ui_exited.emit()
|
|
|
|
|
has_entered = val
|
|
|
|
|
if not has_entered:
|
|
|
|
|
delay_passed = false
|
|
|
|
|
var delay_passed:bool = false
|
|
|
|
|
|
2026-01-18 21:29:57 +00:00
|
|
|
func _on_ray_entered(_area : Area3D):
|
2026-01-15 12:04:52 +00:00
|
|
|
var parent := _area.get_parent() as Interactable
|
2026-01-18 21:29:57 +00:00
|
|
|
if not parent.visible: return
|
2026-01-19 14:52:26 +00:00
|
|
|
assert(parent != null, "Ray entered non-interactable area!")
|
2026-01-19 15:41:57 +00:00
|
|
|
#printt("ray entered", parent.name, parent)
|
2026-01-15 12:04:52 +00:00
|
|
|
parent.hover = true
|
2026-01-16 17:11:22 +00:00
|
|
|
# Switch to pointing hand cursor when hovering over interactable
|
|
|
|
|
if hand_cursor:
|
|
|
|
|
hand_cursor.texture = cursor_point
|
2026-01-15 12:04:52 +00:00
|
|
|
|
|
|
|
|
func _on_ray_exited(_area):
|
|
|
|
|
var parent := _area.get_parent() as Interactable
|
2026-01-18 21:29:57 +00:00
|
|
|
if not parent.visible: return
|
2026-01-19 15:41:57 +00:00
|
|
|
#printt("ray exited", parent.name, parent)
|
2026-01-15 12:04:52 +00:00
|
|
|
parent.hover = false
|
2026-01-16 17:11:22 +00:00
|
|
|
# Switch back to default cursor when not hovering
|
|
|
|
|
if hand_cursor:
|
|
|
|
|
hand_cursor.texture = cursor_default
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2025-06-10 19:09:47 +00:00
|
|
|
|
2026-01-12 17:39:34 +00:00
|
|
|
func _physics_process(delta: float):
|
|
|
|
|
if enabled:
|
2024-09-15 09:30:31 +00:00
|
|
|
_handle_movement(delta)
|
|
|
|
|
_handle_rotation(delta)
|
2026-01-12 17:39:34 +00:00
|
|
|
if jitter_strength > 0 and not State.reduce_motion:
|
|
|
|
|
_handle_jitter(delta)
|
2023-03-06 14:06:36 +00:00
|
|
|
|
|
|
|
|
func _handle_movement(delta:float):
|
2024-09-15 09:30:31 +00:00
|
|
|
var input:Vector2 = Vector2(Input.get_action_strength("player_right") - Input.get_action_strength("player_left"),
|
|
|
|
|
Input.get_action_strength("player_backwards")*0.8 - Input.get_action_strength("player_forwards"))
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2024-09-15 09:30:31 +00:00
|
|
|
if input.length()>1:
|
|
|
|
|
input = input.normalized()
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2024-09-15 09:30:31 +00:00
|
|
|
var direction: Vector3 = Vector3(input.x, 0, input.y)
|
2026-01-19 14:52:26 +00:00
|
|
|
|
|
|
|
|
direction = yaw.global_transform.basis.x * direction.x + transform.basis.y * direction.y + yaw.global_transform.basis.z * direction.z
|
|
|
|
|
|
2024-09-15 09:30:31 +00:00
|
|
|
if linear_velocity.length() > (linear_velocity + (direction*max_speed - linear_velocity)).length():
|
|
|
|
|
direction = Vector3.ZERO
|
|
|
|
|
else:
|
|
|
|
|
direction *= (direction*max_speed - linear_velocity).length()*max_acceleration
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2024-09-15 09:30:31 +00:00
|
|
|
linear_damp = damp * max(0.5, 1 - input.length())
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2024-09-15 09:30:31 +00:00
|
|
|
apply_central_impulse(direction*delta)
|
2023-03-06 14:06:36 +00:00
|
|
|
|
|
|
|
|
func _handle_rotation(delta:float):
|
2024-09-15 09:30:31 +00:00
|
|
|
var smoothness = min(3, 60.0/Engine.get_frames_per_second())
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2025-12-13 12:41:18 +00:00
|
|
|
var input_speed := Vector2( Input.get_action_strength("look_right")-Input.get_action_strength("look_left"), Input.get_action_strength("look_up")-Input.get_action_strength("look_down")) * gamepad_response
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2025-10-07 22:33:15 +00:00
|
|
|
# secretly, inverted y axis is the default
|
|
|
|
|
if not State.inverty_y_axis: input_speed *= Vector2(1, -1)
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2024-09-15 09:30:31 +00:00
|
|
|
if current_mouse_rotation.length()>0:
|
|
|
|
|
input_speed = current_mouse_rotation
|
|
|
|
|
current_mouse_rotation = Vector2.ZERO
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2024-09-15 09:30:31 +00:00
|
|
|
rotation_speed = rotation_speed * (1-mouse_jerk*smoothness) + input_speed * mouse_jerk * smoothness
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2024-09-15 09:30:31 +00:00
|
|
|
if rotation_speed.y > 0 and pitch.rotation_degrees.x < 0:
|
|
|
|
|
rotation_speed.y *= 1-pow(pitch.rotation_degrees.x/-max_angle, 4)
|
|
|
|
|
elif rotation_speed.y < 0 and pitch.rotation_degrees.x > 0 :
|
|
|
|
|
rotation_speed.y *= 1-pow(pitch.rotation_degrees.x/max_angle, 4)
|
2026-01-19 14:52:26 +00:00
|
|
|
|
|
|
|
|
|
2025-10-07 22:33:15 +00:00
|
|
|
yaw.rotate_y(deg_to_rad(-rotation_speed.x * delta * mouse_sensitivity.x * State.input_sensitivity))
|
|
|
|
|
pitch.rotate_x(deg_to_rad(-rotation_speed.y * delta * mouse_sensitivity.y * State.input_sensitivity))
|
2023-03-06 14:06:36 +00:00
|
|
|
|
|
|
|
|
func _handle_jitter(delta):
|
2024-09-15 09:30:31 +00:00
|
|
|
loc_noise_spot += Vector3(delta * camera_jitter_speed * location_jitter_speed)
|
|
|
|
|
rot_noise_spot += Vector3(delta * camera_jitter_speed * angular_jitter_speed)
|
|
|
|
|
pitch.position = Vector3(
|
|
|
|
|
noise.get_noise_1d(loc_noise_spot.x),
|
|
|
|
|
noise.get_noise_1d(loc_noise_spot.y),
|
|
|
|
|
noise.get_noise_1d(loc_noise_spot.z)
|
|
|
|
|
) * location_jitter * jitter_strength
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2024-09-15 09:30:31 +00:00
|
|
|
if not State.reduce_motion: mount.rotation = Vector3(
|
|
|
|
|
noise.get_noise_1d(rot_noise_spot.x),
|
|
|
|
|
noise.get_noise_1d(rot_noise_spot.y),
|
|
|
|
|
noise.get_noise_1d(rot_noise_spot.z)
|
|
|
|
|
) * angular_jitter * jitter_strength
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2023-03-06 14:06:36 +00:00
|
|
|
|
|
|
|
|
func _handle_mouse_input(event:InputEventMouseMotion):
|
2024-09-15 09:30:31 +00:00
|
|
|
if event.relative.length() < mouse_jerk_rejection:
|
|
|
|
|
current_mouse_rotation = event.relative
|
2023-03-06 14:06:36 +00:00
|
|
|
|
2025-09-04 23:50:38 +00:00
|
|
|
# Variables to keep track of crouch state.
|
|
|
|
|
var trigger_slow_crouch: bool = false
|
|
|
|
|
var crouch_held: bool = false
|
|
|
|
|
var crouch_toggled: bool = false
|
2026-01-12 17:39:34 +00:00
|
|
|
var crouch_start_time: float = 0
|
2025-09-04 23:50:38 +00:00
|
|
|
|
2026-01-12 17:39:34 +00:00
|
|
|
func _input(event: InputEvent) -> void:
|
|
|
|
|
if not enabled:
|
|
|
|
|
return
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2026-01-12 17:39:34 +00:00
|
|
|
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
|
|
|
|
_handle_mouse_input(event)
|
|
|
|
|
get_viewport().set_input_as_handled()
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2026-01-12 17:39:34 +00:00
|
|
|
if event is InputEventMouseButton and event.pressed:
|
|
|
|
|
if Input.is_action_just_pressed("zoom_in_mouse"):
|
|
|
|
|
zoomed = true
|
|
|
|
|
elif Input.is_action_just_pressed("zoom_out_mouse"):
|
|
|
|
|
zoomed = false
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2026-01-12 17:39:34 +00:00
|
|
|
if event.is_action_pressed("collect_memento_ui") or event.is_action_pressed("option_memento_ui"):
|
|
|
|
|
if focus_ray.is_colliding():
|
|
|
|
|
var collider := focus_ray.get_collider()
|
|
|
|
|
if collider is InteractiveSprite:
|
|
|
|
|
collider.handle(event)
|
|
|
|
|
get_viewport().set_input_as_handled()
|
2026-01-19 14:52:26 +00:00
|
|
|
|
2026-01-12 17:39:34 +00:00
|
|
|
if event.is_action_pressed("crouch"):
|
|
|
|
|
crouch_start_time = Time.get_unix_time_from_system()
|
|
|
|
|
if crouch_toggled:
|
|
|
|
|
crouch_start_time = 0
|
|
|
|
|
else:
|
|
|
|
|
crouch_held = true
|
|
|
|
|
crouched = true
|
|
|
|
|
elif event.is_action_released("crouch"):
|
|
|
|
|
crouch_held = false
|
|
|
|
|
if Time.get_unix_time_from_system() > crouch_start_time + 0.5:
|
|
|
|
|
if crouched and can_stand_up():
|
|
|
|
|
crouch_toggled = false
|
|
|
|
|
crouched = false
|
|
|
|
|
else:
|
|
|
|
|
crouch_toggled = true
|
2023-07-19 11:54:36 +00:00
|
|
|
|
2023-03-26 22:30:27 +00:00
|
|
|
func _on_bed_enter(_body):
|
2026-01-12 18:21:21 +00:00
|
|
|
if not crouched:
|
2025-09-04 23:50:38 +00:00
|
|
|
trigger_slow_crouch = true
|
2024-09-15 09:30:31 +00:00
|
|
|
crouched = true
|
2023-03-06 14:06:36 +00:00
|
|
|
|
2023-03-26 22:30:27 +00:00
|
|
|
func _on_bed_exit(_body):
|
2025-09-04 23:50:38 +00:00
|
|
|
if crouched and not crouch_held:
|
|
|
|
|
trigger_slow_crouch = true
|
2026-01-19 14:52:26 +00:00
|
|
|
crouched = false
|
2025-08-17 22:13:20 +00:00
|
|
|
|
|
|
|
|
var inside_crouch_volume: Array[CrouchVolume] = []
|
|
|
|
|
|
|
|
|
|
#returns true, if the player character can stand upright.
|
|
|
|
|
func can_stand_up() -> bool:
|
|
|
|
|
for area: Area3D in $CrouchDetector.get_overlapping_areas():
|
|
|
|
|
if area is CrouchVolume:
|
|
|
|
|
return false
|
|
|
|
|
return true
|
|
|
|
|
|
2026-01-12 18:21:21 +00:00
|
|
|
func enter_crouch(body):
|
|
|
|
|
if body is CrouchVolume:
|
|
|
|
|
crouched = true
|
|
|
|
|
|
2025-08-17 22:13:20 +00:00
|
|
|
func exit_crouch(body):
|
2026-01-12 18:21:21 +00:00
|
|
|
if body is CrouchVolume:
|
2025-08-17 22:13:20 +00:00
|
|
|
crouched = false
|