class_name Walker extends CharacterBody3D signal focus_required(me: Node3D) signal got_in(vehicle: SeatedVehicle) signal got_out signal can_get_in(possible: bool) signal chocolate_collected ## How fast the player moves in meters per second. @export var speed = 14 ## Convert an axis from [-1, -1] to rad @export var turn_to_rad: float = 0.020 ## The downward acceleration when in the air, in meters per second squared. @export var fall_acceleration = 75 ## Vertical impulse applied to the character upon jumping in meters per second. @export var jump_impulse = 20 @export_group("Boarding", "board_") @export var board_door_access_duration: float = 1.0 @export var board_seat_access_duration: float = 0.5 var target_velocity := Vector3.ZERO var target_character_direction := Vector3.ZERO # From command var _vehicle: SeatedVehicle = null var _can_get_in: bool = false var _in_transition: bool = false @onready var _vehicle_range: RayCast3D = $VehicleRange func _ready() -> void: focus_required.emit(self) func trigger_jump() -> void: if is_on_floor(): target_velocity.y = jump_impulse func trigger_direction(dir: Vector2) -> void: target_character_direction = Vector3(dir.x, 0.0, -dir.y) ## Return true if inside a vehicle func is_onboard() -> bool: return _vehicle != null ## Return true if the player can get in a vehicle func can_get_in_vehicle() -> bool: return !is_onboard() and _get_closest_vehicle() != null ## Give this walker a chocolate bar func give_chocolate(): chocolate_collected.emit() ## Return local pocket position where the chocolate is collected func get_pocket_position() -> Vector3: return $ChocolatePocket.get_position() func _physics_process(delta: float) -> void: if is_onboard(): pass # Nothing else: _move_with_feet(delta) _signal_when_can_get_in() func _move_with_feet(delta: float): # Walk according to the camera angle var camera_basis: Basis = get_viewport().get_camera_3d().get_camera_transform().basis var target_world_direction: Vector3 = camera_basis * target_character_direction target_world_direction.y = 0.0 var target_walk_velocity = target_world_direction.normalized() * speed * target_character_direction.length() target_velocity.x = target_walk_velocity.x target_velocity.z = target_walk_velocity.z # Face forward _look_forward(target_velocity) # Gravity if not is_on_floor(): target_velocity.y = target_velocity.y - (fall_acceleration * delta) # Moving the Character velocity = target_velocity move_and_slide() ## Trigger only signal when ability is changed func _signal_when_can_get_in() -> void: var can_get_in_now : bool = can_get_in_vehicle() if _can_get_in != can_get_in_now: can_get_in.emit(can_get_in_now) _can_get_in = can_get_in_now func _on_dir_changed(dir: Vector2) -> void: if is_onboard(): return trigger_direction(dir) func _on_main_action(pressed: bool) -> void: if is_onboard(): return if pressed: trigger_jump() func _on_get_in_action(commander: LocalInput) -> void: if is_onboard(): _get_out_vehicle() else: _get_in_vehicle(commander) ## Seek the first vehicle in front of the player ## and get in to it ## and take the wheel ! func _get_in_vehicle(commander: LocalInput) -> void: if _in_transition: return var closest_vehicle: SeatedVehicle = _get_closest_vehicle() if closest_vehicle == null: return # No vehicle to get inside _vehicle = closest_vehicle reparent(_vehicle) add_collision_exception_with(_vehicle) _vehicle.become_driven_by(self) _vehicle.drive_with(commander) _move_to_seat(_vehicle) got_in.emit(_vehicle) ## Get out of the current vehicle func _get_out_vehicle() -> void: if _in_transition: return _init_transition() var tween = create_tween() tween.tween_property(self, "transform", _vehicle.get_door().get_transform(), board_seat_access_duration) tween.tween_callback(_finish_getting_out) func _finish_getting_out() -> void: _end_transition() _vehicle.get_out() remove_collision_exception_with(_vehicle) reparent(_vehicle.get_parent()) _vehicle = null _get_head_up() got_out.emit() ## Make the player stand up func _get_head_up(): set_rotation(Vector3(0.0, rotation.y, 0.0)) ## Make the player look forward func _look_forward(forward: Vector3) -> void: var horizontal_forward: Vector3 = forward.slide(Vector3.UP) if horizontal_forward.is_zero_approx(): return else: look_at(position + horizontal_forward) ## Return closest vehicle within reach ## or null if there are none func _get_closest_vehicle() -> SeatedVehicle: _vehicle_range.force_raycast_update() var object_within_range: Object = _vehicle_range.get_collider() if object_within_range is SeatedVehicle: return object_within_range as SeatedVehicle return null ## Move gently to a free seat from the nearest door func _move_to_seat(vehicule: SeatedVehicle) -> void: _init_transition() var tween: Tween = create_tween() var door: Node3D = vehicule.get_closest_door(get_position()) tween.tween_property(self, "position", door.get_position(), board_door_access_duration) tween.parallel().tween_property(self, "quaternion", door.get_quaternion(), board_door_access_duration) var seat: Node3D = vehicule.get_free_seat() tween.tween_property(self, "position", seat.get_position(), board_seat_access_duration) tween.parallel().tween_property(self, "quaternion", seat.get_quaternion(), board_seat_access_duration) tween.tween_callback(_end_transition) ## Lock getting in / out while still doing it func _init_transition() -> void: _in_transition = true ## Allow walker to get in / out again func _end_transition() -> void: _in_transition = false