W mojej małej skaczącej piłce spotkałem się z innym problemem.
Moja piłka odbija się dobrze, z wyjątkiem ostatnich chwil, kiedy ma się zatrzymać. Ruch piłki jest płynny dla głównej części, ale pod koniec piłka szarpnie się na chwilę, gdy osiada na dole ekranu.
Rozumiem, dlaczego tak się dzieje, ale nie wydaje mi się, żeby to łagodziło.
Byłbym wdzięczny za wszelkie porady, które można zaoferować.
Mój kod aktualizacji to:
public void Update()
{
// Apply gravity if we're not already on the ground
if(Position.Y < GraphicsViewport.Height - Texture.Height)
{
Velocity += Physics.Gravity.Force;
}
Velocity *= Physics.Air.Resistance;
Position += Velocity;
if (Position.X < 0 || Position.X > GraphicsViewport.Width - Texture.Width)
{
// We've hit a vertical (side) boundary
// Apply friction
Velocity *= Physics.Surfaces.Concrete;
// Invert velocity
Velocity.X = -Velocity.X;
Position.X = Position.X + Velocity.X;
}
if (Position.Y < 0 || Position.Y > GraphicsViewport.Height - Texture.Height)
{
// We've hit a horizontal boundary
// Apply friction
Velocity *= Physics.Surfaces.Grass;
// Invert Velocity
Velocity.Y = -Velocity.Y;
Position.Y = Position.Y + Velocity.Y;
}
}
Może powinienem również podkreślić, że Gravity
, Resistance
Grass
i Concrete
są typu Vector2
.
Odpowiedzi:
Oto kroki niezbędne do ulepszenia pętli symulacji fizyki.
1. Timepep
Główny problem, jaki widzę w twoim kodzie, polega na tym, że nie uwzględnia on kroku kroku fizyki. Powinno być oczywiste, że coś jest nie tak,
Position += Velocity;
ponieważ jednostki się nie zgadzają. AlboVelocity
faktycznie nie jest prędkością, albo czegoś brakuje.Nawet jeśli twoje wartości prędkości i grawitacji są skalowane w taki sposób, że każda klatka dzieje się w jednostce czasu
1
(co oznacza, że np.Velocity
Faktycznie oznacza odległość przebytą w ciągu jednej sekundy), czas musi pojawić się gdzieś w kodzie, niejawnie (poprzez ustalenie zmiennych tak, aby ich nazwy odzwierciedlają to, co naprawdę przechowują) lub jawnie (poprzez wprowadzenie pomiaru czasu). Uważam, że najłatwiej jest zadeklarować jednostkę czasu:I używaj tej wartości wszędzie tam, gdzie jest to potrzebne:
Zauważ, że każdy porządny kompilator uprości mnożenie
1.0
, dzięki czemu część nie spowolni.Teraz
Position += Velocity * TimeStep
wciąż nie jest całkiem dokładne (zobacz to pytanie, aby zrozumieć, dlaczego), ale prawdopodobnie tak się stanie.Ponadto należy wziąć pod uwagę czas:
Trochę trudniej jest to naprawić; jednym z możliwych sposobów jest:
2. Podwójne aktualizacje
Teraz sprawdź, co robisz podczas odbijania (pokazano tylko odpowiedni kod):
Możesz zobaczyć, że
TimeStep
jest używane dwukrotnie podczas odrzuceń. Zasadniczo daje to kuli dwa razy więcej czasu na aktualizację. Oto co powinno się stać:3. Grawitacja
Sprawdź teraz tę część kodu:
Dodajesz grawitację przez cały czas trwania ramki. Ale co jeśli piłka faktycznie odbije się podczas tej klatki? Wtedy prędkość zostanie odwrócona, ale dodana grawitacja sprawi, że piłka przyspieszy od ziemi! Tak więc nadmiar grawitacji będą musiały być usuwane, kiedy odbijania , a następnie ponownie dodaje się w odpowiednim kierunku.
Może się zdarzyć, że nawet ponowne dodanie grawitacji we właściwym kierunku spowoduje zbyt duże przyspieszenie prędkości. Aby tego uniknąć, możesz albo pominąć dodawanie grawitacji (w końcu to nie jest tak dużo i trwa tylko rama), albo prędkość klamry do zera.
4. Naprawiono kod
A oto w pełni zaktualizowany kod:
5. Dalsze uzupełnienia
Aby uzyskać jeszcze lepszą stabilność symulacji, możesz zdecydować się na przeprowadzenie symulacji fizyki z większą częstotliwością. Jest to trywialne z uwagi na powyższe zmiany
TimeStep
, ponieważ wystarczy podzielić ramkę na tyle części, ile chcesz. Na przykład:źródło
velocity += gravity
jest złe ivelocity += gravity * timestep
ma sens. W końcu może dać ten sam wynik, ale bez komentarza: „Wiem, co tu robię” nadal oznacza błąd w kodzie, niechlujny programista, brak wiedzy o fizyce lub po prostu prototypowy kod, który musi usprawniać się.gravity
jest w rzeczywistości… nie grawitacją. Ale mogę to wyjaśnić w poście.Dodaj zaznaczenie, aby zatrzymać odbicie, używając minimalnej prędkości pionowej. A kiedy dostaniesz minimalne odbicie, ustaw piłkę w ziemi.
źródło
Myślę więc, że problemem jest to, że twoja piłka zbliża się do limitu. Matematycznie piłka nigdy nie zatrzymuje się na powierzchni, zbliża się do powierzchni.
Twoja gra nie wykorzystuje jednak ciągłego czasu. Jest to mapa, która wykorzystuje przybliżenie do równania różniczkowego. I to przybliżenie nie jest poprawne w tej ograniczającej sytuacji (możesz, ale musiałbyś podjąć mniejsze kroki, które, jak zakładam, są niewykonalne.
Fizycznie rzecz biorąc, dzieje się tak, że gdy piłka jest bardzo blisko powierzchni, przylega do niej, jeśli całkowita siła jest poniżej określonego progu.
@Zhen odpowiedź byłaby w porządku, jeśli twój system jest jednorodny, co nie jest. Ma pewną grawitację na osi y.
Powiedziałbym więc, że rozwiązaniem nie byłoby, aby prędkość była poniżej określonego progu, ale całkowita siła przyłożona do piłki po aktualizacji powinna być poniżej określonego progu.
Siła ta jest udziałem siły wywieranej przez ścianę na piłkę + grawitację.
Warunek powinien być podobny do tego
if (newVelocity + Physics.Gravity.Force <próg)
zauważ, że newVelocity.y jest wartością dodatnią, jeśli odbicie jest na ścianie dna, a grawitacja jest wartością ujemną.
Zauważ również, że newVelocity i Physics.Gravity.Force nie mają takich samych wymiarów, jak napisałeś
co oznacza, że podobnie jak ty zakładam, że delta_time = 1 i ballMass = 1.
Mam nadzieję że to pomoże
źródło
Masz aktualizację pozycji w ramach kontroli kolizji, jest ona zbędna i błędna. I dodaje piłce energii, potencjalnie pomagając jej w ciągłym ruchu. Oprócz tego, że grawitacja nie jest stosowana w niektórych ramkach, daje to dziwny ruch. Usunąć to.
Teraz możesz zobaczyć inny problem, polegający na tym, że piłka „utknęła” poza wyznaczonym obszarem, nieustannie odbijając się w przód iw tył.
Prostym sposobem rozwiązania tego problemu jest sprawdzenie, czy piłka porusza się we właściwym kierunku przed jej zmianą.
Dlatego powinieneś zrobić:
W:
I podobnie dla kierunku Y.
Aby piłka dobrze się zatrzymała, w pewnym momencie musisz zatrzymać grawitację. Twoja obecna implementacja zapewnia, że kula zawsze będzie się wynurzać, ponieważ grawitacja nie hamuje jej, dopóki jest pod ziemią. Powinieneś zmienić na zawsze działającą grawitację. Prowadzi to jednak do tego, że kula powoli osiada po ziemi. Szybkim rozwiązaniem tego jest, po zastosowaniu grawitacji, jeśli piłka znajduje się poniżej poziomu powierzchni i porusza się w dół, zatrzymaj ją:
Te zmiany łącznie powinny zapewnić przyzwoitą symulację. Pamiętaj jednak, że wciąż jest to bardzo prosta symulacja.
źródło
Zastosuj metodę mutatora dla wszystkich zmian prędkości, a następnie w ramach tej metody możesz sprawdzić zaktualizowaną prędkość, aby ustalić, czy porusza się wystarczająco wolno, aby ją zatrzymać. Większość znanych mi systemów fizyki nazywa to „restytucją”.
W powyższej metodzie ograniczamy odbijanie, gdy jest ono wzdłuż tej samej osi co grawitacja.
Inną kwestią do rozważenia byłoby wykrycie, kiedy piłka zderzy się z ziemią, a jeśli porusza się dość wolno w momencie zderzenia, ustaw prędkość wzdłuż osi grawitacji na zero.
źródło
Kolejna rzecz: mnożymy przez stałą tarcia. Zmień to - obniż stałą tarcia, ale dodaj stałą absorpcję energii na odbiciu. Spowoduje to znacznie szybsze tłumienie ostatnich odbić.
źródło