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>
This commit is contained in:
parent
463c00f47f
commit
12b70c7498
40 changed files with 1903 additions and 88 deletions
117
PROJEKTDOKU.md
117
PROJEKTDOKU.md
|
|
@ -38,15 +38,28 @@ Gegner bekämpfen und ihre Charaktere mit verschiedenen Klassen und Ausrüstunge
|
||||||
|
|
||||||
### world.tscn
|
### world.tscn
|
||||||
Hauptszene der Spielwelt. Zeigt bei Start das Hauptmenü (Einstellungen), dann Klassenauswahl.
|
Hauptszene der Spielwelt. Zeigt bei Start das Hauptmenü (Einstellungen), dann Klassenauswahl.
|
||||||
|
Enthält Overworld (Gras-Terrain, Berg, Dungeon-Tor, Felsen, Bäume) und generiert Dungeon-Ebenen zur Laufzeit.
|
||||||
```
|
```
|
||||||
World (Node3D)
|
World (Node3D)
|
||||||
├── Player (player.tscn)
|
├── Player (player.tscn)
|
||||||
├── Enemy (enemy.tscn)
|
├── Enemy (enemy.tscn) # Overworld-Gegner
|
||||||
├── Boden (StaticBody3D)
|
├── Boden (StaticBody3D) # Gras-Terrain mit Noise-Shader
|
||||||
│ ├── MeshInstance3D (Schachbrett-Shader)
|
│ ├── MeshInstance3D
|
||||||
│ └── CollisionShape3D
|
│ └── CollisionShape3D
|
||||||
├── DirectionalLight3D
|
├── DirectionalLight3D
|
||||||
└── NavigationRegion3D
|
├── NavigationRegion3D # Overworld NavMesh (im Editor backen!)
|
||||||
|
├── Mountain (CSGCombiner3D) # Berg mit 4 CSGSphere3D, use_collision
|
||||||
|
├── DungeonGate # Tor zum Dungeon (Area3D + Label)
|
||||||
|
│ ├── GateArea (Area3D)
|
||||||
|
│ └── GateLabel (Label3D)
|
||||||
|
├── Rocks (9x CSGSphere3D)
|
||||||
|
├── Trees (6x CSGCylinder3D + CSGSphere3D)
|
||||||
|
└── [DungeonContainer] (zur Laufzeit) # Wird bei Dungeon-Eintritt generiert
|
||||||
|
├── DungeonNavRegion (NavigationRegion3D)
|
||||||
|
├── Floor/Wall/Ceiling Tiles (CSGBox3D)
|
||||||
|
├── Enemies
|
||||||
|
├── ReturnPortal (Area3D)
|
||||||
|
└── DeeperPortal (Area3D)
|
||||||
```
|
```
|
||||||
|
|
||||||
### player.tscn
|
### player.tscn
|
||||||
|
|
@ -63,12 +76,12 @@ Player (CharacterBody3D)
|
||||||
UI-Panels (CharacterPanel, InventoryPanel, LootWindow, SkillPanel) werden zur Laufzeit erstellt.
|
UI-Panels (CharacterPanel, InventoryPanel, LootWindow, SkillPanel) werden zur Laufzeit erstellt.
|
||||||
|
|
||||||
### enemy.tscn
|
### enemy.tscn
|
||||||
Ein Gegner mit Patrol-KI und Kampf-Animationen.
|
Ein Gegner (Goblin-Modell) mit Patrol-KI, NavMesh-Pathfinding und Kampf-Animationen.
|
||||||
```
|
```
|
||||||
Enemy (CharacterBody3D)
|
Enemy (CharacterBody3D)
|
||||||
├── Model (castle_guard_01.fbx — Mixamo Charakter mit Skeleton3D + AnimationPlayer)
|
├── Model (goblin_d_shareyko.fbx — Goblin mit Skeleton3D + AnimationPlayer)
|
||||||
├── CollisionShape3D
|
├── CollisionShape3D (CapsuleShape3D, radius=0.35, height=1.4)
|
||||||
├── NavigationAgent3D
|
├── NavigationAgent3D (radius=0.4, für NavMesh-Pfadfindung)
|
||||||
└── HealthDisplay (Node3D)
|
└── HealthDisplay (Node3D)
|
||||||
└── Label3D
|
└── Label3D
|
||||||
```
|
```
|
||||||
|
|
@ -88,6 +101,12 @@ Enemy (CharacterBody3D)
|
||||||
|
|
||||||
Lock-On springt automatisch zurück sobald LMB/RMB losgelassen wird und Ziel noch markiert ist.
|
Lock-On springt automatisch zurück sobald LMB/RMB losgelassen wird und Ziel noch markiert ist.
|
||||||
|
|
||||||
|
### Kamera-Kollision
|
||||||
|
- Raycast vom Pivot zur gewünschten Kameraposition
|
||||||
|
- Bei Wandkollision: Kamera rückt näher an den Spieler (min 1.0 Abstand)
|
||||||
|
- Ignoriert CharacterBody3D (Gegner) — nur Wände/Terrain
|
||||||
|
- Smooth-Zoom zurück wenn Wand nicht mehr im Weg
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Klassen, Stats & Level-System
|
## Klassen, Stats & Level-System
|
||||||
|
|
@ -300,27 +319,35 @@ Stats werden in `_ready()` basierend auf `mob_level` skaliert (Basiswerte = Leve
|
||||||
| PATROL | Läuft zwischen zufälligen Punkten im Spawn-Radius, walk-Animation, Turn-Animationen beim Richtungswechsel |
|
| PATROL | Läuft zwischen zufälligen Punkten im Spawn-Radius, walk-Animation, Turn-Animationen beim Richtungswechsel |
|
||||||
| CHASING | Rennt zum Spieler (run-Animation), aktiviert bei detection_range oder Schaden |
|
| CHASING | Rennt zum Spieler (run-Animation), aktiviert bei detection_range oder Schaden |
|
||||||
| ATTACKING | Steht, dreht sich zum Spieler, greift in attack_speed-Intervallen an |
|
| ATTACKING | Steht, dreht sich zum Spieler, greift in attack_speed-Intervallen an |
|
||||||
| DEAD | Spielt death-Animation, wird nach 2s entfernt |
|
| DEAD | Spielt death-Animation, wird nach 10s entfernt |
|
||||||
|
|
||||||
### Patrol-System
|
### Patrol-System
|
||||||
- Enemy patrouilliert um seinen Spawnpunkt im konfigurierbaren Radius
|
- Enemy patrouilliert um seinen Spawnpunkt im konfigurierbaren Radius
|
||||||
- Wartet 2-5 Sekunden zwischen Patrol-Punkten (idle-Animation)
|
- Wartet 2-5 Sekunden zwischen Patrol-Punkten (idle-Animation)
|
||||||
- Spielt Turn-Animation (left_turn_90 / right_turn_90) vor dem Loslaufen
|
- Spielt Turn-Animation (left_turn_90 / right_turn_90) vor dem Loslaufen
|
||||||
- Läuft mit langsamer patrol_speed und walk-Animation
|
- Läuft mit langsamer patrol_speed und walk-Animation
|
||||||
|
- Patrol-Ziel wird auf nächsten begehbaren NavMesh-Punkt gesnapped
|
||||||
- Bei Aggro sofort Wechsel zu run-Animation und move_speed
|
- Bei Aggro sofort Wechsel zu run-Animation und move_speed
|
||||||
|
|
||||||
### Aggro
|
### Pathfinding (NavigationAgent3D)
|
||||||
|
- Nutzt `NavigationAgent3D` mit NavMesh-Pfadfindung (Dungeon)
|
||||||
|
- Gegner laufen um Wände und Ecken herum zum Spieler
|
||||||
|
- Pfad wird nur neu berechnet wenn Spieler sich > 1.5 Einheiten bewegt hat (Anti-Flicker)
|
||||||
|
- Fallback auf direkte Bewegung wenn kein NavMesh vorhanden (Overworld)
|
||||||
|
|
||||||
|
### Aggro & Leash
|
||||||
- **Detection Range:** Spieler wird automatisch erkannt wenn in 15m Reichweite
|
- **Detection Range:** Spieler wird automatisch erkannt wenn in 15m Reichweite
|
||||||
- **Schaden-Aggro:** Bei Schaden sofort Wechsel zu CHASING, auch aus PATROL
|
- **Schaden-Aggro:** Bei Schaden sofort Wechsel zu CHASING, auch aus PATROL
|
||||||
|
- **Leash Range:** Gegner verliert Aggro wenn > 30 Einheiten vom Spawnpunkt entfernt
|
||||||
- **Spieler-Suche:** Per `get_nodes_in_group("player")`
|
- **Spieler-Suche:** Per `get_nodes_in_group("player")`
|
||||||
|
|
||||||
### Respawn
|
### Respawn
|
||||||
- Gegner spawnen nach 5 Sekunden am Ursprungsort neu
|
- Gegner spawnen nach 60 Sekunden am Ursprungsort neu
|
||||||
- Verwaltet durch world.gd (`_on_enemy_died` Signal)
|
- Verwaltet durch world.gd (`_on_enemy_died` Signal)
|
||||||
|
|
||||||
### Animationen
|
### Animationen (Goblin-Modell)
|
||||||
Werden zur Laufzeit aus FBX-Dateien geladen (gleiche Methode wie Player):
|
Werden zur Laufzeit aus FBX-Dateien geladen (assets/Goblin+Animation/):
|
||||||
- idle, walk, run, autoattack, death, turn_left, turn_right
|
- idle, walking, standard run, attack, die, left turn 90, right turn 90
|
||||||
- Root Motion wird automatisch entfernt (`_strip_root_motion`)
|
- Root Motion wird automatisch entfernt (`_strip_root_motion`)
|
||||||
|
|
||||||
### Loot-Drops
|
### Loot-Drops
|
||||||
|
|
@ -342,7 +369,7 @@ Charaktermodelle stammen von Mixamo (castle_guard_01.fbx) und werden mit separat
|
||||||
4. Loop-Modus wird gesetzt (Bewegungsanimationen loopen, Angriff/Tod nicht)
|
4. Loop-Modus wird gesetzt (Bewegungsanimationen loopen, Angriff/Tod nicht)
|
||||||
5. Animation wird der AnimationLibrary hinzugefügt
|
5. Animation wird der AnimationLibrary hinzugefügt
|
||||||
|
|
||||||
### Verfügbare Animationen (assets/Warrior+Animation/)
|
### Spieler-Animationen (assets/Warrior+Animation/)
|
||||||
| Animation | Datei | Loop | Verwendung |
|
| Animation | Datei | Loop | Verwendung |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| idle | idle.fbx | Ja | Stillstehen |
|
| idle | idle.fbx | Ja | Stillstehen |
|
||||||
|
|
@ -361,8 +388,19 @@ Charaktermodelle stammen von Mixamo (castle_guard_01.fbx) und werden mit separat
|
||||||
| death | Dying Backwards.fbx | Nein | Tod |
|
| death | Dying Backwards.fbx | Nein | Tod |
|
||||||
| roll | Quick Roll To Run.fbx | Nein | Ausweichrolle |
|
| roll | Quick Roll To Run.fbx | Nein | Ausweichrolle |
|
||||||
| turn_180 | Running Turn 180.fbx | Nein | 180°-Drehung |
|
| turn_180 | Running Turn 180.fbx | Nein | 180°-Drehung |
|
||||||
| turn_left | Left Turn 90.fbx | Nein | 90° links (Enemy Patrol) |
|
| turn_left | Left Turn 90.fbx | Nein | 90° links |
|
||||||
| turn_right | Right Turn 90.fbx | Nein | 90° rechts (Enemy Patrol) |
|
| turn_right | Right Turn 90.fbx | Nein | 90° rechts |
|
||||||
|
|
||||||
|
### Gegner-Animationen (assets/Goblin+Animation/)
|
||||||
|
| Animation | Datei | Loop | Verwendung |
|
||||||
|
|---|---|---|---|
|
||||||
|
| idle | idle.fbx | Ja | Stillstehen |
|
||||||
|
| walk | walking.fbx | Ja | Patrol-Laufen |
|
||||||
|
| run | standard run.fbx | Ja | Rennen (Aggro) |
|
||||||
|
| autoattack | attack.fbx | Nein | Angriff |
|
||||||
|
| death | die.fbx | Nein | Tod |
|
||||||
|
| turn_left | left turn 90.fbx | Nein | 90° links (Patrol) |
|
||||||
|
| turn_right | right turn 90.fbx | Nein | 90° rechts (Patrol) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -450,13 +488,53 @@ Rarity steigt durch Skill-Generationen (Verbindung/Fusion) — nicht durch Grind
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Dungeon-System (world.gd)
|
||||||
|
|
||||||
|
### Überblick
|
||||||
|
Prozedural generierte Dungeon-Ebenen innerhalb derselben Szene (kein Szenenwechsel). Overworld wird versteckt, Dungeon als Kind-Nodes generiert. Spieler-State (HP, Inventar, Level) bleibt erhalten.
|
||||||
|
|
||||||
|
### Dungeon-Generierung
|
||||||
|
- **Grid-basiert:** 60x60 Zellen, TILE_SIZE = 3.0
|
||||||
|
- **Räume:** 6-10 Räume, Größe 4-9 Zellen
|
||||||
|
- **Gänge:** L-förmige Korridore zwischen Räumen (Breite 2)
|
||||||
|
- **Geometrie:** CSGBox3D für Boden, Wände (Höhe 6.0), Decke, jeweils mit `use_collision`
|
||||||
|
- **Beleuchtung:** OmniLight3D pro Raum + Decken-SpotLights in Gängen
|
||||||
|
- **Environment:** Eigene WorldEnvironment mit Fog, kein Himmel
|
||||||
|
|
||||||
|
### NavMesh (Navigation)
|
||||||
|
- Pro Dungeon-Ebene wird ein `NavigationRegion3D` mit manuell gebautem `NavigationMesh` erstellt
|
||||||
|
- Positions-basierte Vertex-Deduplizierung: gleiche Position → gleicher Index → zusammenhängendes NavMesh
|
||||||
|
- Wand-Margin (0.5): Kanten an Wänden werden nach innen versetzt, damit Gegner Abstand halten
|
||||||
|
- Edge-Connection-Margin auf 0.6 gesetzt
|
||||||
|
|
||||||
|
### Portal-System
|
||||||
|
| Portal | Farbe | Funktion |
|
||||||
|
|---|---|---|
|
||||||
|
| Rückkehr-Portal | Blau | Im ersten Raum, führt eine Ebene hoch oder raus |
|
||||||
|
| Deeper-Portal | Rot/Orange | Im letzten Raum, führt eine Ebene tiefer |
|
||||||
|
|
||||||
|
- **Ebene 1:** Rückkehr-Portal → direkt zur Overworld
|
||||||
|
- **Ebene 2+:** Rückkehr-Portal → Auswahl: "Eine Ebene hoch" oder "Dungeon verlassen"
|
||||||
|
- **Interaktion:** E-Taste, Labels zeigen Ebenen-Info
|
||||||
|
|
||||||
|
### Dungeon-Persistenz
|
||||||
|
- Generierte Ebenen werden in `saved_dungeons` Dictionary gespeichert (Level → {grid, rooms})
|
||||||
|
- Beim Zurückgehen wird das gespeicherte Layout wiederhergestellt
|
||||||
|
- Beim Verlassen des Dungeons werden alle gespeicherten Ebenen gelöscht
|
||||||
|
|
||||||
|
### Gegner im Dungeon
|
||||||
|
- Spawnen in Räumen 2 bis N-1 (nicht im Start- und End-Raum)
|
||||||
|
- `mob_level = dungeon_level` (steigt mit jeder Ebene)
|
||||||
|
- Nutzen NavMesh-Pathfinding um Wände herum
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Geplante Features
|
## Geplante Features
|
||||||
- [ ] Skill Mastery & Fusion System (siehe Konzept oben)
|
- [ ] Skill Mastery & Fusion System (siehe Konzept oben)
|
||||||
- [ ] Wut-Ressource für Krieger
|
- [ ] Wut-Ressource für Krieger
|
||||||
- [ ] Spell-System (Feuerbälle etc.)
|
- [ ] Spell-System (Feuerbälle etc.)
|
||||||
- [ ] Schadenstypen (Physical, Fire, Ice, Lightning, Poison)
|
- [ ] Schadenstypen (Physical, Fire, Ice, Lightning, Poison)
|
||||||
- [ ] Mehrere Gegnertypen
|
- [ ] Mehrere Gegnertypen
|
||||||
- [ ] Dungeon-Level mit Wänden und Räumen
|
|
||||||
- [ ] Multiplayer (bis zu 6 Spieler)
|
- [ ] Multiplayer (bis zu 6 Spieler)
|
||||||
- [ ] Boss-Gegner
|
- [ ] Boss-Gegner
|
||||||
- [ ] Item-Shop / Händler
|
- [ ] Item-Shop / Händler
|
||||||
|
|
@ -467,7 +545,8 @@ Rarity steigt durch Skill-Generationen (Verbindung/Fusion) — nicht durch Grind
|
||||||
```
|
```
|
||||||
DungeonCrawler/
|
DungeonCrawler/
|
||||||
├── assets/
|
├── assets/
|
||||||
│ ├── Warrior+Animation/ # Mixamo Charakter + Animationen (castle_guard_01.fbx + FBX)
|
│ ├── Warrior+Animation/ # Spieler-Charakter + Animationen (castle_guard_01.fbx + FBX)
|
||||||
|
│ ├── Goblin+Animation/ # Gegner-Modell + Animationen (goblin_d_shareyko.fbx + FBX)
|
||||||
│ └── kenney_animated-characters-1/ # Kenney Animated Characters Pack
|
│ └── kenney_animated-characters-1/ # Kenney Animated Characters Pack
|
||||||
├── classes/ # Klassen-Definitionen (.tres)
|
├── classes/ # Klassen-Definitionen (.tres)
|
||||||
│ ├── warrior.tres
|
│ ├── warrior.tres
|
||||||
|
|
|
||||||
BIN
assets/Goblin+Animation/attack.fbx
Normal file
BIN
assets/Goblin+Animation/attack.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/attack.fbx.import
Normal file
44
assets/Goblin+Animation/attack.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://1nh16lj56b6v"
|
||||||
|
path="res://.godot/imported/attack.fbx-74f24f5aa3abe1e08504a4c0a89ebd90.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/attack.fbx"
|
||||||
|
dest_files=["res://.godot/imported/attack.fbx-74f24f5aa3abe1e08504a4c0a89ebd90.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
|
||||||
BIN
assets/Goblin+Animation/die.fbx
Normal file
BIN
assets/Goblin+Animation/die.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/die.fbx.import
Normal file
44
assets/Goblin+Animation/die.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://b7opwpnodeke4"
|
||||||
|
path="res://.godot/imported/die.fbx-9b1eb5481e87e52c2e23336481a99db2.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/die.fbx"
|
||||||
|
dest_files=["res://.godot/imported/die.fbx-9b1eb5481e87e52c2e23336481a99db2.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
|
||||||
BIN
assets/Goblin+Animation/goblin_d_shareyko.fbx
Normal file
BIN
assets/Goblin+Animation/goblin_d_shareyko.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/goblin_d_shareyko.fbx.import
Normal file
44
assets/Goblin+Animation/goblin_d_shareyko.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://bpmvu5c2c0nty"
|
||||||
|
path="res://.godot/imported/goblin_d_shareyko.fbx-19a0d07a917db1228319e2d9380f00d1.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/goblin_d_shareyko.fbx"
|
||||||
|
dest_files=["res://.godot/imported/goblin_d_shareyko.fbx-19a0d07a917db1228319e2d9380f00d1.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
|
||||||
BIN
assets/Goblin+Animation/goblin_d_shareyko_0.png
Normal file
BIN
assets/Goblin+Animation/goblin_d_shareyko_0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6 MiB |
44
assets/Goblin+Animation/goblin_d_shareyko_0.png.import
Normal file
44
assets/Goblin+Animation/goblin_d_shareyko_0.png.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cvy7pen32ajri"
|
||||||
|
path.s3tc="res://.godot/imported/goblin_d_shareyko_0.png-6f7e60f7d1dfca06b1589009655e64ba.s3tc.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
generator_parameters={
|
||||||
|
"md5": "8e5ff3ab5d9e47e8fcfa88b364ca7a3e"
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/goblin_d_shareyko_0.png"
|
||||||
|
dest_files=["res://.godot/imported/goblin_d_shareyko_0.png-6f7e60f7d1dfca06b1589009655e64ba.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
|
||||||
BIN
assets/Goblin+Animation/goblin_d_shareyko_1.png
Normal file
BIN
assets/Goblin+Animation/goblin_d_shareyko_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 MiB |
44
assets/Goblin+Animation/goblin_d_shareyko_1.png.import
Normal file
44
assets/Goblin+Animation/goblin_d_shareyko_1.png.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://4fwlvu5lvb76"
|
||||||
|
path.s3tc="res://.godot/imported/goblin_d_shareyko_1.png-e66469a7a38b139ca8bd4daaee8d6fd3.s3tc.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
generator_parameters={
|
||||||
|
"md5": "9cfcb6de764cdf7fff1d5c61a40607c7"
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/goblin_d_shareyko_1.png"
|
||||||
|
dest_files=["res://.godot/imported/goblin_d_shareyko_1.png-e66469a7a38b139ca8bd4daaee8d6fd3.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/Goblin+Annimation/goblin_d_shareyko_1.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
|
||||||
BIN
assets/Goblin+Animation/goblin_d_shareyko_2.png
Normal file
BIN
assets/Goblin+Animation/goblin_d_shareyko_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.9 MiB |
43
assets/Goblin+Animation/goblin_d_shareyko_2.png.import
Normal file
43
assets/Goblin+Animation/goblin_d_shareyko_2.png.import
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dlytifou244vw"
|
||||||
|
path="res://.godot/imported/goblin_d_shareyko_2.png-fa1609abb3cfae8642f95c6d3f7a9e0b.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
generator_parameters={
|
||||||
|
"md5": "3b587dba802d634d0c459461e6207307"
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/goblin_d_shareyko_2.png"
|
||||||
|
dest_files=["res://.godot/imported/goblin_d_shareyko_2.png-fa1609abb3cfae8642f95c6d3f7a9e0b.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
|
||||||
BIN
assets/Goblin+Animation/idle.fbx
Normal file
BIN
assets/Goblin+Animation/idle.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/idle.fbx.import
Normal file
44
assets/Goblin+Animation/idle.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://vkx02tvlsdh"
|
||||||
|
path="res://.godot/imported/idle.fbx-10b8e348b24126ecb7911637de5ed41b.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/idle.fbx"
|
||||||
|
dest_files=["res://.godot/imported/idle.fbx-10b8e348b24126ecb7911637de5ed41b.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
|
||||||
BIN
assets/Goblin+Animation/jump.fbx
Normal file
BIN
assets/Goblin+Animation/jump.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/jump.fbx.import
Normal file
44
assets/Goblin+Animation/jump.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://btlqv3sdusu3"
|
||||||
|
path="res://.godot/imported/jump.fbx-c935f1d10578ccbe3975acc112927dfc.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/jump.fbx"
|
||||||
|
dest_files=["res://.godot/imported/jump.fbx-c935f1d10578ccbe3975acc112927dfc.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
|
||||||
BIN
assets/Goblin+Animation/left strafe walking.fbx
Normal file
BIN
assets/Goblin+Animation/left strafe walking.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/left strafe walking.fbx.import
Normal file
44
assets/Goblin+Animation/left strafe walking.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://c5btq8xjlu871"
|
||||||
|
path="res://.godot/imported/left strafe walking.fbx-91dd542134cbe48608f36538bb0cc694.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/left strafe walking.fbx"
|
||||||
|
dest_files=["res://.godot/imported/left strafe walking.fbx-91dd542134cbe48608f36538bb0cc694.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
|
||||||
BIN
assets/Goblin+Animation/left strafe.fbx
Normal file
BIN
assets/Goblin+Animation/left strafe.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/left strafe.fbx.import
Normal file
44
assets/Goblin+Animation/left strafe.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://cic7ndddknq5d"
|
||||||
|
path="res://.godot/imported/left strafe.fbx-cb42c3e72794269447ef40352a1eda03.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/left strafe.fbx"
|
||||||
|
dest_files=["res://.godot/imported/left strafe.fbx-cb42c3e72794269447ef40352a1eda03.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
|
||||||
BIN
assets/Goblin+Animation/left turn 90.fbx
Normal file
BIN
assets/Goblin+Animation/left turn 90.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/left turn 90.fbx.import
Normal file
44
assets/Goblin+Animation/left turn 90.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://d1assifgeqf6q"
|
||||||
|
path="res://.godot/imported/left turn 90.fbx-f708d959e637559ca294a793eaf09bda.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/left turn 90.fbx"
|
||||||
|
dest_files=["res://.godot/imported/left turn 90.fbx-f708d959e637559ca294a793eaf09bda.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
|
||||||
BIN
assets/Goblin+Animation/right strafe walking.fbx
Normal file
BIN
assets/Goblin+Animation/right strafe walking.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/right strafe walking.fbx.import
Normal file
44
assets/Goblin+Animation/right strafe walking.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://cwb5bwv8tv38m"
|
||||||
|
path="res://.godot/imported/right strafe walking.fbx-aa95bdbda650ae471db0052435fc180b.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/right strafe walking.fbx"
|
||||||
|
dest_files=["res://.godot/imported/right strafe walking.fbx-aa95bdbda650ae471db0052435fc180b.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
|
||||||
BIN
assets/Goblin+Animation/right strafe.fbx
Normal file
BIN
assets/Goblin+Animation/right strafe.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/right strafe.fbx.import
Normal file
44
assets/Goblin+Animation/right strafe.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://pfcvjhiyap1j"
|
||||||
|
path="res://.godot/imported/right strafe.fbx-4e62ff6b8b107948b9183770097152fe.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/right strafe.fbx"
|
||||||
|
dest_files=["res://.godot/imported/right strafe.fbx-4e62ff6b8b107948b9183770097152fe.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
|
||||||
BIN
assets/Goblin+Animation/right turn 90.fbx
Normal file
BIN
assets/Goblin+Animation/right turn 90.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/right turn 90.fbx.import
Normal file
44
assets/Goblin+Animation/right turn 90.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://p23ow3gyhmjk"
|
||||||
|
path="res://.godot/imported/right turn 90.fbx-37203ca5c7b5d817bc21c0c0e296e833.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/right turn 90.fbx"
|
||||||
|
dest_files=["res://.godot/imported/right turn 90.fbx-37203ca5c7b5d817bc21c0c0e296e833.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
|
||||||
BIN
assets/Goblin+Animation/standard run.fbx
Normal file
BIN
assets/Goblin+Animation/standard run.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/standard run.fbx.import
Normal file
44
assets/Goblin+Animation/standard run.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://fbyvl4ux7pxl"
|
||||||
|
path="res://.godot/imported/standard run.fbx-dc8227b4d9d45456cda6b01eff9a394f.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/standard run.fbx"
|
||||||
|
dest_files=["res://.godot/imported/standard run.fbx-dc8227b4d9d45456cda6b01eff9a394f.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
|
||||||
BIN
assets/Goblin+Animation/walking.fbx
Normal file
BIN
assets/Goblin+Animation/walking.fbx
Normal file
Binary file not shown.
44
assets/Goblin+Animation/walking.fbx.import
Normal file
44
assets/Goblin+Animation/walking.fbx.import
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://cb05qpxan1tw6"
|
||||||
|
path="res://.godot/imported/walking.fbx-99408d8bc40ff5ad02c3ded8ae6dba68.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/Goblin+Animation/walking.fbx"
|
||||||
|
dest_files=["res://.godot/imported/walking.fbx-99408d8bc40ff5ad02c3ded8ae6dba68.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
|
||||||
|
|
@ -27,12 +27,14 @@ extends Node3D
|
||||||
|
|
||||||
var pitch: float = 0.0
|
var pitch: float = 0.0
|
||||||
var world_yaw: float = 0.0 # Absolute Weltausrichtung der Kamera (unabhängig von Spielerrotation)
|
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
|
@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
|
world_yaw = get_parent().rotation.y
|
||||||
|
desired_zoom = camera.position.z
|
||||||
|
|
||||||
func _input(event):
|
func _input(event):
|
||||||
var player = get_parent()
|
var player = get_parent()
|
||||||
|
|
@ -56,9 +58,9 @@ func _input(event):
|
||||||
|
|
||||||
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)
|
desired_zoom = clamp(desired_zoom + 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)
|
desired_zoom = clamp(desired_zoom - zoom_speed, min_zoom, max_zoom)
|
||||||
|
|
||||||
func _process(delta):
|
func _process(delta):
|
||||||
var player = get_parent()
|
var player = get_parent()
|
||||||
|
|
@ -77,3 +79,23 @@ func _process(delta):
|
||||||
|
|
||||||
# Lokale Rotation so setzen dass Kamera immer auf world_yaw zeigt
|
# Lokale Rotation so setzen dass Kamera immer auf world_yaw zeigt
|
||||||
rotation.y = world_yaw - player.rotation.y
|
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)
|
||||||
|
|
|
||||||
82
enemy.gd
82
enemy.gd
|
|
@ -34,6 +34,7 @@ signal enemy_dropped_loot(loot: Dictionary, world_pos: Vector3)
|
||||||
@export var xp_reward: int = 20
|
@export var xp_reward: int = 20
|
||||||
@export var mob_level: int = 1
|
@export var mob_level: int = 1
|
||||||
@export var detection_range: float = 15.0
|
@export var detection_range: float = 15.0
|
||||||
|
@export var leash_range: float = 30.0 # Max Entfernung vom Spawn bevor Aggro verloren geht
|
||||||
@export var loot_table: LootTable = null
|
@export var loot_table: LootTable = null
|
||||||
|
|
||||||
var current_hp: int
|
var current_hp: int
|
||||||
|
|
@ -62,13 +63,13 @@ const GRAVITY = 9.8
|
||||||
# ANIMATION
|
# ANIMATION
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
const ANIM_IDLE = "res://assets/Warrior+Animation/idle.fbx"
|
const ANIM_IDLE = "res://assets/Goblin+Animation/idle.fbx"
|
||||||
const ANIM_WALK = "res://assets/Warrior+Animation/walking.fbx"
|
const ANIM_WALK = "res://assets/Goblin+Animation/walking.fbx"
|
||||||
const ANIM_RUN = "res://assets/Warrior+Animation/running.fbx"
|
const ANIM_RUN = "res://assets/Goblin+Animation/standard run.fbx"
|
||||||
const ANIM_AUTOATTACK = "res://assets/Warrior+Animation/Autoattack.fbx"
|
const ANIM_AUTOATTACK = "res://assets/Goblin+Animation/attack.fbx"
|
||||||
const ANIM_DEATH = "res://assets/Warrior+Animation/Dying Backwards.fbx"
|
const ANIM_DEATH = "res://assets/Goblin+Animation/die.fbx"
|
||||||
const ANIM_TURN_LEFT = "res://assets/Warrior+Animation/Left Turn 90.fbx"
|
const ANIM_TURN_LEFT = "res://assets/Goblin+Animation/left turn 90.fbx"
|
||||||
const ANIM_TURN_RIGHT = "res://assets/Warrior+Animation/Right Turn 90.fbx"
|
const ANIM_TURN_RIGHT = "res://assets/Goblin+Animation/right turn 90.fbx"
|
||||||
|
|
||||||
var anim_player: AnimationPlayer = null
|
var anim_player: AnimationPlayer = null
|
||||||
var current_anim: String = ""
|
var current_anim: String = ""
|
||||||
|
|
@ -108,6 +109,7 @@ func _ready():
|
||||||
# NavigationAgent konfigurieren
|
# NavigationAgent konfigurieren
|
||||||
nav_agent.path_desired_distance = 0.5
|
nav_agent.path_desired_distance = 0.5
|
||||||
nav_agent.target_desired_distance = attack_range * 0.9
|
nav_agent.target_desired_distance = attack_range * 0.9
|
||||||
|
nav_agent.radius = 0.4 # Gegner-Breite für Pfadberechnung
|
||||||
|
|
||||||
# Animationen einrichten
|
# Animationen einrichten
|
||||||
_setup_animations()
|
_setup_animations()
|
||||||
|
|
@ -240,6 +242,16 @@ func _physics_process(delta):
|
||||||
|
|
||||||
var distance = global_position.distance_to(target.global_position)
|
var distance = global_position.distance_to(target.global_position)
|
||||||
|
|
||||||
|
# Leash-Check: Zu weit vom Spawn → Aggro verlieren, zurücklaufen
|
||||||
|
var dist_from_spawn = global_position.distance_to(spawn_position)
|
||||||
|
if dist_from_spawn > leash_range and (state == State.CHASING or state == State.ATTACKING):
|
||||||
|
target = null
|
||||||
|
state = State.PATROL
|
||||||
|
_pick_patrol_target()
|
||||||
|
_do_patrol(delta)
|
||||||
|
move_and_slide()
|
||||||
|
return
|
||||||
|
|
||||||
match state:
|
match state:
|
||||||
State.IDLE:
|
State.IDLE:
|
||||||
_play_anim("idle")
|
_play_anim("idle")
|
||||||
|
|
@ -272,18 +284,40 @@ func _physics_process(delta):
|
||||||
|
|
||||||
move_and_slide()
|
move_and_slide()
|
||||||
|
|
||||||
func _chase_target():
|
func _has_navmesh() -> bool:
|
||||||
if target == null:
|
var map_rid = get_world_3d().navigation_map
|
||||||
|
return NavigationServer3D.map_get_regions(map_rid).size() > 0
|
||||||
|
|
||||||
|
func _move_toward(goal: Vector3, speed: float):
|
||||||
|
# Nur neuen Pfad berechnen wenn sich das Ziel deutlich bewegt hat
|
||||||
|
if nav_agent.target_position.distance_to(goal) > 1.5:
|
||||||
|
nav_agent.target_position = goal
|
||||||
|
if nav_agent.is_navigation_finished():
|
||||||
return
|
return
|
||||||
var direction = (target.global_position - global_position)
|
var path = nav_agent.get_current_navigation_path()
|
||||||
|
var next_pos = nav_agent.get_next_path_position()
|
||||||
|
var direction = (next_pos - global_position)
|
||||||
|
direction.y = 0
|
||||||
|
if direction.length() < 0.1:
|
||||||
|
if not _has_navmesh() or path.size() <= 1:
|
||||||
|
# Kein NavMesh oder kein gültiger Pfad → direkte Bewegung
|
||||||
|
direction = (goal - global_position)
|
||||||
direction.y = 0
|
direction.y = 0
|
||||||
if direction.length() < 0.1:
|
if direction.length() < 0.1:
|
||||||
return
|
return
|
||||||
direction = direction.normalized()
|
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)
|
_face_direction(direction)
|
||||||
|
|
||||||
|
|
||||||
|
func _chase_target():
|
||||||
|
if target == null:
|
||||||
|
return
|
||||||
|
if global_position.distance_to(target.global_position) < 0.5:
|
||||||
|
return
|
||||||
|
_move_toward(target.global_position, move_speed)
|
||||||
|
|
||||||
func _do_patrol(delta: float):
|
func _do_patrol(delta: float):
|
||||||
# Turn-Animation läuft → warten
|
# Turn-Animation läuft → warten
|
||||||
if is_turning:
|
if is_turning:
|
||||||
|
|
@ -302,9 +336,8 @@ func _do_patrol(delta: float):
|
||||||
_turn_toward_patrol_target()
|
_turn_toward_patrol_target()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Zum Patrol-Ziel laufen
|
# Zum Patrol-Ziel laufen (via NavMesh mit Fallback)
|
||||||
var dist = global_position.distance_to(patrol_target)
|
if global_position.distance_to(patrol_target) <= 1.0:
|
||||||
if dist <= 1.0:
|
|
||||||
# Ziel erreicht → kurz warten, neues Ziel
|
# Ziel erreicht → kurz warten, neues Ziel
|
||||||
velocity.x = 0
|
velocity.x = 0
|
||||||
velocity.z = 0
|
velocity.z = 0
|
||||||
|
|
@ -313,12 +346,7 @@ func _do_patrol(delta: float):
|
||||||
_pick_patrol_target()
|
_pick_patrol_target()
|
||||||
return
|
return
|
||||||
|
|
||||||
var direction = (patrol_target - global_position)
|
_move_toward(patrol_target, patrol_speed)
|
||||||
direction.y = 0
|
|
||||||
direction = direction.normalized()
|
|
||||||
velocity.x = direction.x * patrol_speed
|
|
||||||
velocity.z = direction.z * patrol_speed
|
|
||||||
_face_direction(direction)
|
|
||||||
_play_anim("walk")
|
_play_anim("walk")
|
||||||
|
|
||||||
func _turn_toward_patrol_target():
|
func _turn_toward_patrol_target():
|
||||||
|
|
@ -343,7 +371,15 @@ func _turn_toward_patrol_target():
|
||||||
func _pick_patrol_target():
|
func _pick_patrol_target():
|
||||||
var angle = randf() * TAU
|
var angle = randf() * TAU
|
||||||
var dist = randf_range(3.0, patrol_radius)
|
var dist = randf_range(3.0, patrol_radius)
|
||||||
patrol_target = spawn_position + Vector3(cos(angle) * dist, 0, sin(angle) * dist)
|
var raw_target = spawn_position + Vector3(cos(angle) * dist, 0, sin(angle) * dist)
|
||||||
|
# Auf nächsten begehbaren Punkt snappen (verhindert Ziele in Wänden)
|
||||||
|
var map_rid = get_world_3d().navigation_map
|
||||||
|
var snapped = NavigationServer3D.map_get_closest_point(map_rid, raw_target)
|
||||||
|
# Fallback: wenn Snap fehlschlägt (kein NavMesh), Rohziel verwenden
|
||||||
|
if snapped == Vector3.ZERO and raw_target != Vector3.ZERO:
|
||||||
|
patrol_target = raw_target
|
||||||
|
else:
|
||||||
|
patrol_target = snapped
|
||||||
|
|
||||||
func _face_target():
|
func _face_target():
|
||||||
if target == null:
|
if target == null:
|
||||||
|
|
@ -424,5 +460,5 @@ func _die():
|
||||||
# Kollision deaktivieren und Node entfernen
|
# Kollision deaktivieren und Node entfernen
|
||||||
set_deferred("collision_layer", 0)
|
set_deferred("collision_layer", 0)
|
||||||
set_deferred("collision_mask", 0)
|
set_deferred("collision_mask", 0)
|
||||||
await get_tree().create_timer(2.0).timeout
|
await get_tree().create_timer(10.0).timeout
|
||||||
queue_free()
|
queue_free()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
[gd_scene format=3 uid="uid://cvojaeanxugfj"]
|
[gd_scene format=3 uid="uid://cvojaeanxugfj"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://gaqwoakxyhet" path="res://enemy.gd" id="1_enemy"]
|
[ext_resource type="Script" uid="uid://gaqwoakxyhet" path="res://enemy.gd" id="1_enemy"]
|
||||||
[ext_resource type="PackedScene" uid="uid://daeym1tdcnhhd" path="res://assets/Warrior+Animation/castle_guard_01.fbx" id="2_model"]
|
[ext_resource type="PackedScene" path="res://assets/Goblin+Animation/goblin_d_shareyko.fbx" id="2_model"]
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_7k104"]
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_7k104"]
|
||||||
height = 2.208252
|
radius = 0.35
|
||||||
|
height = 1.4
|
||||||
|
|
||||||
[node name="Enemy" type="CharacterBody3D" unique_id=393882142]
|
[node name="Enemy" type="CharacterBody3D" unique_id=393882142]
|
||||||
script = ExtResource("1_enemy")
|
script = ExtResource("1_enemy")
|
||||||
|
|
@ -12,13 +13,13 @@ script = ExtResource("1_enemy")
|
||||||
[node name="Model" parent="." unique_id=842107644 instance=ExtResource("2_model")]
|
[node name="Model" parent="." unique_id=842107644 instance=ExtResource("2_model")]
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1531674298]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1531674298]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.010422, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.7, 0)
|
||||||
shape = SubResource("CapsuleShape3D_7k104")
|
shape = SubResource("CapsuleShape3D_7k104")
|
||||||
|
|
||||||
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="." unique_id=838601477]
|
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="." unique_id=838601477]
|
||||||
|
|
||||||
[node name="HealthDisplay" type="Node3D" parent="." unique_id=1341862509]
|
[node name="HealthDisplay" type="Node3D" parent="." unique_id=1341862509]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.2, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0)
|
||||||
|
|
||||||
[node name="Label3D" type="Label3D" parent="HealthDisplay" unique_id=80852959]
|
[node name="Label3D" type="Label3D" parent="HealthDisplay" unique_id=80852959]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1362207, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1362207, 0)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ drop_chance = 0.15
|
||||||
[sub_resource type="Resource" id="entry_5"]
|
[sub_resource type="Resource" id="entry_5"]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
item = ExtResource("7")
|
item = ExtResource("7")
|
||||||
drop_chance = 0.1
|
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,11 @@ walk_toggle={
|
||||||
"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)
|
"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)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
interact={
|
||||||
|
"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":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
[physics]
|
[physics]
|
||||||
|
|
||||||
|
|
|
||||||
709
world.gd
709
world.gd
|
|
@ -3,18 +3,19 @@
|
||||||
# Spielwelt-Controller
|
# Spielwelt-Controller
|
||||||
#
|
#
|
||||||
# Verantwortlichkeiten:
|
# Verantwortlichkeiten:
|
||||||
# • Prozeduraler Boden-Shader (Schachbrettmuster, world-space stabil)
|
# • Overworld + Dungeon in einer Szene (kein Szenenwechsel)
|
||||||
# • Prozeduraler Himmel (ProceduralSkyMaterial + WorldEnvironment)
|
# • Prozeduraler Boden-Shader, Himmel
|
||||||
# • Hauptmenü → Klassenauswahl → Spielinitialisierung
|
# • Hauptmenü → Klassenauswahl → Spielinitialisierung
|
||||||
# • Startausrüstung je nach Klasse
|
# • Startausrüstung je nach Klasse
|
||||||
# • Gegner-Setup, Loot-Weiterleitung, Respawn nach Tod
|
# • Gegner-Setup, Loot-Weiterleitung, Respawn nach Tod
|
||||||
|
# • Dungeon-Generierung und Portal-System
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
const ENEMY_SCENE = preload("res://enemy.tscn")
|
const ENEMY_SCENE = preload("res://enemy.tscn")
|
||||||
const MAIN_MENU = preload("res://main_menu.tscn")
|
const MAIN_MENU = preload("res://main_menu.tscn")
|
||||||
const CLASS_SELECTION_MENU = preload("res://class_selection_menu.tscn")
|
const CLASS_SELECTION_MENU = preload("res://class_selection_menu.tscn")
|
||||||
const RESPAWN_TIME = 5.0
|
const RESPAWN_TIME = 60.0
|
||||||
|
|
||||||
# Startausrüstung
|
# Startausrüstung
|
||||||
const STARTER_SWORD = preload("res://equipment/iron_sword.tres")
|
const STARTER_SWORD = preload("res://equipment/iron_sword.tres")
|
||||||
|
|
@ -25,8 +26,41 @@ const STARTER_POTION = preload("res://consumables/small_hp_potion.tres")
|
||||||
# Loot Tables
|
# Loot Tables
|
||||||
const GOBLIN_LOOT = preload("res://loot_tables/goblin_loot.tres")
|
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 player = $Player
|
||||||
@onready var floor_mesh = $Boden/MeshInstance3D
|
@onready var floor_mesh = $Boden/MeshInstance3D
|
||||||
|
@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():
|
func _ready():
|
||||||
_setup_floor_material()
|
_setup_floor_material()
|
||||||
|
|
@ -55,37 +89,667 @@ func _setup_sky():
|
||||||
env.ambient_light_energy = 0.6
|
env.ambient_light_energy = 0.6
|
||||||
env.tonemap_mode = Environment.TONE_MAPPER_FILMIC
|
env.tonemap_mode = Environment.TONE_MAPPER_FILMIC
|
||||||
|
|
||||||
var world_env = WorldEnvironment.new()
|
overworld_env = WorldEnvironment.new()
|
||||||
world_env.environment = env
|
overworld_env.name = "OverworldEnv"
|
||||||
add_child(world_env)
|
overworld_env.environment = env
|
||||||
|
add_child(overworld_env)
|
||||||
|
|
||||||
func _setup_floor_material():
|
func _setup_floor_material():
|
||||||
var shader = Shader.new()
|
var shader = Shader.new()
|
||||||
shader.code = """
|
shader.code = """
|
||||||
shader_type spatial;
|
shader_type spatial;
|
||||||
uniform float grid_size : hint_range(0.5, 10.0) = 2.0;
|
uniform vec4 grass_color_a : source_color = vec4(0.18, 0.42, 0.12, 1.0);
|
||||||
uniform vec4 color_a : source_color = vec4(0.22, 0.22, 0.22, 1.0);
|
uniform vec4 grass_color_b : source_color = vec4(0.22, 0.50, 0.15, 1.0);
|
||||||
uniform vec4 color_b : source_color = vec4(0.38, 0.38, 0.38, 1.0);
|
uniform vec4 dirt_color : source_color = vec4(0.35, 0.25, 0.15, 1.0);
|
||||||
|
uniform float noise_scale : hint_range(0.01, 0.5) = 0.08;
|
||||||
|
uniform float dirt_threshold : hint_range(0.0, 1.0) = 0.72;
|
||||||
|
|
||||||
|
float hash(vec2 p) {
|
||||||
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
||||||
|
}
|
||||||
|
|
||||||
|
float noise(vec2 p) {
|
||||||
|
vec2 i = floor(p);
|
||||||
|
vec2 f = fract(p);
|
||||||
|
f = f * f * (3.0 - 2.0 * f);
|
||||||
|
float a = hash(i);
|
||||||
|
float b = hash(i + vec2(1.0, 0.0));
|
||||||
|
float c = hash(i + vec2(0.0, 1.0));
|
||||||
|
float d = hash(i + vec2(1.0, 1.0));
|
||||||
|
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
||||||
|
}
|
||||||
|
|
||||||
void fragment() {
|
void fragment() {
|
||||||
vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||||
vec2 cell = floor(world_pos.xz / grid_size);
|
vec2 uv = world_pos.xz * noise_scale;
|
||||||
float checker = mod(cell.x + cell.y, 2.0);
|
float n1 = noise(uv * 3.0);
|
||||||
ALBEDO = mix(color_a.rgb, color_b.rgb, checker);
|
float n2 = noise(uv * 7.0 + vec2(50.0));
|
||||||
ROUGHNESS = 0.85;
|
float n3 = noise(uv * 15.0 + vec2(100.0));
|
||||||
|
float combined = n1 * 0.5 + n2 * 0.35 + n3 * 0.15;
|
||||||
|
vec3 grass = mix(grass_color_a.rgb, grass_color_b.rgb, n2);
|
||||||
|
vec3 col = mix(grass, dirt_color.rgb, step(dirt_threshold, combined));
|
||||||
|
ALBEDO = col;
|
||||||
|
ROUGHNESS = 0.92;
|
||||||
METALLIC = 0.0;
|
METALLIC = 0.0;
|
||||||
|
NORMAL_MAP = vec3(n2 * 0.3, n3 * 0.3, 1.0);
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
var mat = ShaderMaterial.new()
|
var mat = ShaderMaterial.new()
|
||||||
mat.shader = shader
|
mat.shader = shader
|
||||||
floor_mesh.material_override = mat
|
floor_mesh.material_override = mat
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 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
|
# Nach Hauptmenü: Klassenauswahl anzeigen
|
||||||
func _on_start_game():
|
func _on_start_game():
|
||||||
var menu = CLASS_SELECTION_MENU.instantiate()
|
var menu = CLASS_SELECTION_MENU.instantiate()
|
||||||
add_child(menu)
|
add_child(menu)
|
||||||
menu.class_selected.connect(_on_class_selected)
|
menu.class_selected.connect(_on_class_selected)
|
||||||
# Spiel wieder pausieren für Klassenauswahl
|
|
||||||
get_tree().paused = true
|
get_tree().paused = true
|
||||||
|
|
||||||
# Klasse ausgewählt: Spieler initialisieren
|
# Klasse ausgewählt: Spieler initialisieren
|
||||||
|
|
@ -120,7 +784,10 @@ func _on_class_selected(character_class: CharacterClass):
|
||||||
if child.has_method("take_damage") and child != player:
|
if child.has_method("take_damage") and child != player:
|
||||||
_setup_enemy(child)
|
_setup_enemy(child)
|
||||||
|
|
||||||
# Gegner initialisieren und Signal verbinden
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# GEGNER-SYSTEM
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func _setup_enemy(enemy):
|
func _setup_enemy(enemy):
|
||||||
if enemy and player:
|
if enemy and player:
|
||||||
enemy.target = player
|
enemy.target = player
|
||||||
|
|
@ -130,26 +797,26 @@ func _setup_enemy(enemy):
|
||||||
enemy.enemy_died.connect(_on_enemy_died)
|
enemy.enemy_died.connect(_on_enemy_died)
|
||||||
if not enemy.enemy_dropped_loot.is_connected(_on_enemy_dropped_loot):
|
if not enemy.enemy_dropped_loot.is_connected(_on_enemy_dropped_loot):
|
||||||
enemy.enemy_dropped_loot.connect(_on_enemy_dropped_loot)
|
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):
|
func _on_enemy_dropped_loot(loot: Dictionary, world_pos: Vector3):
|
||||||
if player:
|
if player:
|
||||||
player.receive_loot(loot, world_pos)
|
player.receive_loot(loot, world_pos)
|
||||||
|
|
||||||
# Gegner gestorben: Nach 5 Sekunden respawnen
|
|
||||||
func _on_enemy_died(spawn_position: Vector3, xp_reward: int):
|
func _on_enemy_died(spawn_position: Vector3, xp_reward: int):
|
||||||
if player:
|
if player:
|
||||||
player.gain_xp(xp_reward)
|
player.gain_xp(xp_reward)
|
||||||
print("Respawn in ", RESPAWN_TIME, " Sekunden...")
|
print("Respawn in ", RESPAWN_TIME, " Sekunden...")
|
||||||
await get_tree().create_timer(RESPAWN_TIME).timeout
|
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)
|
_spawn_enemy(spawn_position)
|
||||||
|
|
||||||
# Neuen Gegner an Position spawnen
|
|
||||||
func _spawn_enemy(position: Vector3):
|
func _spawn_enemy(position: Vector3):
|
||||||
var new_enemy = ENEMY_SCENE.instantiate()
|
var new_enemy = ENEMY_SCENE.instantiate()
|
||||||
add_child(new_enemy)
|
add_child(new_enemy)
|
||||||
new_enemy.global_position = position
|
new_enemy.global_position = position
|
||||||
_setup_enemy(new_enemy)
|
_setup_enemy(new_enemy)
|
||||||
print("Gegner respawned!")
|
|
||||||
|
|
|
||||||
333
world.tscn
333
world.tscn
|
|
@ -4,34 +4,337 @@
|
||||||
[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="3_enemy"]
|
[ext_resource type="PackedScene" uid="uid://cvojaeanxugfj" path="res://enemy.tscn" id="3_enemy"]
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_fj7yv"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_floor"]
|
||||||
size = Vector3(200, 0.5, 200)
|
size = Vector3(200, 0.5, 200)
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="BoxMesh_tlwt5"]
|
[sub_resource type="BoxMesh" id="BoxMesh_floor"]
|
||||||
size = Vector3(200, 0.5, 200)
|
size = Vector3(200, 0.5, 200)
|
||||||
|
|
||||||
[sub_resource type="NavigationMesh" id="NavigationMesh_fj7yv"]
|
[sub_resource type="NavigationMesh" id="NavigationMesh_fj7yv"]
|
||||||
|
|
||||||
[node name="World" type="Node3D" unique_id=2007838514]
|
[sub_resource type="StandardMaterial3D" id="mat_rock"]
|
||||||
|
albedo_color = Color(0.4, 0.38, 0.35, 1)
|
||||||
|
roughness = 0.95
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_stone"]
|
||||||
|
albedo_color = Color(0.3, 0.28, 0.25, 1)
|
||||||
|
roughness = 0.9
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_entrance"]
|
||||||
|
albedo_color = Color(0.02, 0.02, 0.02, 1)
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="BoxShape3D_gate"]
|
||||||
|
size = Vector3(5, 4, 4)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_rock_small"]
|
||||||
|
albedo_color = Color(0.45, 0.42, 0.38, 1)
|
||||||
|
roughness = 0.95
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_trunk"]
|
||||||
|
albedo_color = Color(0.35, 0.22, 0.1, 1)
|
||||||
|
roughness = 0.9
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="mat_leaf"]
|
||||||
|
albedo_color = Color(0.15, 0.45, 0.12, 1)
|
||||||
|
roughness = 0.85
|
||||||
|
|
||||||
|
[node name="World" type="Node3D" unique_id=1518976304]
|
||||||
script = ExtResource("1_tlwt5")
|
script = ExtResource("1_tlwt5")
|
||||||
|
|
||||||
[node name="Boden" type="StaticBody3D" parent="." unique_id=2101916269]
|
[node name="Boden" type="StaticBody3D" parent="." unique_id=1937472568]
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Boden" unique_id=1873339390]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="Boden" unique_id=1499976920]
|
||||||
shape = SubResource("BoxShape3D_fj7yv")
|
shape = SubResource("BoxShape3D_floor")
|
||||||
|
|
||||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Boden" unique_id=1214783061]
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="Boden" unique_id=1316024844]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
|
mesh = SubResource("BoxMesh_floor")
|
||||||
mesh = SubResource("BoxMesh_tlwt5")
|
|
||||||
|
|
||||||
[node name="Player" parent="." unique_id=937297102 instance=ExtResource("1_f3sb7")]
|
[node name="Player" parent="." unique_id=841198255 instance=ExtResource("1_f3sb7")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4345045, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.435, 0)
|
||||||
|
|
||||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1394887598]
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1642167272]
|
||||||
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="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=827244005]
|
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=1093030835]
|
||||||
navigation_mesh = SubResource("NavigationMesh_fj7yv")
|
navigation_mesh = SubResource("NavigationMesh_fj7yv")
|
||||||
|
|
||||||
[node name="enemy" parent="." unique_id=393882142 instance=ExtResource("3_enemy")]
|
[node name="enemy" parent="." unique_id=1435537835 instance=ExtResource("3_enemy")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.38837337, -35.95893)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.388, -36)
|
||||||
|
|
||||||
|
[node name="Mountain" type="CSGCombiner3D" parent="." unique_id=677409308]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -80)
|
||||||
|
use_collision = true
|
||||||
|
|
||||||
|
[node name="MainBody" type="CSGSphere3D" parent="Mountain" unique_id=578990011]
|
||||||
|
transform = Transform3D(1.5, 0, 0, 0, 0.6, 0, 0, 0, 1, 0, -5, 0)
|
||||||
|
radius = 30.0
|
||||||
|
radial_segments = 24
|
||||||
|
rings = 16
|
||||||
|
material = SubResource("mat_rock")
|
||||||
|
|
||||||
|
[node name="Peak" type="CSGSphere3D" parent="Mountain" unique_id=1930092098]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 10, 0)
|
||||||
|
radius = 15.0
|
||||||
|
radial_segments = 16
|
||||||
|
rings = 12
|
||||||
|
material = SubResource("mat_rock")
|
||||||
|
|
||||||
|
[node name="LeftHill" type="CSGSphere3D" parent="Mountain" unique_id=1517453171]
|
||||||
|
transform = Transform3D(1.2, 0, 0, 0, 0.5, 0, 0, 0, 0.8, -25, -8, 5)
|
||||||
|
radius = 20.0
|
||||||
|
radial_segments = 16
|
||||||
|
rings = 12
|
||||||
|
material = SubResource("mat_rock")
|
||||||
|
|
||||||
|
[node name="RightHill" type="CSGSphere3D" parent="Mountain" unique_id=334184404]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 0.45, 0, 0, 0, 0.9, 22, -8, 8)
|
||||||
|
radius = 18.0
|
||||||
|
radial_segments = 16
|
||||||
|
rings = 12
|
||||||
|
material = SubResource("mat_rock")
|
||||||
|
|
||||||
|
[node name="DungeonGate" type="Node3D" parent="." unique_id=1787139888]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -45)
|
||||||
|
|
||||||
|
[node name="LeftPillar" type="CSGBox3D" parent="DungeonGate" unique_id=975630240]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.5, 3, 0)
|
||||||
|
use_collision = true
|
||||||
|
size = Vector3(1.5, 6, 1.5)
|
||||||
|
material = SubResource("mat_stone")
|
||||||
|
|
||||||
|
[node name="RightPillar" type="CSGBox3D" parent="DungeonGate" unique_id=682177996]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.5, 3, 0)
|
||||||
|
use_collision = true
|
||||||
|
size = Vector3(1.5, 6, 1.5)
|
||||||
|
material = SubResource("mat_stone")
|
||||||
|
|
||||||
|
[node name="TopBar" type="CSGBox3D" parent="DungeonGate" unique_id=1917431148]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 6.6, 0)
|
||||||
|
use_collision = true
|
||||||
|
size = Vector3(6.5, 1.2, 1.5)
|
||||||
|
material = SubResource("mat_stone")
|
||||||
|
|
||||||
|
[node name="Entrance" type="CSGBox3D" parent="DungeonGate" unique_id=782262731]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.75, -0.5)
|
||||||
|
size = Vector3(3.5, 5.5, 2)
|
||||||
|
material = SubResource("mat_entrance")
|
||||||
|
|
||||||
|
[node name="GateArea" type="Area3D" parent="DungeonGate" unique_id=1947692918]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 2)
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="DungeonGate/GateArea" unique_id=1018035780]
|
||||||
|
shape = SubResource("BoxShape3D_gate")
|
||||||
|
|
||||||
|
[node name="GateLabel" type="Label3D" parent="DungeonGate" unique_id=1720057230]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 7.5, 1)
|
||||||
|
visible = false
|
||||||
|
billboard = 1
|
||||||
|
modulate = Color(1, 0.85, 0.3, 1)
|
||||||
|
text = "Dungeon betreten [E]"
|
||||||
|
font_size = 48
|
||||||
|
|
||||||
|
[node name="Rock1" type="CSGSphere3D" parent="." unique_id=427636709]
|
||||||
|
transform = Transform3D(1.1, 0, 0, 0, 0.5, 0, 0, 0, 0.9, 15, 0.5, -20)
|
||||||
|
radius = 1.8
|
||||||
|
radial_segments = 8
|
||||||
|
material = SubResource("mat_rock_small")
|
||||||
|
|
||||||
|
[node name="Rock2" type="CSGSphere3D" parent="." unique_id=176525307]
|
||||||
|
transform = Transform3D(0.9, 0, 0, 0, 0.6, 0, 0, 0, 1.1, -18, 0.4, -15)
|
||||||
|
radius = 1.4
|
||||||
|
radial_segments = 8
|
||||||
|
material = SubResource("mat_rock_small")
|
||||||
|
|
||||||
|
[node name="Rock3" type="CSGSphere3D" parent="." unique_id=1182279621]
|
||||||
|
transform = Transform3D(1.2, 0, 0, 0, 0.45, 0, 0, 0, 0.8, 25, 0.7, 10)
|
||||||
|
radius = 2.2
|
||||||
|
radial_segments = 8
|
||||||
|
material = SubResource("mat_rock_small")
|
||||||
|
|
||||||
|
[node name="Rock4" type="CSGSphere3D" parent="." unique_id=1966368230]
|
||||||
|
transform = Transform3D(0.8, 0, 0, 0, 0.55, 0, 0, 0, 1, -22, 0.3, 25)
|
||||||
|
radius = 1.0
|
||||||
|
radial_segments = 8
|
||||||
|
material = SubResource("mat_rock_small")
|
||||||
|
|
||||||
|
[node name="Rock5" type="CSGSphere3D" parent="." unique_id=48323199]
|
||||||
|
transform = Transform3D(1.3, 0, 0, 0, 0.5, 0, 0, 0, 0.9, 30, 0.6, -40)
|
||||||
|
radius = 2.0
|
||||||
|
radial_segments = 8
|
||||||
|
material = SubResource("mat_rock_small")
|
||||||
|
|
||||||
|
[node name="Rock6" type="CSGSphere3D" parent="." unique_id=604365640]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 0.7, 0, 0, 0, 1.2, -28, 0.5, -45)
|
||||||
|
radius = 1.6
|
||||||
|
radial_segments = 8
|
||||||
|
material = SubResource("mat_rock_small")
|
||||||
|
|
||||||
|
[node name="Rock7" type="CSGSphere3D" parent="." unique_id=1821485412]
|
||||||
|
transform = Transform3D(0.9, 0, 0, 0, 0.4, 0, 0, 0, 1, 12, 0.3, 30)
|
||||||
|
radius = 1.2
|
||||||
|
radial_segments = 8
|
||||||
|
material = SubResource("mat_rock_small")
|
||||||
|
|
||||||
|
[node name="Rock8" type="CSGSphere3D" parent="." unique_id=1387702407]
|
||||||
|
transform = Transform3D(1.1, 0, 0, 0, 0.6, 0, 0, 0, 0.85, -10, 0.6, -50)
|
||||||
|
radius = 2.4
|
||||||
|
radial_segments = 8
|
||||||
|
material = SubResource("mat_rock_small")
|
||||||
|
|
||||||
|
[node name="Rock9" type="CSGSphere3D" parent="." unique_id=1450102956]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 0.5, 0, 0, 0, 1.1, 35, 0.4, -25)
|
||||||
|
radius = 1.5
|
||||||
|
radial_segments = 8
|
||||||
|
material = SubResource("mat_rock_small")
|
||||||
|
|
||||||
|
[node name="Tree1" type="Node3D" parent="." unique_id=618772270]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 20, 0, -10)
|
||||||
|
|
||||||
|
[node name="Trunk" type="CSGCylinder3D" parent="Tree1" unique_id=1186519127]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.75, 0)
|
||||||
|
radius = 0.3
|
||||||
|
height = 5.5
|
||||||
|
material = SubResource("mat_trunk")
|
||||||
|
|
||||||
|
[node name="Crown1" type="CSGSphere3D" parent="Tree1" unique_id=2089075716]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.3, 4.2, -0.2)
|
||||||
|
radius = 2.2
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Crown2" type="CSGSphere3D" parent="Tree1" unique_id=2070989054]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.4, 5.4, 0.3)
|
||||||
|
radius = 2.0
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Tree2" type="Node3D" parent="." unique_id=1889546293]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0, -30)
|
||||||
|
|
||||||
|
[node name="Trunk" type="CSGCylinder3D" parent="Tree2" unique_id=997380154]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.25, 0)
|
||||||
|
radius = 0.3
|
||||||
|
height = 6.5
|
||||||
|
material = SubResource("mat_trunk")
|
||||||
|
|
||||||
|
[node name="Crown1" type="CSGSphere3D" parent="Tree2" unique_id=809808235]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 5, 0.2)
|
||||||
|
radius = 2.5
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Crown2" type="CSGSphere3D" parent="Tree2" unique_id=481648384]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.3, 6.2, -0.4)
|
||||||
|
radius = 2.2
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Crown3" type="CSGSphere3D" parent="Tree2" unique_id=184574046]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.1, 7.4, 0.1)
|
||||||
|
radius = 1.8
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Tree3" type="Node3D" parent="." unique_id=1385340799]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 28, 0, 20)
|
||||||
|
|
||||||
|
[node name="Trunk" type="CSGCylinder3D" parent="Tree3" unique_id=387398909]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.25, 0)
|
||||||
|
radius = 0.3
|
||||||
|
height = 4.5
|
||||||
|
material = SubResource("mat_trunk")
|
||||||
|
|
||||||
|
[node name="Crown1" type="CSGSphere3D" parent="Tree3" unique_id=1139907459]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.2, 3.5, 0.5)
|
||||||
|
radius = 2.0
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Crown2" type="CSGSphere3D" parent="Tree3" unique_id=391069386]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.4, 4.7, -0.3)
|
||||||
|
radius = 2.3
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Tree4" type="Node3D" parent="." unique_id=613675839]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -25, 0, 15)
|
||||||
|
|
||||||
|
[node name="Trunk" type="CSGCylinder3D" parent="Tree4" unique_id=220117087]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3, 0)
|
||||||
|
radius = 0.3
|
||||||
|
height = 6.0
|
||||||
|
material = SubResource("mat_trunk")
|
||||||
|
|
||||||
|
[node name="Crown1" type="CSGSphere3D" parent="Tree4" unique_id=129254377]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6, 4.5, -0.1)
|
||||||
|
radius = 2.4
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Crown2" type="CSGSphere3D" parent="Tree4" unique_id=929192645]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 5.7, 0.4)
|
||||||
|
radius = 2.1
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Tree5" type="Node3D" parent="." unique_id=94347379]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, 0, 35)
|
||||||
|
|
||||||
|
[node name="Trunk" type="CSGCylinder3D" parent="Tree5" unique_id=199856993]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.5, 0)
|
||||||
|
radius = 0.3
|
||||||
|
height = 7.0
|
||||||
|
material = SubResource("mat_trunk")
|
||||||
|
|
||||||
|
[node name="Crown1" type="CSGSphere3D" parent="Tree5" unique_id=1965121271]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.3, 5.3, 0.6)
|
||||||
|
radius = 2.6
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Crown2" type="CSGSphere3D" parent="Tree5" unique_id=1961694117]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 6.5, -0.2)
|
||||||
|
radius = 2.3
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Crown3" type="CSGSphere3D" parent="Tree5" unique_id=1346853660]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 7.7, 0.3)
|
||||||
|
radius = 1.9
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Tree6" type="Node3D" parent="." unique_id=1309473387]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -30, 0, -10)
|
||||||
|
|
||||||
|
[node name="Trunk" type="CSGCylinder3D" parent="Tree6" unique_id=1889196546]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0)
|
||||||
|
radius = 0.3
|
||||||
|
height = 5.0
|
||||||
|
material = SubResource("mat_trunk")
|
||||||
|
|
||||||
|
[node name="Crown1" type="CSGSphere3D" parent="Tree6" unique_id=1159575861]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.2, 3.8, -0.4)
|
||||||
|
radius = 2.1
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
||||||
|
[node name="Crown2" type="CSGSphere3D" parent="Tree6" unique_id=2073540260]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.6, 5, 0.2)
|
||||||
|
radius = 2.4
|
||||||
|
radial_segments = 10
|
||||||
|
rings = 8
|
||||||
|
material = SubResource("mat_leaf")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue