extends CharacterBody3D class_name PlayerController ## Simple top-down-ish 3D movement for billboard levels. @export var move_speed: float = 6.0 @export var attack_range: float = 1.8 @export var attack_damage: int = 1 signal interacted signal attacked @onready var _billboard: BillboardSprite = $BillboardSprite var _locked: bool = false func _ready() -> void: add_to_group("player") if _billboard and _billboard.texture == null: var tex: Texture2D = load("res://assets/sprites/placeholders/player.png") if tex: _billboard.set_texture(tex) func _physics_process(_delta: float) -> void: if _locked or get_tree().paused: velocity = Vector3.ZERO return var input := Vector2( Input.get_axis("move_left", "move_right"), Input.get_axis("move_up", "move_down") ) var dir := Vector3(input.x, 0.0, input.y) if dir.length() > 1.0: dir = dir.normalized() velocity = dir * move_speed move_and_slide() if Input.is_action_just_pressed("interact"): _try_interact() if Input.is_action_just_pressed("attack"): _try_attack() func lock_input(locked: bool) -> void: _locked = locked func _try_interact() -> void: interacted.emit() for body in get_tree().get_nodes_in_group("interactable"): if body is Node3D and global_position.distance_to(body.global_position) <= 2.2: if body.has_method("on_interact"): body.on_interact(self) return func _try_attack() -> void: attacked.emit() AudioDirector.play_hit() for body in get_tree().get_nodes_in_group("enemy"): if body is Node3D and global_position.distance_to(body.global_position) <= attack_range: if body.has_method("take_hit"): body.take_hit(attack_damage)