Bewegung: - Souls-Modus (kein Ziel): Charakter dreht sich zur Laufrichtung relativ zu camera_pivot.world_yaw; bei RMB gehalten → Strafe statt Drehung - Walk-Toggle (NumLock): RPG-Strafe-Modus mit langsamerer Geschwindigkeit - Lock-On (Ziel markiert): Spieler dreht sich smooth zum Gegner, WASD = Strafe - Ausweichrolle (Shift): rollt in Eingaberichtung (world_yaw-relativ im Souls-Modus) - Sofort-180°-Snap statt animierter Drehung bei >150° Winkelunterschied Kamera (camera_pivot.gd): - world_yaw: absolute Weltausrichtung, unabhängig von Spielerrotation (kein Feedback-Loop) - LMB gehalten: Kamera orbitet, Spieler dreht sich nicht - RMB gehalten: Spieler + Kamera drehen sich gemeinsam - Soft Lock-On: camera_pivot dreht Spieler smooth zum Ziel Animationen: - Neue FBX-Animationen: Quick Roll, Running Jump, Walking Jump, Running Strafe L/R, Running Turn 180 - Animationen im Souls-Modus: immer "run" vorwärts; S = walk_back - Root-Motion-Strip: XZ-Bewegung auf Knochen-Tracks wird genullt Welt: - Boden-Shader: Schachbrettmuster in World-Space (INV_VIEW_MATRIX) - ProceduralSkyMaterial + WorldEnvironment per Code - Alte assets/animations und assets/models durch Warrior+Animation ersetzt Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
145 lines
5 KiB
GDScript
145 lines
5 KiB
GDScript
# World.gd
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Spielwelt-Controller
|
|
#
|
|
# Verantwortlichkeiten:
|
|
# • Prozeduraler Boden-Shader (Schachbrettmuster, world-space stabil)
|
|
# • Prozeduraler Himmel (ProceduralSkyMaterial + WorldEnvironment)
|
|
# • Hauptmenü → Klassenauswahl → Spielinitialisierung
|
|
# • Startausrüstung je nach Klasse
|
|
# • Gegner-Setup, Loot-Weiterleitung, Respawn nach Tod
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
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 = 5.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")
|
|
|
|
# Loot Tables
|
|
const GOBLIN_LOOT = preload("res://loot_tables/goblin_loot.tres")
|
|
|
|
@onready var player = $Player
|
|
@onready var floor_mesh = $Boden/MeshInstance3D
|
|
|
|
func _ready():
|
|
_setup_floor_material()
|
|
_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():
|
|
var sky_mat = ProceduralSkyMaterial.new()
|
|
sky_mat.sky_top_color = Color(0.15, 0.35, 0.75)
|
|
sky_mat.sky_horizon_color = Color(0.55, 0.75, 1.0)
|
|
sky_mat.ground_horizon_color = Color(0.35, 0.30, 0.25)
|
|
sky_mat.ground_bottom_color = Color(0.1, 0.1, 0.1)
|
|
sky_mat.sun_angle_max = 30.0
|
|
sky_mat.sun_curve = 0.15
|
|
|
|
var sky = Sky.new()
|
|
sky.sky_material = sky_mat
|
|
|
|
var env = Environment.new()
|
|
env.background_mode = Environment.BG_SKY
|
|
env.sky = sky
|
|
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
|
|
env.ambient_light_energy = 0.6
|
|
env.tonemap_mode = Environment.TONE_MAPPER_FILMIC
|
|
|
|
var world_env = WorldEnvironment.new()
|
|
world_env.environment = env
|
|
add_child(world_env)
|
|
|
|
func _setup_floor_material():
|
|
var shader = Shader.new()
|
|
shader.code = """
|
|
shader_type spatial;
|
|
uniform float grid_size : hint_range(0.5, 10.0) = 2.0;
|
|
uniform vec4 color_a : source_color = vec4(0.22, 0.22, 0.22, 1.0);
|
|
uniform vec4 color_b : source_color = vec4(0.38, 0.38, 0.38, 1.0);
|
|
|
|
void fragment() {
|
|
vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
|
vec2 cell = floor(world_pos.xz / grid_size);
|
|
float checker = mod(cell.x + cell.y, 2.0);
|
|
ALBEDO = mix(color_a.rgb, color_b.rgb, checker);
|
|
ROUGHNESS = 0.85;
|
|
METALLIC = 0.0;
|
|
}
|
|
"""
|
|
var mat = ShaderMaterial.new()
|
|
mat.shader = shader
|
|
floor_mesh.material_override = mat
|
|
|
|
# Nach Hauptmenü: Klassenauswahl anzeigen
|
|
func _on_start_game():
|
|
var menu = CLASS_SELECTION_MENU.instantiate()
|
|
add_child(menu)
|
|
menu.class_selected.connect(_on_class_selected)
|
|
# Spiel wieder pausieren für Klassenauswahl
|
|
get_tree().paused = true
|
|
|
|
# Klasse ausgewählt: Spieler initialisieren
|
|
func _on_class_selected(character_class: CharacterClass):
|
|
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
|
|
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
|
|
var enemy = get_node("Enemy")
|
|
_setup_enemy(enemy)
|
|
|
|
# Gegner initialisieren und Signal verbinden
|
|
func _setup_enemy(enemy):
|
|
if enemy and player:
|
|
enemy.target = player
|
|
if enemy.loot_table == null:
|
|
enemy.loot_table = GOBLIN_LOOT
|
|
enemy.enemy_died.connect(_on_enemy_died)
|
|
enemy.enemy_dropped_loot.connect(_on_enemy_dropped_loot)
|
|
else:
|
|
print("Fehler: Player oder Enemy nicht gefunden!")
|
|
|
|
# Loot-Drop an Spieler weiterleiten
|
|
func _on_enemy_dropped_loot(loot: Dictionary, world_pos: Vector3):
|
|
if player:
|
|
player.receive_loot(loot, world_pos)
|
|
|
|
# Gegner gestorben: Nach 5 Sekunden respawnen
|
|
func _on_enemy_died(spawn_position: Vector3, _xp_reward: int):
|
|
print("Respawn in ", RESPAWN_TIME, " Sekunden...")
|
|
await get_tree().create_timer(RESPAWN_TIME).timeout
|
|
_spawn_enemy(spawn_position)
|
|
|
|
# Neuen Gegner an Position spawnen
|
|
func _spawn_enemy(position: Vector3):
|
|
var new_enemy = ENEMY_SCENE.instantiate()
|
|
add_child(new_enemy)
|
|
new_enemy.global_position = position
|
|
_setup_enemy(new_enemy)
|
|
print("Gegner respawned!")
|