class_name Ghost extends CharacterBody2D var move_speed: float = 200.0 var shift_speed: float = 400.0 var held_item: Collectable = null enum State { Left, Right, Up, Down } var state: State = State.Right @onready var animationPlayer = $AnimationPlayer @onready var interaction_area: Area2D = $InteractionArea func _ready() -> void: add_to_group("ghost") func _process(_delta: float) -> void: if GameManager.game_over: velocity = Vector2.ZERO return var shift := Input.is_action_pressed("shift") var direction := Vector2( Input.get_action_strength("right") - Input.get_action_strength("left"), Input.get_action_strength("down") - Input.get_action_strength("up") ) if direction.length() > 0: direction = direction.normalized() velocity = direction * (shift_speed if shift else move_speed) _update_animation() func _physics_process(_delta: float) -> void: if GameManager.game_over: return move_and_slide() func _unhandled_input(event: InputEvent) -> void: if GameManager.game_over: return if event.is_action_pressed("interact"): _interact() func _interact() -> void: if held_item == null: _try_pick_up() else: _try_drop() func _try_pick_up() -> void: var nearest: Collectable = null var nearest_dist := INF for area in interaction_area.get_overlapping_areas(): if area is Collectable and area.can_pick_up(): var d := global_position.distance_squared_to(area.global_position) if d < nearest_dist: nearest_dist = d nearest = area if nearest != null: SoundManager.play_key_pickup() _get_collectable(nearest) func _try_drop() -> void: for area in interaction_area.get_overlapping_areas(): if area is DropZone and area.accepts(held_item): var item := held_item held_item = null area.deliver(item) return _drop_on_floor() func _drop_on_floor() -> void: var item := held_item held_item = null var gpos := item.global_position item.reparent(get_tree().current_scene) item.global_position = gpos item.show_behind_parent = false item.mark_dropped() func _get_collectable(collectable: Collectable) -> void: held_item = collectable held_item.mark_picked_up() held_item.reparent(self) _apply_held_item_state() func _held_item_position() -> Vector2: match state: State.Left: return held_item.positionLeft State.Right: return held_item.positionRight State.Up: return held_item.positionUp _: return held_item.positionDown func _move_held_item(target: Vector2) -> void: if held_item == null: return var tween = create_tween().set_parallel(true) tween.tween_property(held_item, "position", target, 0.15).set_ease(Tween.EASE_OUT) func _apply_held_item_state() -> void: if held_item == null: return held_item.show_behind_parent = state == State.Up _move_held_item(_held_item_position()) func _set_state(new_state: State, animation: String) -> void: if state == new_state: return state = new_state animationPlayer.play(animation) _apply_held_item_state() func _update_animation() -> void: # Left/right take priority over up/down, preserving the correct animation on diagonals. if velocity.x < 0: _set_state(State.Left, "idle_left") elif velocity.x > 0: _set_state(State.Right, "idle_right") elif velocity.y < 0: _set_state(State.Up, "idle_up") elif velocity.y > 0: _set_state(State.Down, "idle_down")