ai part 3

pull/28/head
Dr. Sascha Woitschetzki 2025-08-20 14:17:17 +07:00
parent 5a8b173b48
commit 1d204d1c6c
8 changed files with 223 additions and 166 deletions

@ -1,7 +1,7 @@
[gd_scene load_steps=20 format=4 uid="uid://do5dueeowthyn"]
[ext_resource type="Script" uid="uid://uigg23n8ddht" path="res://scripts/item_desctructable.gd" id="1_txp5f"]
[ext_resource type="Texture2D" uid="uid://dgv6crdc05p61" path="res://resources/models/dungeon/Textures/dungeon_texture.png" id="2_5cqwm"]
[ext_resource type="Script" uid="uid://did0e7iwyya4u" path="res://scripts/item_interactable.gd" id="1_txp5f"]
[ext_resource type="Texture2D" uid="uid://dy2dk8nax2yxg" path="res://resources/models/dungeon/dungeon_texture.png" id="2_5cqwm"]
[ext_resource type="Script" uid="uid://3rbry6yfuo6u" path="res://scripts/hurtbox.gd" id="3_2kbl0"]
[ext_resource type="AudioStream" uid="uid://bxnu674tk5euv" path="res://resources/audio/wallhit.mp3" id="4_tc4p0"]

File diff suppressed because one or more lines are too long

@ -96,4 +96,4 @@ func _on_state_changed(_old_state: States, new_state: States) -> void:
States.dead:
# Belohnung vergeben, wenn der Spieler existiert
if player:
player.gold += 100
player.gold += 100

@ -1,13 +1,25 @@
class_name NavigationManager extends NavigationRegion3D
class_name NavigationManager
extends NavigationRegion3D
@export var navigation_sources_root: Node
const LOG_TAG := "NavigationManager"
@export var navigation_objects_node: Node
func _ready() -> void:
pass
#var navigation_objects = navigation_objects_node.get_children()
# for navigation_object in navigation_objects:
# navigation_object.recalculate_navigation_map.connect(_recalculate_navigation_map)
if navigation_sources_root:
_connect_navigation_sources(navigation_sources_root)
func _connect_navigation_sources(root: Node) -> void:
var children := root.get_children()
for child in children:
if child.has_signal("recalculate_navigation_map"):
var callable := Callable(self, "_on_recalculate_navigation_map")
if child.is_connected("recalculate_navigation_map", callable):
child.disconnect("recalculate_navigation_map", callable)
child.connect("recalculate_navigation_map", callable)
func _recalculate_navigation_map(requester: Item) -> void:
print(requester.item_name + " requests new navigation mesh bake.")
func _on_recalculate_navigation_map(requester: Node) -> void:
print("[%s] %s requests new navigation mesh bake." % [LOG_TAG, requester.name])
bake_navigation_mesh()

@ -1,18 +1,17 @@
class_name Player extends Unit
const PITCH_MIN_DEG := -90.0
const PITCH_MAX_DEG := 30.0
const JOY_DEADZONE := 0.10
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 gold: int:
get:
return _gold
@ -22,56 +21,95 @@ var gold: int:
_gold = value
gold_changed.emit(_gold)
func _ready() -> void:
health = maximum_health
stamina = maximum_stamina
player_loaded.emit()
func _physics_process(delta: float) -> void:
# Gravity
velocity.y += -gravity * delta
# Movementw
get_move_input(delta)
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)
# Smooth face direction to camera
if velocity.length() > 1.0:
model.rotation.y = lerp_angle(model.rotation.y, spring_arm.rotation.y, rotation_speed * delta)
# Joypad camera
var axis_x := Input.get_joy_axis(0, JOY_AXIS_RIGHT_X)
var axis_y := Input.get_joy_axis(0, JOY_AXIS_RIGHT_Y)
if absf(axis_x) > JOY_DEADZONE or absf(axis_y) > JOY_DEADZONE:
move_camera_joypad(axis_x, axis_y, delta)
func rotate_camera(pitch_delta: float, yaw_delta: float) -> void:
# Apply deltas and clamp pitch; yaw is unbounded
# Deltas anwenden; Pitch clampen, Yaw ungebunden
spring_arm.rotation.x += pitch_delta
spring_arm.rotation_degrees.x = clampf(spring_arm.rotation_degrees.x, PITCH_MIN_DEG, PITCH_MAX_DEG)
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:
# Scale by sensitivity and frame time; invert to match mouse feel
# 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
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:
# Move camera via mouse
# 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()
# --- Extracted/neu strukturierte Hilfsfunktionen ---
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 motion := event as InputEventMouseMotion
var pitch_delta := -motion.relative.y * mouse_sensitivity
var yaw_delta := -motion.relative.x * mouse_sensitivity
var yaw_delta := -motion.relative.x * mouse_sensitivity
rotate_camera(pitch_delta, yaw_delta)
var on_floor := is_on_floor()
# Jump
func handle_jump(on_floor: bool) -> void:
if on_floor and Input.is_action_just_pressed("jump"):
if enough_stamina_available(jump_cost):
use_stamina(jump_cost)
@ -79,36 +117,16 @@ func _input(event: InputEvent) -> void:
state = States.jumping
anim_tree.set("parameters/conditions/grounded", false)
# We just hit the floor after being in the air
func update_ground_state(on_floor: bool) -> void:
# Gerade gelandet
if on_floor and not last_floor:
if state != States.idle:
state = States.idle
anim_tree.set("parameters/conditions/grounded", true)
# We're in the air, but we didn't jump
# 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)
last_floor = on_floor
# Menu
if event.is_action_pressed("Menue"):
get_tree().quit()
func get_move_input(delta: float) -> void:
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: Walk while blocking
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

