Jak zaimplementować siłę tarcia w grze

Jakiś czas temu oglądałem wideo na YouTube, w którym indie game deweloper @randy pokazywał jak zaimplementował fizykę w swojej grze. Moją uwagę zwróciła niepoprawnie (niezgodnie z zasadami fizyki) zaimplementowana siła tarcia. W tym wpisie odpowiem dlaczego.

Najpierw zobaczmy jak zaimplementowana została podstawowa kinematyka ruchu przez randiego. Poniżej przedstawiam pseudo kod:

new_velocity = old_velocity + acceleration * delta_time  // prędkość v(t)
new_position = old_position + new_velocity * delta_time  // przmieszczenie d(t)

Wszystko tutaj wygląda dobrze. Zmiana przemieszczenia w czasie, to jest fizyczna interpretacja prędkości, natomiast zmiana prędkości w czasie, to jest przyspieszenie.

A teraz popatrzmy jak @randy zaimplementował siłę tarcia:

new_acceleration = old_acceleration - velocity * friction * delta_time

Choć powyższy kod generuje efekt spowalniania ciała, które jest w ruchu, to jest to implementacja niezgodna z tym jak tarcie działa w rzeczywistości. Dodam tylko, że my tutaj mówimy o tarciu dynamicznym, które działa na ciało poruszające się z prędkością v. Siła taka nie zależy od prędkości, a jedynie od nacisku ciała na podłoże (masa + siła grawitacji). Powyższy wzór zakłada, że im szybciej porusza się ciało sunące po podłożu, tym bardziej tarcie je spowalnia.

Tarcie jako „siła” powinno wpływać na (zmieniać) prędkość ciała. A ponieważ zarówno masa jak i siła grawitacji są stałe, to tarcie powinno być stałe przez cały okres trwania ruchu.

W każdym przedziale czasu o długości delta_time bierzemy wartość prędkości (która jest średnią prędkością w tym przedziale czasu, bo w grach czas nie jest ciągły jak w rzeczywistości) i odejmujemy od niej wartość siły tarcia. Tarcie jest siłą, a więc zmianą prędkości w czasie, więc również mnożymy ją przez delta_time.

new_velocity = old_velocity + acceleration * delta_time - friction * delta_time
             = old_velocity + (acceleration - friction) * delta_time
new_position = old_position + new_velocity * delta_time

Wychodzi na to, że tarcie jest zwykłą stałą, która zmniejsza przyspieszenie.

Ponadto zauważmy, jak w rezultacie, do wyliczenia new_position mnożymy tarcie friction przez delta_time do kwadratu. Ponieważ tarcie reprezentuje zmianę prędkości w czasie, a prędkość reprezentuje zmianę przemieszczenia w czasie, to oznacza, że tarcie jest w tej sytuacji drugą pochodną od przemieszczenia po czasie.

d"(t) = v'(t) = a(t) <- tarcie w chwili t

Oczywiście w grach nie mamy prawdziwej „chwili” t o długości dążącej do zera, więc takie idealne wzory stanowią jedynie model do aproksymowania praw fizyki w przedziałach czasu między jedną wyświetloną klatką gry a drugą.

Całkując powyższy wzór po czasie (dt) i zakładając, że tarcie a(t) jest stałe i równe a (co wcześniej ustaliliśmy, że jest prawdą), otrzymujemy:

v(t) = v0 + a * t
d(t) = d0 + v0 * t + (a * t * t) / 2

Zatem wyszły klasyczne wzory na prędkość i przemieszczenie w ruchu jednostajnie przyspieszonym. Zero zaskoczenia, bo tarcie to po prostu siła, a więc przyspieszenie (spowolnienie) w kierunku przeciwnym do kierunku ruchu.

Na koniec dodam, że istnieje znana siła hamująca proporcjonalna do prędkości i jest to opór powietrza. Więc w rzeczywistości do wyliczenia spowolnienia i tak należy tę prędkość uwzględnić. Może się zatem okazać, że tarcie zaimplementowane przez @randiego wcale nie jest aż taką złą aproksymacją jak się na pierwszy rzut oka wydaje :)