Jak mogę wprowadzić grawitację?

Odpowiedzi:

52

Jak zauważyli inni w komentarzach, podstawowa metoda integracji Eulera opisana w odpowiedzi tenpn ma kilka problemów:

  • Nawet w przypadku zwykłego ruchu, takiego jak skoki balistyczne pod stałym ciężarem, wprowadza błąd systematyczny.

  • Błąd zależy od timepeptu, co oznacza, że ​​zmiana timepeptu zmienia systemowe trajektorie obiektów, co może być zauważone przez graczy, jeśli gra używa zmiennej timepeptu. Nawet w przypadku gier o ustalonym czasie fizyki, zmiana czasu podczas programowania może znacząco wpłynąć na fizykę gry, na przykład na odległość, na jaką latać będzie obiekt wystrzelony z daną siłą, potencjalnie przekraczając wcześniej zaprojektowane poziomy.

  • Nie oszczędza energii, nawet jeśli podstawowa fizyka powinna. W szczególności obiekty, które powinny oscylować równomiernie (np. Wahadła, sprężyny, orbitujące planety itp.) Mogą stale gromadzić energię, dopóki cały system się nie rozpadnie.

Na szczęście nie jest trudno zastąpić integrację Eulera czymś, co jest prawie tak proste, ale nie ma żadnego z tych problemów - a konkretnie integratora symplektycznego drugiego rzędu, takiego jak integracja skoków lub ściśle związana metoda prędkości Verleta . W szczególności, gdy podstawowa integracja Eulera aktualizuje prędkość i położenie jako:

przyspieszenie = siła (czas, pozycja) / masa;
czas + = timepep;
pozycja + = timepep * prędkość;
prędkość + = timepep * przyspieszenie;

metoda prędkości Verlet robi to tak:

przyspieszenie = siła (czas, pozycja) / masa;
czas + = timepep;
pozycja + = timepep * ( prędkość + timepep * przyspieszenie / 2) ;
newAcceleration = siła (czas, pozycja) / masa; 
prędkość + = odmierzanie czasu * ( przyspieszenie + nowe przyspieszenie ) / 2 ;

Jeśli masz wiele współdziałających obiektów, powinieneś zaktualizować wszystkie ich pozycje przed ponownym obliczeniem sił i zaktualizowaniem prędkości. Nowe przyspieszenie (a) można następnie zapisać i wykorzystać do aktualizacji pozycji (pozycji) przy następnym takcie czasowym, zmniejszając liczbę wywołań force()do jednego (na obiekt) na czas, tak jak w przypadku metody Eulera.

Ponadto, jeśli przyspieszenie jest zwykle stałe (podobnie jak grawitacja podczas skoków balistycznych), możemy uprościć powyższe, aby:

czas + = timepep;
pozycja + = timepep * ( prędkość + timepep * przyspieszenie / 2) ;
prędkość + = timepep * przyspieszenie;

gdzie dodatkowy termin pogrubiony jest jedyną zmianą w porównaniu do podstawowej integracji Eulera.

W porównaniu z integracją Eulera metody Verlet prędkości i leapfrog mają kilka fajnych właściwości:

  • W przypadku stałego przyspieszenia dają dokładne wyniki (w każdym razie do błędów zaokrąglenia zmiennoprzecinkowego), co oznacza, że ​​trajektorie skoku balistycznego pozostają takie same, nawet jeśli zmiana czasu jest zmieniona.

  • Są to integratory drugiego rzędu, co oznacza, że ​​nawet przy różnym przyspieszeniu średni błąd integracji jest tylko proporcjonalny do kwadratu pomiaru czasu. Może to pozwolić na dłuższe kroki bez utraty dokładności.

  • symplektyczni , co oznacza, że ​​oszczędzają energię, jeśli robią to podstawy fizyki (przynajmniej tak długo, jak czas jest stały). W szczególności oznacza to, że nie dostaniesz takich rzeczy, jak planety spontanicznie wylatujące z ich orbit, lub obiekty połączone ze sobą sprężynami, które stopniowo będą się coraz bardziej chwiejne, aż cała rzecz wybuchnie.

