CASE STUDIESProjekt własny

Polonez Autodrive: ewolucja interaktywnej kolorowanki 3D

Synthwave’owa kolorowanka z mini-grą miała w pierwszej wersji 16 błędów klasy P0, trzy niezależne zegary i sterowanie zależne od odświeżania monitora. Analiza pierwszej wersji, przeniesienie projektu z czystego JS i HTML na Vite z nowym szkieletem oraz proceduralna scena w miejsce plików FBX.

Kiedy
2024–2026
Rola
Autor: modele 3D, kod, UI, wdrożenie
Stack
Three.jsVanilla JSGLSL3Ds MaxViteVitest
Polonez Autodrive: Polonez jadący ku pasiastemu słońcu w stylu synthwave
  • 16

    błędów P0 znalezionych w wersji 1

  • 121

    testów jednostkowych w wersji 2

  • −29%

    draw calli na klatkę po ograniczeniu cieni

Problem

Pierwsza wersja z 2024 roku działała, ale źle się starzała: Three.js r116, czysty JavaScript bez bundlera, GUI na 600 linii kopiowanego kodu. Analiza pierwszej wersji, którą zrobiłem w październiku 2026, wypisała 16 błędów klasy P0. Trzy niezależne zegary (pętla gry, osobny requestAnimationFrame na obiekt i setTimeout) sprawiały, że na monitorze 144 Hz auto sterowało się 2,4 raza szybciej niż na 60 Hz. Pasek ładowania był fikcją z timerów od 2 do 8 sekund i przy gzipie pokazywał „Infinity%”. Do tego wycieki GPU bez dispose, hitbox większy od modelu, wireframe jako osobny klon synchronizowany ręcznie i ten sam plik FBX ładowany dwa razy.

Drugi problem pojawił się przy aktualizacji bibliotek: nowy FBXLoader triangulował niepłaskie czworokąty wzgórz algorytmem earcut i gubił 37 z 252 trójkątów. Modele z 3ds Max wracały z kamerami w hierarchii i kołami o osi poza środkiem, przez co opona biła przy obrocie.

Podejście

Zamiast poprawiać stary kod w miejscu, przeniosłem projekt z czystego JS i HTML na Vite i nowy szkielet: skoro aktualizacja bibliotek zmieniała każdy plik, stary kod był więcej wart jako specyfikacja niż jako baza. Oznaczyłem wersję 1 tagiem, zrobiłem zrzuty referencyjne i w siedmiu fazach doszedłem do parytetu funkcji na Three.js r186 z Vite, a potem dokładałem nowe rzeczy.

Szkielet wersji 2 to jedna pętla na THREE.Timer z obcięciem delty do 0,1 s i podpięciem pod document, żeby ukryta karta nie skakała po powrocie; czysta maszyna stanów gry (idle, free, countdown, playing, gameover); ładowanie przez LoadingManager i Promise.all z prawdziwym postępem; jeden plik konfiguracji jako źródło palety, warstw, parametrów rozgrywki i tekstów.

Rozwiązanie

  • Własny FanFBXLoader podmienia triangulację na wachlarz, tak jak robił to r116, więc wzgórza wracają w komplecie. Funkcja czyszcząca usuwa kamery z eksportu, a koła są centrowane na osi po wczytaniu.
  • Scena proceduralna zamiast plików: seedowany szum simplex z ridged fBm, siatka wysokości dzielona wzdłuż przekątnej o mniejszej różnicy wysokości (grzbiety zamiast wzoru), chunki wzgórz jako taśmociąg wyrastający przed autem i zapadający się za nim, pasmo gór rozstąpione przed słońcem na drogę, miasto ze 175 wież z własną mgłą.
  • Kolory w czasie rzeczywistym: lil-gui budowane w pętli z konfiguracji warstw, losowe palety ze schematów barw (komplementarne, triada, split, analogiczne) z pilnowanym kontrastem linii do wypełnienia co najmniej 3:1, przejście tweenem w sekundę, a HUD i panel przejmują odcień sceny.
  • Wireframe liczony w shaderze z atrybutem odległości do krawędzi: szerokość w jednostkach świata i zanik małych trójkątów, bo linie GL mają 1 piksel i palmy w oddali zlewały się w plamę. Selektywny bloom na osobnej warstwie.
  • Mini-gra: hitbox policzony raz na model, bounds auta raz na klatkę, pula sklonowanych materiałów do fade’ów, bo dispose przy spawnie dawał 40–70 ms przycięcia.
  • Dotyk: przyciski ekranowe z setPointerCapture, minimalne 70° FOV w poziomie w pionowym kadrze i kamera przesuwająca się bokiem za autem.

Efekt

Wersja 2 to około 5,6 tys. linii JavaScriptu z typowaniem w JSDoc, 121 testów jednostkowych w 23 plikach i CI, które przy każdym pushu sprawdza typy, testy i build. Cienie ograniczone do otoczenia auta zdjęły 18 procent trójkątów i 29 procent draw calli na klatkę. Z repozytorium zniknęło około 1,9 MB martwych plików, w tym 820 kB PNG słońca i siatki, które teraz liczą shadery.

Czego się nauczyłem

  • Jedna pętla i jedno źródło danych usuwają całe klasy błędów, nie pojedyncze bugi.
  • Przeniesienie projektu na nowy szkielet bywa tańsze niż poprawianie go w miejscu, gdy aktualizacja i tak dotyka każdego pliku. Warunek: stary kod zostaje jako specyfikacja, z tagiem i zrzutami referencyjnymi.

© 2026 SKOFTWARE Maciej Skorus

Polityka Cookies