Souls/RPG-Hybrid Bewegungssystem, Kamera, Animationen & Dokumentation

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>
This commit is contained in:
Andre 2026-03-16 22:26:10 +01:00
parent 716816e1e5
commit 394b3a89b8
63 changed files with 1706 additions and 1428 deletions

View file

@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://cfmx5li2h2gsb" uid="uid://vo540cksibhf"
path="res://.godot/imported/Autoattack.fbx-3670ec5144a5943b8d9ee95f7186efd3.scn" path="res://.godot/imported/Autoattack.fbx-53cd4a5b8eaf2a74c3bff45c47238010.scn"
[deps] [deps]
source_file="res://assets/animations/Autoattack.fbx" source_file="res://assets/Warrior+Animation/Autoattack.fbx"
dest_files=["res://.godot/imported/Autoattack.fbx-3670ec5144a5943b8d9ee95f7186efd3.scn"] dest_files=["res://.godot/imported/Autoattack.fbx-53cd4a5b8eaf2a74c3bff45c47238010.scn"]
[params] [params]

View file

@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://dpm8k57akvg3k" uid="uid://b3n7056tyskib"
path="res://.godot/imported/Dying Backwards.fbx-77c7ef392c2c96235d9f4f9f7c331de8.scn" path="res://.godot/imported/Dying Backwards.fbx-f2b9c7b8a945c1b32b59e6174df75635.scn"
[deps] [deps]
source_file="res://assets/animations/Dying Backwards.fbx" source_file="res://assets/Warrior+Animation/Dying Backwards.fbx"
dest_files=["res://.godot/imported/Dying Backwards.fbx-77c7ef392c2c96235d9f4f9f7c331de8.scn"] dest_files=["res://.godot/imported/Dying Backwards.fbx-f2b9c7b8a945c1b32b59e6174df75635.scn"]
[params] [params]

View file

@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://cmpv0gty3rcm6" uid="uid://r25qslfmbg4s"
path="res://.godot/imported/Heavy Strike.fbx-5d5103d3203a6a38edd177bdd35ccb16.scn" path="res://.godot/imported/Heavy Strike.fbx-f9eed104269d2005bd3440c7e91ac7ea.scn"
[deps] [deps]
source_file="res://assets/animations/Heavy Strike.fbx" source_file="res://assets/Warrior+Animation/Heavy Strike.fbx"
dest_files=["res://.godot/imported/Heavy Strike.fbx-5d5103d3203a6a38edd177bdd35ccb16.scn"] dest_files=["res://.godot/imported/Heavy Strike.fbx-f9eed104269d2005bd3440c7e91ac7ea.scn"]
[params] [params]

Binary file not shown.

View file

@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://wurcf4qd6sn1" uid="uid://ctup6ycp8fuqk"
path="res://.godot/imported/Left Strafe Walking.fbx-918d1a60e568dd43e53b31199b3f57d3.scn" path="res://.godot/imported/Quick Roll To Run.fbx-b02b0c01f60ef724d35b77bd3136b689.scn"
[deps] [deps]
source_file="res://assets/animations/Left Strafe Walking.fbx" source_file="res://assets/Warrior+Animation/Quick Roll To Run.fbx"
dest_files=["res://.godot/imported/Left Strafe Walking.fbx-918d1a60e568dd43e53b31199b3f57d3.scn"] dest_files=["res://.godot/imported/Quick Roll To Run.fbx-b02b0c01f60ef724d35b77bd3136b689.scn"]
[params] [params]

Binary file not shown.

View file