Jednak metoda prędkości Verlet / leapfrog jest prawie tak prosta i szybka jak podstawowa integracja Eulera, a na pewno znacznie prostsza niż alternatywy, takie jak integracja Runge-Kutta czwartego rzędu (która, ogólnie rzecz biorąc, bardzo fajny integrator, nie ma własności symplektycznej i wymaga czterech ocen w force()funkcji dla każdego kroku czasowego). Dlatego zdecydowanie polecam je wszystkim, którzy piszą dowolny kod fizyki gry, nawet jeśli jest to tak proste, jak przeskakiwanie z jednej platformy na drugą.


Edycja: Podczas gdy formalne wyprowadzenie metody Verleta prędkości jest ważne tylko wtedy, gdy siły są niezależne od prędkości, w praktyce można z niej dobrze korzystać nawet przy siłach zależnych od prędkości, takich jak opór płynu . Aby uzyskać najlepsze wyniki, należy użyć początkowej wartości przyspieszenia, aby oszacować nową prędkość dla drugiego wezwania do force():

przyspieszenie = siła (czas, pozycja, prędkość) / masa;
czas + = timepep;
pozycja + = timepep * ( prędkość + timepep * przyspieszenie / 2) ;
prędkość + = timepep * przyspieszenie;
newAcceleration = siła (czas, pozycja, prędkość) / masa; 
prędkość + = timepep * (newAcceleration - przyspieszenie) / 2 ;

Nie jestem pewien, czy ten konkretny wariant metody Verleta prędkości ma konkretną nazwę, ale przetestowałem go i wydaje się, że działa bardzo dobrze. Nie jest tak dokładny jak Runge-Kutta rzędu Foutha (jak można by oczekiwać po metodzie drugiego rzędu), ale jest znacznie lepszy niż Euler lub Verve naiwna prędkość bez pośredniej oceny prędkości i nadal zachowuje symplektyczną właściwość normalnej vellet Verlet dla konserwatywnych, niezależnych od prędkości sił.

Edycja 2: Bardzo podobny algorytm został opisany np. Przez Groota i Warrena ( J. Chem. Phys. 1997) , chociaż wydaje się, że czytając między wierszami, poświęcili oni pewną dokładność dla dodatkowej prędkości, zapisując newAccelerationwartość obliczoną przy użyciu oszacowanej prędkości i ponowne użycie go jako accelerationnastępnego kroku czasu. Wprowadzają również parametr 0 ≤ λ ≤ 1, który jest mnożony accelerationw początkowej estymacji prędkości; z jakiegoś powodu zalecają λ = 0,5, mimo że wszystkie moje testy sugerują, że λ= 1 (co jest efektywnie tym, czego używam powyżej) działa równie dobrze lub lepiej, z ponownym użyciem przyspieszenia lub bez niego. Może ma to coś wspólnego z faktem, że ich siły obejmują stochastyczny komponent ruchu Browna.

Ilmari Karonen
źródło
Velocity Verlet jest fajny, ale nie może mieć potencjału zależnego od prędkości, więc tarcie nie może być zrealizowane. Myślę, że Runge-Kutta 2 jest najlepszy do moich celów;)
Pizzirani Leonardo
1
@PizziraniLeonardo: Możesz użyć (wariant) Verlet prędkości bardzo dobrze nawet dla sił zależnych od prędkości; zobacz moją edycję powyżej.
Ilmari Karonen,
1
Literatura nie nadaje tej interpretacji Velocity Verlet innej nazwy. Opiera się on na strategii predyktor-korektor, jak również stwierdzono w tym dokumencie fire.nist.gov/bfrlpubs/build99/PDF/b99014.pdf .
teodron
3
@ Unit978: To zależy od gry, a konkretnie od zastosowanego modelu fizyki. W force(time, position, velocity)mojej powyższej odpowiedzi jest tylko skrótem dla „siły działającej na przedmiot przy positionporuszaniu się velocityo time”. Zazwyczaj siła zależałaby od takich rzeczy, jak to, czy obiekt spada swobodnie, czy siedzi na twardej powierzchni, czy inne pobliskie obiekty wywierają na niego siłę, jak szybko porusza się po powierzchni (tarcie) i / lub przez ciecz lub gaz (drag) itp.
Ilmari Karonen
1
To świetna odpowiedź, ale jest niekompletna bez mówienia o ustalonym czasie ( gafferongames.com/game-physics/fix-your-timestep ). Dodałbym osobną odpowiedź, ale większość ludzi zatrzymuje się na zaakceptowanej odpowiedzi, zwłaszcza gdy ma ona najwięcej głosów o tak dużym marginesie, jak ma to miejsce w tym przypadku. Myślę, że społeczności lepiej służy rozszerzenie tej.
Jibb Smart 21.04.16
13

