To pytanie jest kolejnym pytaniem z mojego poprzedniego, dotyczącym wykrywania i rozwiązywania kolizji, które można znaleźć tutaj .
Jeśli nie chcesz czytać poprzedniego pytania, oto krótki opis działania mojego silnika fizyki:
Każda jednostka fizyczna jest przechowywana w klasie o nazwie SSSPBody.
Obsługiwane są tylko AABB.
Każde ciało SSSPBody jest przechowywane w klasie o nazwie SSSPWorld, która aktualizuje każde ciało i obsługuje grawitację.
Każda ramka, SSSPWorld aktualizuje każde ciało.
Każde zaktualizowane ciało szuka pobliskich obiektów w haszu przestrzennym, sprawdza, czy muszą wykryć kolizje z nimi. Jeśli tak, wywołują zdarzenie „kolizji” i sprawdzają, czy muszą rozwiązać z nimi kolizje. Jeśli tak, obliczają wektor penetracji i kierunkowe nakładanie się, a następnie zmieniają pozycję w celu rozwiązania penetracji.
Kiedy ciało zderza się z innym, przenosi prędkość na drugie, po prostu ustawiając prędkość ciała na własną.
Prędkość ciała jest ustawiona na 0, jeśli nie zmieniła pozycji od ostatniej klatki. Jeśli zderzy się również z ruchomym ciałem (takim jak winda lub ruchome platformy), oblicza różnicę ruchu windy, aby sprawdzić, czy ciało nie poruszyło się z ostatniej pozycji.
Ponadto ciało wywołuje zdarzenie „zmiażdżenia”, gdy wszystkie jego narożniki AABB nachodzą na coś w ramce.
To jest PEŁNY kod źródłowy mojej gry. Jest podzielony na trzy projekty. SFMLStart to prosta biblioteka obsługująca wprowadzanie danych, rysowanie i aktualizację encji. SFMLStartPhysics jest najważniejszy, w którym znajdują się klasy SSSPBody i SSSPWorld. PlatformerPhysicsTest to projekt gry, zawierający całą logikę gry.
I to jest „aktualizacja” metoda w klasie SSSPBody komentuje i uproszczonej. Możesz na to spojrzeć tylko wtedy, gdy nie masz ochoty patrzeć na cały projekt SFMLStartSimplePhysics. (A nawet jeśli to zrobisz, powinieneś jeszcze rzucić na to okiem, ponieważ jest to komentarz).
.Gif pokazuje dwa problemy.
- Jeśli ciała zostaną umieszczone w innej kolejności, wystąpią różne wyniki. Skrzynie po lewej są identyczne jak skrzynki po prawej stronie, umieszczone tylko w odwrotnej kolejności (w edytorze).
- Obie skrzynki powinny być napędzane w kierunku górnej części ekranu. W sytuacji po lewej skrzynie nie są napędzane. Po prawej stronie jest tylko jeden z nich. Obie sytuacje są niezamierzone.
Pierwszy problem: kolejność aktualizacji
Jest to dość proste do zrozumienia. W sytuacji po lewej, najwyższa skrzynia jest aktualizowana przed drugą. Nawet jeśli skrzynia na dole „przenosi” prędkość na drugą, musi poczekać na przejście następnej klatki. Ponieważ się nie poruszał, prędkość dolnej skrzyni jest ustawiona na 0.
Nie mam pojęcia, jak to naprawić. Wolałbym, żeby to rozwiązanie nie zależało od „sortowania” listy aktualizacji, ponieważ czuję, że robię coś złego w całym projekcie silnika fizyki.
Jak główne silniki fizyki (Box2D, Bullet, Chipmunk) radzą sobie z kolejnością aktualizacji?
Drugi problem: tylko jedna skrzynia jest napędzana w kierunku sufitu
Jeszcze nie rozumiem, dlaczego tak się dzieje. To, co robi „sprężyna”, ustawia prędkość ciała na -4000 i ponownie ustawia ją nad samą sprężyną. Nawet jeśli wyłączę kod zmiany położenia, problem nadal występuje.
Mój pomysł jest taki, że kiedy dolna skrzynia zderza się z górną skrzynią, jej prędkość jest ustawiona na 0. Nie jestem pewien, dlaczego tak się dzieje.
Pomimo szansy na wyglądanie jak ktoś, kto zrezygnuje z pierwszego problemu, zamieściłem cały kod źródłowy projektu powyżej. Nie mam niczego, co by to udowodniło, ale uwierzcie mi, starałem się to naprawić, ale po prostu nie mogłem znaleźć rozwiązania i nie mam wcześniejszego doświadczenia z fizyką i kolizjami. Próbowałem rozwiązać te dwa problemy od ponad tygodnia, a teraz jestem zdesperowany.
Nie sądzę, że mogę znaleźć rozwiązanie samodzielnie, bez usuwania wielu funkcji z gry (na przykład przenoszenia prędkości i sprężyn).
Bardzo dziękuję za czas poświęcony na przeczytanie tego pytania, a jeszcze bardziej, jeśli nawet spróbujesz znaleźć rozwiązanie lub sugestię.
Odpowiedzi:
W rzeczywistości problemy z kolejnością aktualizacji są dość powszechne w normalnych silnikach fizyki impulsowej, nie można po prostu opóźnić przyłożenia siły, jak sugeruje Vigil, skończyłoby to przerywaniem zachowania energii, gdy obiekt zderza się jednocześnie z 2 innymi. Zwykle jednak udaje im się stworzyć coś, co wydaje się całkiem realne, nawet jeśli inna kolejność aktualizacji przyniosłaby znacznie inny wynik.
W każdym razie, w twoim celu jest wystarczająca czkawka w systemie impulsowym, że sugerowałbym zamiast tego zbudować model masowo-sprężynowy.
Podstawową ideą jest to, że zamiast próby rozwiązania kolizji w jednym kroku przykładasz siłę do zderzających się obiektów, siła ta powinna być równoważna z nakładaniem się obiektów, jest to porównywalne z tym, jak rzeczywiste obiekty podczas kolizji przekształcają swoje wielką energią w tym systemie jest to, że pozwala on na przemieszczanie się energii przez obiekt bez konieczności odbijania się tam i z powrotem, i można to zrobić całkowicie niezależnie od kolejności aktualizacji.
Aby obiekty zatrzymywały się, a nie odbijały w nieskończoność, będziesz musiał zastosować jakąś formę tłumienia, możesz znacznie wpłynąć na styl i styl gry w zależności od tego, jak to zrobisz, ale bardzo podstawowym podejściem byłoby przyłóż siłę do dwóch dotykających obiektów, równoważną ich wewnętrznemu ruchowi, możesz zastosować ją tylko wtedy, gdy poruszają się ku sobie, lub też gdy odsuwają się od siebie, ten ostatni może być użyty, aby całkowicie zapobiec odbijaniu się obiektów kiedy uderzą o ziemię, ale sprawią, że będą trochę lepkie.
Można również uzyskać efekt tarcia, hamując obiekt w kierunku prostopadłym do zderzenia, czas hamowania powinien być równoważny z nakładaniem się.
Możesz łatwo ominąć pojęcie masy, sprawiając, że wszystkie obiekty mają tę samą masę, a obiekty nieruchome będą działać tak, jakby miały nieskończoną masę, jeśli po prostu zaniedbujesz je.
Trochę pseudokodu, na wypadek gdyby powyższe nie było wystarczająco jasne:
Istotą właściwości addXvelocity i addYvelocity jest to, że są one dodawane do prędkości obiektu po zakończeniu obsługi kolizji.
Edycja:
Możesz robić rzeczy w następującej kolejności, w której każdy pocisk musi zostać wykonany na wszystkich elementach przed wykonaniem następnego:
Zdaję sobie również sprawę, że poniższe informacje mogą nie być całkowicie jasne w moim początkowym poście, pod wpływem grawitacji obiekty nakładają się na siebie, co sugeruje, że ich pole kolizji powinno być nieco wyższe niż ich reprezentacja graficzna, aby uniknąć nakładania się naocznie. Ten problem będzie mniejszy, jeśli fizyka działa z wyższą częstotliwością aktualizacji. Sugeruję, abyś spróbował pracować z częstotliwością 120 Hz, aby uzyskać rozsądny kompromis między czasem procesora a dokładnością fizyki.
Edycja2:
Bardzo podstawowy przepływ silnika fizyki:
acceleration = [Complicated formulas]
velocity += acceleration
position += velocity
źródło
Cóż, oczywiście nie jesteś kimś, kto łatwo się poddaje, jesteś prawdziwym człowiekiem z żelaza, rzuciłbym ręce w powietrze znacznie wcześniej, ponieważ projekt ten bardzo przypomina las wodorostów :)
Po pierwsze, pozycje i prędkości ustawia się wszędzie, z punktu widzenia podsystemu fizyki jest to przepis na katastrofę. Ponadto, zmieniając integralne rzeczy za pomocą różnych podsystemów, utwórz prywatne metody, takie jak „ChangeVelocityByPhysicsEngine”, „ChangeVelocityBySpring”, „LimitVelocity”, „TransferVelocity” lub coś w tym rodzaju. Doda to możliwość sprawdzania zmian dokonanych przez określoną część logiki i zapewni dodatkowe znaczenie dla tych zmian prędkości. W ten sposób debugowanie byłoby łatwiejsze.
Pierwszy problem
Na samo pytanie. Teraz stosujesz tylko poprawki pozycji i prędkości „na bieżąco” w kolejności wyglądu i logiki gry. To nie zadziała w przypadku złożonych interakcji bez dokładnego zakodowania fizyki każdej złożonej rzeczy. Oddzielny silnik fizyki nie jest wtedy potrzebny.
Aby wykonywać złożone interakcje bez hacków, musisz dodać dodatkowy krok między wykrywaniem kolizji na podstawie pozycji, które zostały zmienione przez prędkości początkowe, a końcowymi zmianami pozycji na podstawie „prędkości pospiesznej”. Wyobrażam sobie, że wyglądałoby to tak:
Mogą pojawić się dodatkowe rzeczy, takie jak szarpanie się, odmowa kumulowania się, gdy FPS jest mały, lub inne rzeczy, bądź przygotowany :)
Drugi problem
Prędkość pionowa obu „martwych” skrzyń nigdy nie zmienia się od zera. O dziwo, w pętli aktualizacji PhysSpring przypisujesz prędkość, ale w pętli aktualizacji PhysCrate jest już zero. Można znaleźć linię, w której prędkość nie działa, ale przestałem tutaj debugować, ponieważ jest to sytuacja „Reap What You Sew”. Czas przestać kodować i zacząć wszystko od nowa, kiedy debugowanie staje się trudne. Ale jeśli dojdzie do punktu, w którym nawet autor kodu nie jest w stanie zrozumieć, co się dzieje w kodzie, twoja baza kodu jest już martwa, nie zdając sobie z tego sprawy :)
Trzeci problem
Myślę, że coś jest nie tak, gdy trzeba odtworzyć część Farseer, aby zrobić prostą platformówkę opartą na kafelkach. Osobiście pomyślałbym o twoim obecnym silniku jako o ogromnym doświadczeniu, a następnie porzucił go całkowicie, aby uzyskać prostszą i prostszą fizykę opartą na płytkach. Mając to na uwadze, rozsądnie byłoby zająć się takimi rzeczami jak debugowanie. Zapewnij, a może nawet, o zgrozo, testy jednostkowe, ponieważ wcześniej można byłoby złapać nieoczekiwane rzeczy.
źródło
Twoim problemem jest to, że są to zasadniczo błędne założenia dotyczące ruchu, więc to, co dostajesz, nie przypomina ruchu, ponieważ go znasz.
Kiedy ciało zderza się z innym, pęd zostaje zachowany. Myślenie o tym jako o „A uderza B” w porównaniu z „B uderza A” oznacza zastosowanie czasownika przechodniego w sytuacji nieprzechodniej. Zderzają się A i B; wynikowy pęd musi być równy momentowi początkowemu. Oznacza to, że jeśli A i B mają równą masę, oba teraz podróżują ze średnią ich pierwotnych prędkości.
Prawdopodobnie będziesz również potrzebować trochę kolizji i iteracyjnego rozwiązania, w przeciwnym razie napotkasz problemy ze stabilnością. Prawdopodobnie powinieneś przeczytać niektóre prezentacje Erin Catto GDC.
źródło
Myślę, że podjąłeś naprawdę szlachetny wysiłek, ale wydaje się, że istnieją podstawowe problemy ze strukturą kodu. Jak zasugerowali inni, pomocne może być podzielenie operacji na dyskretne części, np .:
Dzięki rozdzieleniu faz wszystkie obiekty są aktualizowane synchronicznie i nie będziesz mieć zależności od kolejności, z którymi się obecnie zmagasz. Kod okazuje się również ogólnie prostszy i łatwiejszy do zmiany. Każda z tych faz jest dość ogólna i często możliwe jest zastąpienie lepszych algorytmów po posiadaniu działającego systemu.
To powiedziawszy, każda z tych części jest nauką samą w sobie i może zająć dużo czasu, próbując znaleźć optymalne rozwiązanie. Lepiej zacząć od niektórych najczęściej używanych algorytmów:
Dobrym (i oczywistym) miejscem na początek są reguły ruchu Newtona .
źródło