@ -0,0 +1,44 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bhte2kawlr8bv"
path="res://.godot/imported/Running Jump.fbx-02348a505e41bceab5ba1604094098b3.scn"
[deps]
source_file="res://assets/Warrior+Animation/Running Jump.fbx"
dest_files=["res://.godot/imported/Running Jump.fbx-02348a505e41bceab5ba1604094098b3.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

Binary file not shown.

View file

@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://bhv5ig8xnw4v0" uid="uid://gkl3ij5dvghc"
path="res://.godot/imported/Right Strafe Walking.fbx-f1318b812bb12a00a11ec8112c1f6855.scn" path="res://.godot/imported/Running Left Strafe.fbx-f8ffe3f38c4c7e18db7a5cb37788743e.scn"
[deps] [deps]
source_file="res://assets/animations/Right Strafe Walking.fbx" source_file="res://assets/Warrior+Animation/Running Left Strafe.fbx"
dest_files=["res://.godot/imported/Right Strafe Walking.fbx-f1318b812bb12a00a11ec8112c1f6855.scn"] dest_files=["res://.godot/imported/Running Left Strafe.fbx-f8ffe3f38c4c7e18db7a5cb37788743e.scn"]
[params] [params]

Binary file not shown.

View file

@ -0,0 +1,44 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://lm2ofk411i4"
path="res://.godot/imported/Running Right Strafe run.fbx-31b53a15a609d5d0f6711845d5941119.scn"
[deps]
source_file="res://assets/Warrior+Animation/Running Right Strafe run.fbx"
dest_files=["res://.godot/imported/Running Right Strafe run.fbx-31b53a15a609d5d0f6711845d5941119.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

Binary file not shown.

View file

@ -0,0 +1,44 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://7j8e63c4gtt8"
path="res://.godot/imported/Running Turn 180.fbx-863af9daa2d6c9284445a0bd16da3f45.scn"
[deps]
source_file="res://assets/Warrior+Animation/Running Turn 180.fbx"
dest_files=["res://.godot/imported/Running Turn 180.fbx-863af9daa2d6c9284445a0bd16da3f45.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

View file

@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://bck2nreqc2gb3" uid="uid://1unm2efm7s3f"
path="res://.godot/imported/Walking Backwards.fbx-485a35ec4e48207190c0e10717297ab7.scn" path="res://.godot/imported/Walking Backwards.fbx-cf1183868a4d9d213ebca12d53b3ae81.scn"
[deps] [deps]
source_file="res://assets/animations/Walking Backwards.fbx" source_file="res://assets/Warrior+Animation/Walking Backwards.fbx"
dest_files=["res://.godot/imported/Walking Backwards.fbx-485a35ec4e48207190c0e10717297ab7.scn"] dest_files=["res://.godot/imported/Walking Backwards.fbx-cf1183868a4d9d213ebca12d53b3ae81.scn"]
[params] [params]

Binary file not shown.

View file

@ -0,0 +1,44 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cs35mwwqnitrp"
path="res://.godot/imported/Walking Jump.fbx-327d0240715eb7b17d5a21fdf85a64e6.scn"
[deps]
source_file="res://assets/Warrior+Animation/Walking Jump.fbx"
dest_files=["res://.godot/imported/Walking Jump.fbx-327d0240715eb7b17d5a21fdf85a64e6.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

View file

@ -0,0 +1,44 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://daeym1tdcnhhd"
path="res://.godot/imported/castle_guard_01.fbx-799eb31d8eba8e9d75bc4ca0ebabde43.scn"
[deps]
source_file="res://assets/Warrior+Animation/castle_guard_01.fbx"
dest_files=["res://.godot/imported/castle_guard_01.fbx-799eb31d8eba8e9d75bc4ca0ebabde43.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

View file

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

View file

@ -2,8 +2,8 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://clc13cbh72eov" uid="uid://d07eqssia5uce"
path.s3tc="res://.godot/imported/Walking_0.png-04bee5fb7233febb0f01a436248762b5.s3tc.ctex" path.s3tc="res://.godot/imported/castle_guard_01_0.png-64069e01a3d6f24be61f8b235530fefe.s3tc.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc"], "imported_formats": ["s3tc_bptc"],
"vram_texture": true "vram_texture": true
@ -14,8 +14,8 @@ generator_parameters={
[deps] [deps]
source_file="res://assets/animations/Walking_0.png" source_file="res://assets/Warrior+Animation/castle_guard_01_0.png"
dest_files=["res://.godot/imported/Walking_0.png-04bee5fb7233febb0f01a436248762b5.s3tc.ctex"] dest_files=["res://.godot/imported/castle_guard_01_0.png-64069e01a3d6f24be61f8b235530fefe.s3tc.ctex"]
[params] [params]

View file

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View file

@ -2,8 +2,8 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://dphpf7qa3hkvh" uid="uid://b3ofnfnpmjbv4"
path="res://.godot/imported/Walking_1.png-ba1e8e03f6c24586962fc1b62dccdfe9.ctex" path="res://.godot/imported/castle_guard_01_1.png-cc0fe514ff22e36663b36dadc87f514d.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
@ -13,8 +13,8 @@ generator_parameters={
[deps] [deps]
source_file="res://assets/animations/Walking_1.png" source_file="res://assets/Warrior+Animation/castle_guard_01_1.png"
dest_files=["res://.godot/imported/Walking_1.png-ba1e8e03f6c24586962fc1b62dccdfe9.ctex"] dest_files=["res://.godot/imported/castle_guard_01_1.png-cc0fe514ff22e36663b36dadc87f514d.ctex"]
[params] [params]

View file

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View file

@ -2,8 +2,8 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://coqdhbh5uwkjf" uid="uid://cgqa0kap7nde7"
path.s3tc="res://.godot/imported/warrior_2.png-0164e54eb7a56547f2b489b55a55ca25.s3tc.ctex" path.s3tc="res://.godot/imported/castle_guard_01_2.png-7d061f5d8fcddd99f52285a75a1f6982.s3tc.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc"], "imported_formats": ["s3tc_bptc"],
"vram_texture": true "vram_texture": true
@ -14,8 +14,8 @@ generator_parameters={
[deps] [deps]
source_file="res://assets/models/warrior_2.png" source_file="res://assets/Warrior+Animation/castle_guard_01_2.png"
dest_files=["res://.godot/imported/warrior_2.png-0164e54eb7a56547f2b489b55a55ca25.s3tc.ctex"] dest_files=["res://.godot/imported/castle_guard_01_2.png-7d061f5d8fcddd99f52285a75a1f6982.s3tc.ctex"]
[params] [params]
@ -30,7 +30,7 @@ compress/channel_pack=0
mipmaps/generate=true mipmaps/generate=true
mipmaps/limit=-1 mipmaps/limit=-1
roughness/mode=1 roughness/mode=1
roughness/src_normal="res://assets/models/warrior_2.png" roughness/src_normal="res://assets/Warrior+Animation/castle_guard_01_2.png"
process/channel_remap/red=0 process/channel_remap/red=0
process/channel_remap/green=1 process/channel_remap/green=1
process/channel_remap/blue=2 process/channel_remap/blue=2

View file

@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://da1w523lg7i2b" uid="uid://bykl7cq8qx0i"
path="res://.godot/imported/warrior.fbx-b4acdad06881ff9e3d6bb32ec03761c9.scn" path="res://.godot/imported/idle.fbx-6a27ea4d436753d86355f51c515589cc.scn"
[deps] [deps]
source_file="res://assets/models/warrior.fbx" source_file="res://assets/Warrior+Animation/idle.fbx"
dest_files=["res://.godot/imported/warrior.fbx-b4acdad06881ff9e3d6bb32ec03761c9.scn"] dest_files=["res://.godot/imported/idle.fbx-6a27ea4d436753d86355f51c515589cc.scn"]
[params] [params]

View file

@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://cxr7xlsq3vvhi" uid="uid://kmypwcf75mm5"
path="res://.godot/imported/Idle.fbx-ecf8c7ec9efbd10accf3a620e1992827.scn" path="res://.godot/imported/jump.fbx-4653cae9b7a0a7fea2fdbf8b3432de58.scn"
[deps] [deps]
source_file="res://assets/animations/Idle.fbx" source_file="res://assets/Warrior+Animation/jump.fbx"
dest_files=["res://.godot/imported/Idle.fbx-ecf8c7ec9efbd10accf3a620e1992827.scn"] dest_files=["res://.godot/imported/jump.fbx-4653cae9b7a0a7fea2fdbf8b3432de58.scn"]
[params] [params]

View file

@ -0,0 +1,44 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://r3rpi8fcmkne"
path="res://.godot/imported/left strafe walking.fbx-5f9622a45ca2758ee53f10a63deb0dde.scn"
[deps]
source_file="res://assets/Warrior+Animation/left strafe walking.fbx"
dest_files=["res://.godot/imported/left strafe walking.fbx-5f9622a45ca2758ee53f10a63deb0dde.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

View file

@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://bjwb78njpjo2d" uid="uid://x3sux8evka5y"
path="res://.godot/imported/Start Walking.fbx-c54b281554787bf73bcae8159e84a930.scn" path="res://.godot/imported/left strafe.fbx-123d2901134abff2b2af22577a6143ca.scn"
[deps] [deps]
source_file="res://assets/animations/Start Walking.fbx" source_file="res://assets/Warrior+Animation/left strafe.fbx"
dest_files=["res://.godot/imported/Start Walking.fbx-c54b281554787bf73bcae8159e84a930.scn"] dest_files=["res://.godot/imported/left strafe.fbx-123d2901134abff2b2af22577a6143ca.scn"]
[params] [params]

View file

@ -0,0 +1,44 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bfn8s86o81t86"
path="res://.godot/imported/left turn 90.fbx-7ede25625141e45b963a6f806ea7a4b6.scn"
[deps]
source_file="res://assets/Warrior+Animation/left turn 90.fbx"
dest_files=["res://.godot/imported/left turn 90.fbx-7ede25625141e45b963a6f806ea7a4b6.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

View file

@ -0,0 +1,44 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cjs5o45dby6wq"
path="res://.godot/imported/left turn.fbx-c8dee54f4dff7f292b4eb1804655f5cb.scn"
[deps]
source_file="res://assets/Warrior+Animation/left turn.fbx"
dest_files=["res://.godot/imported/left turn.fbx-c8dee54f4dff7f292b4eb1804655f5cb.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

View file

@ -0,0 +1,44 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://drr52cg7ns8ak"
path="res://.godot/imported/right strafe walking.fbx-cc75d3596d7e2bd63ef1120c7d273ed9.scn"
[deps]
source_file="res://assets/Warrior+Animation/right strafe walking.fbx"
dest_files=["res://.godot/imported/right strafe walking.fbx-cc75d3596d7e2bd63ef1120c7d273ed9.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

View file

@ -0,0 +1,44 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://ctfgb4y033qak"
path="res://.godot/imported/right strafe.fbx-a432556f9ff1799cf9608c02c0ad131a.scn"
[deps]
source_file="res://assets/Warrior+Animation/right strafe.fbx"
dest_files=["res://.godot/imported/right strafe.fbx-a432556f9ff1799cf9608c02c0ad131a.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

View file

@ -0,0 +1,44 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bfg20q58h3ifm"
path="res://.godot/imported/right turn 90.fbx-1510f429e9c72d07d6b8f5bd0c243b9d.scn"
[deps]
source_file="res://assets/Warrior+Animation/right turn 90.fbx"
dest_files=["res://.godot/imported/right turn 90.fbx-1510f429e9c72d07d6b8f5bd0c243b9d.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
fbx/naming_version=2

View file

@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://dusjhgyamhmgt" uid="uid://clk4yl4dln5dn"
path="res://.godot/imported/Stop Walking.fbx-3d24a0466a0c2e2a6357ddcab01c8e7a.scn" path="res://.godot/imported/right turn.fbx-b101bc8730403d08b841e8c3a0b41769.scn"
[deps] [deps]
source_file="res://assets/animations/Stop Walking.fbx" source_file="res://assets/Warrior+Animation/right turn.fbx"
dest_files=["res://.godot/imported/Stop Walking.fbx-3d24a0466a0c2e2a6357ddcab01c8e7a.scn"] dest_files=["res://.godot/imported/right turn.fbx-b101bc8730403d08b841e8c3a0b41769.scn"]
[params] [params]

View file

@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://bv1wgnjxtwty1" uid="uid://cjbmdhkq1mvwf"
path="res://.godot/imported/Walking.fbx-a17a5e9807a8458d066589326a10c768.scn" path="res://.godot/imported/running.fbx-ca8e5a7b9cad519a1f30381a5dfeccdf.scn"
[deps] [deps]
source_file="res://assets/animations/Walking.fbx" source_file="res://assets/Warrior+Animation/running.fbx"
dest_files=["res://.godot/imported/Walking.fbx-a17a5e9807a8458d066589326a10c768.scn"] dest_files=["res://.godot/imported/running.fbx-ca8e5a7b9cad519a1f30381a5dfeccdf.scn"]
[params] [params]

View file

@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://kaq2rgjrxnm4" uid="uid://c547ty6nvf11c"
path="res://.godot/imported/Jumping.fbx-785df5cde6fd6a2d61091c404e44b339.scn" path="res://.godot/imported/walking.fbx-574a3fce7d862b76db3ed1e8c594f5b6.scn"
[deps] [deps]
source_file="res://assets/animations/Jumping.fbx" source_file="res://assets/Warrior+Animation/walking.fbx"
dest_files=["res://.godot/imported/Jumping.fbx-785df5cde6fd6a2d61091c404e44b339.scn"] dest_files=["res://.godot/imported/walking.fbx-574a3fce7d862b76db3ed1e8c594f5b6.scn"]
[params] [params]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,44 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dm2h4hkvqyoph"
path.s3tc="res://.godot/imported/Walking_2.png-ce8a6c3320aac98a8634c6e5cf2b9141.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "16d2ed20714fd916290f48d68a233060"
}
[deps]
source_file="res://assets/animations/Walking_2.png"
dest_files=["res://.godot/imported/Walking_2.png-ce8a6c3320aac98a8634c6e5cf2b9141.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=1
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=1
roughness/src_normal="res://assets/animations/Walking_2.png"
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

View file

@ -1,44 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://kb2tcclrpsgp"
path.s3tc="res://.godot/imported/warrior_0.png-922555626e5523f977897d33a0262592.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "a1dc5c32f83d0182b6af19d596014cce"
}
[deps]
source_file="res://assets/models/warrior_0.png"
dest_files=["res://.godot/imported/warrior_0.png-922555626e5523f977897d33a0262592.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

View file

@ -1,43 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dnlkuf4pjgbht"
path="res://.godot/imported/warrior_1.png-aafdc73fc8d6c19365ff4d442a16edcf.ctex"
metadata={
"vram_texture": false
}
generator_parameters={
"md5": "4201468097ebb1a15961ee25c2d64310"
}
[deps]
source_file="res://assets/models/warrior_1.png"
dest_files=["res://.godot/imported/warrior_1.png-aafdc73fc8d6c19365ff4d442a16edcf.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

View file

@ -1,32 +1,76 @@
# CameraPivot.gd # CameraPivot.gd
# Steuert die Third-Person-Kamera: Maussteuerung (RMB), Zoom per Mausrad # ─────────────────────────────────────────────────────────────────────────────
# 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 extends Node3D
@export var sensitivity = 0.3 # Mausempfindlichkeit @export var sensitivity = 0.3
@export var min_pitch = -40.0 # Maximale Neigung nach unten @export var min_pitch = -40.0
@export var max_pitch = 20.0 # Maximale Neigung nach oben @export var max_pitch = 20.0
@export var min_zoom = 5.0 # Minimale Kameraentfernung @export var min_zoom = 5.0
@export var max_zoom = 20.0 # Maximale Kameraentfernung @export var max_zoom = 20.0
@export var zoom_speed = 1.0 # Zoom-Geschwindigkeit pro Mausrad-Schritt @export var zoom_speed = 1.0
@export var lock_on_speed = 5.0
var pitch = 0.0 var pitch: float = 0.0
var world_yaw: float = 0.0 # Absolute Weltausrichtung der Kamera (unabhängig von Spielerrotation)
@onready var camera = $Camera3D @onready var camera = $Camera3D
func _ready(): func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
world_yaw = get_parent().rotation.y
func _input(event): func _input(event):
# RMB gehalten: Kamera drehen var player = get_parent()
if event is InputEventMouseMotion and Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT): var has_target = player.target != null and is_instance_valid(player.target)
get_parent().rotate_y(deg_to_rad(-event.relative.x * sensitivity))
pitch -= event.relative.y * sensitivity if event is InputEventMouseMotion:
pitch = clamp(pitch, min_pitch, max_pitch) if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and not has_target:
rotation_degrees.x = pitch # LMB: nur Kamera dreht sich, Spieler bleibt
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) and not has_target:
# RMB: Spieler + Kamera drehen sich gemeinsam
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
# Mausrad: Zoom
if event is InputEventMouseButton: if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN: if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
camera.position.z = clamp(camera.position.z + zoom_speed, min_zoom, max_zoom) camera.position.z = clamp(camera.position.z + zoom_speed, min_zoom, max_zoom)
if event.button_index == MOUSE_BUTTON_WHEEL_UP: if event.button_index == MOUSE_BUTTON_WHEEL_UP:
camera.position.z = clamp(camera.position.z - zoom_speed, min_zoom, max_zoom) camera.position.z = clamp(camera.position.z - zoom_speed, min_zoom, max_zoom)
func _process(delta):
var player = get_parent()
if player.target != null and is_instance_valid(player.target) and not player.is_rolling:
# Soft Lock-On: Spieler dreht sich zum Ziel, Kamera folgt direkt dahinter
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

456
enemy.gd
View file

@ -1,319 +1,205 @@
# Enemy.gd # Enemy.gd
# Steuert den Gegner: KI-Bewegung zum Spieler, Angriff, HP, Zielanzeige # ─────────────────────────────────────────────────────────────────────────────
# Gegner-KI State Machine mit NavigationAgent3D
#
# Zustände:
# IDLE → wartet bis Spieler in detection_range kommt
# CHASING → läuft via NavMesh zum Spieler
# ATTACKING → steht, dreht sich zum Spieler, greift in attack_speed-Intervallen an
# DEAD → Kollision deaktiviert, Node wird nach kurzer Verzögerung entfernt
#
# Signale:
# enemy_died(spawn_position, xp_reward) → World.gd → Respawn + XP
# enemy_dropped_loot(loot, world_pos) → World.gd → Spieler → LootWindow
# ─────────────────────────────────────────────────────────────────────────────
extends CharacterBody3D extends CharacterBody3D
# ═══════════════════════════════════════════════════════════════
# SIGNALE
# ═══════════════════════════════════════════════════════════════
signal enemy_died(spawn_position: Vector3, xp_reward: int) signal enemy_died(spawn_position: Vector3, xp_reward: int)
signal enemy_dropped_loot(loot: Dictionary, world_position: Vector3) signal enemy_dropped_loot(loot: Dictionary, world_pos: Vector3)
# ═══════════════════════════════════════════════════════════════
# STATS
# ═══════════════════════════════════════════════════════════════
@export var max_hp: int = 50
@export var min_damage: int = 3
@export var max_damage: int = 7
@export var attack_range: float = 2.0
@export var attack_speed: float = 2.0 # Sekunden zwischen Angriffen
@export var move_speed: float = 3.0
@export var xp_reward: int = 20
@export var detection_range: float = 15.0
@export var loot_table: LootTable = null
var current_hp: int
var target = null # Spieler
# ═══════════════════════════════════════════════════════════════
# ZUSTAND
# ═══════════════════════════════════════════════════════════════
enum State { IDLE, CHASING, ATTACKING, DEAD }
var state: State = State.IDLE
var attack_cooldown: float = 0.0
var is_dead: bool = false
const SPEED = 3.0
const PATROL_SPEED = 1.5
const GRAVITY = 9.8 const GRAVITY = 9.8
const ATTACK_RANGE = 1.5
const ATTACK_COOLDOWN = 2.0
const AGGRO_RANGE = 8.0 # Entfernung ab der der Gegner angreift
const PATROL_RADIUS = 5.0 # Radius um Spawn-Position für Patrol
const PATROL_WAIT_TIME = 2.0 # Wartezeit am Patrol-Punkt
# Level-Differenz Konstanten # ═══════════════════════════════════════════════════════════════
const LEVEL_DIFF_DAMAGE_MOD = 0.1 # 10% mehr/weniger Schaden pro Level-Differenz # NODE-REFERENZEN
const MAX_LEVEL_DIFF_MOD = 0.5 # Maximal 50% Modifikation # ═══════════════════════════════════════════════════════════════
enum State { PATROL, CHASE, ATTACK } @onready var nav_agent: NavigationAgent3D = $NavigationAgent3D
@onready var health_label: Label3D = $HealthDisplay/Label3D
# Stats-System # ═══════════════════════════════════════════════════════════════
@export var level: int = 1 # READY
@export var base_strength: int = 8 # ═══════════════════════════════════════════════════════════════
@export var base_stamina: int = 10
@export var base_armor: int = 5 # Rüstung reduziert Nahkampfschaden
# Berechnete Stats
var strength: int = 8
var stamina: int = 10
var armor: int = 5
var max_hp: int = 100
var current_hp: int = 100
var attack_damage: int = 5
# XP-Belohnung (skaliert mit Level)
var xp_reward: int = 25
# Loot-System
@export var loot_table: LootTable
var target = null # Spieler-Referenz (wird von World gesetzt)
var can_attack = true
var spawn_position: Vector3 # Ursprüngliche Spawn-Position
var current_state = State.PATROL
var patrol_target: Vector3 # Aktuelles Patrol-Ziel
var is_waiting = false # Ob Gegner am Patrol-Punkt wartet
# Animation System
const ANIMATION_FILES = {
"walk": "res://assets/animations/Walking.fbx",
"autoattack": "res://assets/animations/Autoattack.fbx",
"die": "res://assets/animations/Dying Backwards.fbx",
"idle": "res://assets/animations/Idle.fbx",
}
const LOOP_ANIMATIONS = ["walk", "idle"]
var anim_player: AnimationPlayer = null
var current_anim: String = ""
@onready var health_label = $HealthLabel
func _ready(): func _ready():
_calculate_stats()
current_hp = max_hp current_hp = max_hp
health_label.visible = false _update_health_display()
_update_label()
spawn_position = global_position
_pick_new_patrol_target()
_setup_animations()
# Animationen laden
func _setup_animations():
var model = get_node_or_null("EnemyModel")
if model == null:
return
anim_player = _find_node_by_class(model, "AnimationPlayer")
if anim_player == null:
anim_player = AnimationPlayer.new()
anim_player.name = "AnimationPlayer"
model.add_child(anim_player)
var lib: AnimationLibrary
if anim_player.has_animation_library(""):
lib = anim_player.get_animation_library("")
else:
lib = AnimationLibrary.new()
anim_player.add_animation_library("", lib)
for anim_id in ANIMATION_FILES:
var scene = load(ANIMATION_FILES[anim_id]) as PackedScene
if scene == null:
continue
var instance = scene.instantiate()
var source_ap = _find_node_by_class(instance, "AnimationPlayer")
if source_ap:
var names = source_ap.get_animation_list()
if names.size() > 0:
var anim = source_ap.get_animation(names[0])
if anim_id in ["walk", "idle"]:
anim.loop_mode = Animation.LOOP_LINEAR
if lib.has_animation(anim_id):
lib.remove_animation(anim_id)
lib.add_animation(anim_id, anim)
instance.queue_free()
func _find_node_by_class(node: Node, class_name_str: String) -> Node:
for child in node.get_children():
if child.get_class() == class_name_str:
return child
var result = _find_node_by_class(child, class_name_str)
if result:
return result
return null
func _play_anim(anim_name: String):
if anim_player == null:
return
# Wenn Animation ausgelaufen ist, zurücksetzen
if not anim_player.is_playing():
current_anim = ""
if anim_name != current_anim:
current_anim = anim_name
if anim_player.has_animation(anim_name):
anim_player.play(anim_name)
else:
anim_player.stop()
# Stats basierend auf Level berechnen
func _calculate_stats():
var levels_gained = level - 1
strength = base_strength + levels_gained * 2
stamina = base_stamina + levels_gained * 3
armor = base_armor + levels_gained * 2
# HP = Stamina * 10
max_hp = stamina * 10
# Schaden = Stärke / 2
attack_damage = int(strength * 0.5) + 2
# XP = 25 * Level
xp_reward = 25 * level
print("Enemy Stats (Lv", level, ") - STR:", strength, " STA:", stamina, " ARM:", armor, " HP:", max_hp, " DMG:", attack_damage)
# Schaden mit Rüstung und Level-Differenz berechnen
func calculate_incoming_damage(raw_damage: int, attacker_level: int, is_melee: bool) -> int:
var damage = float(raw_damage)
# Rüstung reduziert nur Nahkampfschaden
if is_melee:
# Rüstungsreduktion: armor / (armor + 50) = Prozent Reduktion
# Bei 5 Rüstung: 5/55 = ~9% Reduktion
# Bei 20 Rüstung: 20/70 = ~29% Reduktion
var armor_reduction = float(armor) / (float(armor) + 50.0)
damage = damage * (1.0 - armor_reduction)
# Level-Differenz Modifikator
var level_diff = attacker_level - level
var level_mod = clamp(level_diff * LEVEL_DIFF_DAMAGE_MOD, -MAX_LEVEL_DIFF_MOD, MAX_LEVEL_DIFF_MOD)
damage = damage * (1.0 + level_mod)
return maxi(1, int(damage)) # Mindestens 1 Schaden
# HP-Label Text aktualisieren
func _update_label():
health_label.text = "Lv" + str(level) + " " + str(current_hp) + "/" + str(max_hp)
# HP-Label anzeigen (wenn Gegner markiert wird)
func show_health():
health_label.visible = true
# HP-Label verstecken (wenn Markierung aufgehoben wird)
func hide_health():
health_label.visible = false health_label.visible = false
# Schaden nehmen und Label aktualisieren # NavigationAgent konfigurieren
func take_damage(amount): nav_agent.path_desired_distance = 0.5
current_hp -= amount nav_agent.target_desired_distance = attack_range * 0.9
_update_label()
# Aggro bei Schaden — sofort angreifen
if current_state == State.PATROL:
current_state = State.CHASE
is_waiting = false
print("Gegner wurde angegriffen und verfolgt den Spieler!")
if current_hp <= 0:
die()
# Schaden mit vollem Schadenssystem (Rüstung, Level-Differenz) # ═══════════════════════════════════════════════════════════════
func take_damage_from(raw_damage: int, attacker_level: int, is_melee: bool = true): # PHYSICS PROCESS
var final_damage = calculate_incoming_damage(raw_damage, attacker_level, is_melee) # ═══════════════════════════════════════════════════════════════
print("Eingehender Schaden: ", raw_damage, " -> ", final_damage, " (nach Rüstung/Level)")
take_damage(final_damage)
# Gegner aus der Szene entfernen
func die():
print("Gegner besiegt! +", xp_reward, " XP")
# XP an Spieler geben
if target and target.has_method("gain_xp"):
target.gain_xp(xp_reward)
# Loot generieren und droppen
_drop_loot()
enemy_died.emit(spawn_position, xp_reward)
# Death-Animation abspielen, dann entfernen
if anim_player and anim_player.has_animation("die"):
_play_anim("die")
# Kollision deaktivieren damit der Gegner nicht mehr im Weg ist
set_physics_process(false)
$CollisionShape3D.set_deferred("disabled", true)
await anim_player.animation_finished
queue_free()
# Loot generieren basierend auf LootTable
func _drop_loot():
if loot_table == null:
# Standard-Gold-Drop wenn keine LootTable zugewiesen
var gold = randi_range(1, 3) * level
var loot = {"gold": gold, "items": []}
enemy_dropped_loot.emit(loot, global_position)
return
var loot = loot_table.generate_loot()
# Gold mit Level skalieren
loot["gold"] = loot["gold"] * level
enemy_dropped_loot.emit(loot, global_position)
func _physics_process(delta): func _physics_process(delta):
if is_dead:
return
# Schwerkraft
if not is_on_floor(): if not is_on_floor():
velocity.y -= GRAVITY * delta velocity.y -= GRAVITY * delta
if target == null: # Cooldown herunterzählen
# Ohne Spieler-Referenz nur patrouillieren if attack_cooldown > 0:
_do_patrol() attack_cooldown -= delta
# Kein Ziel → Idle
if target == null or not is_instance_valid(target):
state = State.IDLE
velocity.x = 0
velocity.z = 0
move_and_slide() move_and_slide()
return return
# Prüfe Distanz zum Spieler für Aggro var distance = global_position.distance_to(target.global_position)
var distance_to_player = global_position.distance_to(target.global_position)
# State-Wechsel basierend auf Distanz match state:
match current_state: State.IDLE:
State.PATROL: if distance <= detection_range:
if distance_to_player <= AGGRO_RANGE: state = State.CHASING
current_state = State.CHASE State.CHASING:
print("Gegner hat Spieler entdeckt!") if distance <= attack_range:
state = State.ATTACKING
velocity.x = 0
velocity.z = 0
else: else:
_do_patrol() _move_toward_target()
State.CHASE: State.ATTACKING:
if distance_to_player <= ATTACK_RANGE: if distance > attack_range * 1.5:
current_state = State.ATTACK state = State.CHASING
else:
_chase_player()
State.ATTACK:
if distance_to_player > ATTACK_RANGE:
current_state = State.CHASE
else: else:
velocity.x = 0 velocity.x = 0
velocity.z = 0 velocity.z = 0
_play_anim("idle") # Idle beim Angriff _face_target()
if can_attack: if attack_cooldown <= 0:
_attack() _perform_attack()
move_and_slide() move_and_slide()
# Neues Patrol-Ziel in der Nähe der Spawn-Position wählen func _move_toward_target():
func _pick_new_patrol_target(): if target == null:
var angle = randf() * TAU # Zufälliger Winkel
var distance = randf_range(2.0, PATROL_RADIUS)
patrol_target = spawn_position + Vector3(cos(angle) * distance, 0, sin(angle) * distance)
# Patrol-Verhalten: Zufällig herumlaufen
func _do_patrol():
if is_waiting:
return return
nav_agent.target_position = target.global_position
var distance_to_patrol = global_position.distance_to(patrol_target) if nav_agent.is_navigation_finished():
return
if distance_to_patrol <= 0.5: var next_pos = nav_agent.get_next_path_position()
# Am Ziel angekommen, warten und neues Ziel wählen var direction = (next_pos - global_position).normalized()
velocity.x = 0
velocity.z = 0
_play_anim("idle")
_wait_at_patrol_point()
else:
# Zum Patrol-Ziel laufen
var direction = (patrol_target - global_position)
direction.y = 0
direction = direction.normalized()
velocity.x = direction.x * PATROL_SPEED
velocity.z = direction.z * PATROL_SPEED
_play_anim("walk")
look_at(Vector3(patrol_target.x, global_position.y, patrol_target.z))
# Am Patrol-Punkt warten
func _wait_at_patrol_point():
is_waiting = true
await get_tree().create_timer(PATROL_WAIT_TIME).timeout
is_waiting = false
_pick_new_patrol_target()
# Spieler verfolgen
func _chase_player():
_play_anim("walk")
var direction = (target.global_position - global_position)
direction.y = 0 direction.y = 0
direction = direction.normalized() velocity.x = direction.x * move_speed
velocity.x = direction.x * SPEED velocity.z = direction.z * move_speed
velocity.z = direction.z * SPEED _face_direction(direction)
look_at(Vector3(target.global_position.x, global_position.y, target.global_position.z))
# Angriff mit Cooldown func _face_target():
func _attack(): if target == null:
can_attack = false return
_play_anim("autoattack") var dir = (target.global_position - global_position)
# Gegner verwendet auch das Schadenssystem mit Level-Differenz dir.y = 0
if target.has_method("take_damage_from"): if dir.length() > 0.01:
target.take_damage_from(attack_damage, level, true) _face_direction(dir.normalized())
else:
target.take_damage(attack_damage) func _face_direction(dir: Vector3):
print("Gegner (Lv", level, ") greift an: ", attack_damage, " Schaden") if dir.length() > 0.01:
await get_tree().create_timer(ATTACK_COOLDOWN).timeout rotation.y = atan2(dir.x, dir.z)
can_attack = true
# ═══════════════════════════════════════════════════════════════
# KAMPF
# ═══════════════════════════════════════════════════════════════
func _perform_attack():
if target == null or not is_instance_valid(target):
return
var damage = randi_range(min_damage, max_damage)
target.take_damage(damage)
attack_cooldown = attack_speed
print(name + " greift an: " + str(damage) + " Schaden")
func take_damage(amount: int):
if is_dead:
return
current_hp = clamp(current_hp - amount, 0, max_hp)
_update_health_display()
if current_hp <= 0:
_die()
# ═══════════════════════════════════════════════════════════════
# HP-ANZEIGE
# ═══════════════════════════════════════════════════════════════
func show_health():
if health_label:
health_label.visible = true
func hide_health():
if health_label:
health_label.visible = false
func _update_health_display():
if health_label:
health_label.text = str(current_hp) + " / " + str(max_hp)
# ═══════════════════════════════════════════════════════════════
# TOD & LOOT
# ═══════════════════════════════════════════════════════════════
func _die():
is_dead = true
state = State.DEAD
velocity = Vector3.ZERO
print(name + " gestorben!")
# Loot generieren
if loot_table != null:
var loot = loot_table.generate_loot()
enemy_dropped_loot.emit(loot, global_position)
# XP und Respawn-Signal
enemy_died.emit(global_position, xp_reward)
# Kollision deaktivieren und Node entfernen
set_deferred("collision_layer", 0)
set_deferred("collision_mask", 0)
await get_tree().create_timer(1.5).timeout
queue_free()

View file

@ -1 +1 @@
uid://bg5qs3pcfp7p7 uid://gaqwoakxyhet

View file

@ -1,36 +1,26 @@
[gd_scene format=3 uid="uid://cvojaeanxugfj"] [gd_scene format=3 uid="uid://cvojaeanxugfj"]
[ext_resource type="PackedScene" uid="uid://da1w523lg7i2b" path="res://assets/models/warrior.fbx" id="1_7k104"] [ext_resource type="Script" path="res://enemy.gd" id="1_enemy"]
[ext_resource type="Script" uid="uid://bg5qs3pcfp7p7" path="res://enemy.gd" id="2_enemy"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4gyqm"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
radius = 0.6 radius = 0.4
height = 3.0 height = 1.8
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_7k104"] [node name="Enemy" type="CharacterBody3D"]
radius = 0.6 script = ExtResource("1_enemy")
height = 3.0
[node name="Enemy" type="CharacterBody3D" unique_id=332011146] [node name="CollisionShape3D" type="CollisionShape3D" parent="."]
script = ExtResource("2_enemy") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0)
shape = SubResource("CapsuleShape3D_1")
[node name="EnemyModel" parent="." unique_id=846574684 instance=ExtResource("1_7k104")] [node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1323028920] [node name="HealthDisplay" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.35, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.2, 0)
shape = SubResource("CapsuleShape3D_4gyqm")
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="." unique_id=457987844] [node name="Label3D" type="Label3D" parent="HealthDisplay"]
pixel_size = 0.01
[node name="Area3D" type="Area3D" parent="." unique_id=1689838821] billboard = 3
[node name="CollisionShape3D" type="CollisionShape3D" parent="Area3D" unique_id=116643275]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.35, 0)
shape = SubResource("CapsuleShape3D_7k104")
[node name="HealthLabel" type="Label3D" parent="." unique_id=1251847350]
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 3.5, 0)
visible = false
text = "50 / 50" text = "50 / 50"
font_size = 64
outline_size = 8 outline_size = 8

1694
player.gd

File diff suppressed because it is too large Load diff

View file

@ -1 +1 @@
uid://b6n25e5fh82ra uid://cwe8o6mk0hsi4

View file

@ -1,41 +1,31 @@
[gd_scene format=3 uid="uid://dniyuebl8yhtv"] [gd_scene format=3 uid="uid://dniyuebl8yhtv"]
[ext_resource type="Script" uid="uid://b6n25e5fh82ra" path="res://player.gd" id="1_4flbx"] [ext_resource type="Script" uid="uid://cwe8o6mk0hsi4" path="res://player.gd" id="1_player"]
[ext_resource type="PackedScene" uid="uid://da1w523lg7i2b" path="res://assets/models/warrior.fbx" id="2_hqtel"] [ext_resource type="Script" uid="uid://bwtwon54po4w3" path="res://camera_pivot.gd" id="2_campivot"]
[ext_resource type="Script" uid="uid://bwtwon54po4w3" path="res://camera_pivot.gd" id="2_onrkg"] [ext_resource type="PackedScene" uid="uid://bej3excyoxrdh" path="res://hud.tscn" id="3_hud"]
[ext_resource type="PackedScene" uid="uid://bej3excyoxrdh" path="res://hud.tscn" id="4_hqtel"] [ext_resource type="PackedScene" uid="uid://daeym1tdcnhhd" path="res://assets/Warrior+Animation/castle_guard_01.fbx" id="4_model"]
[ext_resource type="PackedScene" uid="uid://character_panel" path="res://character_panel.tscn" id="5_char_panel"]
[ext_resource type="PackedScene" uid="uid://inventory_panel" path="res://inventory_panel.tscn" id="6_inv_panel"]
[ext_resource type="PackedScene" uid="uid://loot_window" path="res://loot_window.tscn" id="7_loot_win"]
[ext_resource type="PackedScene" uid="uid://skill_panel" path="res://skill_panel.tscn" id="8_skill_panel"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4flbx"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
radius = 0.6 radius = 0.4
height = 3.0 height = 1.8
[node name="CharacterBody3D" type="CharacterBody3D" unique_id=937297102] [node name="Player" type="CharacterBody3D" unique_id=1565111917]
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0) script = ExtResource("1_player")
script = ExtResource("1_4flbx")
[node name="PlayerModel" parent="." unique_id=926968795 instance=ExtResource("2_hqtel")] [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=481888033]
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0)
shape = SubResource("CapsuleShape3D_1")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1359412306] [node name="Model" type="Node3D" parent="." unique_id=297754421]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.35, 0)
shape = SubResource("CapsuleShape3D_4flbx")
[node name="CameraPivot" type="Node3D" parent="." unique_id=638440275] [node name="castle_guard_01" parent="Model" unique_id=1352499997 instance=ExtResource("4_model")]
script = ExtResource("2_onrkg") transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0)
[node name="Camera3D" type="Camera3D" parent="CameraPivot" unique_id=1625345908] [node name="CameraPivot" type="Node3D" parent="." unique_id=2063743808]
transform = Transform3D(2, 0, 0, 0, 1.8126155, 0.84523654, 0, -0.84523654, 1.8126155, 0, 5, 5) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
script = ExtResource("2_campivot")
[node name="HUD" parent="." unique_id=1901284390 instance=ExtResource("4_hqtel")] [node name="Camera3D" type="Camera3D" parent="CameraPivot" unique_id=1026091049]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 7)
[node name="CharacterPanel" parent="." instance=ExtResource("5_char_panel")] [node name="HUD" parent="." unique_id=904219504 instance=ExtResource("3_hud")]
[node name="InventoryPanel" parent="." instance=ExtResource("6_inv_panel")]
[node name="LootWindow" parent="." instance=ExtResource("7_loot_win")]
[node name="SkillPanel" parent="." instance=ExtResource("8_skill_panel")]

View file

@ -112,6 +112,16 @@ toggle_skills={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null)
] ]
} }
roll={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":1,"echo":false,"script":null)
]
}
walk_toggle={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194330,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
[physics] [physics]

View file

@ -1,5 +1,14 @@
# World.gd # World.gd
# Initialisiert die Spielwelt: weist dem Gegner den Spieler als Ziel zu # ─────────────────────────────────────────────────────────────────────────────
# 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 extends Node3D
const ENEMY_SCENE = preload("res://enemy.tscn") const ENEMY_SCENE = preload("res://enemy.tscn")
@ -16,13 +25,60 @@ const STARTER_CHEST = preload("res://equipment/leather_chest.tres")
const GOBLIN_LOOT = preload("res://loot_tables/goblin_loot.tres") const GOBLIN_LOOT = preload("res://loot_tables/goblin_loot.tres")
@onready var player = $Player @onready var player = $Player
@onready var floor_mesh = $Boden/MeshInstance3D
func _ready(): func _ready():
_setup_floor_material()
_setup_sky()
# Hauptmenü anzeigen # Hauptmenü anzeigen
var main_menu = MAIN_MENU.instantiate() var main_menu = MAIN_MENU.instantiate()
add_child(main_menu) add_child(main_menu)
main_menu.start_game.connect(_on_start_game) 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 # Nach Hauptmenü: Klassenauswahl anzeigen
func _on_start_game(): func _on_start_game():
var menu = CLASS_SELECTION_MENU.instantiate() var menu = CLASS_SELECTION_MENU.instantiate()

View file

@ -2,7 +2,6 @@
[ext_resource type="PackedScene" uid="uid://dniyuebl8yhtv" path="res://player.tscn" id="1_f3sb7"] [ext_resource type="PackedScene" uid="uid://dniyuebl8yhtv" path="res://player.tscn" id="1_f3sb7"]
[ext_resource type="Script" uid="uid://cx56h588mfsk0" path="res://world.gd" id="1_tlwt5"] [ext_resource type="Script" uid="uid://cx56h588mfsk0" path="res://world.gd" id="1_tlwt5"]
[ext_resource type="PackedScene" uid="uid://cvojaeanxugfj" path="res://enemy.tscn" id="2_fj7yv"]
[sub_resource type="BoxShape3D" id="BoxShape3D_fj7yv"] [sub_resource type="BoxShape3D" id="BoxShape3D_fj7yv"]
size = Vector3(200, 0.5, 200) size = Vector3(200, 0.5, 200)
@ -21,16 +20,14 @@ script = ExtResource("1_tlwt5")
shape = SubResource("BoxShape3D_fj7yv") shape = SubResource("BoxShape3D_fj7yv")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Boden" unique_id=1214783061] [node name="MeshInstance3D" type="MeshInstance3D" parent="Boden" unique_id=1214783061]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
mesh = SubResource("BoxMesh_tlwt5") mesh = SubResource("BoxMesh_tlwt5")
[node name="Player" parent="." unique_id=937297102 instance=ExtResource("1_f3sb7")] [node name="Player" parent="." unique_id=937297102 instance=ExtResource("1_f3sb7")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1394887598] [node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1394887598]
transform = Transform3D(-45, 0, 0, 0, -45, 0, 0, 0, -45, 0, 0, 0) transform = Transform3D(-45, 0, 0, 0, -45, 0, 0, 0, -45, 0, 0, 0)
[node name="Enemy" parent="." unique_id=332011146 instance=ExtResource("2_fj7yv")]
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0.3, -7.038)
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=827244005] [node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=827244005]
navigation_mesh = SubResource("NavigationMesh_fj7yv") navigation_mesh = SubResource("NavigationMesh_fj7yv")