Jak z prostego demo zrobiła się… gra
Zawsze zaczyna się niewinnie. „Zrobię szybkie demo modelu 3D auta. Nic wielkiego.”
No i potem wjeżdża mój perfekcjonizm i mówi:
„a co jeśli to jednak może być lepsze?”
I tak demo zaczyna rosnąć. Najpierw był model. Potem pomysł:
skoro już jest model… to czemu nie dodać free roam?
A skoro jest free roam:
- scena
- niebo
- podłoże
HDRI, własny asfalt, pierwsze ruchy auta…
I w tym momencie już wiadomo — to nie będzie mały projekt.

Gdzie zaczęła się prawdziwa zabawa
Auto się ruszało. Super.
Ale:
- koła nie mogą wyglądać sztucznie
- kamera musi „czuć” auto
- skręt nie może być binarny
No i wtedy wjeżdża fizyka.
Nie chciałem zrobić czegoś płaskiego.
Chciałem, żeby auto:
- miało masę
- reagowało na gaz i hamulec
pozwalało na kontrolowany poślizg
Fizyka — czyli gdzie zaczyna się magia
Zamiast prostego modelu ruchu zrobiłem system oparty o kilka kluczowych elementów:
- slip angle (kąt poślizgu opony)
- rozdzielenie przodu i tyłu
- przeniesienie masy
- ograniczenia przyczepności (traction circle)
Slip angle (czyli dlaczego auto w ogóle skręca)
const vLatFront = physics.lateralSpeed + physics.yawRate * halfWB
const vLatRear = physics.lateralSpeed - physics.yawRate * halfWB
let targetFrontSlip = 0
let targetRearSlip = 0
if (absForward > 0.1) {
targetFrontSlip =
Math.atan2(vLatFront, absForward) -
physics.steeringAngle * Math.sign(physics.forwardSpeed)
targetRearSlip = Math.atan2(vLatRear, absForward)
}
Tutaj dzieje się kluczowa rzecz:
- przód i tył auta mają inne kąty poślizgu
- to właśnie różnica między nimi decyduje, czy auto skręca stabilnie czy zaczyna uciekać tyłem
To jest fundament całego prowadzenia.
Płynne zachowanie opon (bez szarpania)
const slipRelaxationAlpha = 1 - Math.exp(-8 * dt)
physics.frontSlipAngle +=
(targetFrontSlip - physics.frontSlipAngle) * slipRelaxationAlpha
physics.rearSlipAngle +=
(targetRearSlip - physics.rearSlipAngle) * slipRelaxationAlphaBez tego:
- wszystko byłoby natychmiastowe i nienaturalne
Dzięki temu:
- opony „dochodziły” do swojego zachowania
- auto jest przewidywalne i płynne
Zakres przyczepności
const frontLatAvailable = Math.sqrt(
Math.max(0, 1.0 - Math.pow(frontLongUsage * 0.95, 2))
)
const rearLatAvailable = Math.sqrt(
Math.max(0, 1.0 - Math.pow(rearLongUsage * 0.95, 2))
)To ogranicza sytuacje typu:
- pełne hamowanie + maksymalny skręt = zero konsekwencji
Tutaj:
- im więcej hamujesz lub przyspieszasz
- tym mniej masz przyczepności
Mamy podsterowność
Przeniesienie masy
const weightTransferLong =
(longitudinalAccel * config.cgHeight) / config.wheelbase
physics.weightTransfer = THREE.MathUtils.lerp(
physics.weightTransfer,
weightTransferLong * 0.015,
Math.min(1, 2 * dt)
)Efekt:
- przy hamowaniu przód „siada”
- przy przyspieszaniu tył dostaje więcej przyczepności
Wizualnie bardzo tego nie widać ale wpływa mocno na prowadzenie.
Trik dla lepszego prowadzenia przy małych prędkościach
const lowSpeedBoost = speedPercent < 0.5
? THREE.MathUtils.lerp(
config.lowSpeedGripBoost,
1.0,
speedPercent / 0.5
)
: 1.0Problemem realistycznych parametrów fizyki przy nie realistycznej skali pojazdu była zerowa skrętność przy małych prędkościach.
Nie mogłem się tego pozbyć zmieniając wartości samego modelu opon.
Zatem przy niższych prędkościach zastosowałem trik aby zwiększyć sterowność.
Trail braking
const isTrailBraking =
brakeUsage > 0.3 && steeringAmount > rotationThreshold
const brakingRearReduction = THREE.MathUtils.lerp(
1.0,
config.brakingRearGripReduction,
rotationIntensity
)To jedna z ważniejszych rzeczy pod klawiaturę.
Efekt:
- hamujesz + skręcasz → tył zaczyna lekko rotować (albo mocno)
- możesz „ustawić” auto do zakrętu
Geometryczna korekcja skrętu
const geometricYawRate =
absForward > 0.1
? (physics.forwardSpeed * Math.tan(physics.steeringAngle)) /
config.wheelbase
: 0
const yawError = geometricYawRate - physics.yawRate
const desiredCorrAccel = yawError * config.geometricSpringRate
const clampedCorrAccel = THREE.MathUtils.clamp(
desiredCorrAccel,
-maxCorrAccel,
maxCorrAccel
)
physics.yawRate += clampedCorrAccel * geometricBlend * dtDzięki temu auto skręca płynnie a nie gwałtownie jak w grach arcade.
Choć na klawiaturze i tak ciężko o ten efekt, to jazda jest zdecydowanie przyjemniejsza.
Adaptive damping
const effectiveLateralDamp = THREE.MathUtils.lerp(
gripDamp,
slideDamp,
smoothstep(maxSaturation, 0.3, 1.0)
)
physics.lateralSpeed *= Math.max(
0,
1 - effectiveLateralDamp * dt
)Efekt:
- w normalnej jeździe auto jest stabilne
- w poślizgu pozwala się „rozwinąć” driftowi i go kontrolować

Niech nastanie światłość
Model 3d jest dość ograniczony, również przez fakt formatu .glb
W związku z tym mój pomysł na włączanie świateł był niemożliwy do osiągnięcia tylko przy użyciu meshy odbijających światło.
Zamiast z nim walczyć, użyłem sprytnych rozwiązań:
- emissive mesh’e do bloom
- spotlighty do realnego oświetlenia
- dodatkowe światła „spill” dla miękkiego efektu
Umieszczone idealnie w przestrzeni 3d względem modelu auta.
<mesh position={[-0.78, 0.72, 2.28]}>
<sphereGeometry args={[0.03, 8, 8]} />
<meshStandardMaterial
emissive="#e8f4ff"
emissiveIntensity={0}
transparent
/>
</mesh>Efekt:
- światła wyglądają naturalnie
- oświetlają scenę
- mają głębię i intensywność
Wszystko razem
Do tego doszło:
- drift
- dym
- dźwięk na bazie prawdziwych sampli Alfa Romeo
- dynamiczna kamera
- przechyły nadwozia
- dopracowane detale
Z czegoś co miało być tylko eksperymentem z modelem 3d, zrobiła się prawie gra.

Dlaczego tak?
Bo nie uznaję półśrodków. Nie podpiszę się pod czymś, co jest „okej”.
Albo coś robię dobrze, albo wcale. I z każdym projektem ta poprzeczka idzie wyżej.
Dla klientów to oznacza jedno:
dostają rzeczy dopracowane w najmniejszych detalach.
Dla mnie…
no cóż — powiedzmy, że czasem moje życie poza projektami cierpi 😅
Możecie poupalać to dzieło sztuki tutaj:
https://alfaromeodemo.netlify.app/





