@nice2dev/game-engine 1.1.6 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,7 +41,7 @@ const TUTORIAL_RPG = {
41
41
  de: 'Baue dein eigenes RPG! ⚔️',
42
42
  },
43
43
  content: {
44
- en: 'In this tutorial you\'ll build a classic top-down RPG!\n\n• Character stats (HP, MP, ATK, DEF, SPD)\n• XP and leveling system\n• Inventory and items\n• NPCs with dialogue\n• Turn-based battle system\n• Tilemap world with warps\n\nReady for an adventure?',
44
+ en: "In this tutorial you'll build a classic top-down RPG!\n\n• Character stats (HP, MP, ATK, DEF, SPD)\n• XP and leveling system\n• Inventory and items\n• NPCs with dialogue\n• Turn-based battle system\n• Tilemap world with warps\n\nReady for an adventure?",
45
45
  pl: 'W tym tutorialu zbudujesz klasyczne RPG z widokiem z góry!\n\n• Statystyki postaci (HP, MP, ATK, DEF, SPD)\n• System XP i poziomów\n• Ekwipunek i przedmioty\n• NPC z dialogami\n• System walki turowej\n• Świat z tilemapy z przejściami\n\nGotowy na przygodę?',
46
46
  de: 'In diesem Tutorial baust du ein klassisches Top-Down-RPG!\n\n• Charakterwerte (HP, MP, ATK, DEF, SPD)\n• XP- und Levelsystem\n• Inventar und Items\n• NPCs mit Dialogen\n• Rundenbasiertes Kampfsystem\n• Tilemap-Welt mit Warps\n\nBereit für ein Abenteuer?',
47
47
  },
@@ -58,7 +58,7 @@ const TUTORIAL_RPG = {
58
58
  de: 'Charakterwerte & Leveling',
59
59
  },
60
60
  content: {
61
- en: 'Define your hero\'s stats. Use the RPG template config for stat growth curves and XP requirements.',
61
+ en: "Define your hero's stats. Use the RPG template config for stat growth curves and XP requirements.",
62
62
  pl: 'Zdefiniuj statystyki bohatera. Użyj konfiguracji szablonu RPG dla krzywych wzrostu statystyk i wymogów XP.',
63
63
  de: 'Definiere die Werte deines Helden. Verwende die RPG-Template-Konfiguration für Wachstumskurven und XP-Anforderungen.',
64
64
  },
@@ -112,7 +112,7 @@ const TUTORIAL_RPG = {
112
112
  pl: 'Stwórz NPC z drzewami dialogów. Gracz może wybierać odpowiedzi, które rozgałęziają rozmowę.',
113
113
  de: 'Erstelle NPCs mit Dialogbäumen. Spieler können Antworten wählen, die das Gespräch verzweigen.',
114
114
  },
115
- codeTemplate: "// Dialogue system\ninterface DialogNode {\n id: string;\n text: string;\n",
115
+ codeTemplate: '// Dialogue system\ninterface DialogNode {\n id: string;\n text: string;\n',
116
116
  codeSolution: "interface DialogNode {\n id: string;\n text: string;\n speaker: string;\n choices?: { text: string; next: string; condition?: string }[];\n next?: string; // auto-advance to next node\n}\n\nconst innkeeperDialog: DialogNode[] = [\n {\n id: 'greet',\n speaker: 'Innkeeper',\n text: 'Welcome, traveler! Would you like to rest?',\n choices: [\n { text: 'Yes, I need rest. (10 gold)', next: 'rest' },\n { text: 'No thanks.', next: 'bye' },\n { text: 'Any rumors?', next: 'rumors' },\n ],\n },\n {\n id: 'rest',\n speaker: 'Innkeeper',\n text: 'Sleep well! *HP and MP fully restored*',\n next: 'greet',\n },\n {\n id: 'rumors',\n speaker: 'Innkeeper',\n text: 'I heard monsters in the eastern cave grew stronger...',\n next: 'greet',\n },\n {\n id: 'bye',\n speaker: 'Innkeeper',\n text: 'Safe travels!',\n },\n];",