Zrób każdą pętlę aktualizacji swojej gry:

if (collidingBelow())
    gravity = 0;
else gravity = [insert gravity value here];

velocity.y += gravity;

Na przykład w platformówce po skoku grawitacja byłaby włączona (kolizja poniżej informuje, czy grunt znajduje się tuż pod tobą), a po uderzeniu w ziemię byłaby wyłączona.

Oprócz tego, aby wdrożyć skoki, wykonaj następujące czynności:

if (pressingJumpButton() && collidingBelow())
    velocity.y = [insert jump speed here]; // the jump speed should be negative

I oczywiście, w pętli aktualizacji musisz również zaktualizować swoją pozycję:

position += velocity;
Pecant
źródło
6
Co masz na myśli? Wystarczy wybrać własną wartość grawitacji, a ponieważ zmienia ona prędkość, a nie tylko pozycję, wygląda naturalnie.
Pecant
1
Nigdy nie lubię wyłączać grawitacji. Myślę, że grawitacja powinna być stała. Rzeczą, która powinna się zmienić (imho), jest twoja umiejętność skakania.
ultifinitus
2
Jeśli to pomaga, pomyśl o tym jako o „spadaniu”, a nie o „grawitacji”. Funkcja jako całość kontroluje, czy obiekt spada z powodu grawitacji. Sama grawitacja istnieje tak samo jak [wstaw tutaj wartość grawitacji]. W tym sensie grawitacja jest stała, po prostu nie używasz jej do niczego, chyba że obiekt jest w powietrzu.
Jason Pineo
2
Ten kod zależy od liczby klatek na sekundę, co nie jest świetne, ale jeśli masz ciągłą aktualizację, to się śmiejesz.
tenpn
1
-1 przepraszam. Dodanie prędkości i grawitacji lub położenia i prędkości, które po prostu nie ma sensu. Rozumiem skrót, który robisz, ale jest zły. Uderzyłbym każdego ucznia, stażystę lub kolegę robiąc to z największą wskazówką, jaką mogłem znaleźć. Spójność jednostek ma znaczenie.
sam hocevar,
8

Właściwa integracja fizyki newtonowskiej niezależna od częstotliwości odświeżania:

Vector forces = 0.0f;

// gravity
forces += down * m_gravityConstant; // 9.8m/s/s on earth

// left/right movement
forces += right * m_movementConstant * controlInput; // where input is scaled -1..1

// add other forces in for taste - usual suspects include air resistence
// proportional to the square of velocity, against the direction of movement. 
// this has the effect of capping max speed.

Vector acceleration = forces / m_massConstant; 
m_velocity += acceleration * timeStep;
m_position += velocity * timeStep;

Dostosuj grawitację Stała, ruch Stała i masowa Stała, aż poczuje się dobrze. Jest to intuicyjna rzecz i może zająć trochę czasu, aby poczuć się świetnie.

Łatwo jest rozszerzyć wektor sił, aby dodać nową rozgrywkę - na przykład dodać siłę od jakiejkolwiek pobliskiej eksplozji lub w kierunku czarnych dziur.

* edycja: wyniki te z czasem będą błędne, ale mogą być „wystarczająco dobre” dla twojej wierności lub umiejętności. Zobacz ten link http://lol.zoy.org/blog/2011/12/14/understanding-motion-in-games, aby uzyskać więcej informacji.

tenpn
źródło
4
Nie używaj integracji Eulera. Zobacz ten artykuł Glenna Fiedlera, który wyjaśnia problemy i rozwiązania lepiej niż ja. :)
Martin Sojka
1
Rozumiem, że Euler z czasem jest niedokładny, ale myślę, że istnieją scenariusze, w których to tak naprawdę nie ma znaczenia. Dopóki zasady są spójne dla wszystkich i „wydaje się” właściwe, nie ma problemu. A jeśli dopiero uczysz się o phisiscach, bardzo łatwo je zapamiętać i wdrożyć.
tenpn
... ale dobry link. ;)
tenpn
4
Możesz rozwiązać większość problemów z integracją Eulera, po prostu zastępując position += velocity * timesteppowyższy position += (velocity - acceleration * timestep / 2) * timestep(gdzie velocity - acceleration * timestep / 2jest po prostu średnia ze starych i nowych prędkości). W szczególności ten integrator daje dokładne wyniki, jeśli przyspieszenie jest stałe, jak zwykle w przypadku grawitacji. Aby uzyskać lepszą dokładność przy różnym przyspieszeniu, możesz dodać podobną korektę do aktualizacji prędkości, aby uzyskać integrację prędkości Verlet .
Ilmari Karonen
Twoje argumenty mają sens, a niedokładność często nie jest wielkim problemem. Nie należy jednak twierdzić, że jest to integracja „właściwa niezależna od liczby klatek”, ponieważ po prostu nie jest (niezależna od liczby klatek na sekundę).
sam hocevar,
3

Jeśli chcesz wprowadzić grawitację na nieco większą skalę, możesz użyć tego rodzaju obliczeń dla każdej pętli:

for each object in the scene
  for each other_object in the scene not equal to object
    if object.mass * other_object.mass / object.distanceSquaredBetweenCenterOfMasses(other_object) < epsilon
      abort the calculation for this pair
    if object.mass is much, much bigger than other_object.mass
      abort the calculation for this pair
    force = gravitational_constant
            * object.mass * other_object.mass
            / object.distanceSquaredBetweenCenterOfMasses(other_object)
    object.addForceAtCenterOfMass(force * object.normalizedDirectionalVectorTo(other_object))
  end for loop
end for loop

W przypadku jeszcze większych (galaktycznych) skal sama grawitacja nie wystarczy, aby stworzyć „prawdziwy” ruch. Interakcja układów gwiezdnych jest w znacznym i bardzo widocznym stopniu podyktowana równaniami Naviera-Stokesa dla dynamiki płynów, a ty będziesz musiał pamiętać o skończonej prędkości światła - a więc i grawitacji - również.

Martin Sojka
źródło
1

Kod dostarczony przez Ilmari Karonen jest prawie poprawny, ale jest niewielka usterka. Rzeczywiście obliczasz przyspieszenie 2 razy na tik, nie jest to zgodne z równaniami podręcznika.

acceleration = force(time, position) / mass; // Here
time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
newAcceleration = force(time, position) / mass;
velocity += timestep * (acceleration + newAcceleration) / 2;

Następujący mod jest poprawny:

time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
oldAcceletation = acceleration; // Store it
acceleration = force(time, position) / mass;
velocity += timestep * (acceleration + oldAcceleration) / 2;

Twoje zdrowie'

Satanfu
źródło
Myślę, że się mylisz, ponieważ przyspieszenie zależy od prędkości
super
-4

Program odpowiadający Pecant zignorował czas klatki, a to od czasu do czasu zmienia twoje zachowanie fizyczne.

Jeśli zamierzasz stworzyć bardzo prostą grę, możesz stworzyć swój własny silnik fizyki - przypisać masę i wszelkiego rodzaju parametry fizyki do każdego poruszającego się obiektu, a także wykonać wykrywanie kolizji, a następnie zaktualizować ich pozycję i prędkość dla każdej klatki. Aby przyspieszyć ten postęp, musisz uprościć siatkę kolizyjną, zmniejszyć liczbę wezwań do wykrywania kolizji itp. W większości przypadków jest to uciążliwe.

Lepiej jest używać silnika fizyki, takiego jak physix, ODE i kula. Każdy z nich będzie dla Ciebie stabilny i wystarczająco wydajny.

http://www.nvidia.com/object/physx_new.html

http://bulletphysics.org/wordpress/

Raymond
źródło
4
-1 Niepomocna odpowiedź, która nie odpowiada na pytanie.
doppelgreener
4
cóż, jeśli chcesz dostosować go do czasu, możesz po prostu przeskalować prędkość o czas, który upłynął od ostatniej aktualizacji ().
Pecant