class_name Player extends Unit const CAMERA_PITCH_MIN_DEG := -90.0 const CAMERA_PITCH_MAX_DEG := 30.0 const JOYPAD_DEADZONE := 0.10 @export var mouse_sensitivity: float = 0.006 @export var rotation_speed: float = 24.0 @export var joypad_sensitivity: float = 2.0 @export var spring_arm: SpringArm3D signal gold_changed(current_gold: int) signal player_loaded() var _gold: int = 0 var was_on_floor: bool = true var gold: int: get: return _gold set(value): if _gold == value: return _gold = value gold_changed.emit(_gold) func _ready() -> void: health = maximum_health stamina = maximum_stamina player_loaded.emit() func _physics_process(delta: float) -> void: # Physik: Schwerkraft, Bewegung, Ausrichtung, Kamera per Joypad apply_gravity(delta) update_movement(delta) move_and_slide() update_facing(delta) handle_joypad_camera(delta) func rotate_camera(pitch_delta: float, yaw_delta: float) -> void: # Deltas anwenden; Pitch clampen, Yaw ungebunden spring_arm.rotation.x += pitch_delta spring_arm.rotation_degrees.x = clampf(spring_arm.rotation_degrees.x, CAMERA_PITCH_MIN_DEG, CAMERA_PITCH_MAX_DEG) spring_arm.rotation.y += yaw_delta func move_camera_joypad(x: float, y: float, delta: float) -> void: # Skalierung nach Sensitivität und Framezeit; invertiert für Maus-ähnliches Gefühl var pitch_delta := -y * joypad_sensitivity * delta var yaw_delta := -x * joypad_sensitivity * delta rotate_camera(pitch_delta, yaw_delta) func player_game_over(): GameManager.game_over() queue_free() func _input(event: InputEvent) -> void: # Kamera per Maus handle_mouse_camera(event) # Boden-/Luft-Logik var on_floor := is_on_floor() handle_jump(on_floor) update_ground_state(on_floor) # Menü if event.is_action_pressed("Menue"): get_tree().quit() func apply_gravity(delta: float) -> void: velocity.y += -gravity * delta func update_movement(delta: float) -> void: # Bewegungsinput lesen und anwenden var vy := velocity.y velocity.y = 0.0 var input_vector := Input.get_vector("left", "right", "forward", "back") var direction := Vector3(input_vector.x, 0.0, input_vector.y).rotated(Vector3.UP, spring_arm.rotation.y) if state == States.blocking: direction = Vector3.ZERO # TODO: Gehen während Blocken erlauben velocity = velocity.lerp(direction * speed, acceleration * delta) var v_local := velocity * model.transform.basis anim_tree.set("parameters/IWR/blend_position", Vector2(v_local.x, -v_local.z) / speed) velocity.y = vy func update_facing(delta: float) -> void: # Sanft in Blickrichtung der Kamera drehen, wenn wir uns merklich bewegen if velocity.length() > 1.0: model.rotation.y = lerp_angle(model.rotation.y, spring_arm.rotation.y, rotation_speed * delta) func handle_joypad_camera(delta: float) -> void: var right_stick_x := Input.get_joy_axis(0, JOY_AXIS_RIGHT_X) var right_stick_y := Input.get_joy_axis(0, JOY_AXIS_RIGHT_Y) if absf(right_stick_x) > JOYPAD_DEADZONE or absf(right_stick_y) > JOYPAD_DEADZONE: move_camera_joypad(right_stick_x, right_stick_y, delta) func handle_mouse_camera(event: InputEvent) -> void: if event is InputEventMouseMotion: var motion := event as InputEventMouseMotion var pitch_delta := -motion.relative.y * mouse_sensitivity var yaw_delta := -motion.relative.x * mouse_sensitivity rotate_camera(pitch_delta, yaw_delta) func handle_jump(on_floor: bool) -> void: if on_floor and Input.is_action_just_pressed("jump"): if can_spend_stamina(jump_cost): spend_stamina(jump_cost) velocity.y = jump_speed state = States.jumping anim_tree.set("parameters/conditions/grounded", false) func update_ground_state(on_floor: bool) -> void: # Gerade gelandet if on_floor and not was_on_floor: if state != States.idle: state = States.idle anim_tree.set("parameters/conditions/grounded", true) # In der Luft ohne aktiven Sprung if not on_floor and state != States.jumping: anim_state.travel("Jump_Idle") anim_tree.set("parameters/conditions/grounded", false) anim_tree.set("parameters/conditions/jumping", state == States.jumping) was_on_floor = on_floor