117
117
  codeValidation: {
118
118
  containsAll: ['DialogNode', 'speaker', 'choices', 'next'],
@@ -163,7 +163,7 @@ const TUTORIAL_RPG = {
163
163
  de: 'Erstelle mehrere Räume verbunden durch Warps. Verwende Tilemaps für jeden Bereich (Stadt, Wald, Höhle, Burg).',
164
164
  },
165
165
  requiredAction: 'create-world-map',
166
- codeTemplate: "// Define world areas\nconst areas = [\n",
166
+ codeTemplate: '// Define world areas\nconst areas = [\n',
167
167
  codeSolution: "const areas = [\n { id: 'town', name: 'Starter Town', tilemap: 'town-map', npcs: ['innkeeper', 'shopkeeper'] },\n { id: 'forest', name: 'Dark Forest', tilemap: 'forest-map', enemies: ['wolf', 'slime'] },\n { id: 'cave', name: 'Crystal Cave', tilemap: 'cave-map', enemies: ['bat', 'golem'] },\n { id: 'castle', name: 'Demon Castle', tilemap: 'castle-map', enemies: ['skeleton', 'demon-lord'] },\n];\n\nconst warps = [\n { from: 'town', to: 'forest', x: 19, y: 7 },\n { from: 'forest', to: 'cave', x: 15, y: 2 },\n { from: 'forest', to: 'town', x: 0, y: 7 },\n { from: 'cave', to: 'castle', x: 10, y: 0 },\n];",
168
168
  codeValidation: {
169
169
  containsAll: ['areas', 'warps', 'tilemap'],
@@ -201,7 +201,7 @@ const TUTORIAL_RPG = {
201
201
  de: 'RPG fertig! 🏰',
202
202
  },
203
203
  content: {
204
- en: 'Amazing! You\'ve built a full RPG with:\n• Character stats & leveling\n• Inventory with rarity system\n• NPCs with branching dialogue\n• Turn-based combat\n• Multi-area world map with warps\n\nChallenge: Add a party system with 4 heroes!',
204
+ en: "Amazing! You've built a full RPG with:\n• Character stats & leveling\n• Inventory with rarity system\n• NPCs with branching dialogue\n• Turn-based combat\n• Multi-area world map with warps\n\nChallenge: Add a party system with 4 heroes!",
205
205
  pl: 'Niesamowite! Zbudowałeś pełne RPG z:\n• Statystyki i poziomy postaci\n• Ekwipunek z systemem rzadkości\n• NPC z rozgałęzionymi dialogami\n• Walki turowe\n• Mapa świata z wieloma obszarami\n\nWyzwanie: Dodaj system drużyny z 4 bohaterami!',
206
206
  de: 'Fantastisch! Du hast ein vollständiges RPG gebaut mit:\n• Charakterwerte & Leveling\n• Inventar mit Seltenheitssystem\n• NPCs mit verzweigten Dialogen\n• Rundenbasierter Kampf\n• Weltkarte mit mehreren Bereichen\n\nHerausforderung: Füge ein Gruppensystem mit 4 Helden hinzu!',
207
207
  },
@@ -241,7 +241,7 @@ const TUTORIAL_TOWER_DEFENSE = {
241
241
  de: 'Baue Tower Defense! 🗼',
242
242
  },
243
243
  content: {
244
- en: 'In this tutorial you\'ll build a tower defense game!\n\n• Map with path for enemies\n• Tower types (arrow, cannon, ice, laser)\n• Enemy waves with scaling difficulty\n• Tower targeting modes\n• Upgrade system\n• Gold & economy\n\nDefend your base!',
244
+ en: "In this tutorial you'll build a tower defense game!\n\n• Map with path for enemies\n• Tower types (arrow, cannon, ice, laser)\n• Enemy waves with scaling difficulty\n• Tower targeting modes\n• Upgrade system\n• Gold & economy\n\nDefend your base!",
245
245
  pl: 'W tym tutorialu zbudujesz grę tower defense!\n\n• Mapa ze ścieżką dla wrogów\n• Typy wież (strzały, armata, lód, laser)\n• Fale wrogów ze skalującą się trudnością\n• Tryby celowania wież\n• System ulepszeń\n• Złoto i ekonomia\n\nBroń swojej bazy!',
246
246
  de: 'In diesem Tutorial baust du ein Tower-Defense-Spiel!\n\n• Karte mit Feindpfad\n• Turmtypen (Pfeil, Kanone, Eis, Laser)\n• Feindwellen mit steigender Schwierigkeit\n• Turm-Zielmodi\n• Upgrade-System\n• Gold & Wirtschaft\n\nVerteidige deine Basis!',
247
247
  },
@@ -288,7 +288,7 @@ const TUTORIAL_TOWER_DEFENSE = {
288
288
  de: 'Feinde folgen einem vordefinierten Pfad vom Start zum Ende. Definiere Wegpunkte, zwischen denen Feinde laufen.',
289
289
  },
290
290
  codeTemplate: '// Enemy path waypoints\nconst path = [\n',
291
- codeSolution: "const path = [\n { x: 0, y: 200 }, // start (left edge)\n { x: 200, y: 200 }, // turn down\n { x: 200, y: 400 },\n { x: 400, y: 400 }, // turn up\n { x: 400, y: 100 },\n { x: 600, y: 100 }, // final stretch\n { x: 600, y: 300 },\n { x: 800, y: 300 }, // end (base)\n];\n\nfunction moveAlongPath(enemy: any, dt: number) {\n const target = path[enemy.waypointIndex];\n const dx = target.x - enemy.x;\n const dy = target.y - enemy.y;\n const dist = Math.sqrt(dx * dx + dy * dy);\n\n if (dist < 5) {\n enemy.waypointIndex++;\n if (enemy.waypointIndex >= path.length) {\n // Enemy reached the base!\n enemy.reachedEnd = true;\n }\n } else {\n enemy.x += (dx / dist) * enemy.speed * dt;\n enemy.y += (dy / dist) * enemy.speed * dt;\n }\n}",
291
+ codeSolution: 'const path = [\n { x: 0, y: 200 }, // start (left edge)\n { x: 200, y: 200 }, // turn down\n { x: 200, y: 400 },\n { x: 400, y: 400 }, // turn up\n { x: 400, y: 100 },\n { x: 600, y: 100 }, // final stretch\n { x: 600, y: 300 },\n { x: 800, y: 300 }, // end (base)\n];\n\nfunction moveAlongPath(enemy: any, dt: number) {\n const target = path[enemy.waypointIndex];\n const dx = target.x - enemy.x;\n const dy = target.y - enemy.y;\n const dist = Math.sqrt(dx * dx + dy * dy);\n\n if (dist < 5) {\n enemy.waypointIndex++;\n if (enemy.waypointIndex >= path.length) {\n // Enemy reached the base!\n enemy.reachedEnd = true;\n }\n } else {\n enemy.x += (dx / dist) * enemy.speed * dt;\n enemy.y += (dy / dist) * enemy.speed * dt;\n }\n}',
292
292
  codeValidation: {
293
293
  containsAll: ['path', 'waypointIndex', 'moveAlongPath'],
294
294
  },
@@ -363,7 +363,7 @@ const TUTORIAL_TOWER_DEFENSE = {
363
363
  de: 'Türme können aufgewertet werden für mehr Schaden und Reichweite. Verdiene Gold durch Feind-Kills. Verkaufe Türme für Rückerstattung.',
364
364
  },
365
365
  codeTemplate: '// Upgrade system\nlet gold = 200;\n',
366
- codeSolution: "let gold = 200;\n\nfunction upgradeTower(tower: any): boolean {\n const upgradeCost = Math.floor(tower.cost * 0.75 * tower.level);\n if (gold < upgradeCost) return false;\n\n gold -= upgradeCost;\n tower.level++;\n tower.damage = Math.floor(tower.damage * 1.3);\n tower.range += 10;\n tower.fireRate *= 1.1;\n return true;\n}\n\nfunction sellTower(tower: any): number {\n const refund = Math.floor(tower.cost * 0.6);\n gold += refund;\n return refund;\n}\n\nfunction onEnemyKilled(enemy: any) {\n gold += enemy.goldReward;\n}",
366
+ codeSolution: 'let gold = 200;\n\nfunction upgradeTower(tower: any): boolean {\n const upgradeCost = Math.floor(tower.cost * 0.75 * tower.level);\n if (gold < upgradeCost) return false;\n\n gold -= upgradeCost;\n tower.level++;\n tower.damage = Math.floor(tower.damage * 1.3);\n tower.range += 10;\n tower.fireRate *= 1.1;\n return true;\n}\n\nfunction sellTower(tower: any): number {\n const refund = Math.floor(tower.cost * 0.6);\n gold += refund;\n return refund;\n}\n\nfunction onEnemyKilled(enemy: any) {\n gold += enemy.goldReward;\n}',
367
367
  codeValidation: {
368
368
  containsAll: ['upgradeTower', 'sellTower', 'gold'],
369
369
  },
@@ -383,7 +383,7 @@ const TUTORIAL_TOWER_DEFENSE = {
383
383
  de: 'Tower Defense fertig! 🏆',
384
384
  },
385
385
  content: {
386
- en: 'You\'ve built a full tower defense game!\n• 4 tower types with different targeting\n• Enemy pathfinding along waypoints\n• Escalating wave system\n• Upgrade & sell mechanics\n• Gold economy\n\nChallenge: Add flying enemies that ignore the path!',
386
+ en: "You've built a full tower defense game!\n• 4 tower types with different targeting\n• Enemy pathfinding along waypoints\n• Escalating wave system\n• Upgrade & sell mechanics\n• Gold economy\n\nChallenge: Add flying enemies that ignore the path!",
387
387
  pl: 'Zbudowałeś kompletną grę tower defense!\n• 4 typy wież z różnym celowaniem\n• Pathfinding wrogów po waypointach\n• Eskalujący system fal\n• Mechaniki ulepszania i sprzedaży\n• Ekonomia złota\n\nWyzwanie: Dodaj latających wrogów ignorujących ścieżkę!',
388
388
  de: 'Du hast ein komplettes Tower-Defense-Spiel gebaut!\n• 4 Turmtypen mit verschiedenem Zielen\n• Feind-Pathfinding entlang Wegpunkten\n• Eskalierendes Wellensystem\n• Upgrade- & Verkaufsmechaniken\n• Gold-Wirtschaft\n\nHerausforderung: Füge fliegende Feinde hinzu, die den Pfad ignorieren!',
389
389
  },
@@ -423,7 +423,7 @@ const TUTORIAL_3D_GAME = {
423
423
  de: 'Willkommen in 3D! 🎮',
424
424
  },
425
425
  content: {
426
- en: 'In this tutorial you\'ll enter the third dimension!\n\n• Set up a 3D world (World3D)\n• Add meshes (cube, sphere, plane)\n• Lighting (directional, point, spot)\n• Camera controllers (FPS, orbit)\n• 3D physics (rigid bodies, colliders)\n• Character controller (walking, jumping)\n\nLet\'s go 3D!',
426
+ en: "In this tutorial you'll enter the third dimension!\n\n• Set up a 3D world (World3D)\n• Add meshes (cube, sphere, plane)\n• Lighting (directional, point, spot)\n• Camera controllers (FPS, orbit)\n• 3D physics (rigid bodies, colliders)\n• Character controller (walking, jumping)\n\nLet's go 3D!",
427
427
  pl: 'W tym tutorialu wejdziesz w trzeci wymiar!\n\n• Skonfiguruj świat 3D (World3D)\n• Dodaj meshe (sześcian, sfera, płaszczyzna)\n• Oświetlenie (kierunkowe, punktowe, spot)\n• Kontrolery kamery (FPS, orbita)\n• Fizyka 3D (rigid body, kolidery)\n• Kontroler postaci (chodzenie, skakanie)\n\nWejdźmy w 3D!',
428
428
  de: 'In diesem Tutorial betrittst du die dritte Dimension!\n\n• 3D-Welt einrichten (World3D)\n• Meshes hinzufügen (Würfel, Kugel, Ebene)\n• Beleuchtung (Richtung, Punkt, Spot)\n• Kamera-Controller (FPS, Orbit)\n• 3D-Physik (Starrkörper, Kollider)\n• Character-Controller (Laufen, Springen)\n\nAb in die 3. Dimension!',
429
429
  },
@@ -590,7 +590,7 @@ const TUTORIAL_3D_GAME = {
590
590
  de: '3D-Spiel fertig! 🌟',
591
591
  },
592
592
  content: {
593
- en: 'You\'ve built your first 3D game!\n• World3D scene with multiple objects\n• Primitive meshes (cube, sphere, plane)\n• Directional and point lighting\n• Orbit camera controller\n• 3D physics with rigid bodies\n• Character controller with jumping\n\nChallenge: Add a skybox and third-person camera!',
593
+ en: "You've built your first 3D game!\n• World3D scene with multiple objects\n• Primitive meshes (cube, sphere, plane)\n• Directional and point lighting\n• Orbit camera controller\n• 3D physics with rigid bodies\n• Character controller with jumping\n\nChallenge: Add a skybox and third-person camera!",
594
594
  pl: 'Zbudowałeś swoją pierwszą grę 3D!\n• Scena World3D z wieloma obiektami\n• Prymitywne meshe (sześcian, sfera, płaszczyzna)\n• Oświetlenie kierunkowe i punktowe\n• Kontroler kamery orbitalnej\n• Fizyka 3D z ciałami sztywnymi\n• Kontroler postaci ze skakaniem\n\nWyzwanie: Dodaj skybox i kamerę trzeciej osoby!',
595
595
  de: 'Du hast dein erstes 3D-Spiel gebaut!\n• World3D-Szene mit mehreren Objekten\n• Primitive Meshes (Würfel, Kugel, Ebene)\n• Richtungs- und Punktbeleuchtung\n• Orbit-Kamera-Controller\n• 3D-Physik mit Starrkörpern\n• Character-Controller mit Springen\n\nHerausforderung: Füge eine Skybox und Third-Person-Kamera hinzu!',
596
596
  },
@@ -630,9 +630,9 @@ const TUTORIAL_PUBLISHING = {
630
630
  de: 'Veröffentliche dein Spiel! 🚀',
631
631
  },
632
632
  content: {
633
- en: 'Your game is ready — now let\'s share it with the world!\n\n• Feature detection (check browser support)\n• Build optimization (minify, tree-shake)\n• PWA export (installable web app)\n• Electron export (Windows/Mac/Linux)\n• Capacitor export (iOS/Android)\n• Performance benchmarking\n\nLet\'s deploy!',
633
+ en: "Your game is ready — now let's share it with the world!\n\n• Feature detection (check browser support)\n• Build optimization (minify, tree-shake)\n• PWA export (installable web app)\n• Electron export (Windows/Mac/Linux)\n• Capacitor export (iOS/Android)\n• Performance benchmarking\n\nLet's deploy!",
634
634
  pl: 'Twoja gra jest gotowa — czas podzielić się nią ze światem!\n\n• Wykrywanie funkcji (sprawdź wsparcie przeglądarki)\n• Optymalizacja buildu (minifikacja, tree-shaking)\n• Eksport PWA (instalowalna web app)\n• Eksport Electron (Windows/Mac/Linux)\n• Eksport Capacitor (iOS/Android)\n• Benchmarkowanie wydajności\n\nWdrażamy!',
635
- de: 'Dein Spiel ist bereit — teile es mit der Welt!\n\n• Feature-Erkennung (Browser-Support)\n• Build-Optimierung (Minifizierung, Tree-Shaking)\n• PWA-Export (installierbare Web-App)\n• Electron-Export (Windows/Mac/Linux)\n• Capacitor-Export (iOS/Android)\n• Performance-Benchmarking\n\nLos geht\'s!',
635
+ de: "Dein Spiel ist bereit — teile es mit der Welt!\n\n• Feature-Erkennung (Browser-Support)\n• Build-Optimierung (Minifizierung, Tree-Shaking)\n• PWA-Export (installierbare Web-App)\n• Electron-Export (Windows/Mac/Linux)\n• Capacitor-Export (iOS/Android)\n• Performance-Benchmarking\n\nLos geht's!",
636
636
  },
637
637
  skippable: true,
638
638
  tooltipPosition: 'center',
@@ -742,10 +742,38 @@ const TUTORIAL_PUBLISHING = {
742
742
  de: 'Was ist eine PWA (Progressive Web App)?',
743
743
  },
744
744
  quizOptions: [
745
- { id: 'a', text: { en: 'A native mobile app', pl: 'Natywna aplikacja mobilna', de: 'Eine native mobile App' } },
746
- { id: 'b', text: { en: 'A web app that can be installed and works offline', pl: 'Web app instalowalna i działająca offline', de: 'Eine Web-App die installierbar ist und offline funktioniert' } },
747
- { id: 'c', text: { en: 'A special browser plugin', pl: 'Specjalna wtyczka przeglądarki', de: 'Ein spezielles Browser-Plugin' } },
748
- { id: 'd', text: { en: 'A server-side application', pl: 'Aplikacja serwerowa', de: 'Eine serverseitige Anwendung' } },
745
+ {
746
+ id: 'a',
747
+ text: {
748
+ en: 'A native mobile app',
749
+ pl: 'Natywna aplikacja mobilna',
750
+ de: 'Eine native mobile App',
751
+ },
752
+ },
753
+ {
754
+ id: 'b',
755
+ text: {
756
+ en: 'A web app that can be installed and works offline',
757
+ pl: 'Web app instalowalna i działająca offline',
758
+ de: 'Eine Web-App die installierbar ist und offline funktioniert',
759
+ },
760
+ },
761
+ {
762
+ id: 'c',
763
+ text: {
764
+ en: 'A special browser plugin',
765
+ pl: 'Specjalna wtyczka przeglądarki',
766
+ de: 'Ein spezielles Browser-Plugin',
767
+ },
768
+ },
769
+ {
770
+ id: 'd',
771
+ text: {
772
+ en: 'A server-side application',
773
+ pl: 'Aplikacja serwerowa',
774
+ de: 'Eine serverseitige Anwendung',
775
+ },
776
+ },
749
777
  ],
750
778
  quizCorrectId: 'b',
751
779
  },
@@ -770,12 +798,7 @@ const TUTORIAL_PUBLISHING = {
770
798
  Convenience export
771
799
  ══════════════════════════════════════════════════════════════ */
772
800
  function getTutorials_T12_T15() {
773
- return [
774
- TUTORIAL_RPG,
775
- TUTORIAL_TOWER_DEFENSE,
776
- TUTORIAL_3D_GAME,
777
- TUTORIAL_PUBLISHING,
778
- ];
801
+ return [TUTORIAL_RPG, TUTORIAL_TOWER_DEFENSE, TUTORIAL_3D_GAME, TUTORIAL_PUBLISHING];
779
802
  }
780
803
 
781
804
  exports.TUTORIAL_3D_GAME = TUTORIAL_3D_GAME;
@@ -35,13 +35,27 @@ class GameConfig {
35
35
  get settings() {
36
36
  return this._settings;
37
37
  }
38
- get width() { return this._settings.width; }
39
- get height() { return this._settings.height; }
40
- get targetFps() { return this._settings.targetFps; }
41
- get maxPlayers() { return this._settings.maxPlayers; }
42
- get physics() { return this._settings.physics; }
43
- get audio() { return this._settings.audio; }
44
- get rendering() { return this._settings.rendering; }
38
+ get width() {
39
+ return this._settings.width;
40
+ }
41
+ get height() {
42
+ return this._settings.height;
43
+ }
44
+ get targetFps() {
45
+ return this._settings.targetFps;
46
+ }
47
+ get maxPlayers() {
48
+ return this._settings.maxPlayers;
49
+ }
50
+ get physics() {
51
+ return this._settings.physics;
52
+ }
53
+ get audio() {
54
+ return this._settings.audio;
55
+ }
56
+ get rendering() {
57
+ return this._settings.rendering;
58
+ }
45
59
  update(overrides) {
46
60
  this._settings = mergeSettings(this._settings, overrides);
47
61
  }
@@ -22,9 +22,24 @@ function generateServiceWorkerConfig(config) {
22
22
  strategy: config.cacheStrategy,
23
23
  precache: [config.startUrl, '/index.html', '/manifest.json'],
24
24
  runtimeCaching: [
25
- { urlPattern: /\.(?:png|jpg|jpeg|webp|svg|gif)$/i, handler: 'CacheFirst', options: { cacheName: 'images', expiration: { maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60 } } },
26
- { urlPattern: /\.(?:js|css)$/i, handler: 'StaleWhileRevalidate', options: { cacheName: 'static' } },
27
- { urlPattern: /\.(?:mp3|ogg|wav)$/i, handler: 'CacheFirst', options: { cacheName: 'audio', expiration: { maxEntries: 50 } } },
25
+ {
26
+ urlPattern: /\.(?:png|jpg|jpeg|webp|svg|gif)$/i,
27
+ handler: 'CacheFirst',
28
+ options: {
29
+ cacheName: 'images',
30
+ expiration: { maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60 },
31
+ },
32
+ },
33
+ {
34
+ urlPattern: /\.(?:js|css)$/i,
35
+ handler: 'StaleWhileRevalidate',
36
+ options: { cacheName: 'static' },
37
+ },
38
+ {
39
+ urlPattern: /\.(?:mp3|ogg|wav)$/i,
40
+ handler: 'CacheFirst',
41
+ options: { cacheName: 'audio', expiration: { maxEntries: 50 } },
42
+ },
28
43
  ],
29
44
  offlineFallback: config.offlineSupport ? '/offline.html' : undefined,
30
45
  };
@@ -122,19 +137,37 @@ function createDefaultMobileLayout() {
122
137
  }
123
138
  const MOBILE_PERF_PROFILES = {
124
139
  low: {
125
- tier: 'low', maxFPS: 30, renderScale: 0.5, maxParticles: 100,
126
- maxAudioSources: 8, shadowQuality: 'off', postProcessing: false,
127
- textureQuality: 'low', physicsBroadPhaseSize: 64,
140
+ tier: 'low',
141
+ maxFPS: 30,
142
+ renderScale: 0.5,
143
+ maxParticles: 100,
144
+ maxAudioSources: 8,
145
+ shadowQuality: 'off',
146
+ postProcessing: false,
147
+ textureQuality: 'low',
148
+ physicsBroadPhaseSize: 64,
128
149
  },
129
150
  medium: {
130
- tier: 'medium', maxFPS: 60, renderScale: 0.75, maxParticles: 500,
131
- maxAudioSources: 16, shadowQuality: 'low', postProcessing: true,
132
- textureQuality: 'medium', physicsBroadPhaseSize: 128,
151
+ tier: 'medium',
152
+ maxFPS: 60,
153
+ renderScale: 0.75,
154
+ maxParticles: 500,
155
+ maxAudioSources: 16,
156
+ shadowQuality: 'low',
157
+ postProcessing: true,
158
+ textureQuality: 'medium',
159
+ physicsBroadPhaseSize: 128,
133
160
  },
134
161
  high: {
135
- tier: 'high', maxFPS: 60, renderScale: 1, maxParticles: 2000,
136
- maxAudioSources: 32, shadowQuality: 'high', postProcessing: true,
137
- textureQuality: 'high', physicsBroadPhaseSize: 256,
162
+ tier: 'high',
163
+ maxFPS: 60,
164
+ renderScale: 1,
165
+ maxParticles: 2000,
166
+ maxAudioSources: 32,
167
+ shadowQuality: 'high',
168
+ postProcessing: true,
169
+ textureQuality: 'high',
170
+ physicsBroadPhaseSize: 256,
138
171
  },
139
172
  };
140
173
 
@@ -13,12 +13,12 @@ const TUTORIAL_ANIMATIONS = {
13
13
  id: 'animations-sprites-tweens',
14
14
  title: {
15
15
  en: 'Animations: Sprites, Tweens & Easing',
16
- pl: 'Animacje: Sprite\'y, Tweeny i Easing',
16
+ pl: "Animacje: Sprite'y, Tweeny i Easing",
17
17
  de: 'Animationen: Sprites, Tweens & Easing',
18
18
  },
19
19
  description: {
20
20
  en: 'Learn to animate game objects using sprite sheets, tweens, easing functions, and state machines.',
21
- pl: 'Naucz się animować obiekty za pomocą sprite sheet\'ów, tweenów, easing\'ów i maszyn stanów.',
21
+ pl: "Naucz się animować obiekty za pomocą sprite sheet'ów, tweenów, easing'ów i maszyn stanów.",
22
22
  de: 'Lerne, Spielobjekte mit Spritesheets, Tweens, Easing und Zustandsmaschinen zu animieren.',
23
23
  },
24
24
  category: 'game-basics',
@@ -39,8 +39,8 @@ const TUTORIAL_ANIMATIONS = {
39
39
  de: 'Willkommen bei Animationen! 🎬',
40
40
  },
41
41
  content: {
42
- en: 'In this tutorial you will learn:\n• Creating animation clips from sprite sheets\n• Using tweens for smooth movement\n• Easing functions (bounce, elastic, cubic)\n• Animation state machines\n\nLet\'s bring your game to life!',
43
- pl: 'W tym tutorialu nauczysz się:\n• Tworzenia klipów animacji ze sprite sheet\'ów\n• Używania tweenów do płynnego ruchu\n• Funkcji easing (bounce, elastic, cubic)\n• Maszyn stanów animacji\n\nOżywmy Twoją grę!',
42
+ en: "In this tutorial you will learn:\n• Creating animation clips from sprite sheets\n• Using tweens for smooth movement\n• Easing functions (bounce, elastic, cubic)\n• Animation state machines\n\nLet's bring your game to life!",
43
+ pl: "W tym tutorialu nauczysz się:\n• Tworzenia klipów animacji ze sprite sheet'ów\n• Używania tweenów do płynnego ruchu\n• Funkcji easing (bounce, elastic, cubic)\n• Maszyn stanów animacji\n\nOżywmy Twoją grę!",
44
44
  de: 'In diesem Tutorial lernst du:\n• Animationsclips aus Spritesheets erstellen\n• Tweens für flüssige Bewegungen\n• Easing-Funktionen (Bounce, Elastic, Cubic)\n• Animations-Zustandsmaschinen\n\nLass uns dein Spiel zum Leben erwecken!',
45
45
  },
46
46
  skippable: true,
@@ -57,7 +57,7 @@ const TUTORIAL_ANIMATIONS = {
57
57
  },
58
58
  content: {
59
59
  en: 'Use `createClipFromSheet` to define an animation clip from a sprite sheet.\nParameters: name, columns in the sheet, start index, frame count, frame size, and duration per frame.',
60
- pl: 'Użyj `createClipFromSheet` aby zdefiniować klip animacji ze sprite sheet\'a.\nParametry: nazwa, kolumny w arkuszu, indeks startowy, ilość klatek, rozmiar klatki i czas trwania klatki.',
60
+ pl: "Użyj `createClipFromSheet` aby zdefiniować klip animacji ze sprite sheet'a.\nParametry: nazwa, kolumny w arkuszu, indeks startowy, ilość klatek, rozmiar klatki i czas trwania klatki.",
61
61
  de: 'Verwende `createClipFromSheet` um einen Animationsclip aus einem Spritesheet zu erstellen.\nParameter: Name, Spalten im Sheet, Startindex, Framezahl, Framegröße und Dauer pro Frame.',
62
62
  },
63
63
  codeTemplate: '// Create a "walk" animation clip\nconst walkClip = ',
@@ -193,7 +193,7 @@ const TUTORIAL_ANIMATIONS = {
193
193
  de: 'Animations-Checkpoint ✅',
194
194
  },
195
195
  content: {
196
- en: 'Great progress! You\'ve learned the core animation APIs. Your progress is saved.',
196
+ en: "Great progress! You've learned the core animation APIs. Your progress is saved.",
197
197
  pl: 'Świetne postępy! Opanowałeś podstawowe API animacji. Twoje postępy zostały zapisane.',
198
198
  de: 'Toller Fortschritt! Du hast die Kern-Animations-APIs gelernt. Dein Fortschritt wurde gespeichert.',
199
199
  },
@@ -249,7 +249,7 @@ const TUTORIAL_TILEMAPS = {
249
249
  de: 'Welten mit Kacheln bauen! 🗺️',
250
250
  },
251
251
  content: {
252
- en: 'Tilemaps are the foundation of 2D game worlds — platformers, RPGs, strategy games.\n\nYou will learn:\n• Creating a tilemap with layers\n• Placing and filling tiles\n• Auto-tiling (smart borders)\n• Collision detection with tiles\n• Serializing tilemaps (save/load)\n\nLet\'s build a world! 🌍',
252
+ en: "Tilemaps are the foundation of 2D game worlds — platformers, RPGs, strategy games.\n\nYou will learn:\n• Creating a tilemap with layers\n• Placing and filling tiles\n• Auto-tiling (smart borders)\n• Collision detection with tiles\n• Serializing tilemaps (save/load)\n\nLet's build a world! 🌍",
253
253
  pl: 'Tilemapy to fundament świata gier 2D — platformówek, RPG, strategii.\n\nNauczysz się:\n• Tworzenia tilemapy z warstwami\n• Umieszczania i wypełniania kafelków\n• Auto-tilingu (inteligentne krawędzie)\n• Detekcji kolizji z kafelkami\n• Serializacji tilemap (zapis/odczyt)\n\nZbudujmy świat! 🌍',
254
254
  de: 'Tilemaps sind das Fundament von 2D-Spielwelten — Platformer, RPGs, Strategiespiele.\n\nDu lernst:\n• Erstellen einer Tilemap mit Ebenen\n• Kacheln platzieren und füllen\n• Auto-Tiling (intelligente Ränder)\n• Kollisionserkennung mit Kacheln\n• Tilemaps serialisieren (Speichern/Laden)\n\nBauen wir eine Welt! 🌍',
255
255
  },
@@ -495,7 +495,7 @@ const TUTORIAL_VISUAL_SCRIPTING = {
495
495
  de: 'Baue einen Script-Graphen',
496
496
  },
497
497
  content: {
498
- en: 'A `ScriptGraph` has nodes (instances) and connections between their ports.\nLet\'s create a simple graph that moves an entity when a key is pressed.',
498
+ en: "A `ScriptGraph` has nodes (instances) and connections between their ports.\nLet's create a simple graph that moves an entity when a key is pressed.",
499
499
  pl: '`ScriptGraph` zawiera węzły (instancje) i połączenia między ich portami.\nUtwórzmy prosty graf, który przesuwa encję po naciśnięciu klawisza.',
500
500
  de: 'Ein `ScriptGraph` hat Knoten (Instanzen) und Verbindungen zwischen Ports.\nErstellen wir einen einfachen Graphen, der eine Entity bewegt, wenn eine Taste gedrückt wird.',
501
501
  },
@@ -525,9 +525,26 @@ const TUTORIAL_VISUAL_SCRIPTING = {
525
525
  de: 'Was steuert ein "flow"-Port in Visual Scripting?',
526
526
  },
527
527
  quizOptions: [
528
- { id: 'a', text: { en: 'The color of the node', pl: 'Kolor węzła', de: 'Die Farbe des Knotens' } },
529
- { id: 'b', text: { en: 'The execution order (which node runs next)', pl: 'Kolejność wykonania (który węzeł uruchomić dalej)', de: 'Die Ausführungsreihenfolge (welcher Knoten als nächstes läuft)' } },
530
- { id: 'c', text: { en: 'The animation speed', pl: 'Prędkość animacji', de: 'Die Animationsgeschwindigkeit' } },
528
+ {
529
+ id: 'a',
530
+ text: { en: 'The color of the node', pl: 'Kolor węzła', de: 'Die Farbe des Knotens' },
531
+ },
532
+ {
533
+ id: 'b',
534
+ text: {
535
+ en: 'The execution order (which node runs next)',
536
+ pl: 'Kolejność wykonania (który węzeł uruchomić dalej)',
537
+ de: 'Die Ausführungsreihenfolge (welcher Knoten als nächstes läuft)',
538
+ },
539
+ },
540
+ {
541
+ id: 'c',
542
+ text: {
543
+ en: 'The animation speed',
544
+ pl: 'Prędkość animacji',
545
+ de: 'Die Animationsgeschwindigkeit',
546
+ },
547
+ },
531
548
  { id: 'd', text: { en: 'The sound volume', pl: 'Głośność dźwięku', de: 'Die Lautstärke' } },
532
549
  ],
533
550
  quizCorrectId: 'b',
@@ -542,7 +559,7 @@ const TUTORIAL_VISUAL_SCRIPTING = {
542
559
  de: 'Starte den Graphen! 🚀',
543
560
  },
544
561
  content: {
545
- en: 'The `GraphExecutor` takes a node library and a script graph, then executes it each frame.\nConnect it to the engine\'s update loop.',
562
+ en: "The `GraphExecutor` takes a node library and a script graph, then executes it each frame.\nConnect it to the engine's update loop.",
546
563
  pl: '`GraphExecutor` przyjmuje bibliotekę węzłów i graf, a następnie wykonuje go co klatkę.\nPodłącz go do pętli update silnika.',
547
564
  de: 'Der `GraphExecutor` nimmt eine Knoten-Bibliothek und einen Graphen und führt ihn pro Frame aus.\nVerbinde ihn mit der Update-Schleife der Engine.',
548
565
  },
@@ -567,7 +584,7 @@ const TUTORIAL_VISUAL_SCRIPTING = {
567
584
  de: 'Probiere es! Verbinde Knoten',
568
585
  },
569
586
  content: {
570
- en: 'In the editor, try dragging a connection from one node\'s output port to another node\'s input port.\nThe wire turns green when compatible ports are connected!',
587
+ en: "In the editor, try dragging a connection from one node's output port to another node's input port.\nThe wire turns green when compatible ports are connected!",
571
588
  pl: 'W edytorze spróbuj przeciągnąć połączenie z portu wyjściowego jednego węzła na port wejściowy drugiego.\nPrzewód zmienia kolor na zielony gdy porty są kompatybilne!',
572
589
  de: 'Im Editor: Ziehe eine Verbindung vom Ausgang eines Knotens zum Eingang eines anderen.\nDie Leitung wird grün, wenn kompatible Ports verbunden sind!',
573
590
  },
@@ -624,7 +641,7 @@ const TUTORIAL_LOCAL_MULTIPLAYER = {
624
641
  de: 'Zusammen spielen! 🎮🎮',
625
642
  },
626
643
  content: {
627
- en: 'Local multiplayer lets friends play together on the same screen!\n\nYou will learn:\n• `PlayerManager` — managing up to 12 players\n• Gamepad input for each player\n• Split-screen viewports\n• Drop-in / drop-out\n\nGrab some controllers and let\'s go! 🕹️',
644
+ en: "Local multiplayer lets friends play together on the same screen!\n\nYou will learn:\n• `PlayerManager` — managing up to 12 players\n• Gamepad input for each player\n• Split-screen viewports\n• Drop-in / drop-out\n\nGrab some controllers and let's go! 🕹️",
628
645
  pl: 'Multiplayer lokalny pozwala znajomym grać razem na jednym ekranie!\n\nNauczysz się:\n• `PlayerManager` — zarządzanie do 12 graczy\n• Wejście z gamepada dla każdego gracza\n• Podzielony ekran (split-screen)\n• Dynamiczne dołączanie/odłączanie graczy\n\nWeź kontrolery i zaczynamy! 🕹️',
629
646
  de: 'Lokaler Multiplayer lässt Freunde zusammen auf einem Bildschirm spielen!\n\nDu lernst:\n• `PlayerManager` — bis zu 12 Spieler verwalten\n• Gamepad-Eingabe für jeden Spieler\n• Splitscreen-Ansichten\n• Drop-in / Drop-out\n\nSchnappt euch Controller und los! 🕹️',
630
647
  },
@@ -767,8 +784,8 @@ const TUTORIAL_LOCAL_MULTIPLAYER = {
767
784
  pl: 'Spraw, aby gamepady zaczęły wibrować przy zdarzeniach jak obrażenia czy eksplozje, używając metody `vibrate`.',
768
785
  de: 'Lass Gamepads bei Events wie Schaden oder Explosionen vibrieren mit der `vibrate`-Methode.',
769
786
  },
770
- codeTemplate: '// Vibrate a player\'s gamepad\n',
771
- codeSolution: '// Vibrate player 1\'s gamepad for 200ms\nengine.input.vibrate(\n 1, // player index\n 200, // duration (ms)\n 0.5, // weak motor magnitude (0-1)\n 1.0 // strong motor magnitude (0-1)\n);',
787
+ codeTemplate: "// Vibrate a player's gamepad\n",
788
+ codeSolution: "// Vibrate player 1's gamepad for 200ms\nengine.input.vibrate(\n 1, // player index\n 200, // duration (ms)\n 0.5, // weak motor magnitude (0-1)\n 1.0 // strong motor magnitude (0-1)\n);",
772
789
  codeValidation: {
773
790
  containsAll: ['vibrate'],
774
791
  },