extends CharacterBody3D @export var move_speed: float = 5.0 @export var gravity: float = 9.8 @export var max_health: int = 10 @export var attack_damage: int = 1 @export var attack_range: float = 1.0 @export var attack_cooldown: float = 0.5 signal health_changed(current: int, max: int) var camera_rig: Node3D var health: int var is_dead: bool = false var _attack_cooldown_left: float = 0.0 var _click_move_target: Vector3 = Vector3.ZERO var _click_move_active: bool = false @onready var sprite: AnimatedSprite3D = $AnimatedSprite3D # Nur in world/island.tscn vorhanden, nicht in diesem Debug-Prototyp -> optional @onready var equipment: Equipment = get_node_or_null("Equipment") func _ready() -> void: camera_rig = get_tree().get_first_node_in_group("camera_rig") if camera_rig == null: push_error("CameraRig nicht gefunden! Gruppe 'camera_rig' gesetzt?") health = max_health # Diablo-artig: Linksklick bewegt zusätzlich zu WASD, Leertaste greift an. # Läuft über _unhandled_input, damit ein offenes Interaktions-Menü (Buttons # verbrauchen den Klick/die Leertaste zuerst) automatisch Vorrang hat. func _unhandled_input(event: InputEvent) -> void: if InteractionManager.active: return if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed: _try_click_move(event.position) elif event.is_action_pressed("attack"): _try_attack() func _try_click_move(screen_pos: Vector2) -> void: if camera_rig == null: return var cam: Camera3D = camera_rig.camera var from := cam.project_ray_origin(screen_pos) var dir := cam.project_ray_normal(screen_pos) var space_state := get_world_3d().direct_space_state var query := PhysicsRayQueryParameters3D.create(from, from + dir * 1000.0) query.exclude = [get_rid()] var result := space_state.intersect_ray(query) if result: _click_move_target = result.position _click_move_active = true func _try_attack() -> void: if _attack_cooldown_left > 0.0: return _attack_cooldown_left = attack_cooldown for enemy in get_tree().get_nodes_in_group("enemies"): if global_position.distance_to(enemy.global_position) <= attack_range: enemy.take_damage(attack_damage) func take_damage(amount: int) -> void: if is_dead: return health = max(0, health - amount) health_changed.emit(health, max_health) if health <= 0: _die() func heal_full() -> void: health = max_health health_changed.emit(health, max_health) func _die() -> void: is_dead = true get_tree().change_scene_to_file("res://boot/game_over_screen.tscn") func _physics_process(delta: float) -> void: # Bewegung sperren während Interaction if InteractionManager.active: velocity = Vector3.ZERO return if is_dead: return if _attack_cooldown_left > 0.0: _attack_cooldown_left -= delta # Schwerkraft if not is_on_floor(): velocity.y -= gravity * delta # Eingabe var input_dir = Vector2( Input.get_axis("move_left", "move_right"), Input.get_axis("move_forward", "move_backward") ) if input_dir != Vector2.ZERO: _click_move_active = false # WASD hat Vorrang vor einem laufenden Klick-Ziel var cam_basis = camera_rig.global_transform.basis var forward = -cam_basis.z var right = cam_basis.x forward.y = 0 right.y = 0 forward = forward.normalized() right = right.normalized() var move_dir = (forward * -input_dir.y + right * input_dir.x).normalized() velocity.x = move_dir.x * move_speed velocity.z = move_dir.z * move_speed _update_animation(input_dir) elif _click_move_active: var to_target = _click_move_target - global_position to_target.y = 0 if to_target.length() < 0.15: _click_move_active = false velocity.x = move_toward(velocity.x, 0, move_speed) velocity.z = move_toward(velocity.z, 0, move_speed) sprite.play("idle") else: var move_dir = to_target.normalized() velocity.x = move_dir.x * move_speed velocity.z = move_dir.z * move_speed # Weltrichtung zurück in die bestehende bildschirmrelative # Animationslogik übersetzen, statt eine zweite Logik zu bauen. var cam_basis = camera_rig.global_transform.basis var forward = -cam_basis.z var right = cam_basis.x forward.y = 0 right.y = 0 forward = forward.normalized() right = right.normalized() var screen_input := Vector2(move_dir.dot(right), -move_dir.dot(forward)) _update_animation(screen_input) else: velocity.x = move_toward(velocity.x, 0, move_speed) velocity.z = move_toward(velocity.z, 0, move_speed) sprite.play("idle") move_and_slide() func _update_animation(input_dir: Vector2) -> void: if input_dir.y > 0.1: sprite.play("move_down") elif input_dir.y < -0.1: sprite.play("move_up") elif input_dir.x > 0.1: sprite.play("move_right") elif input_dir.x < -0.1: sprite.play("move_left") else: sprite.play("idle")