DungeonCrawler/world.gd
Andre 08c506a3a8 World: Medieval Town + Fantasy Door Gate, alte Platzhalter entfernt
- medieval_town.glb als Stadt-Asset eingefügt (4% Skalierung)
- fantasy_door_model.glb ersetzt das alte CSG-DungeonGate
- Boden-Shader entfernt (Stadt hat eigenen Boden)
- Unsichtbarer Kollisionsboden (2500x2500) hinzugefügt
- WorldEnvironment mit Sky, Fog, SSAO, Glow aus Szene statt Code
- Alte CSG-Objekte entfernt (Mountain, Rocks, Trees)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 13:06:26 +01:00

764 lines
25 KiB
GDScript

# World.gd
# ─────────────────────────────────────────────────────────────────────────────
# Spielwelt-Controller
#
# Verantwortlichkeiten:
# • Overworld + Dungeon in einer Szene (kein Szenenwechsel)
# • Prozeduraler Boden-Shader, Himmel
# • Hauptmenü → Klassenauswahl → Spielinitialisierung
# • Startausrüstung je nach Klasse
# • Gegner-Setup, Loot-Weiterleitung, Respawn nach Tod
# • Dungeon-Generierung und Portal-System
# ─────────────────────────────────────────────────────────────────────────────
extends Node3D
const ENEMY_SCENE = preload("res://enemy.tscn")
const MAIN_MENU = preload("res://main_menu.tscn")
const CLASS_SELECTION_MENU = preload("res://class_selection_menu.tscn")
const RESPAWN_TIME = 60.0
# Startausrüstung
const STARTER_SWORD = preload("res://equipment/iron_sword.tres")
const STARTER_STAFF = preload("res://equipment/wooden_staff.tres")
const STARTER_CHEST = preload("res://equipment/leather_chest.tres")
const STARTER_POTION = preload("res://consumables/small_hp_potion.tres")
# Loot Tables
const GOBLIN_LOOT = preload("res://loot_tables/goblin_loot.tres")
# Dungeon-Konstanten
const TILE_SIZE = 3.0
const WALL_HEIGHT = 6.0
const GRID_WIDTH = 60
const GRID_HEIGHT = 60
const MIN_ROOMS = 6
const MAX_ROOMS = 10
const MIN_ROOM_SIZE = 4
const MAX_ROOM_SIZE = 9
@onready var player = $Player
@onready var world_env = $WorldEnvironment
@onready var gate_area = $DungeonGate/GateArea
@onready var gate_label = $DungeonGate/GateLabel
var in_dungeon: bool = false
var dungeon_level: int = 0 # 0 = Overworld, 1+ = Dungeon-Ebene
var overworld_nodes: Array = [] # Nodes die im Dungeon versteckt werden
var dungeon_container: Node3D = null
var dungeon_env: WorldEnvironment = null
var overworld_env: WorldEnvironment = null
var return_portal_area: Area3D = null
var return_portal_label: Label3D = null
var deeper_portal_area: Area3D = null
var deeper_portal_label: Label3D = null
var player_overworld_pos: Vector3 = Vector3.ZERO
var portal_choice_panel: PanelContainer = null
var overworld_env_resource: Environment = null
# Dungeon-Daten
var dungeon_grid: Array = []
var dungeon_rooms: Array = []
# Gespeicherte Dungeon-Ebenen (Level → {grid, rooms})
var saved_dungeons: Dictionary = {}
func _ready():
_setup_sky()
# Hauptmenü anzeigen
var main_menu = MAIN_MENU.instantiate()
add_child(main_menu)
main_menu.start_game.connect(_on_start_game)
func _setup_sky():
overworld_env = world_env
overworld_env_resource = world_env.environment
# ─────────────────────────────────────────────────────────────────────────────
# PROCESS
# ─────────────────────────────────────────────────────────────────────────────
func _process(_delta):
if in_dungeon:
_check_return_portal()
_check_deeper_portal()
else:
_check_gate_proximity()
func _check_gate_proximity():
if not player or not gate_area or not gate_label:
return
var dist = player.global_position.distance_to(gate_area.global_position)
gate_label.visible = dist < 6.0
if dist < 6.0 and Input.is_action_just_pressed("interact"):
_enter_dungeon()
func _check_return_portal():
if not player or not return_portal_area or not return_portal_label:
return
if portal_choice_panel and is_instance_valid(portal_choice_panel):
return # Menü ist offen
var dist = player.global_position.distance_to(return_portal_area.global_position)
return_portal_label.visible = dist < 6.0
if dist < 6.0 and Input.is_action_just_pressed("interact"):
if dungeon_level <= 1:
_exit_dungeon()
else:
_show_portal_choice()
func _check_deeper_portal():
if not player or not deeper_portal_area or not deeper_portal_label:
return
var dist = player.global_position.distance_to(deeper_portal_area.global_position)
deeper_portal_label.visible = dist < 6.0
if dist < 6.0 and Input.is_action_just_pressed("interact"):
_go_deeper()
# ─────────────────────────────────────────────────────────────────────────────
# DUNGEON BETRETEN / VERLASSEN
# ─────────────────────────────────────────────────────────────────────────────
func _enter_dungeon():
in_dungeon = true
dungeon_level = 1
player_overworld_pos = player.global_position
# Overworld-Nodes verstecken
_hide_overworld()
# Ersten Dungeon generieren
_generate_dungeon_level()
func _go_deeper():
# Aktuelle Ebene speichern
_save_current_dungeon()
_clear_dungeon()
dungeon_level += 1
_generate_dungeon_level()
func _go_up():
# Aktuelle Ebene speichern
_save_current_dungeon()
_clear_dungeon()
dungeon_level -= 1
_generate_dungeon_level()
func _save_current_dungeon():
saved_dungeons[dungeon_level] = {
"grid": dungeon_grid.duplicate(true),
"rooms": dungeon_rooms.duplicate(true)
}
func _show_portal_choice():
get_tree().paused = true
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
portal_choice_panel = PanelContainer.new()
portal_choice_panel.process_mode = Node.PROCESS_MODE_ALWAYS
var vbox = VBoxContainer.new()
vbox.add_theme_constant_override("separation", 10)
var title = Label.new()
title.text = "Wohin möchtest du gehen?"
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
vbox.add_child(title)
var btn_up = Button.new()
btn_up.text = "Eine Ebene höher (Ebene " + str(dungeon_level - 1) + ")"
btn_up.pressed.connect(_on_portal_go_up)
vbox.add_child(btn_up)
var btn_out = Button.new()
btn_out.text = "Zurück zur Oberwelt"
btn_out.pressed.connect(_on_portal_exit)
vbox.add_child(btn_out)
var btn_cancel = Button.new()
btn_cancel.text = "Abbrechen"
btn_cancel.pressed.connect(_on_portal_cancel)
vbox.add_child(btn_cancel)
portal_choice_panel.add_child(vbox)
player.hud.add_child(portal_choice_panel)
portal_choice_panel.anchors_preset = Control.PRESET_CENTER
portal_choice_panel.position -= portal_choice_panel.size / 2
func _on_portal_go_up():
_close_portal_choice()
_go_up()
func _on_portal_exit():
_close_portal_choice()
_exit_dungeon()
func _on_portal_cancel():
_close_portal_choice()
func _close_portal_choice():
if portal_choice_panel and is_instance_valid(portal_choice_panel):
portal_choice_panel.queue_free()
portal_choice_panel = null
get_tree().paused = false
func _hide_overworld():
overworld_nodes.clear()
for child in get_children():
if child == player:
continue
if child.name == "OverworldEnv":
continue
if child is Node3D or child is StaticBody3D:
overworld_nodes.append(child)
child.visible = false
if child is StaticBody3D:
child.process_mode = Node.PROCESS_MODE_DISABLED
if overworld_env:
overworld_env_resource = overworld_env.environment
overworld_env.environment = null
func _generate_dungeon_level():
dungeon_container = Node3D.new()
dungeon_container.name = "DungeonContainer"
add_child(dungeon_container)
# Gespeichertes Level laden oder neu generieren
if saved_dungeons.has(dungeon_level):
dungeon_grid = saved_dungeons[dungeon_level]["grid"].duplicate(true)
dungeon_rooms = saved_dungeons[dungeon_level]["rooms"].duplicate(true)
else:
_generate_dungeon()
_build_dungeon_geometry()
_build_dungeon_navmesh()
# Edge-Connection-Margin erhöhen damit NavMesh-Zellen sich verbinden
var map_rid = get_world_3d().navigation_map
NavigationServer3D.map_set_edge_connection_margin(map_rid, 0.6)
_place_return_portal()
_place_deeper_portal()
_setup_dungeon_lighting()
_setup_dungeon_environment()
# Spieler-Spawn: beim Hochgehen im letzten Raum, sonst im ersten
var spawn_room: Rect2i
if saved_dungeons.has(dungeon_level) and dungeon_level < _get_max_visited_level():
# Von unten zurückgekommen → Spawn am Deeper-Portal (letzter Raum)
spawn_room = dungeon_rooms[dungeon_rooms.size() - 1]
else:
# Neu betreten oder von oben → Spawn am Eingang (erster Raum)
spawn_room = dungeon_rooms[0]
var spawn = Vector3(
(spawn_room.position.x + spawn_room.size.x / 2.0) * TILE_SIZE,
0.5,
(spawn_room.position.y + spawn_room.size.y / 2.0) * TILE_SIZE
)
player.global_position = spawn
# Gegner im Dungeon spawnen
_spawn_dungeon_enemies()
func _get_max_visited_level() -> int:
var max_level = 0
for key in saved_dungeons.keys():
if key > max_level:
max_level = key
return max_level
func _clear_dungeon():
if dungeon_container:
dungeon_container.queue_free()
dungeon_container = null
if dungeon_env:
dungeon_env.queue_free()
dungeon_env = null
return_portal_area = null
return_portal_label = null
deeper_portal_area = null
deeper_portal_label = null
dungeon_grid.clear()
dungeon_rooms.clear()
func _exit_dungeon():
in_dungeon = false
dungeon_level = 0
_clear_dungeon()
saved_dungeons.clear()
# Overworld-Nodes wieder zeigen
for node in overworld_nodes:
if is_instance_valid(node):
node.visible = true
if node is StaticBody3D:
node.process_mode = Node.PROCESS_MODE_INHERIT
overworld_nodes.clear()
if overworld_env:
overworld_env.environment = overworld_env_resource
# Spieler zurück zur Overworld
player.global_position = player_overworld_pos
# ─────────────────────────────────────────────────────────────────────────────
# DUNGEON-GENERIERUNG
# ─────────────────────────────────────────────────────────────────────────────
func _generate_dungeon():
dungeon_grid.clear()
dungeon_rooms.clear()
for x in range(GRID_WIDTH):
var col = []
for y in range(GRID_HEIGHT):
col.append(0)
dungeon_grid.append(col)
var attempts = 0
var target_rooms = randi_range(MIN_ROOMS, MAX_ROOMS)
while dungeon_rooms.size() < target_rooms and attempts < 200:
attempts += 1
var w = randi_range(MIN_ROOM_SIZE, MAX_ROOM_SIZE)
var h = randi_range(MIN_ROOM_SIZE, MAX_ROOM_SIZE)
var x = randi_range(2, GRID_WIDTH - w - 2)
var y = randi_range(2, GRID_HEIGHT - h - 2)
var new_room = Rect2i(x, y, w, h)
if _dungeon_room_fits(new_room):
dungeon_rooms.append(new_room)
_carve_room(new_room)
for i in range(dungeon_rooms.size() - 1):
_carve_corridor(dungeon_rooms[i], dungeon_rooms[i + 1])
func _dungeon_room_fits(new_room: Rect2i) -> bool:
var expanded = Rect2i(new_room.position - Vector2i(2, 2), new_room.size + Vector2i(4, 4))
for room in dungeon_rooms:
if expanded.intersects(room):
return false
return true
func _carve_room(room: Rect2i):
for x in range(room.position.x, room.position.x + room.size.x):
for y in range(room.position.y, room.position.y + room.size.y):
dungeon_grid[x][y] = 1
func _carve_corridor(room_a: Rect2i, room_b: Rect2i):
var ax = room_a.position.x + room_a.size.x / 2
var ay = room_a.position.y + room_a.size.y / 2
var bx = room_b.position.x + room_b.size.x / 2
var by = room_b.position.y + room_b.size.y / 2
var corridor_width = 2
var start_x = min(ax, bx)
var end_x = max(ax, bx)
for x in range(start_x, end_x + 1):
for w in range(corridor_width):
if x >= 0 and x < GRID_WIDTH and ay + w >= 0 and ay + w < GRID_HEIGHT:
dungeon_grid[x][ay + w] = 1
var start_y = min(ay, by)
var end_y = max(ay, by)
for y in range(start_y, end_y + 1):
for w in range(corridor_width):
if bx + w >= 0 and bx + w < GRID_WIDTH and y >= 0 and y < GRID_HEIGHT:
dungeon_grid[bx + w][y] = 1
func _build_dungeon_geometry():
var floor_mat = StandardMaterial3D.new()
floor_mat.albedo_color = Color(0.25, 0.22, 0.2)
floor_mat.roughness = 0.9
var wall_mat = StandardMaterial3D.new()
wall_mat.albedo_color = Color(0.35, 0.3, 0.28)
wall_mat.roughness = 0.95
var ceiling_mat = StandardMaterial3D.new()
ceiling_mat.albedo_color = Color(0.2, 0.18, 0.16)
ceiling_mat.roughness = 0.95
for x in range(GRID_WIDTH):
for y in range(GRID_HEIGHT):
if dungeon_grid[x][y] == 1:
# Boden
var floor_tile = CSGBox3D.new()
floor_tile.size = Vector3(TILE_SIZE, 0.3, TILE_SIZE)
floor_tile.position = Vector3(x * TILE_SIZE, -0.15, y * TILE_SIZE)
floor_tile.material = floor_mat
floor_tile.use_collision = true
dungeon_container.add_child(floor_tile)
# Decke
var ceil_tile = CSGBox3D.new()
ceil_tile.size = Vector3(TILE_SIZE, 0.3, TILE_SIZE)
ceil_tile.position = Vector3(x * TILE_SIZE, WALL_HEIGHT + 0.15, y * TILE_SIZE)
ceil_tile.material = ceiling_mat
dungeon_container.add_child(ceil_tile)
# Wände
var directions = [
Vector2i(-1, 0), Vector2i(1, 0),
Vector2i(0, -1), Vector2i(0, 1)
]
for dir in directions:
var nx = x + dir.x
var ny = y + dir.y
if nx < 0 or nx >= GRID_WIDTH or ny < 0 or ny >= GRID_HEIGHT or dungeon_grid[nx][ny] == 0:
var wall = CSGBox3D.new()
if dir.x != 0:
wall.size = Vector3(0.3, WALL_HEIGHT, TILE_SIZE)
wall.position = Vector3(
x * TILE_SIZE + dir.x * TILE_SIZE / 2.0,
WALL_HEIGHT / 2.0,
y * TILE_SIZE
)
else:
wall.size = Vector3(TILE_SIZE, WALL_HEIGHT, 0.3)
wall.position = Vector3(
x * TILE_SIZE,
WALL_HEIGHT / 2.0,
y * TILE_SIZE + dir.y * TILE_SIZE / 2.0
)
wall.material = wall_mat
wall.use_collision = true
dungeon_container.add_child(wall)
func _place_return_portal():
var first = dungeon_rooms[0]
var portal_pos = Vector3(
(first.position.x + 1.0) * TILE_SIZE,
0,
(first.position.y + 1.0) * TILE_SIZE
)
var portal = Node3D.new()
portal.name = "ReturnPortal"
portal.position = portal_pos
dungeon_container.add_child(portal)
var portal_mat = StandardMaterial3D.new()
portal_mat.albedo_color = Color(0.2, 0.4, 1.0)
portal_mat.emission_enabled = true
portal_mat.emission = Color(0.2, 0.4, 1.0)
portal_mat.emission_energy_multiplier = 2.0
# Portal-Ring
var frame = CSGTorus3D.new()
frame.inner_radius = 1.2
frame.outer_radius = 1.6
frame.ring_sides = 16
frame.sides = 24
frame.position = Vector3(0, 2.0, 0)
frame.material = portal_mat
portal.add_child(frame)
# Portal-Fläche
var surface = CSGCylinder3D.new()
surface.radius = 1.2
surface.height = 0.1
surface.sides = 24
surface.position = Vector3(0, 2.0, 0)
surface.material = portal_mat
portal.add_child(surface)
# Trigger
return_portal_area = Area3D.new()
return_portal_area.name = "PortalArea"
return_portal_area.position = Vector3(0, 1.5, 0)
portal.add_child(return_portal_area)
var col = CollisionShape3D.new()
var shape = BoxShape3D.new()
shape.size = Vector3(4, 4, 4)
col.shape = shape
return_portal_area.add_child(col)
# Label
return_portal_label = Label3D.new()
return_portal_label.text = "Zurück zur Oberwelt [E]"
return_portal_label.position = Vector3(0, 4.5, 0)
return_portal_label.font_size = 48
return_portal_label.modulate = Color(0.3, 0.6, 1.0)
return_portal_label.billboard = BaseMaterial3D.BILLBOARD_ENABLED
return_portal_label.visible = false
portal.add_child(return_portal_label)
func _place_deeper_portal():
var last = dungeon_rooms[dungeon_rooms.size() - 1]
var portal_pos = Vector3(
(last.position.x + last.size.x / 2.0) * TILE_SIZE,
0,
(last.position.y + last.size.y / 2.0) * TILE_SIZE
)
var portal = Node3D.new()
portal.name = "DeeperPortal"
portal.position = portal_pos
dungeon_container.add_child(portal)
var portal_mat = StandardMaterial3D.new()
portal_mat.albedo_color = Color(1.0, 0.3, 0.1)
portal_mat.emission_enabled = true
portal_mat.emission = Color(1.0, 0.3, 0.1)
portal_mat.emission_energy_multiplier = 2.0
# Portal-Ring (rot/orange)
var frame = CSGTorus3D.new()
frame.inner_radius = 1.2
frame.outer_radius = 1.6
frame.ring_sides = 16
frame.sides = 24
frame.position = Vector3(0, 2.0, 0)
frame.material = portal_mat
portal.add_child(frame)
# Portal-Fläche
var surface = CSGCylinder3D.new()
surface.radius = 1.2
surface.height = 0.1
surface.sides = 24
surface.position = Vector3(0, 2.0, 0)
surface.material = portal_mat
portal.add_child(surface)
# Trigger
deeper_portal_area = Area3D.new()
deeper_portal_area.name = "DeeperPortalArea"
deeper_portal_area.position = Vector3(0, 1.5, 0)
portal.add_child(deeper_portal_area)
var col = CollisionShape3D.new()
var shape = BoxShape3D.new()
shape.size = Vector3(4, 4, 4)
col.shape = shape
deeper_portal_area.add_child(col)
# Label
deeper_portal_label = Label3D.new()
deeper_portal_label.text = "Ebene " + str(dungeon_level + 1) + " betreten [E]"
deeper_portal_label.position = Vector3(0, 4.5, 0)
deeper_portal_label.font_size = 48
deeper_portal_label.modulate = Color(1.0, 0.5, 0.2)
deeper_portal_label.billboard = BaseMaterial3D.BILLBOARD_ENABLED
deeper_portal_label.visible = false
portal.add_child(deeper_portal_label)
func _setup_dungeon_lighting():
# Licht in jedem Raum
for room in dungeon_rooms:
var center = Vector3(
(room.position.x + room.size.x / 2.0) * TILE_SIZE,
WALL_HEIGHT - 1.0,
(room.position.y + room.size.y / 2.0) * TILE_SIZE
)
var light = OmniLight3D.new()
light.position = center
light.omni_range = 15.0
light.light_energy = 1.2
light.light_color = Color(1.0, 0.85, 0.6)
light.shadow_enabled = true
dungeon_container.add_child(light)
# Korridor-Beleuchtung
for i in range(dungeon_rooms.size() - 1):
var room_a = dungeon_rooms[i]
var room_b = dungeon_rooms[i + 1]
var ax = (room_a.position.x + room_a.size.x / 2.0) * TILE_SIZE
var ay = (room_a.position.y + room_a.size.y / 2.0) * TILE_SIZE
var bx = (room_b.position.x + room_b.size.x / 2.0) * TILE_SIZE
var by = (room_b.position.y + room_b.size.y / 2.0) * TILE_SIZE
# Licht am L-Knick
var corner_light = OmniLight3D.new()
corner_light.position = Vector3(bx, WALL_HEIGHT - 1.0, ay)
corner_light.omni_range = 12.0
corner_light.light_energy = 1.0
corner_light.light_color = Color(1.0, 0.8, 0.5)
dungeon_container.add_child(corner_light)
# Mitte horizontaler Gang
var mid_h = OmniLight3D.new()
mid_h.position = Vector3((ax + bx) / 2.0, WALL_HEIGHT - 1.0, ay)
mid_h.omni_range = 10.0
mid_h.light_energy = 0.8
mid_h.light_color = Color(1.0, 0.8, 0.5)
dungeon_container.add_child(mid_h)
# Mitte vertikaler Gang
var mid_v = OmniLight3D.new()
mid_v.position = Vector3(bx, WALL_HEIGHT - 1.0, (ay + by) / 2.0)
mid_v.omni_range = 10.0
mid_v.light_energy = 0.8
mid_v.light_color = Color(1.0, 0.8, 0.5)
dungeon_container.add_child(mid_v)
func _setup_dungeon_environment():
var env = Environment.new()
env.background_mode = Environment.BG_COLOR
env.background_color = Color(0.02, 0.02, 0.04)
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
env.ambient_light_color = Color(0.08, 0.07, 0.1)
env.ambient_light_energy = 0.3
env.tonemap_mode = Environment.TONE_MAPPER_FILMIC
env.fog_enabled = true
env.fog_light_color = Color(0.05, 0.05, 0.08)
env.fog_density = 0.02
dungeon_env = WorldEnvironment.new()
dungeon_env.name = "DungeonEnv"
dungeon_env.environment = env
add_child(dungeon_env)
func _build_dungeon_navmesh():
var nav_region = NavigationRegion3D.new()
nav_region.name = "DungeonNavRegion"
var nav_mesh = NavigationMesh.new()
var half = TILE_SIZE / 2.0
var margin = 0.5 # Abstand zu Wänden
# Positions-basierte Vertex-Deduplizierung:
# Gleiche Position → gleicher Index → NavMesh ist automatisch zusammenhängend
var pos_to_idx: Dictionary = {} # "x_z" String → vertex index
var vertices = PackedVector3Array()
var cell_indices: Array = [] # [{i0, i1, i2, i3}, ...]
for x in range(GRID_WIDTH):
for y in range(GRID_HEIGHT):
if dungeon_grid[x][y] != 1:
continue
var wall_left = (x <= 0 or dungeon_grid[x - 1][y] == 0)
var wall_right = (x >= GRID_WIDTH - 1 or dungeon_grid[x + 1][y] == 0)
var wall_top = (y <= 0 or dungeon_grid[x][y - 1] == 0)
var wall_bot = (y >= GRID_HEIGHT - 1 or dungeon_grid[x][y + 1] == 0)
var lx = x * TILE_SIZE - half + (margin if wall_left else 0.0)
var rx = (x + 1) * TILE_SIZE - half - (margin if wall_right else 0.0)
var tz = y * TILE_SIZE - half + (margin if wall_top else 0.0)
var bz = (y + 1) * TILE_SIZE - half - (margin if wall_bot else 0.0)
var corners = [
Vector3(lx, 0.0, tz), Vector3(rx, 0.0, tz),
Vector3(rx, 0.0, bz), Vector3(lx, 0.0, bz)
]
var indices = PackedInt32Array()
for c in corners:
var key = "%0.4f_%0.4f" % [c.x, c.z]
if not pos_to_idx.has(key):
pos_to_idx[key] = vertices.size()
vertices.append(c)
indices.append(pos_to_idx[key])
cell_indices.append(indices)
nav_mesh.vertices = vertices
for ci in cell_indices:
nav_mesh.add_polygon(ci)
nav_region.navigation_mesh = nav_mesh
dungeon_container.add_child(nav_region)
func _spawn_dungeon_enemies():
for i in range(1, dungeon_rooms.size()):
# Letzter Raum = Deeper-Portal, keine Gegner
if i == dungeon_rooms.size() - 1:
continue
var room = dungeon_rooms[i]
var center = Vector3(
(room.position.x + room.size.x / 2.0) * TILE_SIZE,
0.5,
(room.position.y + room.size.y / 2.0) * TILE_SIZE
)
var enemy = ENEMY_SCENE.instantiate()
dungeon_container.add_child(enemy)
enemy.global_position = center
# Gegner-Level = Dungeon-Ebene
enemy.mob_level = dungeon_level
_setup_enemy(enemy)
# ─────────────────────────────────────────────────────────────────────────────
# MENÜ & SPIELER-SETUP
# ─────────────────────────────────────────────────────────────────────────────
# Nach Hauptmenü: Klassenauswahl anzeigen
func _on_start_game():
var menu = CLASS_SELECTION_MENU.instantiate()
add_child(menu)
menu.class_selected.connect(_on_class_selected)
get_tree().paused = true
# Klasse ausgewählt: Spieler initialisieren
func _on_class_selected(character_class: CharacterClass):
# Menü-Musik stoppen und entfernen
for child in get_children():
if child is AudioStreamPlayer:
child.stop()
child.queue_free()
player.character_class = character_class
# Skills klassenabhängig aufbauen
player._init_class_skills()
for i in range(9):
player._refresh_action_slot(i)
# Startausrüstung klassenabhängig
if character_class.resource_type == CharacterClass.ResourceType.MANA:
player.equip_item(STARTER_STAFF)
else:
player.equip_item(STARTER_SWORD)
player.equip_item(STARTER_CHEST)
player._calculate_stats()
player.current_hp = player.max_hp
var potion = STARTER_POTION.duplicate()
potion.stack_size = 3
player.inventory.add_item(potion)
player.assign_to_action_bar(2, potion)
player.current_resource = player.max_resource
player.hud.update_health(player.current_hp, player.max_hp)
player.hud.update_resource(player.current_resource, player.max_resource, player.get_resource_name())
print("Klasse gewählt: ", character_class.class_name_de)
# Jetzt Gegner initialisieren
for child in get_children():
if child.has_method("take_damage") and child != player:
_setup_enemy(child)
# ─────────────────────────────────────────────────────────────────────────────
# GEGNER-SYSTEM
# ─────────────────────────────────────────────────────────────────────────────
func _setup_enemy(enemy):
if enemy and player:
enemy.target = player
if enemy.loot_table == null:
enemy.loot_table = GOBLIN_LOOT
if not enemy.enemy_died.is_connected(_on_enemy_died):
enemy.enemy_died.connect(_on_enemy_died)
if not enemy.enemy_dropped_loot.is_connected(_on_enemy_dropped_loot):
enemy.enemy_dropped_loot.connect(_on_enemy_dropped_loot)
func _on_enemy_dropped_loot(loot: Dictionary, world_pos: Vector3):
if player:
player.receive_loot(loot, world_pos)
func _on_enemy_died(spawn_position: Vector3, xp_reward: int):
if player:
player.gain_xp(xp_reward)
print("Respawn in ", RESPAWN_TIME, " Sekunden...")
await get_tree().create_timer(RESPAWN_TIME).timeout
if in_dungeon and dungeon_container:
var new_enemy = ENEMY_SCENE.instantiate()
dungeon_container.add_child(new_enemy)
new_enemy.global_position = spawn_position
_setup_enemy(new_enemy)
elif not in_dungeon:
_spawn_enemy(spawn_position)
func _spawn_enemy(position: Vector3):
var new_enemy = ENEMY_SCENE.instantiate()
add_child(new_enemy)
new_enemy.global_position = position
_setup_enemy(new_enemy)