@ -6,47 +6,49 @@ signal hit(collider: Object)
@export var initial_speed: float = 50.0
@export var damage: float = 3.0
@export var lifetime_after_hit: float = 3.0
@export var uses_x_forward: bool = true # Mesh zeigt mit +X nach vorne
@export var mesh_uses_x_forward: bool = true # Mesh zeigt mit +X nach vorne
var speed: float = 0.0
var direction: Vector3 = Vector3.ZERO
var speed: float = 0.0
var velocity_dir: Vector3 = Vector3.ZERO
@onready var ray_cast: RayCast3D = $RayCast3D
func _physics_process(delta: float) -> void:
if speed > 0.0:
var move_dir := direction if direction != Vector3.ZERO else -global_transform.basis.z
global_translate(move_dir * speed * delta)
if speed <= 0.0:
return
var move_dir := velocity_dir if velocity_dir != Vector3.ZERO else _forward_basis()
global_position += move_dir * speed * delta
if ray_cast and ray_cast.is_colliding():
var collider: Object = ray_cast.get_collider()
_on_hit(collider)
func shoot(new_direction: Vector3 = Vector3.ZERO) -> void:
direction = new_direction.normalized() if new_direction != Vector3.ZERO else -global_transform.basis.z
velocity_dir = new_direction.normalized() if new_direction != Vector3.ZERO else _forward_basis()
speed = initial_speed
# Beim Abfeuern aus der Waffen-Hierarchie lösen, damit Maus-/Kamerabewegung den Pfeil nicht beeinflusst
_detach_to_world()
# Visuelle Ausrichtung konsistent zur Flugrichtung setzen
_align_visual_to_direction()
if ray_cast:
ray_cast.enabled = true
func _forward_basis() -> Vector3:
return -global_transform.basis.z
func _align_visual_to_direction() -> void:
look_at(global_transform.origin + direction, Vector3.UP)
look_at(global_transform.origin + velocity_dir, Vector3.UP)
# Godot richtet -Z nach vorne aus; wenn das Mesh +X als "vorne" hat, um -90° um Y korrigieren
if uses_x_forward:
rotate_z(deg_to_rad(90.0))
if mesh_uses_x_forward:
rotate_y(deg_to_rad(-90.0))
func _detach_to_world() -> void:
var target_parent: Node = get_tree().current_scene if get_tree().current_scene != null else get_tree().root
var tree := get_tree()
var target_parent: Node = tree.current_scene if tree.current_scene != null else tree.root
if target_parent == null:
return
var gt := global_transform
@ -63,10 +65,8 @@ func _on_hit(collider: Object) -> void:
speed = 0.0
if ray_cast:
ray_cast.enabled = false
emit_signal("hit", collider)
_apply_damage_if_supported(collider)
if lifetime_after_hit > 0.0:
var timer := get_tree().create_timer(lifetime_after_hit)
timer.timeout.connect(queue_free)

@ -5,13 +5,7 @@ extends Player
const ATTACK_ANIMS: Array[String] = ["2H_Ranged_Shoot"]
const RELOAD_ANIMS: Array[String] = ["2H_Ranged_Reload"]
const AIM_ANIMS: Array[String] = ["2H_Ranged_Aiming"]
const FOOTSTEP_SOUNDS: Array[String] = [
"res://resources/audio/footstep_grass_000.ogg",
"res://resources/audio/footstep_grass_001.ogg",
"res://resources/audio/footstep_grass_002.ogg",
"res://resources/audio/footstep_grass_003.ogg",
"res://resources/audio/footstep_grass_004.ogg"
]
const FOOTSTEP_SOUNDS: Array[String] = ["res://resources/audio/footstep_grass_000.ogg", "res://resources/audio/footstep_grass_001.ogg", "res://resources/audio/footstep_grass_002.ogg", "res://resources/audio/footstep_grass_003.ogg", "res://resources/audio/footstep_grass_004.ogg"]
# Tuning-Konstanten
const FOV_AIM: float = 40.0
const FOV_DEFAULT: float = 70.0

@ -1,71 +1,108 @@
extends Node
@export var max_enemy := 3
@export var enemies_folder: Node
@export var enemies: Array[PackedScene]
@export var enemies_ui: PackedScene
@export var spawn_points: Array[SpawnPoint]
@export var max_enemies: int = 3
@export var enemies_container: Node
@export var enemy_scenes: Array[PackedScene]
@export var enemies_ui_manager: Node
@export var spawn_points_container: Node
var player: Player
var current_enemies: Array[Enemy] = []
var enemy_number := 0
var enemy_ui_ready := false:
var _enemy_counter: int = 0
var _enemy_ui_ready: bool = false
var spawn_points: Array[SpawnPoint]
var enemy_ui_ready: bool:
get:
return enemy_ui_ready
return _enemy_ui_ready
set(value):
enemy_ui_ready = value
_enemy_ui_ready = value
if value:
enemy_count_changed()
ensure_enemy_count()
func _ready() -> void:
player = get_tree().get_first_node_in_group("player")
if player == null:
print("Error: No player found!!")
func enemy_count_changed() -> void:
print("Enough enemies? " + str(current_enemies.size()) + "/" + str(max_enemy))
if current_enemies.size() < max_enemy:
var empty_spawn_points = find_empty_spawn_points()
if empty_spawn_points.size() == 0:
print("No free SpawnPoints found!")
else:
spawn_new_enemy(empty_spawn_points.pick_random())
enemy_count_changed()
else:
print("Error: No player found!")
# SpawnPoints aus dem Container laden (falls gesetzt)
populate_spawn_points_from_container()
func populate_spawn_points_from_container() -> void:
if spawn_points_container == null:
push_warning("spawn_points_container is not assigned; cannot auto-collect SpawnPoints.")
return
var collected: Array[SpawnPoint] = []
var children: Array = spawn_points_container.get_children()
for child in children:
if child is SpawnPoint:
collected.append(child)
spawn_points = collected
func ensure_enemy_count() -> void:
var current := current_enemies.size()
print("Enough enemies? %d/%d" % [current, max_enemies])
if current >= max_enemies:
print("Enough enemies!")
return
var empty_points := find_empty_spawn_points()
if empty_points.is_empty():
print("No free SpawnPoints found!")
return
var need: int = min(max_enemies - current, empty_points.size())
for i in need:
spawn_enemy(empty_points[i])
func spawn_enemy(spawn_point: SpawnPoint) -> void:
var scene: PackedScene = enemy_scenes.pick_random()
if scene == null:
push_warning("No enemy scene available to spawn.")
return
var new_enemy := scene.instantiate() as Enemy
if new_enemy == null:
push_warning("PackedScene did not instantiate as Enemy.")
return
func spawn_new_enemy(spawn_point: SpawnPoint) -> void:
var new_enemy = enemies.pick_random().instantiate() as Enemy
new_enemy.player = player
enemy_number += 1
new_enemy.name = new_enemy.name + str(enemy_number)
_enemy_counter += 1
new_enemy.name = "%s%d" % [new_enemy.name, _enemy_counter]
new_enemy.unit_name = new_enemy.name
print("Spawn " + new_enemy.unit_name + " on " + spawn_point.name)
print("Spawn %s on %s" % [new_enemy.unit_name, spawn_point.name])
new_enemy.position = spawn_point.position
new_enemy.unit_died.connect(_on_unit_died)
enemies_folder.add_child(new_enemy)
enemies_container.add_child(new_enemy)
spawn_point.isFull = true
spawn_point.enemy = new_enemy
current_enemies.append(new_enemy)
enemies_ui_manager.update_enemies_ui(current_enemies)
func find_empty_spawn_points() -> Array[SpawnPoint]:
var empty_spawn_points: Array[SpawnPoint] = []
for spawn_point in spawn_points:
if not spawn_point.isFull:
empty_spawn_points.append(spawn_point)
print("Count of spawn points: " + str(spawn_points.size()))
for sp in spawn_points:
if not sp.isFull:
empty_spawn_points.append(sp)
else:
print("Spawn point " + sp.name + " is full!")
return empty_spawn_points
func _on_unit_died(unit: Unit) -> void:
if unit is Enemy:
print(unit.unit_name + " died -> Remove from current_enemies.")
var index = current_enemies.find(unit)
print("%s died -> Remove from current_enemies." % unit.unit_name)
var index := current_enemies.find(unit)
if index != -1:
current_enemies.remove_at(index)
enemy_count_changed()
ensure_enemy_count()
else:
print("Enemy is not in the list!")
else: