Jak mogę wprowadzić grawitację? Nie dla konkretnego języka, tylko pseudokod ...
Jak mogę wprowadzić grawitację? Nie dla konkretnego języka, tylko pseudokod ...
Odpowiedzi:
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.
Są 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 newAcceleration
wartość obliczoną przy użyciu oszacowanej prędkości i ponowne użycie go jako acceleration
następnego kroku czasu. Wprowadzają również parametr 0 ≤ λ ≤ 1, który jest mnożony acceleration
w 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.
force(time, position, velocity)
mojej powyższej odpowiedzi jest tylko skrótem dla „siły działającej na przedmiot przy position
poruszaniu się velocity
o 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.
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;
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.
position += velocity * timestep
powyższy position += (velocity - acceleration * timestep / 2) * timestep
(gdzie velocity - acceleration * timestep / 2
jest 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 .
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ż.
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'
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.