commit f55f9f4a7a1536d5886c4892e85301af26b83744 Author: michi Date: Fri Aug 1 18:40:40 2025 +0200 Initial commit: Browser Survival Game diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..138f9c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,85 @@ +# Node modules +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage +.grunt + +# Bower dependency directory +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Logs +logs +*.log + +# Temporary files +tmp/ +temp/ + +# Build outputs +dist/ +build/ + +# Cache +.cache/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d84432d --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# Browser Survival Game + +Ein 2D-Survival-Spiel im Browser mit prozeduraler Weltgenerierung. + +## Features + +- **Prozedurale Weltgenerierung**: Unendliche Welt mit Perlin Noise-basierten Biomen +- **Chunk-System**: Optimierte Performance durch dynamisches Laden/Entladen von Weltabschnitten +- **Ressourcen-Management**: Sammle Stöcker, Holz und Steine +- **Crafting-System**: Erstelle Werkzeuge (Axt, Spitzhacke) und Gebäude (Lagerfeuer) +- **2D-Grafiken**: Vollständiges Sprite-System mit animierten Objekten +- **Kamera-System**: Sanftes Spieler-Tracking und Koordinatentransformation + +## Steuerung + +- **WASD / Pfeiltasten**: Spieler bewegen +- **Tab**: Inventar anzeigen/verstecken +- **Q**: Axt craften (benötigt 2 Stöcker) +- **R**: Spitzhacke craften (benötigt 1 Stock + 2 Holz) +- **E**: Lagerfeuer craften (benötigt 5 Holz) + +## Spielmechaniken + +### Ressourcen sammeln +- **Stöcker**: Können ohne Werkzeug gesammelt werden +- **Bäume**: Benötigen eine Axt zum Fällen +- **Steine**: Benötigen eine Spitzhacke zum Abbauen + +### Gesundheitssystem +- Gesundheit regeneriert sich in der Nähe von Lagerfeuern +- Gesundheitsanzeige immer sichtbar + +## Technische Details + +### Weltgenerierung +- **Perlin Noise**: Für natürliche Biom-Verteilung +- **Chunk-Größe**: 400x400 Pixel +- **Render-Distanz**: 2 Chunks um den Spieler +- **Tile-Größe**: 32x32 Pixel + +### Assets +- Vollständiges Tileset mit 64+ Sumpf-Varianten +- Verschiedene Baum-, Stein- und Stick-Sprites +- Animierte Fackeln für Lagerfeuer +- Schatten-System für Tiefeneffekt + +## Installation + +1. Repository klonen +2. `index.html` in einem modernen Browser öffnen +3. Spielen! + +## Entwicklung + +Das Spiel ist in reinem JavaScript geschrieben und benötigt keine Build-Tools oder Dependencies. + +### Dateistruktur +``` +├── index.html # Haupt-HTML-Datei +├── game.js # Gesamte Spiellogik +└── assets/ # Grafik-Assets + ├── 1 Tiles/ # Boden-Tiles + ├── 2 Objects/ # Spielobjekte + └── 3 Animated Objects/ # Animierte Objekte +``` + +## Geplante Features + +- Weitere Biome und Ressourcen +- Feinde und Kampfsystem +- Erweiterte Gebäude +- Multiplayer-Unterstützung +- Sound-Effekte und Musik + +## Lizenz + +MIT License - Siehe LICENSE-Datei für Details. \ No newline at end of file diff --git a/assets/1 Tiles/SwampTile_01.png b/assets/1 Tiles/SwampTile_01.png new file mode 100644 index 0000000..9777869 Binary files /dev/null and b/assets/1 Tiles/SwampTile_01.png differ diff --git a/assets/1 Tiles/SwampTile_02.png b/assets/1 Tiles/SwampTile_02.png new file mode 100644 index 0000000..964cd52 Binary files /dev/null and b/assets/1 Tiles/SwampTile_02.png differ diff --git a/assets/1 Tiles/SwampTile_03.png b/assets/1 Tiles/SwampTile_03.png new file mode 100644 index 0000000..b600089 Binary files /dev/null and b/assets/1 Tiles/SwampTile_03.png differ diff --git a/assets/1 Tiles/SwampTile_04.png b/assets/1 Tiles/SwampTile_04.png new file mode 100644 index 0000000..e178d01 Binary files /dev/null and b/assets/1 Tiles/SwampTile_04.png differ diff --git a/assets/1 Tiles/SwampTile_05.png b/assets/1 Tiles/SwampTile_05.png new file mode 100644 index 0000000..f5cf07e Binary files /dev/null and b/assets/1 Tiles/SwampTile_05.png differ diff --git a/assets/1 Tiles/SwampTile_06.png b/assets/1 Tiles/SwampTile_06.png new file mode 100644 index 0000000..3f531b9 Binary files /dev/null and b/assets/1 Tiles/SwampTile_06.png differ diff --git a/assets/1 Tiles/SwampTile_07.png b/assets/1 Tiles/SwampTile_07.png new file mode 100644 index 0000000..33eaaac Binary files /dev/null and b/assets/1 Tiles/SwampTile_07.png differ diff --git a/assets/1 Tiles/SwampTile_08.png b/assets/1 Tiles/SwampTile_08.png new file mode 100644 index 0000000..e23a800 Binary files /dev/null and b/assets/1 Tiles/SwampTile_08.png differ diff --git a/assets/1 Tiles/SwampTile_09.png b/assets/1 Tiles/SwampTile_09.png new file mode 100644 index 0000000..bc60436 Binary files /dev/null and b/assets/1 Tiles/SwampTile_09.png differ diff --git a/assets/1 Tiles/SwampTile_10.png b/assets/1 Tiles/SwampTile_10.png new file mode 100644 index 0000000..de870f7 Binary files /dev/null and b/assets/1 Tiles/SwampTile_10.png differ diff --git a/assets/1 Tiles/SwampTile_12.png b/assets/1 Tiles/SwampTile_12.png new file mode 100644 index 0000000..1149553 Binary files /dev/null and b/assets/1 Tiles/SwampTile_12.png differ diff --git a/assets/1 Tiles/SwampTile_13.png b/assets/1 Tiles/SwampTile_13.png new file mode 100644 index 0000000..875dfdc Binary files /dev/null and b/assets/1 Tiles/SwampTile_13.png differ diff --git a/assets/1 Tiles/SwampTile_14.png b/assets/1 Tiles/SwampTile_14.png new file mode 100644 index 0000000..8dcd218 Binary files /dev/null and b/assets/1 Tiles/SwampTile_14.png differ diff --git a/assets/1 Tiles/SwampTile_15.png b/assets/1 Tiles/SwampTile_15.png new file mode 100644 index 0000000..1c8bb6c Binary files /dev/null and b/assets/1 Tiles/SwampTile_15.png differ diff --git a/assets/1 Tiles/SwampTile_16.png b/assets/1 Tiles/SwampTile_16.png new file mode 100644 index 0000000..9fa70db Binary files /dev/null and b/assets/1 Tiles/SwampTile_16.png differ diff --git a/assets/1 Tiles/SwampTile_17.png b/assets/1 Tiles/SwampTile_17.png new file mode 100644 index 0000000..61ff30b Binary files /dev/null and b/assets/1 Tiles/SwampTile_17.png differ diff --git a/assets/1 Tiles/SwampTile_18.png b/assets/1 Tiles/SwampTile_18.png new file mode 100644 index 0000000..8b2cf4d Binary files /dev/null and b/assets/1 Tiles/SwampTile_18.png differ diff --git a/assets/1 Tiles/SwampTile_19.png b/assets/1 Tiles/SwampTile_19.png new file mode 100644 index 0000000..81bfb96 Binary files /dev/null and b/assets/1 Tiles/SwampTile_19.png differ diff --git a/assets/1 Tiles/SwampTile_20.png b/assets/1 Tiles/SwampTile_20.png new file mode 100644 index 0000000..ac85094 Binary files /dev/null and b/assets/1 Tiles/SwampTile_20.png differ diff --git a/assets/1 Tiles/SwampTile_21.png b/assets/1 Tiles/SwampTile_21.png new file mode 100644 index 0000000..e211165 Binary files /dev/null and b/assets/1 Tiles/SwampTile_21.png differ diff --git a/assets/1 Tiles/SwampTile_22.png b/assets/1 Tiles/SwampTile_22.png new file mode 100644 index 0000000..aaba65c Binary files /dev/null and b/assets/1 Tiles/SwampTile_22.png differ diff --git a/assets/1 Tiles/SwampTile_23.png b/assets/1 Tiles/SwampTile_23.png new file mode 100644 index 0000000..b39b1f1 Binary files /dev/null and b/assets/1 Tiles/SwampTile_23.png differ diff --git a/assets/1 Tiles/SwampTile_24.png b/assets/1 Tiles/SwampTile_24.png new file mode 100644 index 0000000..f8114eb Binary files /dev/null and b/assets/1 Tiles/SwampTile_24.png differ diff --git a/assets/1 Tiles/SwampTile_25.png b/assets/1 Tiles/SwampTile_25.png new file mode 100644 index 0000000..f4f2fe6 Binary files /dev/null and b/assets/1 Tiles/SwampTile_25.png differ diff --git a/assets/1 Tiles/SwampTile_26.png b/assets/1 Tiles/SwampTile_26.png new file mode 100644 index 0000000..d1786ad Binary files /dev/null and b/assets/1 Tiles/SwampTile_26.png differ diff --git a/assets/1 Tiles/SwampTile_27.png b/assets/1 Tiles/SwampTile_27.png new file mode 100644 index 0000000..5a2260d Binary files /dev/null and b/assets/1 Tiles/SwampTile_27.png differ diff --git a/assets/1 Tiles/SwampTile_28.png b/assets/1 Tiles/SwampTile_28.png new file mode 100644 index 0000000..2fa083e Binary files /dev/null and b/assets/1 Tiles/SwampTile_28.png differ diff --git a/assets/1 Tiles/SwampTile_29.png b/assets/1 Tiles/SwampTile_29.png new file mode 100644 index 0000000..2c68443 Binary files /dev/null and b/assets/1 Tiles/SwampTile_29.png differ diff --git a/assets/1 Tiles/SwampTile_30.png b/assets/1 Tiles/SwampTile_30.png new file mode 100644 index 0000000..4825e24 Binary files /dev/null and b/assets/1 Tiles/SwampTile_30.png differ diff --git a/assets/1 Tiles/SwampTile_31.png b/assets/1 Tiles/SwampTile_31.png new file mode 100644 index 0000000..b575c8e Binary files /dev/null and b/assets/1 Tiles/SwampTile_31.png differ diff --git a/assets/1 Tiles/SwampTile_32.png b/assets/1 Tiles/SwampTile_32.png new file mode 100644 index 0000000..1f45bd6 Binary files /dev/null and b/assets/1 Tiles/SwampTile_32.png differ diff --git a/assets/1 Tiles/SwampTile_33.png b/assets/1 Tiles/SwampTile_33.png new file mode 100644 index 0000000..3f4ac25 Binary files /dev/null and b/assets/1 Tiles/SwampTile_33.png differ diff --git a/assets/1 Tiles/SwampTile_34.png b/assets/1 Tiles/SwampTile_34.png new file mode 100644 index 0000000..6ed789a Binary files /dev/null and b/assets/1 Tiles/SwampTile_34.png differ diff --git a/assets/1 Tiles/SwampTile_35.png b/assets/1 Tiles/SwampTile_35.png new file mode 100644 index 0000000..c4fc9aa Binary files /dev/null and b/assets/1 Tiles/SwampTile_35.png differ diff --git a/assets/1 Tiles/SwampTile_36.png b/assets/1 Tiles/SwampTile_36.png new file mode 100644 index 0000000..038a07d Binary files /dev/null and b/assets/1 Tiles/SwampTile_36.png differ diff --git a/assets/1 Tiles/SwampTile_37.png b/assets/1 Tiles/SwampTile_37.png new file mode 100644 index 0000000..8b22ca7 Binary files /dev/null and b/assets/1 Tiles/SwampTile_37.png differ diff --git a/assets/1 Tiles/SwampTile_39.png b/assets/1 Tiles/SwampTile_39.png new file mode 100644 index 0000000..750ffd8 Binary files /dev/null and b/assets/1 Tiles/SwampTile_39.png differ diff --git a/assets/1 Tiles/SwampTile_40.png b/assets/1 Tiles/SwampTile_40.png new file mode 100644 index 0000000..0c1f674 Binary files /dev/null and b/assets/1 Tiles/SwampTile_40.png differ diff --git a/assets/1 Tiles/SwampTile_41.png b/assets/1 Tiles/SwampTile_41.png new file mode 100644 index 0000000..430020a Binary files /dev/null and b/assets/1 Tiles/SwampTile_41.png differ diff --git a/assets/1 Tiles/SwampTile_42.png b/assets/1 Tiles/SwampTile_42.png new file mode 100644 index 0000000..e8dc276 Binary files /dev/null and b/assets/1 Tiles/SwampTile_42.png differ diff --git a/assets/1 Tiles/SwampTile_43.png b/assets/1 Tiles/SwampTile_43.png new file mode 100644 index 0000000..e0921d4 Binary files /dev/null and b/assets/1 Tiles/SwampTile_43.png differ diff --git a/assets/1 Tiles/SwampTile_44.png b/assets/1 Tiles/SwampTile_44.png new file mode 100644 index 0000000..9e73a38 Binary files /dev/null and b/assets/1 Tiles/SwampTile_44.png differ diff --git a/assets/1 Tiles/SwampTile_45.png b/assets/1 Tiles/SwampTile_45.png new file mode 100644 index 0000000..2ad332f Binary files /dev/null and b/assets/1 Tiles/SwampTile_45.png differ diff --git a/assets/1 Tiles/SwampTile_46.png b/assets/1 Tiles/SwampTile_46.png new file mode 100644 index 0000000..08caa8b Binary files /dev/null and b/assets/1 Tiles/SwampTile_46.png differ diff --git a/assets/1 Tiles/SwampTile_47.png b/assets/1 Tiles/SwampTile_47.png new file mode 100644 index 0000000..fd30ef8 Binary files /dev/null and b/assets/1 Tiles/SwampTile_47.png differ diff --git a/assets/1 Tiles/SwampTile_48.png b/assets/1 Tiles/SwampTile_48.png new file mode 100644 index 0000000..56d3155 Binary files /dev/null and b/assets/1 Tiles/SwampTile_48.png differ diff --git a/assets/1 Tiles/SwampTile_49.png b/assets/1 Tiles/SwampTile_49.png new file mode 100644 index 0000000..4cd1a7e Binary files /dev/null and b/assets/1 Tiles/SwampTile_49.png differ diff --git a/assets/1 Tiles/SwampTile_50.png b/assets/1 Tiles/SwampTile_50.png new file mode 100644 index 0000000..8cab8de Binary files /dev/null and b/assets/1 Tiles/SwampTile_50.png differ diff --git a/assets/1 Tiles/SwampTile_51.png b/assets/1 Tiles/SwampTile_51.png new file mode 100644 index 0000000..7fff06e Binary files /dev/null and b/assets/1 Tiles/SwampTile_51.png differ diff --git a/assets/1 Tiles/SwampTile_52.png b/assets/1 Tiles/SwampTile_52.png new file mode 100644 index 0000000..0a5e30f Binary files /dev/null and b/assets/1 Tiles/SwampTile_52.png differ diff --git a/assets/1 Tiles/SwampTile_53.png b/assets/1 Tiles/SwampTile_53.png new file mode 100644 index 0000000..c3fd744 Binary files /dev/null and b/assets/1 Tiles/SwampTile_53.png differ diff --git a/assets/1 Tiles/SwampTile_54.png b/assets/1 Tiles/SwampTile_54.png new file mode 100644 index 0000000..9f02436 Binary files /dev/null and b/assets/1 Tiles/SwampTile_54.png differ diff --git a/assets/1 Tiles/SwampTile_55.png b/assets/1 Tiles/SwampTile_55.png new file mode 100644 index 0000000..6c6ed4a Binary files /dev/null and b/assets/1 Tiles/SwampTile_55.png differ diff --git a/assets/1 Tiles/SwampTile_56.png b/assets/1 Tiles/SwampTile_56.png new file mode 100644 index 0000000..6831644 Binary files /dev/null and b/assets/1 Tiles/SwampTile_56.png differ diff --git a/assets/1 Tiles/SwampTile_57.png b/assets/1 Tiles/SwampTile_57.png new file mode 100644 index 0000000..a91d5b5 Binary files /dev/null and b/assets/1 Tiles/SwampTile_57.png differ diff --git a/assets/1 Tiles/SwampTile_58.png b/assets/1 Tiles/SwampTile_58.png new file mode 100644 index 0000000..dac0f4e Binary files /dev/null and b/assets/1 Tiles/SwampTile_58.png differ diff --git a/assets/1 Tiles/SwampTile_59.png b/assets/1 Tiles/SwampTile_59.png new file mode 100644 index 0000000..5f0e6fa Binary files /dev/null and b/assets/1 Tiles/SwampTile_59.png differ diff --git a/assets/1 Tiles/SwampTile_60.png b/assets/1 Tiles/SwampTile_60.png new file mode 100644 index 0000000..07f99f1 Binary files /dev/null and b/assets/1 Tiles/SwampTile_60.png differ diff --git a/assets/1 Tiles/SwampTile_61.png b/assets/1 Tiles/SwampTile_61.png new file mode 100644 index 0000000..a27f045 Binary files /dev/null and b/assets/1 Tiles/SwampTile_61.png differ diff --git a/assets/1 Tiles/SwampTile_62.png b/assets/1 Tiles/SwampTile_62.png new file mode 100644 index 0000000..503b8a2 Binary files /dev/null and b/assets/1 Tiles/SwampTile_62.png differ diff --git a/assets/1 Tiles/SwampTile_63.png b/assets/1 Tiles/SwampTile_63.png new file mode 100644 index 0000000..4cd4a08 Binary files /dev/null and b/assets/1 Tiles/SwampTile_63.png differ diff --git a/assets/1 Tiles/SwampTile_64.png b/assets/1 Tiles/SwampTile_64.png new file mode 100644 index 0000000..10ecad2 Binary files /dev/null and b/assets/1 Tiles/SwampTile_64.png differ diff --git a/assets/1 Tiles/gras.png b/assets/1 Tiles/gras.png new file mode 100644 index 0000000..156a79c Binary files /dev/null and b/assets/1 Tiles/gras.png differ diff --git a/assets/1 Tiles/weg.png b/assets/1 Tiles/weg.png new file mode 100644 index 0000000..e696c18 Binary files /dev/null and b/assets/1 Tiles/weg.png differ diff --git a/assets/2 Objects/1 Shadow/1.png b/assets/2 Objects/1 Shadow/1.png new file mode 100644 index 0000000..8741b74 Binary files /dev/null and b/assets/2 Objects/1 Shadow/1.png differ diff --git a/assets/2 Objects/1 Shadow/2.png b/assets/2 Objects/1 Shadow/2.png new file mode 100644 index 0000000..2dc7827 Binary files /dev/null and b/assets/2 Objects/1 Shadow/2.png differ diff --git a/assets/2 Objects/1 Shadow/3.png b/assets/2 Objects/1 Shadow/3.png new file mode 100644 index 0000000..ec1043c Binary files /dev/null and b/assets/2 Objects/1 Shadow/3.png differ diff --git a/assets/2 Objects/1 Shadow/4.png b/assets/2 Objects/1 Shadow/4.png new file mode 100644 index 0000000..17d6627 Binary files /dev/null and b/assets/2 Objects/1 Shadow/4.png differ diff --git a/assets/2 Objects/1 Shadow/5.png b/assets/2 Objects/1 Shadow/5.png new file mode 100644 index 0000000..94279c4 Binary files /dev/null and b/assets/2 Objects/1 Shadow/5.png differ diff --git a/assets/2 Objects/1 Shadow/6.png b/assets/2 Objects/1 Shadow/6.png new file mode 100644 index 0000000..fee12e5 Binary files /dev/null and b/assets/2 Objects/1 Shadow/6.png differ diff --git a/assets/2 Objects/2 Tree/1.png b/assets/2 Objects/2 Tree/1.png new file mode 100644 index 0000000..b1c4977 Binary files /dev/null and b/assets/2 Objects/2 Tree/1.png differ diff --git a/assets/2 Objects/2 Tree/10.png b/assets/2 Objects/2 Tree/10.png new file mode 100644 index 0000000..c0e1fe7 Binary files /dev/null and b/assets/2 Objects/2 Tree/10.png differ diff --git a/assets/2 Objects/2 Tree/11.png b/assets/2 Objects/2 Tree/11.png new file mode 100644 index 0000000..23ae997 Binary files /dev/null and b/assets/2 Objects/2 Tree/11.png differ diff --git a/assets/2 Objects/2 Tree/12.png b/assets/2 Objects/2 Tree/12.png new file mode 100644 index 0000000..2249dac Binary files /dev/null and b/assets/2 Objects/2 Tree/12.png differ diff --git a/assets/2 Objects/2 Tree/2.png b/assets/2 Objects/2 Tree/2.png new file mode 100644 index 0000000..f9dafef Binary files /dev/null and b/assets/2 Objects/2 Tree/2.png differ diff --git a/assets/2 Objects/2 Tree/3.png b/assets/2 Objects/2 Tree/3.png new file mode 100644 index 0000000..c180f10 Binary files /dev/null and b/assets/2 Objects/2 Tree/3.png differ diff --git a/assets/2 Objects/2 Tree/4.png b/assets/2 Objects/2 Tree/4.png new file mode 100644 index 0000000..04d1702 Binary files /dev/null and b/assets/2 Objects/2 Tree/4.png differ diff --git a/assets/2 Objects/2 Tree/5.png b/assets/2 Objects/2 Tree/5.png new file mode 100644 index 0000000..32f5157 Binary files /dev/null and b/assets/2 Objects/2 Tree/5.png differ diff --git a/assets/2 Objects/2 Tree/6.png b/assets/2 Objects/2 Tree/6.png new file mode 100644 index 0000000..d82b032 Binary files /dev/null and b/assets/2 Objects/2 Tree/6.png differ diff --git a/assets/2 Objects/2 Tree/7.png b/assets/2 Objects/2 Tree/7.png new file mode 100644 index 0000000..907485b Binary files /dev/null and b/assets/2 Objects/2 Tree/7.png differ diff --git a/assets/2 Objects/2 Tree/8.png b/assets/2 Objects/2 Tree/8.png new file mode 100644 index 0000000..1a04c85 Binary files /dev/null and b/assets/2 Objects/2 Tree/8.png differ diff --git a/assets/2 Objects/2 Tree/9.png b/assets/2 Objects/2 Tree/9.png new file mode 100644 index 0000000..dc06674 Binary files /dev/null and b/assets/2 Objects/2 Tree/9.png differ diff --git a/assets/2 Objects/3 Grass/1.png b/assets/2 Objects/3 Grass/1.png new file mode 100644 index 0000000..55d5f70 Binary files /dev/null and b/assets/2 Objects/3 Grass/1.png differ diff --git a/assets/2 Objects/3 Grass/10.png b/assets/2 Objects/3 Grass/10.png new file mode 100644 index 0000000..64597a2 Binary files /dev/null and b/assets/2 Objects/3 Grass/10.png differ diff --git a/assets/2 Objects/3 Grass/11.png b/assets/2 Objects/3 Grass/11.png new file mode 100644 index 0000000..83e006e Binary files /dev/null and b/assets/2 Objects/3 Grass/11.png differ diff --git a/assets/2 Objects/3 Grass/12.png b/assets/2 Objects/3 Grass/12.png new file mode 100644 index 0000000..35b0851 Binary files /dev/null and b/assets/2 Objects/3 Grass/12.png differ diff --git a/assets/2 Objects/3 Grass/2.png b/assets/2 Objects/3 Grass/2.png new file mode 100644 index 0000000..009fdf2 Binary files /dev/null and b/assets/2 Objects/3 Grass/2.png differ diff --git a/assets/2 Objects/3 Grass/3.png b/assets/2 Objects/3 Grass/3.png new file mode 100644 index 0000000..aa4b18b Binary files /dev/null and b/assets/2 Objects/3 Grass/3.png differ diff --git a/assets/2 Objects/3 Grass/4.png b/assets/2 Objects/3 Grass/4.png new file mode 100644 index 0000000..87b7e48 Binary files /dev/null and b/assets/2 Objects/3 Grass/4.png differ diff --git a/assets/2 Objects/3 Grass/5.png b/assets/2 Objects/3 Grass/5.png new file mode 100644 index 0000000..666f550 Binary files /dev/null and b/assets/2 Objects/3 Grass/5.png differ diff --git a/assets/2 Objects/3 Grass/6.png b/assets/2 Objects/3 Grass/6.png new file mode 100644 index 0000000..5c337a0 Binary files /dev/null and b/assets/2 Objects/3 Grass/6.png differ diff --git a/assets/2 Objects/3 Grass/7.png b/assets/2 Objects/3 Grass/7.png new file mode 100644 index 0000000..8e01837 Binary files /dev/null and b/assets/2 Objects/3 Grass/7.png differ diff --git a/assets/2 Objects/3 Grass/8.png b/assets/2 Objects/3 Grass/8.png new file mode 100644 index 0000000..53eec15 Binary files /dev/null and b/assets/2 Objects/3 Grass/8.png differ diff --git a/assets/2 Objects/3 Grass/9.png b/assets/2 Objects/3 Grass/9.png new file mode 100644 index 0000000..36365a1 Binary files /dev/null and b/assets/2 Objects/3 Grass/9.png differ diff --git a/assets/2 Objects/4 Stone/1.png b/assets/2 Objects/4 Stone/1.png new file mode 100644 index 0000000..76e5bf9 Binary files /dev/null and b/assets/2 Objects/4 Stone/1.png differ diff --git a/assets/2 Objects/4 Stone/10.png b/assets/2 Objects/4 Stone/10.png new file mode 100644 index 0000000..68ade4b Binary files /dev/null and b/assets/2 Objects/4 Stone/10.png differ diff --git a/assets/2 Objects/4 Stone/2.png b/assets/2 Objects/4 Stone/2.png new file mode 100644 index 0000000..9147e69 Binary files /dev/null and b/assets/2 Objects/4 Stone/2.png differ diff --git a/assets/2 Objects/4 Stone/3.png b/assets/2 Objects/4 Stone/3.png new file mode 100644 index 0000000..3c8edb6 Binary files /dev/null and b/assets/2 Objects/4 Stone/3.png differ diff --git a/assets/2 Objects/4 Stone/4.png b/assets/2 Objects/4 Stone/4.png new file mode 100644 index 0000000..f745f9c Binary files /dev/null and b/assets/2 Objects/4 Stone/4.png differ diff --git a/assets/2 Objects/4 Stone/5.png b/assets/2 Objects/4 Stone/5.png new file mode 100644 index 0000000..5f0a59f Binary files /dev/null and b/assets/2 Objects/4 Stone/5.png differ diff --git a/assets/2 Objects/4 Stone/6.png b/assets/2 Objects/4 Stone/6.png new file mode 100644 index 0000000..b955c72 Binary files /dev/null and b/assets/2 Objects/4 Stone/6.png differ diff --git a/assets/2 Objects/4 Stone/7.png b/assets/2 Objects/4 Stone/7.png new file mode 100644 index 0000000..af9b964 Binary files /dev/null and b/assets/2 Objects/4 Stone/7.png differ diff --git a/assets/2 Objects/4 Stone/8.png b/assets/2 Objects/4 Stone/8.png new file mode 100644 index 0000000..0d101fe Binary files /dev/null and b/assets/2 Objects/4 Stone/8.png differ diff --git a/assets/2 Objects/4 Stone/9.png b/assets/2 Objects/4 Stone/9.png new file mode 100644 index 0000000..9572107 Binary files /dev/null and b/assets/2 Objects/4 Stone/9.png differ diff --git a/assets/2 Objects/5 Pointer/1.png b/assets/2 Objects/5 Pointer/1.png new file mode 100644 index 0000000..73f3a51 Binary files /dev/null and b/assets/2 Objects/5 Pointer/1.png differ diff --git a/assets/2 Objects/5 Pointer/2.png b/assets/2 Objects/5 Pointer/2.png new file mode 100644 index 0000000..39bd930 Binary files /dev/null and b/assets/2 Objects/5 Pointer/2.png differ diff --git a/assets/2 Objects/5 Pointer/3.png b/assets/2 Objects/5 Pointer/3.png new file mode 100644 index 0000000..37527e9 Binary files /dev/null and b/assets/2 Objects/5 Pointer/3.png differ diff --git a/assets/2 Objects/5 Pointer/4.png b/assets/2 Objects/5 Pointer/4.png new file mode 100644 index 0000000..760abe7 Binary files /dev/null and b/assets/2 Objects/5 Pointer/4.png differ diff --git a/assets/2 Objects/5 Pointer/5.png b/assets/2 Objects/5 Pointer/5.png new file mode 100644 index 0000000..cd8b68e Binary files /dev/null and b/assets/2 Objects/5 Pointer/5.png differ diff --git a/assets/2 Objects/5 Pointer/6.png b/assets/2 Objects/5 Pointer/6.png new file mode 100644 index 0000000..7f68aeb Binary files /dev/null and b/assets/2 Objects/5 Pointer/6.png differ diff --git a/assets/2 Objects/6 Decor/1.png b/assets/2 Objects/6 Decor/1.png new file mode 100644 index 0000000..4c749bd Binary files /dev/null and b/assets/2 Objects/6 Decor/1.png differ diff --git a/assets/2 Objects/6 Decor/10.png b/assets/2 Objects/6 Decor/10.png new file mode 100644 index 0000000..4f99493 Binary files /dev/null and b/assets/2 Objects/6 Decor/10.png differ diff --git a/assets/2 Objects/6 Decor/11.png b/assets/2 Objects/6 Decor/11.png new file mode 100644 index 0000000..79b3ba1 Binary files /dev/null and b/assets/2 Objects/6 Decor/11.png differ diff --git a/assets/2 Objects/6 Decor/12.png b/assets/2 Objects/6 Decor/12.png new file mode 100644 index 0000000..6033325 Binary files /dev/null and b/assets/2 Objects/6 Decor/12.png differ diff --git a/assets/2 Objects/6 Decor/13.png b/assets/2 Objects/6 Decor/13.png new file mode 100644 index 0000000..04543dd Binary files /dev/null and b/assets/2 Objects/6 Decor/13.png differ diff --git a/assets/2 Objects/6 Decor/14.png b/assets/2 Objects/6 Decor/14.png new file mode 100644 index 0000000..b61382d Binary files /dev/null and b/assets/2 Objects/6 Decor/14.png differ diff --git a/assets/2 Objects/6 Decor/2.png b/assets/2 Objects/6 Decor/2.png new file mode 100644 index 0000000..c64dd1b Binary files /dev/null and b/assets/2 Objects/6 Decor/2.png differ diff --git a/assets/2 Objects/6 Decor/3.png b/assets/2 Objects/6 Decor/3.png new file mode 100644 index 0000000..16254f1 Binary files /dev/null and b/assets/2 Objects/6 Decor/3.png differ diff --git a/assets/2 Objects/6 Decor/4.png b/assets/2 Objects/6 Decor/4.png new file mode 100644 index 0000000..2f16672 Binary files /dev/null and b/assets/2 Objects/6 Decor/4.png differ diff --git a/assets/2 Objects/6 Decor/5.png b/assets/2 Objects/6 Decor/5.png new file mode 100644 index 0000000..a61289e Binary files /dev/null and b/assets/2 Objects/6 Decor/5.png differ diff --git a/assets/2 Objects/6 Decor/6.png b/assets/2 Objects/6 Decor/6.png new file mode 100644 index 0000000..e306c25 Binary files /dev/null and b/assets/2 Objects/6 Decor/6.png differ diff --git a/assets/2 Objects/6 Decor/7.png b/assets/2 Objects/6 Decor/7.png new file mode 100644 index 0000000..e10470e Binary files /dev/null and b/assets/2 Objects/6 Decor/7.png differ diff --git a/assets/2 Objects/6 Decor/8.png b/assets/2 Objects/6 Decor/8.png new file mode 100644 index 0000000..a01839a Binary files /dev/null and b/assets/2 Objects/6 Decor/8.png differ diff --git a/assets/2 Objects/6 Decor/9.png b/assets/2 Objects/6 Decor/9.png new file mode 100644 index 0000000..eeb817f Binary files /dev/null and b/assets/2 Objects/6 Decor/9.png differ diff --git a/assets/2 Objects/6 Decor/Dirt1.png b/assets/2 Objects/6 Decor/Dirt1.png new file mode 100644 index 0000000..a42b7d4 Binary files /dev/null and b/assets/2 Objects/6 Decor/Dirt1.png differ diff --git a/assets/2 Objects/6 Decor/Dirt2.png b/assets/2 Objects/6 Decor/Dirt2.png new file mode 100644 index 0000000..495de51 Binary files /dev/null and b/assets/2 Objects/6 Decor/Dirt2.png differ diff --git a/assets/2 Objects/6 Decor/Dirt3.png b/assets/2 Objects/6 Decor/Dirt3.png new file mode 100644 index 0000000..7e99b2b Binary files /dev/null and b/assets/2 Objects/6 Decor/Dirt3.png differ diff --git a/assets/2 Objects/6 Decor/Dirt4.png b/assets/2 Objects/6 Decor/Dirt4.png new file mode 100644 index 0000000..6190956 Binary files /dev/null and b/assets/2 Objects/6 Decor/Dirt4.png differ diff --git a/assets/2 Objects/6 Decor/Dirt5.png b/assets/2 Objects/6 Decor/Dirt5.png new file mode 100644 index 0000000..ff79fa5 Binary files /dev/null and b/assets/2 Objects/6 Decor/Dirt5.png differ diff --git a/assets/2 Objects/6 Decor/Dirt6.png b/assets/2 Objects/6 Decor/Dirt6.png new file mode 100644 index 0000000..5447730 Binary files /dev/null and b/assets/2 Objects/6 Decor/Dirt6.png differ diff --git a/assets/2 Objects/7 Water/1.png b/assets/2 Objects/7 Water/1.png new file mode 100644 index 0000000..207b765 Binary files /dev/null and b/assets/2 Objects/7 Water/1.png differ diff --git a/assets/2 Objects/7 Water/2.png b/assets/2 Objects/7 Water/2.png new file mode 100644 index 0000000..8ffb9d5 Binary files /dev/null and b/assets/2 Objects/7 Water/2.png differ diff --git a/assets/2 Objects/7 Water/3.png b/assets/2 Objects/7 Water/3.png new file mode 100644 index 0000000..d08556c Binary files /dev/null and b/assets/2 Objects/7 Water/3.png differ diff --git a/assets/2 Objects/7 Water/4.png b/assets/2 Objects/7 Water/4.png new file mode 100644 index 0000000..5e75424 Binary files /dev/null and b/assets/2 Objects/7 Water/4.png differ diff --git a/assets/2 Objects/7 Water/5.png b/assets/2 Objects/7 Water/5.png new file mode 100644 index 0000000..ef918e6 Binary files /dev/null and b/assets/2 Objects/7 Water/5.png differ diff --git a/assets/2 Objects/7 Water/6.png b/assets/2 Objects/7 Water/6.png new file mode 100644 index 0000000..3175d7f Binary files /dev/null and b/assets/2 Objects/7 Water/6.png differ diff --git a/assets/2 Objects/PlaceForTower1.png b/assets/2 Objects/PlaceForTower1.png new file mode 100644 index 0000000..aa7b221 Binary files /dev/null and b/assets/2 Objects/PlaceForTower1.png differ diff --git a/assets/2 Objects/PlaceForTower2.png b/assets/2 Objects/PlaceForTower2.png new file mode 100644 index 0000000..1fea2d1 Binary files /dev/null and b/assets/2 Objects/PlaceForTower2.png differ diff --git a/assets/3 Animated Objects/1 Flag/1.png b/assets/3 Animated Objects/1 Flag/1.png new file mode 100644 index 0000000..f601238 Binary files /dev/null and b/assets/3 Animated Objects/1 Flag/1.png differ diff --git a/assets/3 Animated Objects/1 Flag/2.png b/assets/3 Animated Objects/1 Flag/2.png new file mode 100644 index 0000000..10d5b0e Binary files /dev/null and b/assets/3 Animated Objects/1 Flag/2.png differ diff --git a/assets/3 Animated Objects/1 Flag/3.png b/assets/3 Animated Objects/1 Flag/3.png new file mode 100644 index 0000000..88220d1 Binary files /dev/null and b/assets/3 Animated Objects/1 Flag/3.png differ diff --git a/assets/3 Animated Objects/2 Torch/1.png b/assets/3 Animated Objects/2 Torch/1.png new file mode 100644 index 0000000..e47e91b Binary files /dev/null and b/assets/3 Animated Objects/2 Torch/1.png differ diff --git a/assets/3 Animated Objects/2 Torch/2.png b/assets/3 Animated Objects/2 Torch/2.png new file mode 100644 index 0000000..ad5aada Binary files /dev/null and b/assets/3 Animated Objects/2 Torch/2.png differ diff --git a/assets/3 Animated Objects/2 Torch/3.png b/assets/3 Animated Objects/2 Torch/3.png new file mode 100644 index 0000000..b179bb7 Binary files /dev/null and b/assets/3 Animated Objects/2 Torch/3.png differ diff --git a/assets/3 Animated Objects/2 Torch/4.png b/assets/3 Animated Objects/2 Torch/4.png new file mode 100644 index 0000000..b9d8b99 Binary files /dev/null and b/assets/3 Animated Objects/2 Torch/4.png differ diff --git a/game.js b/game.js new file mode 100644 index 0000000..1744e18 --- /dev/null +++ b/game.js @@ -0,0 +1,1607 @@ +// ===== PIXEL-ÜBERLEBENDER - 2D SURVIVAL GAME ===== +// Eine Schritt-für-Schritt Einführung in die 2D-Spieleentwicklung + +// 1. CANVAS-GRUNDLAGEN +// Das Canvas-Element ist wie eine digitale Leinwand, auf die wir mit JavaScript zeichnen können. +// Es bietet uns eine 2D-Zeichenfläche mit Pixeln, die wir einzeln kontrollieren können. + +// Canvas-Element aus dem HTML-Dokument holen +const canvas = document.getElementById('gameCanvas'); + +// Den 2D-Rendering-Kontext erhalten +// Der Kontext (ctx) ist unser "Pinsel" - damit zeichnen wir auf das Canvas +const ctx = canvas.getContext('2d'); + +// Canvas-Dimensionen für spätere Berechnungen speichern +const CANVAS_WIDTH = canvas.width; // 800 Pixel +const CANVAS_HEIGHT = canvas.height; // 600 Pixel + +// ===== ASSET-SYSTEM ===== + +/** + * Asset Manager für das Laden und Verwalten von Sprites + */ +class AssetManager { + constructor() { + this.images = new Map(); + this.loadedCount = 0; + this.totalCount = 0; + this.isLoaded = false; + } + + /** + * Lädt ein Bild und speichert es im Asset Manager + * @param {string} key - Eindeutiger Schlüssel für das Bild + * @param {string} path - Pfad zur Bilddatei + */ + loadImage(key, path) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + this.images.set(key, img); + this.loadedCount++; + console.log(`✅ Bild geladen: ${key} (${this.loadedCount}/${this.totalCount})`); + resolve(img); + }; + img.onerror = () => { + console.error(`❌ Fehler beim Laden: ${key} von ${path}`); + reject(new Error(`Failed to load ${path}`)); + }; + img.src = path; + }); + } + + /** + * Lädt alle benötigten Assets + */ + async loadAllAssets() { + const assetsToLoad = [ + // Boden-Tiles + { key: 'grass', path: 'assets/1 Tiles/gras.png' }, + { key: 'path', path: 'assets/1 Tiles/weg.png' }, + { key: 'swamp1', path: 'assets/1 Tiles/SwampTile_01.png' }, + { key: 'swamp2', path: 'assets/1 Tiles/SwampTile_02.png' }, + { key: 'swamp3', path: 'assets/1 Tiles/SwampTile_03.png' }, + + // Bäume + { key: 'tree1', path: 'assets/2 Objects/2 Tree/1.png' }, + { key: 'tree2', path: 'assets/2 Objects/2 Tree/2.png' }, + { key: 'tree3', path: 'assets/2 Objects/2 Tree/3.png' }, + { key: 'tree4', path: 'assets/2 Objects/2 Tree/4.png' }, + + // Steine + { key: 'stone1', path: 'assets/2 Objects/4 Stone/1.png' }, + { key: 'stone2', path: 'assets/2 Objects/4 Stone/2.png' }, + { key: 'stone3', path: 'assets/2 Objects/4 Stone/3.png' }, + + // Gras/Stöcker + { key: 'stick1', path: 'assets/2 Objects/3 Grass/1.png' }, + { key: 'stick2', path: 'assets/2 Objects/3 Grass/2.png' }, + { key: 'stick3', path: 'assets/2 Objects/3 Grass/3.png' }, + + // Schatten + { key: 'shadow1', path: 'assets/2 Objects/1 Shadow/1.png' }, + { key: 'shadow2', path: 'assets/2 Objects/1 Shadow/2.png' }, + + // Spieler-Sprite + { key: 'pointer1', path: 'assets/2 Objects/5 Pointer/1.png' }, + + // Animierte Objekte (Lagerfeuer) + { key: 'torch1', path: 'assets/3 Animated Objects/2 Torch/1.png' }, + { key: 'torch2', path: 'assets/3 Animated Objects/2 Torch/2.png' }, + { key: 'torch3', path: 'assets/3 Animated Objects/2 Torch/3.png' }, + { key: 'torch4', path: 'assets/3 Animated Objects/2 Torch/4.png' } + ]; + + this.totalCount = assetsToLoad.length; + console.log(`🎨 Lade ${this.totalCount} Assets...`); + + try { + await Promise.all(assetsToLoad.map(asset => + this.loadImage(asset.key, asset.path) + )); + + this.isLoaded = true; + console.log('🎉 Alle Assets erfolgreich geladen!'); + return true; + } catch (error) { + console.error('❌ Fehler beim Laden der Assets:', error); + return false; + } + } + + /** + * Gibt ein geladenes Bild zurück + * @param {string} key - Schlüssel des Bildes + * @returns {Image|null} Das Bild oder null falls nicht gefunden + */ + getImage(key) { + return this.images.get(key) || null; + } + + /** + * Zeichnet ein Sprite auf das Canvas + * @param {CanvasRenderingContext2D} ctx - Canvas-Kontext + * @param {string} key - Sprite-Schlüssel + * @param {number} x - X-Position + * @param {number} y - Y-Position + * @param {number} width - Breite (optional) + * @param {number} height - Höhe (optional) + */ + drawSprite(ctx, key, x, y, width = null, height = null) { + const img = this.getImage(key); + if (!img) { + // Fallback: Zeichne ein farbiges Rechteck + ctx.fillStyle = '#FF0000'; + ctx.fillRect(x, y, width || 32, height || 32); + return; + } + + if (width && height) { + ctx.drawImage(img, x, y, width, height); + } else { + ctx.drawImage(img, x, y); + } + } +} + +// Globaler Asset Manager +let assetManager = new AssetManager(); + +// ===== WELT-SYSTEM ===== + +// Welt-Konfiguration +const WORLD_CONFIG = { + CHUNK_SIZE: 400, // Größe eines Chunks in Pixeln + RENDER_DISTANCE: 2, // Wie viele Chunks um den Spieler geladen werden + WORLD_SEED: 12345, // Seed für prozedurale Generierung + RESOURCE_DENSITY: { + stick: 0.8, // Stöcker pro 100x100 Pixel Bereich + tree: 0.4, // Bäume pro 100x100 Pixel Bereich + rock: 0.3 // Felsen pro 100x100 Pixel Bereich + } +}; + +/** + * Kamera-System für die scrollende Welt + */ +class Camera { + constructor() { + this.x = 0; // Kamera-Position in Weltkoordinaten + this.y = 0; + this.targetX = 0; // Ziel-Position (für smooth following) + this.targetY = 0; + this.smoothing = 0.1; // Wie sanft die Kamera folgt (0-1) + } + + /** + * Aktualisiert die Kamera-Position um dem Spieler zu folgen + * @param {Player} player - Der Spieler + */ + update(player) { + // Kamera soll auf den Spieler zentriert sein + this.targetX = player.x - CANVAS_WIDTH / 2; + this.targetY = player.y - CANVAS_HEIGHT / 2; + + // Sanfte Kamera-Bewegung (Lerp) + this.x += (this.targetX - this.x) * this.smoothing; + this.y += (this.targetY - this.y) * this.smoothing; + } + + /** + * Konvertiert Weltkoordinaten zu Bildschirmkoordinaten + * @param {number} worldX - X in Weltkoordinaten + * @param {number} worldY - Y in Weltkoordinaten + * @returns {Object} - {x, y} in Bildschirmkoordinaten + */ + worldToScreen(worldX, worldY) { + return { + x: worldX - this.x, + y: worldY - this.y + }; + } + + /** + * Konvertiert Bildschirmkoordinaten zu Weltkoordinaten + * @param {number} screenX - X in Bildschirmkoordinaten + * @param {number} screenY - Y in Bildschirmkoordinaten + * @returns {Object} - {x, y} in Weltkoordinaten + */ + screenToWorld(screenX, screenY) { + return { + x: screenX + this.x, + y: screenY + this.y + }; + } +} + +/** + * Chunk-System für Performance und prozedurale Generierung + */ +class Chunk { + constructor(chunkX, chunkY) { + this.chunkX = chunkX; // Chunk-Koordinaten + this.chunkY = chunkY; + this.worldX = chunkX * WORLD_CONFIG.CHUNK_SIZE; // Welt-Position + this.worldY = chunkY * WORLD_CONFIG.CHUNK_SIZE; + this.resources = []; // Ressourcen in diesem Chunk + this.tiles = []; // Boden-Tiles für diesen Chunk + this.generated = false; // Ob der Chunk bereits generiert wurde + + // Perlin Noise Permutation Table initialisieren + this.initPerlinNoise(); + } + + /** + * Generiert Ressourcen für diesen Chunk prozedural + */ + generate() { + if (this.generated) return; + + this.resources = []; + this.tiles = []; + + // Pseudo-Zufallsgenerator basierend auf Chunk-Position und Seed + const random = this.seededRandom(this.chunkX, this.chunkY, WORLD_CONFIG.WORLD_SEED); + + // Berechne wie viele Ressourcen basierend auf Chunk-Größe + const area = (WORLD_CONFIG.CHUNK_SIZE / 100) ** 2; // Normalisiert auf 100x100 Bereiche + const tileSize = 32; // Größe eines Tiles in Pixeln + + // Boden-Tiles generieren mit Perlin Noise + for (let x = 0; x < WORLD_CONFIG.CHUNK_SIZE; x += tileSize) { + for (let y = 0; y < WORLD_CONFIG.CHUNK_SIZE; y += tileSize) { + const worldX = this.worldX + x; + const worldY = this.worldY + y; + + // Perlin Noise für natürlichere Boden-Verteilung + const noiseScale = 0.05; // Skalierung des Noise + const noiseValue = this.noise(worldX * noiseScale, worldY * noiseScale); + + // Normalisiere Noise-Wert von [-1,1] zu [0,1] + const normalizedNoise = (noiseValue + 1) / 2; + + let tileType; + if (normalizedNoise < 0.3) { + // Sumpfgebiete in niedrigen Bereichen + const swampNoise = this.noise(worldX * 0.1, worldY * 0.1); + const swampVariant = Math.floor(((swampNoise + 1) / 2) * 64) + 1; + tileType = `SwampTile_${swampVariant.toString().padStart(2, '0')}`; + } else if (normalizedNoise < 0.6) { + // Übergangsbereich - gemischte Tiles + const mixNoise = this.noise(worldX * 0.08, worldY * 0.08); + if (mixNoise > 0) { + tileType = 'grass'; + } else { + const swampVariant = Math.floor(((mixNoise + 1) / 2) * 20) + 1; + tileType = `SwampTile_${swampVariant.toString().padStart(2, '0')}`; + } + } else { + // Höhere Bereiche - hauptsächlich Gras + tileType = 'grass'; + } + + this.tiles.push({ + x: worldX, + y: worldY, + type: tileType, + size: tileSize + }); + } + } + + // Ressourcen mit Perlin Noise-basierten Clustern generieren + const resourceDensity = 8; // Anzahl Versuche pro 64x64 Bereich + + for (let attempts = 0; attempts < resourceDensity * area; attempts++) { + const x = this.worldX + random() * WORLD_CONFIG.CHUNK_SIZE; + const y = this.worldY + random() * WORLD_CONFIG.CHUNK_SIZE; + + // Verschiedene Noise-Frequenzen für verschiedene Ressourcen + const stickNoise = this.noise(x * 0.02, y * 0.02); + const treeNoise = this.noise(x * 0.015, y * 0.015 + 100); // Offset für Variation + const rockNoise = this.noise(x * 0.01, y * 0.01 + 200); + + // Normalisiere zu [0,1] + const stickDensity = (stickNoise + 1) / 2; + const treeDensity = (treeNoise + 1) / 2; + const rockDensity = (rockNoise + 1) / 2; + + // Stöcker in Bereichen mit hoher Stick-Dichte + if (stickDensity > 0.6 && random() < WORLD_CONFIG.RESOURCE_DENSITY.stick * stickDensity) { + const stickVariant = Math.floor(random() * 3) + 1; + this.resources.push(new Resource(x, y, 'stick', `stick${stickVariant}`)); + } + + // Bäume in Clustern + if (treeDensity > 0.5 && random() < WORLD_CONFIG.RESOURCE_DENSITY.tree * treeDensity) { + const treeVariant = Math.floor(random() * 4) + 1; + this.resources.push(new Resource(x, y, 'tree', `tree${treeVariant}`)); + } + + // Felsen in spezifischen Gebieten + if (rockDensity > 0.7 && random() < WORLD_CONFIG.RESOURCE_DENSITY.rock * rockDensity) { + const stoneVariant = Math.floor(random() * 3) + 1; + this.resources.push(new Resource(x, y, 'rock', `stone${stoneVariant}`)); + } + } + + this.generated = true; + console.log(`🗺️ Chunk (${this.chunkX}, ${this.chunkY}) generiert: ${this.resources.length} Ressourcen`); + } + + /** + * Seeded Random Number Generator für konsistente prozedurale Generierung + */ + seededRandom(x, y, seed) { + let counter = 0; + const hash = (x * 374761393 + y * 668265263 + seed) % 2147483647; + + return function() { + counter++; + const x = Math.sin(hash + counter) * 43758.5453; + return x - Math.floor(x); + }; + } + + /** + * Initialisiert Perlin Noise Permutation Table + */ + initPerlinNoise() { + this.p = new Array(512); + const permutation = []; + + // Generiere Permutation basierend auf Chunk-Position und Seed + for (let i = 0; i < 256; i++) { + permutation[i] = i; + } + + // Shuffle basierend auf Seed und Chunk-Position + for (let i = 255; i > 0; i--) { + const seed = this.chunkX * 73856093 + this.chunkY * 19349663 + WORLD_CONFIG.WORLD_SEED; + const hash = Math.sin(seed + i) * 43758.5453; + const j = Math.floor((hash - Math.floor(hash)) * (i + 1)); + [permutation[i], permutation[j]] = [permutation[j], permutation[i]]; + } + + for (let i = 0; i < 512; i++) { + this.p[i] = permutation[i & 255]; + } + } + + /** + * Perlin Noise Implementation für natürlichere Generierung + */ + noise(x, y) { + const X = Math.floor(x) & 255; + const Y = Math.floor(y) & 255; + + x -= Math.floor(x); + y -= Math.floor(y); + + const u = this.fade(x); + const v = this.fade(y); + + const A = this.p[X] + Y; + const B = this.p[X + 1] + Y; + + return this.lerp(v, + this.lerp(u, this.grad(this.p[A], x, y), this.grad(this.p[B], x - 1, y)), + this.lerp(u, this.grad(this.p[A + 1], x, y - 1), this.grad(this.p[B + 1], x - 1, y - 1)) + ); + } + + fade(t) { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + lerp(t, a, b) { + return a + t * (b - a); + } + + grad(hash, x, y) { + const h = hash & 15; + const u = h < 8 ? x : y; + const v = h < 4 ? y : h === 12 || h === 14 ? x : 0; + return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v); + } + + /** + * Prüft ob dieser Chunk im sichtbaren Bereich ist + * @param {Camera} camera - Die Kamera + * @returns {boolean} - True wenn sichtbar + */ + isVisible(camera) { + const screenPos = camera.worldToScreen(this.worldX, this.worldY); + return screenPos.x < CANVAS_WIDTH + WORLD_CONFIG.CHUNK_SIZE && + screenPos.x + WORLD_CONFIG.CHUNK_SIZE > 0 && + screenPos.y < CANVAS_HEIGHT + WORLD_CONFIG.CHUNK_SIZE && + screenPos.y + WORLD_CONFIG.CHUNK_SIZE > 0; + } + + /** + * Rendert alle Boden-Tiles dieses Chunks + * @param {CanvasRenderingContext2D} ctx - Canvas-Kontext + * @param {Camera} camera - Kamera für Koordinaten-Transformation + */ + renderTiles(ctx, camera) { + if (!assetManager.isLoaded) return; + + this.tiles.forEach(tile => { + const screenPos = camera.worldToScreen(tile.x, tile.y); + + // Nur zeichnen wenn im sichtbaren Bereich + if (screenPos.x + tile.size < 0 || screenPos.x > CANVAS_WIDTH || + screenPos.y + tile.size < 0 || screenPos.y > CANVAS_HEIGHT) { + return; + } + + assetManager.drawSprite(ctx, tile.type, screenPos.x, screenPos.y, tile.size, tile.size); + }); + } +} + +/** + * Welt-Manager für Chunk-Loading und -Unloading + */ +class World { + constructor() { + this.chunks = new Map(); // Geladene Chunks (key: "x,y") + this.camera = new Camera(); + } + + /** + * Aktualisiert die Welt (lädt/entlädt Chunks basierend auf Spielerposition) + * @param {Player} player - Der Spieler + */ + update(player) { + // Kamera aktualisieren + this.camera.update(player); + + // Berechne welche Chunks geladen werden müssen + const playerChunkX = Math.floor(player.x / WORLD_CONFIG.CHUNK_SIZE); + const playerChunkY = Math.floor(player.y / WORLD_CONFIG.CHUNK_SIZE); + + const neededChunks = new Set(); + + // Chunks um den Spieler laden + for (let dx = -WORLD_CONFIG.RENDER_DISTANCE; dx <= WORLD_CONFIG.RENDER_DISTANCE; dx++) { + for (let dy = -WORLD_CONFIG.RENDER_DISTANCE; dy <= WORLD_CONFIG.RENDER_DISTANCE; dy++) { + const chunkX = playerChunkX + dx; + const chunkY = playerChunkY + dy; + const key = `${chunkX},${chunkY}`; + neededChunks.add(key); + + // Chunk laden falls nicht vorhanden + if (!this.chunks.has(key)) { + const chunk = new Chunk(chunkX, chunkY); + chunk.generate(); + this.chunks.set(key, chunk); + } + } + } + + // Entfernte Chunks entladen (Performance) + for (const [key, chunk] of this.chunks) { + if (!neededChunks.has(key)) { + this.chunks.delete(key); + console.log(`🗑️ Chunk (${chunk.chunkX}, ${chunk.chunkY}) entladen`); + } + } + } + + /** + * Gibt alle Ressourcen in geladenen Chunks zurück + * @returns {Array} - Array aller Ressourcen + */ + getAllResources() { + const allResources = []; + for (const chunk of this.chunks.values()) { + allResources.push(...chunk.resources); + } + return allResources; + } + + /** + * Zeichnet Chunk-Grenzen für Debugging + * @param {CanvasRenderingContext2D} ctx - Canvas-Kontext + */ + renderChunkBorders(ctx) { + ctx.strokeStyle = 'rgba(255, 255, 0, 0.3)'; + ctx.lineWidth = 1; + + for (const chunk of this.chunks.values()) { + if (chunk.isVisible(this.camera)) { + const screenPos = this.camera.worldToScreen(chunk.worldX, chunk.worldY); + ctx.strokeRect(screenPos.x, screenPos.y, WORLD_CONFIG.CHUNK_SIZE, WORLD_CONFIG.CHUNK_SIZE); + } + } + } +} + +// ===== SPIELER-KLASSE ===== + +/** + * Die Player-Klasse repräsentiert unseren Spielcharakter + * + * Warum eine Klasse? + * - Organisiert alle spielerbezogenen Daten und Funktionen + * - Macht den Code wiederverwendbar und erweiterbar + * - Trennt Spielerlogik vom Rest des Spiels (Separation of Concerns) + */ +class Player { + /** + * Konstruktor - wird beim Erstellen eines neuen Spielers aufgerufen + * @param {number} x - Startposition X + * @param {number} y - Startposition Y + */ + constructor(x, y) { + // Position des Spielers (Mittelpunkt) + this.x = x; + this.y = y; + + // Größe des Spielers (Quadrat) + this.width = 20; + this.height = 20; + + // Bewegungsgeschwindigkeit (Pixel pro Frame) + this.speed = 3; + + // Farbe des Spielers + this.color = '#FF6B6B'; // Rot + + // Gesundheit (Survival-Aspekt) + this.health = 100; + this.maxHealth = 100; + + // Gesundheits-Timer (für langsame Abnahme) + this.healthTimer = 0; + this.healthDecayRate = 300; // Alle 5 Sekunden (300 Frames bei 60 FPS) + + // Inventar + this.inventory = { + stick: 0, // Stöcker (für Werkzeuge) + wood: 0, // Holz (von Bäumen mit Axt) + stone: 0 // Stein (von Felsen mit Pickaxe) + }; + + // Werkzeug-System + this.tools = { + axe: false, // Kann Bäume abbauen + pickaxe: false // Kann Steine abbauen + }; + } + + /** + * Update-Methode - wird jeden Frame aufgerufen + * Hier wird die Spielerbewegung basierend auf Eingaben berechnet + */ + update() { + // Bewegungsvektor berechnen (Richtung der Bewegung) + let moveX = 0; + let moveY = 0; + + // Eingaben in Bewegungsvektor umwandeln + if (keys.left || keys.a) { + moveX -= 1; + } + if (keys.right || keys.d) { + moveX += 1; + } + if (keys.up || keys.w) { + moveY -= 1; + } + if (keys.down || keys.s) { + moveY += 1; + } + + // Vektornormalisierung für gleichmäßige Geschwindigkeit + // Problem: Bei diagonaler Bewegung ist die Geschwindigkeit √2 ≈ 1.41x schneller + // Lösung: Vektor normalisieren (Länge = 1) und dann mit Geschwindigkeit multiplizieren + if (moveX !== 0 || moveY !== 0) { + // Länge des Vektors berechnen (Pythagoras: √(x² + y²)) + const length = Math.sqrt(moveX * moveX + moveY * moveY); + + // Vektor normalisieren (durch Länge teilen) + moveX = moveX / length; + moveY = moveY / length; + + // Mit gewünschter Geschwindigkeit multiplizieren + this.x += moveX * this.speed; + this.y += moveY * this.speed; + } + + // In der unendlichen Welt gibt es keine Grenzen! + // Der Spieler kann sich frei bewegen, die Kamera folgt ihm + + // Gesundheits-System aktualisieren + this.updateHealth(); + } + + /** + * Aktualisiert das Gesundheitssystem + * Gesundheit nimmt langsam ab, regeneriert sich am Lagerfeuer + */ + updateHealth() { + this.healthTimer++; + + // Prüfen ob Spieler in der Nähe eines Lagerfeuers ist + let nearCampfire = false; + campfires.forEach(campfire => { + const distance = Math.sqrt( + (this.x - campfire.x) ** 2 + (this.y - campfire.y) ** 2 + ); + if (distance < 50) { // 50 Pixel Radius + nearCampfire = true; + } + }); + + if (nearCampfire) { + // Gesundheit regenerieren am Lagerfeuer + if (this.healthTimer >= 60) { // Jede Sekunde + this.health = Math.min(this.maxHealth, this.health + 2); + this.healthTimer = 0; + } + } else { + // Gesundheit nimmt langsam ab + if (this.healthTimer >= this.healthDecayRate) { + this.health = Math.max(0, this.health - 5); + this.healthTimer = 0; + + if (this.health <= 0) { + console.log('💀 Game Over! Deine Gesundheit ist auf 0 gefallen.'); + // Hier könnte später ein Game Over Screen kommen + } + } + } + } + + /** + * Render-Methode - zeichnet den Spieler auf das Canvas + * @param {CanvasRenderingContext2D} ctx - Der Canvas-Kontext + * @param {Camera} camera - Die Kamera für Koordinaten-Transformation + */ + render(ctx, camera) { + // Weltkoordinaten zu Bildschirmkoordinaten konvertieren + const screenPos = camera.worldToScreen(this.x, this.y); + + // Spieler zeichnen + if (assetManager.isLoaded) { + // Verwende ein Pointer-Sprite für den Spieler + assetManager.drawSprite( + ctx, + 'pointer1', // Verwende das erste Pointer-Sprite + screenPos.x - this.width / 2, + screenPos.y - this.height / 2, + this.width, + this.height + ); + } else { + // Fallback: Einfaches Rechteck + ctx.fillStyle = this.color; + ctx.fillRect( + screenPos.x - this.width/2, // X-Position (links) + screenPos.y - this.height/2, // Y-Position (oben) + this.width, // Breite + this.height // Höhe + ); + + // Schwarzer Rand für bessere Sichtbarkeit + ctx.strokeStyle = '#000000'; + ctx.lineWidth = 2; + ctx.strokeRect( + screenPos.x - this.width/2, + screenPos.y - this.height/2, + this.width, + this.height + ); + } + } +} + +// ===== EINGABE-SYSTEM ===== + +/** + * Das keys-Objekt speichert den Zustand aller Tasten + * + * Warum so? + * - Ermöglicht gleichzeitiges Drücken mehrerer Tasten (z.B. diagonal laufen) + * - Saubere Trennung zwischen Eingabe-Erkennung und Spiellogik + * - Einfach erweiterbar für neue Tasten + */ +const keys = { + left: false, + right: false, + up: false, + down: false, + w: false, + a: false, + s: false, + d: false, + e: false, // Lagerfeuer + q: false, // Axt + r: false, // Pickaxe + tab: false // Inventar toggle +}; + +// UI-Zustand +let showInventory = false; // Inventar standardmäßig ausgeblendet + +/** + * Event-Listener für Tastendruck (keydown) + * Wird aufgerufen, wenn eine Taste gedrückt wird + */ +window.addEventListener('keydown', (event) => { + // event.code gibt uns den physischen Tastencode + switch(event.code) { + case 'ArrowLeft': + keys.left = true; + break; + case 'ArrowRight': + keys.right = true; + break; + case 'ArrowUp': + keys.up = true; + break; + case 'ArrowDown': + keys.down = true; + break; + case 'KeyW': + keys.w = true; + break; + case 'KeyA': + keys.a = true; + break; + case 'KeyS': + keys.s = true; + break; + case 'KeyD': + keys.d = true; + break; + case 'KeyE': + keys.e = true; + break; + case 'KeyQ': + keys.q = true; + break; + case 'KeyR': + keys.r = true; + break; + case 'Tab': + keys.tab = true; + break; + } + + // Verhindert Standard-Browser-Verhalten (z.B. Scrollen mit Pfeiltasten) + event.preventDefault(); +}); + +/** + * Event-Listener für Taste loslassen (keyup) + * Wird aufgerufen, wenn eine Taste losgelassen wird + */ +window.addEventListener('keyup', (event) => { + switch(event.code) { + case 'ArrowLeft': + keys.left = false; + break; + case 'ArrowRight': + keys.right = false; + break; + case 'ArrowUp': + keys.up = false; + break; + case 'ArrowDown': + keys.down = false; + break; + case 'KeyW': + keys.w = false; + break; + case 'KeyA': + keys.a = false; + break; + case 'KeyS': + keys.s = false; + break; + case 'KeyD': + keys.d = false; + break; + case 'KeyE': + keys.e = false; + break; + case 'KeyQ': + keys.q = false; + break; + case 'KeyR': + keys.r = false; + break; + case 'Tab': + keys.tab = false; + break; + } +}); + +// ===== RESSOURCEN-KLASSE ===== + +/** + * Die Resource-Klasse repräsentiert sammelbare Objekte in der Spielwelt + * + * Verschiedene Ressourcentypen: + * - 'tree': Bäume für Holz + * - 'rock': Felsen für Stein + */ +class Resource { + /** + * Konstruktor für eine neue Ressource + * @param {number} x - X-Position + * @param {number} y - Y-Position + * @param {string} type - Typ der Ressource ('tree', 'rock', 'stick') + * @param {string} spriteKey - Schlüssel für das Sprite im AssetManager + */ + constructor(x, y, type, spriteKey = null) { + this.x = x; + this.y = y; + this.type = type; + this.spriteKey = spriteKey; + + // Eigenschaften basierend auf Typ setzen + if (type === 'tree') { + this.width = 64; + this.height = 64; + this.color = '#228B22'; // Waldgrün (Fallback) + this.resource = 'wood'; // Was der Spieler erhält + this.amount = 3; // Wie viel er erhält + this.spriteKey = spriteKey || 'tree1'; + } else if (type === 'rock') { + this.width = 48; + this.height = 48; + this.color = '#696969'; // Dunkelgrau (Fallback) + this.resource = 'stone'; // Was der Spieler erhält + this.amount = 2; // Wie viel er erhält + this.spriteKey = spriteKey || 'stone1'; + } else if (type === 'stick') { + this.width = 32; + this.height = 32; + this.color = '#8B4513'; // Braun (Fallback) + this.resource = 'stick'; // Was der Spieler erhält + this.amount = 1; // Wie viel er erhält + this.spriteKey = spriteKey || 'stick1'; + } + + // Ob die Ressource noch sammelbar ist + this.collected = false; + } + + /** + * Prüft, ob der Spieler diese Ressource berührt (Kollisionserkennung) + * @param {Player} player - Der Spieler + * @returns {boolean} - True wenn Kollision + */ + checkCollision(player) { + // AABB (Axis-Aligned Bounding Box) Kollisionserkennung + // Zwei Rechtecke überlappen sich, wenn sie sich in beiden Achsen überlappen + + // Grenzen der Ressource + const resLeft = this.x - this.width / 2; + const resRight = this.x + this.width / 2; + const resTop = this.y - this.height / 2; + const resBottom = this.y + this.height / 2; + + // Grenzen des Spielers + const playerLeft = player.x - player.width / 2; + const playerRight = player.x + player.width / 2; + const playerTop = player.y - player.height / 2; + const playerBottom = player.y + player.height / 2; + + // Kollision prüfen + return resLeft < playerRight && + resRight > playerLeft && + resTop < playerBottom && + resBottom > playerTop; + } + + /** + * Sammelt die Ressource und gibt sie an das Inventar weiter + * @param {Object} inventory - Das Spieler-Inventar + * @param {Player} player - Der Spieler (für Werkzeug-Überprüfung) + */ + collect(player) { + if (!this.collected) { + // Prüfen ob das richtige Werkzeug vorhanden ist + if (this.type === 'tree' && !player.tools.axe) { + console.log('🪓 Du brauchst eine Axt um Bäume zu fällen!'); + return false; + } + if (this.type === 'rock' && !player.tools.pickaxe) { + console.log('⛏️ Du brauchst eine Spitzhacke um Steine abzubauen!'); + return false; + } + + this.collected = true; + + // Ressource zum Spieler-Inventar hinzufügen + if (!player.inventory[this.resource]) { + player.inventory[this.resource] = 0; + } + player.inventory[this.resource] += this.amount; + + console.log(`📦 ${this.amount} ${this.resource} gesammelt! Gesamt: ${player.inventory[this.resource]}`); + return true; + } + return false; + } + + /** + * Zeichnet die Ressource auf das Canvas + * @param {CanvasRenderingContext2D} ctx - Der Canvas-Kontext + * @param {Camera} camera - Die Kamera für Koordinaten-Transformation + */ + render(ctx, camera) { + if (!this.collected) { + // Weltkoordinaten zu Bildschirmkoordinaten konvertieren + const screenPos = camera.worldToScreen(this.x, this.y); + + // Nur zeichnen wenn im sichtbaren Bereich + if (screenPos.x + this.width/2 < 0 || screenPos.x - this.width/2 > CANVAS_WIDTH || + screenPos.y + this.height/2 < 0 || screenPos.y - this.height/2 > CANVAS_HEIGHT) { + return; // Außerhalb des Bildschirms + } + + // Schatten zeichnen (für 3D-Effekt) + if (assetManager.isLoaded && (this.type === 'tree' || this.type === 'rock')) { + const shadowKey = this.type === 'tree' ? 'shadow2' : 'shadow1'; + assetManager.drawSprite( + ctx, + shadowKey, + screenPos.x - this.width/2 + 5, + screenPos.y - this.height/2 + this.height - 10, + this.width, + 16 + ); + } + + // Sprite zeichnen falls Assets geladen sind + if (assetManager.isLoaded && this.spriteKey) { + assetManager.drawSprite( + ctx, + this.spriteKey, + screenPos.x - this.width/2, + screenPos.y - this.height/2, + this.width, + this.height + ); + } else { + // Fallback: Einfache Rechtecke + ctx.fillStyle = this.color; + ctx.fillRect( + screenPos.x - this.width / 2, + screenPos.y - this.height / 2, + this.width, + this.height + ); + + // Schwarzer Rand + ctx.strokeStyle = '#000000'; + ctx.lineWidth = 1; + ctx.strokeRect( + screenPos.x - this.width / 2, + screenPos.y - this.height / 2, + this.width, + this.height + ); + + // Spezielle Darstellung für Bäume (Krone) + if (this.type === 'tree') { + // Baumkrone (Kreis oben) + ctx.fillStyle = '#006400'; // Dunkelgrün + ctx.beginPath(); + ctx.arc(screenPos.x, screenPos.y - this.height/3, this.width/2, 0, Math.PI * 2); + ctx.fill(); + ctx.stroke(); + } + } + } + } +} + +// ===== LAGERFEUER-KLASSE ===== + +/** + * Die Campfire-Klasse repräsentiert ein gebautes Lagerfeuer + * + * Funktionen: + * - Regeneriert Spieler-Gesundheit in der Nähe + * - Kann mit Holz gebaut werden + * - Visueller Indikator für sichere Zonen + */ +class Campfire { + /** + * Konstruktor für ein neues Lagerfeuer + * @param {number} x - X-Position + * @param {number} y - Y-Position + */ + constructor(x, y) { + this.x = x; + this.y = y; + this.width = 48; + this.height = 48; + + // Animation für Torch-Sprites + this.animationTimer = 0; + this.animationSpeed = 0.15; // Geschwindigkeit der Animation + this.currentFrame = 0; + this.totalFrames = 4; // torch1, torch2, torch3, torch4 + } + + /** + * Aktualisiert die Lagerfeuer-Animation + */ + update() { + // Torch-Sprite Animation + this.animationTimer += this.animationSpeed; + + if (this.animationTimer >= 1) { + this.currentFrame = (this.currentFrame + 1) % this.totalFrames; + this.animationTimer = 0; + } + } + + /** + * Zeichnet das Lagerfeuer + * @param {CanvasRenderingContext2D} ctx - Der Canvas-Kontext + * @param {Camera} camera - Die Kamera für Koordinaten-Transformation + */ + render(ctx, camera) { + // Weltkoordinaten zu Bildschirmkoordinaten konvertieren + const screenPos = camera.worldToScreen(this.x, this.y); + + // Nur zeichnen wenn im sichtbaren Bereich + if (screenPos.x + 50 < 0 || screenPos.x - 50 > CANVAS_WIDTH || + screenPos.y + 50 < 0 || screenPos.y - 50 > CANVAS_HEIGHT) { + return; // Außerhalb des Bildschirms + } + + // Heilungsradius anzeigen (halbtransparent) + ctx.fillStyle = 'rgba(0, 255, 0, 0.1)'; + ctx.beginPath(); + ctx.arc(screenPos.x, screenPos.y, 50, 0, Math.PI * 2); + ctx.fill(); + + // Rand um Heilungsradius + ctx.strokeStyle = 'rgba(0, 255, 0, 0.3)'; + ctx.lineWidth = 2; + ctx.stroke(); + + // Animiertes Torch-Sprite zeichnen + if (assetManager.isLoaded) { + const torchKey = `torch${this.currentFrame + 1}`; + assetManager.drawSprite( + ctx, + torchKey, + screenPos.x - this.width/2, + screenPos.y - this.height/2, + this.width, + this.height + ); + } else { + // Fallback: Einfaches Lagerfeuer + ctx.fillStyle = '#8B4513'; // Braun + ctx.fillRect( + screenPos.x - this.width/2, + screenPos.y - this.height/2, + this.width, + this.height + ); + + // Flammen (animiert) + ctx.fillStyle = '#FF4500'; // Orange-Rot + ctx.fillRect( + screenPos.x - this.width/3, + screenPos.y - this.height/2 - 15, + this.width/1.5, + 15 + ); + } + } +} + +// ===== SPIEL-OBJEKTE ===== + +// Welt-Instanz erstellen +let world; + +// Spieler-Instanz erstellen (Startposition in der Weltmitte) +let player; + +// Array für alle Lagerfeuer (bleiben global, da sie vom Spieler gebaut werden) +let campfires = []; + +// Crafting-Rezepte +const recipes = { + axe: { + name: 'Axt', + cost: { stick: 2 }, + description: 'Ermöglicht das Fällen von Bäumen', + tool: true + }, + pickaxe: { + name: 'Spitzhacke', + cost: { stick: 1, wood: 2 }, + description: 'Ermöglicht das Abbauen von Steinen', + tool: true + }, + campfire: { + name: 'Lagerfeuer', + cost: { wood: 5 }, + description: 'Regeneriert Gesundheit in der Nähe' + } +}; + +/** + * Versucht ein Item zu craften + * @param {string} itemType - Der Typ des Items (z.B. 'axe', 'pickaxe', 'campfire') + */ +function craftItem(itemType) { + const recipe = recipes[itemType]; + if (!recipe) { + console.log(`❌ Unbekanntes Rezept: ${itemType}`); + return false; + } + + // Prüfen ob genügend Ressourcen vorhanden sind + for (const [resource, amount] of Object.entries(recipe.cost)) { + if (!player.inventory[resource] || player.inventory[resource] < amount) { + console.log(`❌ Nicht genügend ${resource}! Benötigt: ${amount}, Vorhanden: ${player.inventory[resource] || 0}`); + return false; + } + } + + // Spezielle Überprüfungen für Werkzeuge + if (itemType === 'axe' && player.tools.axe) { + console.log('🪓 Du hast bereits eine Axt!'); + return false; + } + if (itemType === 'pickaxe' && player.tools.pickaxe) { + console.log('⛏️ Du hast bereits eine Spitzhacke!'); + return false; + } + + // Ressourcen abziehen + for (const [resource, amount] of Object.entries(recipe.cost)) { + player.inventory[resource] -= amount; + } + + // Item erstellen + if (itemType === 'axe') { + player.tools.axe = true; + console.log(`🪓 ${recipe.name} erfolgreich hergestellt!`); + } else if (itemType === 'pickaxe') { + player.tools.pickaxe = true; + console.log(`⛏️ ${recipe.name} erfolgreich hergestellt!`); + } else if (itemType === 'campfire') { + // Lagerfeuer an Spielerposition erstellen + campfires.push(new Campfire(player.x, player.y)); + console.log(`🔥 ${recipe.name} erfolgreich gebaut!`); + } + + return true; +} + +// Die Ressourcen-Generierung wird jetzt von den Chunks übernommen! +// Jeder Chunk generiert automatisch Ressourcen basierend auf seiner Position + +// ===== GRUNDLEGENDE ZEICHENFUNKTIONEN ===== + +/** + * Diese Funktion löscht das gesamte Canvas und zeichnet einen blauen Hintergrund + * + * Warum brauchen wir das? + * - In jedem Frame müssen wir das Canvas "sauber machen" + * - Sonst würden sich die gezeichneten Objekte überlagern + * - Der blaue Hintergrund simuliert einen Himmel oder Wasser + */ +function clearCanvas() { + // Schritt 1: Das gesamte Canvas löschen (transparent machen) + ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); + + // Schritt 2: Einen blauen Hintergrund zeichnen + ctx.fillStyle = '#87CEEB'; // Himmelblau (Sky Blue) + ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); +} + +/** + * Die Render-Funktion - hier wird alles gezeichnet + * + * Das ist das Herzstück unseres Spiels: + * - Wird in jedem Frame aufgerufen + * - Zeichnet alle Spielobjekte in der richtigen Reihenfolge + * - Reihenfolge: Hintergrund → Boden-Tiles → Schatten → Objekte (Y-sortiert) → UI + */ +function render() { + // Canvas leeren und Hintergrund zeichnen + clearCanvas(); + + if (!world || !player) return; + + // 1. Boden-Tiles zeichnen (unterste Ebene) + for (const chunk of world.chunks.values()) { + if (chunk.isVisible(world.camera)) { + chunk.renderTiles(ctx, world.camera); + } + } + + // 2. Alle sichtbaren Objekte sammeln und nach Y-Position sortieren + const allObjects = []; + + // Ressourcen hinzufügen (nur sichtbare) + const allResources = world.getAllResources(); + allResources.forEach(resource => { + if (!resource.collected) { + const screenPos = world.camera.worldToScreen(resource.x, resource.y); + // Nur hinzufügen wenn im sichtbaren Bereich + if (screenPos.x + resource.width >= -50 && screenPos.x <= CANVAS_WIDTH + 50 && + screenPos.y + resource.height >= -50 && screenPos.y <= CANVAS_HEIGHT + 50) { + allObjects.push({ + type: 'resource', + object: resource, + // Sortierung nach Fußpunkt für korrekte Tiefe + y: resource.y + resource.height, + zIndex: resource.type === 'stick' ? 1 : (resource.type === 'tree' ? 3 : 2) + }); + } + } + }); + + // Lagerfeuer hinzufügen (nur sichtbare) + campfires.forEach(campfire => { + const screenPos = world.camera.worldToScreen(campfire.x, campfire.y); + if (screenPos.x + 50 >= -50 && screenPos.x <= CANVAS_WIDTH + 50 && + screenPos.y + 50 >= -50 && screenPos.y <= CANVAS_HEIGHT + 50) { + allObjects.push({ + type: 'campfire', + object: campfire, + y: campfire.y + campfire.height, + zIndex: 2 + }); + } + }); + + // Spieler hinzufügen + allObjects.push({ + type: 'player', + object: player, + y: player.y + player.height, + zIndex: 4 // Spieler immer über allem anderen + }); + + // 3. Nach Z-Index und dann Y-Position sortieren + allObjects.sort((a, b) => { + // Erst nach Z-Index sortieren + if (a.zIndex !== b.zIndex) { + return a.zIndex - b.zIndex; + } + // Dann nach Y-Position (Tiefe) + return a.y - b.y; + }); + + // 4. Alle Objekte in der sortierten Reihenfolge zeichnen + allObjects.forEach(item => { + switch(item.type) { + case 'resource': + item.object.render(ctx, world.camera); + break; + case 'campfire': + item.object.render(ctx, world.camera); + break; + case 'player': + item.object.render(ctx, world.camera); + break; + } + }); + + // 5. UI zeichnen (immer ganz oben) + renderUI(); + + // Hier werden später weitere Spielobjekte gezeichnet: + // - Feinde + // - Partikeleffekte + // - Weitere Gebäude +} + +/** + * Zeichnet die Benutzeroberfläche (UI) + * Zeigt Gesundheit immer an, Inventar nur bei Tab-Taste + */ +function renderUI() { + // Gesundheitsbalken (immer sichtbar, oben links) + const healthBarWidth = 200; + const healthBarHeight = 20; + const healthBarX = 10; + const healthBarY = 10; + + // Hintergrund der Gesundheitsleiste + ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; + ctx.fillRect(healthBarX, healthBarY, healthBarWidth, healthBarHeight + 30); + + ctx.strokeStyle = '#FFFFFF'; + ctx.lineWidth = 2; + ctx.strokeRect(healthBarX, healthBarY, healthBarWidth, healthBarHeight + 30); + + // Gesundheits-Text + ctx.fillStyle = '#FFFFFF'; + ctx.font = '16px Arial'; + ctx.fillText('❤️ Gesundheit:', healthBarX + 10, healthBarY + 20); + + // Gesundheitsbalken-Hintergrund + ctx.fillStyle = '#333333'; + ctx.fillRect(healthBarX + 10, healthBarY + 25, healthBarWidth - 20, 15); + + // Gesundheitsbalken (rot bis grün je nach Gesundheit) + const healthPercent = player ? player.health / player.maxHealth : 0; + const healthColor = healthPercent > 0.6 ? '#00FF00' : + healthPercent > 0.3 ? '#FFFF00' : '#FF0000'; + + ctx.fillStyle = healthColor; + ctx.fillRect(healthBarX + 10, healthBarY + 25, (healthBarWidth - 20) * healthPercent, 15); + + // Gesundheits-Zahlen + ctx.fillStyle = '#FFFFFF'; + ctx.font = '12px Arial'; + const healthText = player ? `${Math.round(player.health)}/${player.maxHealth}` : '0/100'; + ctx.fillText(healthText, healthBarX + healthBarWidth/2 - 15, healthBarY + 36); + + // Tab-Hinweis (klein, unten rechts) + ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; + ctx.fillRect(CANVAS_WIDTH - 120, CANVAS_HEIGHT - 30, 110, 20); + + ctx.strokeStyle = '#FFFFFF'; + ctx.lineWidth = 1; + ctx.strokeRect(CANVAS_WIDTH - 120, CANVAS_HEIGHT - 30, 110, 20); + + ctx.fillStyle = '#FFFFFF'; + ctx.font = '12px Arial'; + ctx.fillText('Tab = Inventar', CANVAS_WIDTH - 115, CANVAS_HEIGHT - 15); + + // Inventar nur anzeigen wenn Tab gedrückt wurde + if (showInventory) { + renderInventoryPanel(); + } +} + +/** + * Zeichnet das Inventar-Panel (nur wenn sichtbar) + */ +function renderInventoryPanel() { + // Inventar-Panel (zentriert) + const panelWidth = 500; + const panelHeight = 300; + const panelX = (CANVAS_WIDTH - panelWidth) / 2; + const panelY = (CANVAS_HEIGHT - panelHeight) / 2; + + // Halbtransparenter Hintergrund über dem ganzen Bildschirm + ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; + ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); + + // Inventar-Panel + ctx.fillStyle = 'rgba(20, 20, 20, 0.95)'; + ctx.fillRect(panelX, panelY, panelWidth, panelHeight); + + ctx.strokeStyle = '#FFFFFF'; + ctx.lineWidth = 3; + ctx.strokeRect(panelX, panelY, panelWidth, panelHeight); + + // Titel + ctx.fillStyle = '#FFFFFF'; + ctx.font = 'bold 24px Arial'; + ctx.fillText('📦 INVENTAR', panelX + 20, panelY + 40); + + // Schließen-Hinweis + ctx.font = '14px Arial'; + ctx.fillText('Tab drücken zum Schließen', panelX + panelWidth - 180, panelY + 40); + + // Ressourcen (linke Spalte) + ctx.font = '18px Arial'; + ctx.fillText('🎒 Ressourcen:', panelX + 30, panelY + 80); + + ctx.font = '16px Arial'; + ctx.fillText(`🪵 Stöcker: ${player.inventory.stick}`, panelX + 50, panelY + 110); + ctx.fillText(`🌳 Holz: ${player.inventory.wood}`, panelX + 50, panelY + 140); + ctx.fillText(`🪨 Stein: ${player.inventory.stone}`, panelX + 50, panelY + 170); + + // Werkzeuge (rechte Spalte) + ctx.font = '18px Arial'; + ctx.fillText('🔧 Werkzeuge:', panelX + 280, panelY + 80); + + ctx.font = '16px Arial'; + ctx.fillText(`🪓 Axt: ${player.tools.axe ? '✅ Vorhanden' : '❌ Nicht vorhanden'}`, panelX + 300, panelY + 110); + ctx.fillText(`⛏️ Pickaxe: ${player.tools.pickaxe ? '✅ Vorhanden' : '❌ Nicht vorhanden'}`, panelX + 300, panelY + 140); + + // Crafting-Rezepte (unten) + ctx.font = '18px Arial'; + ctx.fillText('🔨 Crafting-Rezepte:', panelX + 30, panelY + 220); + + ctx.font = '14px Arial'; + ctx.fillText('Q = Axt (2 Stöcker)', panelX + 50, panelY + 250); + ctx.fillText('R = Pickaxe (1 Stock + 2 Holz)', panelX + 220, panelY + 250); + ctx.fillText('E = Lagerfeuer (5 Holz)', panelX + 50, panelY + 275); +} + +/** + * Die Update-Funktion - hier wird die Spiellogik berechnet + * + * Trennung von Logik und Darstellung: + * - update() = Was passiert (Bewegung, Kollisionen, Spielregeln) + * - render() = Wie es aussieht (Zeichnen auf das Canvas) + */ +function update() { + // Spieler aktualisieren (Bewegung basierend auf Eingaben) + if (player) { + player.update(); + } + + // Welt aktualisieren (Chunks laden/entladen basierend auf Spielerposition) + if (world) { + world.update(player); + } + + // Lagerfeuer aktualisieren (Animation) + campfires.forEach(campfire => { + campfire.update(); + }); + + // Kollisionserkennung: Spieler mit Ressourcen aus allen geladenen Chunks + if (world) { + const allResources = world.getAllResources(); + allResources.forEach(resource => { + if (!resource.collected && resource.checkCollision(player)) { + resource.collect(player); + } + }); + } + + // Tab-Taste für Inventar-Toggle + if (keys.tab) { + showInventory = !showInventory; + keys.tab = false; // Verhindert mehrfaches Umschalten + } + + // Crafting-Eingaben prüfen + if (keys.q) { + craftItem('axe'); + keys.q = false; // Verhindert mehrfaches Crafting + } + if (keys.r) { + craftItem('pickaxe'); + keys.r = false; + } + if (keys.e) { + craftItem('campfire'); + keys.e = false; + } + + // Hier wird später weitere Spiellogik stehen: + // - Feinde bewegen + // - Weitere Crafting-Rezepte + // - Wetter-System +} + +/** + * Der Game Loop - das Herz jedes Spiels + * + * Was ist ein Game Loop? + * - Eine endlose Schleife, die das Spiel am Leben hält + * - Läuft idealerweise 60 mal pro Sekunde (60 FPS) + * - In jedem Durchlauf: Update → Render → Warten → Wiederholen + * + * requestAnimationFrame(): + * - Browser-optimierte Funktion für Animationen + * - Synchronisiert sich automatisch mit der Bildschirmfrequenz + * - Pausiert automatisch, wenn der Tab nicht sichtbar ist (Energiesparen) + */ +function gameLoop() { + // 1. Spiellogik aktualisieren + update(); + + // 2. Alles neu zeichnen + render(); + + // 3. Nächsten Frame anfordern (rekursiver Aufruf) + requestAnimationFrame(gameLoop); +} + +// ===== SPIEL STARTEN ===== + +/** + * Initialisierungsfunktion - wird einmal beim Laden der Seite aufgerufen + */ +async function init() { + console.log('🎮 Pixel-Überlebender wird gestartet...'); + console.log(`📐 Canvas-Größe: ${CANVAS_WIDTH}x${CANVAS_HEIGHT} Pixel`); + + // Assets laden + console.log('🎨 Lade Grafiken...'); + const assetsLoaded = await assetManager.loadAllAssets(); + + if (!assetsLoaded) { + console.error('❌ Fehler beim Laden der Assets! Spiel startet mit Fallback-Grafiken.'); + } + + // Welt erstellen + world = new World(); + console.log('🌍 Unendliche prozedurale Welt erstellt!'); + + // Spieler in der Weltmitte erstellen (0, 0 in Weltkoordinaten) + player = new Player(0, 0); + console.log(`👤 Spieler erstellt an Position (${player.x}, ${player.y})`); + + // Ersten Frame zeichnen + render(); + + // Game Loop starten + gameLoop(); + + console.log('✅ Spiel erfolgreich gestartet!'); + console.log('🎯 Verwende Pfeiltasten oder WASD zum Bewegen!'); + console.log('📦 Berühre Bäume und Felsen zum Sammeln!'); + console.log('📋 Tab = Inventar öffnen/schließen'); + console.log('🔨 Crafting: Q=Axt, R=Pickaxe, E=Lagerfeuer'); + console.log('🪵 Sammle zuerst Stöcker für eine Axt!'); + console.log('🪓 Mit Axt kannst du Bäume fällen'); + console.log('⛏️ Mit Pickaxe kannst du Steine abbauen'); + console.log('❤️ Deine Gesundheit nimmt langsam ab - bleibe am Lagerfeuer!'); + console.log('🗺️ Erkunde die unendliche Welt - neue Chunks werden automatisch geladen!'); + + if (assetsLoaded) { + console.log('✨ Alle Grafiken geladen! Genieße das verbesserte Spielerlebnis!'); + } +} + +// Warten bis die HTML-Seite vollständig geladen ist, dann das Spiel starten +// Das ist wichtig, damit das Canvas-Element bereits existiert +window.addEventListener('load', init); + +// ===== ERKLÄRUNG DER CANVAS-GRUNDLAGEN ===== +/* + +WAS IST DAS CANVAS-ELEMENT? +============================ + +Das -Element ist ein HTML5-Element, das uns eine programmierbare +Zeichenfläche zur Verfügung stellt. Stell dir vor, es ist wie ein digitales +Blatt Papier, auf das wir mit JavaScript zeichnen können. + +Wichtige Eigenschaften: +- Pixelbasiert: Jeder Punkt auf dem Canvas ist ein Pixel +- Koordinatensystem: (0,0) ist oben links, X geht nach rechts, Y nach unten +- Sofortiger Modus: Was gezeichnet wird, bleibt bis zum nächsten Löschen + +WIE GREIFEN WIR DARAUF ZU? +========================== + +1. Element holen: document.getElementById('gameCanvas') +2. Kontext erhalten: canvas.getContext('2d') +3. Mit dem Kontext zeichnen: ctx.fillRect(), ctx.drawImage(), etc. + +DER 2D-KONTEXT: +=============== + +Der Kontext ist unser "Werkzeugkasten" zum Zeichnen: +- fillRect(): Gefüllte Rechtecke +- strokeRect(): Rechteck-Umrisse +- fillStyle: Füllfarbe setzen +- clearRect(): Bereich löschen +- drawImage(): Bilder zeichnen + +KOORDINATENSYSTEM: +================== + +(0,0) -----> X (800) + | + | + | + v + Y (600) + +Das ist anders als in der Mathematik - hier ist Y=0 oben! + +*/ \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..4c6def2 --- /dev/null +++ b/index.html @@ -0,0 +1,57 @@ + + + + + + Pixel-Überlebender - 2D Survival Game + + + +
+

Pixel-Überlebender

+ + + + +
+

Steuerung: Pfeiltasten oder WASD zum Bewegen

+

Sammle Ressourcen und überlebe!

+
+
+ + + + + \ No newline at end of file