DungeonCrawler/camera_pivot.gd
Andre 12b70c7498 Overworld, Dungeon-System, Goblin-Modell, NavMesh-Pathfinding, Kamera-Kollision
- Overworld: Gras-Terrain mit Noise-Shader, Berg, Dungeon-Tor, Felsen, Bäume
- Dungeon: Prozedurale Generierung (Grid, Räume, L-Gänge), Multi-Ebenen mit Persistenz
- Portal-System: Blau (zurück/raus), Rot (tiefer), Auswahl-UI ab Ebene 2+
- Gegner: Goblin-Modell + Animationen statt Warrior, Capsule angepasst
- NavMesh: Manuell gebautes NavigationMesh im Dungeon mit Wand-Margin und shared Vertices
- Pathfinding: Gegner nutzen NavigationAgent3D, laufen um Wände herum
- Leash-System: Gegner verlieren Aggro ab 30 Einheiten vom Spawn
- Kamera-Kollision: Raycast verhindert Durchsehen durch Wände, ignoriert Gegner
- Respawn-Timer auf 60s, Death-Timer auf 10s erhöht
- Dokumentation aktualisiert (Dungeon, NavMesh, Goblin, Kamera)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 01:51:07 +01:00

101 lines
4.6 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CameraPivot.gd
# ─────────────────────────────────────────────────────────────────────────────
# Third-Person Kamerasystem Kind-Node des Spielers
#
# Kamera-Modi:
# • Kein Ziel, LMB gehalten → Kamera orbitet um den Spieler (world_yaw ändert sich,
# Spielerrotation bleibt)
# • Kein Ziel, RMB gehalten → Spieler + Kamera drehen sich gemeinsam
# • Ziel markiert → Soft Lock-On: Spieler dreht sich smooth zum Ziel,
# Kamera bleibt direkt dahinter
# • Mausrad → Zoom (min_zoom … max_zoom)
#
# world_yaw: absolute Weltausrichtung der Kamera in Grad (Y-Achse).
# Unabhängig von player.rotation.y → verhindert Feedback-Loop bei Souls-Rotation.
# camera_pivot.rotation.y wird in _process() immer als (world_yaw - player.rotation.y)
# gesetzt, sodass die Kamera in World-Space stabil bleibt.
# ─────────────────────────────────────────────────────────────────────────────
extends Node3D
@export var sensitivity = 0.3
@export var min_pitch = -40.0
@export var max_pitch = 20.0
@export var min_zoom = 5.0
@export var max_zoom = 20.0
@export var zoom_speed = 1.0
@export var lock_on_speed = 5.0
var pitch: float = 0.0
var world_yaw: float = 0.0 # Absolute Weltausrichtung der Kamera (unabhängig von Spielerrotation)
var desired_zoom: float = 10.0 # Gewünschter Zoom-Abstand
@onready var camera = $Camera3D
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
world_yaw = get_parent().rotation.y
desired_zoom = camera.position.z
func _input(event):
var player = get_parent()
var has_target = player.target != null and is_instance_valid(player.target)
if event is InputEventMouseMotion:
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
# LMB: nur Kamera dreht sich, Spieler bleibt (auch mit Target → umschauen)
world_yaw -= deg_to_rad(event.relative.x * sensitivity)
pitch -= event.relative.y * sensitivity
pitch = clamp(pitch, min_pitch, max_pitch)
rotation_degrees.x = pitch
elif Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
# RMB: Spieler + Kamera drehen sich gemeinsam (auch mit Target → weglaufen)
var delta_yaw = deg_to_rad(-event.relative.x * sensitivity)
world_yaw += delta_yaw
player.rotation.y += delta_yaw
pitch -= event.relative.y * sensitivity
pitch = clamp(pitch, min_pitch, max_pitch)
rotation_degrees.x = pitch
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
desired_zoom = clamp(desired_zoom + zoom_speed, min_zoom, max_zoom)
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
desired_zoom = clamp(desired_zoom - zoom_speed, min_zoom, max_zoom)
func _process(delta):
var player = get_parent()
var rmb_held = Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)
var lmb_held = Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)
if player.target != null and is_instance_valid(player.target) and not player.is_rolling and not rmb_held and not lmb_held:
# Soft Lock-On: Spieler dreht sich zum Ziel, Kamera folgt direkt dahinter
# (RMB gehalten → Lock-On pausiert, Spieler kann sich wegdrehen)
var to_target = player.target.global_position - player.global_position
to_target.y = 0
if to_target.length() > 0.1:
var target_angle = atan2(-to_target.x, -to_target.z)
player.rotation.y = lerp_angle(player.rotation.y, target_angle, delta * lock_on_speed)
world_yaw = player.rotation.y
# Lokale Rotation so setzen dass Kamera immer auf world_yaw zeigt
rotation.y = world_yaw - player.rotation.y
# Kamera-Kollision: Raycast vom Pivot zur gewünschten Kameraposition
var space_state = get_world_3d().direct_space_state
var pivot_pos = global_position
# Kamera sitzt auf lokaler Z-Achse des Pivots
var desired_cam_local = Vector3(0, 0, desired_zoom)
var desired_cam_pos = global_transform * desired_cam_local
var query = PhysicsRayQueryParameters3D.create(pivot_pos, desired_cam_pos)
query.exclude = [player.get_rid()]
query.collide_with_bodies = true
query.collide_with_areas = false
query.collision_mask = 1
var result = space_state.intersect_ray(query)
if result and not result.collider is CharacterBody3D:
var hit_dist = pivot_pos.distance_to(result.position) - 0.5
camera.position.z = clamp(hit_dist, 1.0, desired_zoom)
else:
camera.position.z = lerp(camera.position.z, desired_zoom, delta * 5.0)