Zastanawiam się, jak zwykle zaprojektowane są mechanizmy manipulacji czasem w grach. Szczególnie interesuje mnie cofanie czasu (podobnie jak w najnowszym SSX lub Prince of Persia).
Ta gra to strzelanka 2D z góry.
Mechanizm, który próbuję zaprojektować / wdrożyć, ma następujące wymagania:
1) Działania podmiotów poza postacią gracza są całkowicie deterministyczne.
- Działanie podejmowane przez byt jest oparte na postępach klatek od początku poziomu i / lub pozycji gracza na ekranie
- Istoty odradzają się w ustalonym czasie na poziomie.
2) Odwrócenie czasu działa poprzez cofanie w czasie rzeczywistym.
- Działania gracza również są odwrócone, odtwarza w odwrotnej kolejności to, co zrobił gracz. Gracz nie ma kontroli podczas odwrotnego czasu.
- Czas cofania nie jest ograniczony, w razie potrzeby możemy cofnąć się do początku poziomu.
Jako przykład:
Klatki 0-50: Gracz porusza się w tym czasie o 20 jednostek Wróg 1 odradza się w klatce 20 Wróg 1 porusza się w lewo o 10 jednostek podczas klatki 30-40 Gracz strzela kulą w klatkę 45 Kula przemieszcza się 5 w przód (45-50) i zabija wroga 1 w rama 50
Cofnięcie tego odtwarzałoby się w czasie rzeczywistym: Gracz porusza się w tym czasie o 20 jednostek Wróg 1 odradza się w klatce 50 Kula pojawia się ponownie w klatce 50 Kula porusza się do tyłu 5 i znika (50-45) Wróg porusza się w lewo 10 (40-30) Wróg usunięty przy ramka 20.
Patrząc na ruch, miałem kilka pomysłów, jak to osiągnąć, pomyślałem o interfejsie, który zmieniałby zachowanie, gdy czas się zbliżał lub cofał. Zamiast robić coś takiego:
void update()
{
movement += new Vector(0,5);
}
Zrobiłbym coś takiego:
public interface movement()
{
public void move(Vector v, Entity e);
}
public class advance() implements movement
{
public void move(Vector v, Entity e)
{
e.location += v;
}
}
public class reverse() implements movement
{
public void move(Vector v, Entity e)
{
e.location -= v;
}
}
public void update()
{
moveLogic.move(new vector(5,0));
}
Jednak zdałem sobie sprawę, że nie byłoby to optymalne pod względem wydajności i szybko skomplikowałoby się w przypadku bardziej zaawansowanych działań (takich jak płynny ruch po zakrzywionych ścieżkach itp.).
Odpowiedzi:
Możesz rzucić okiem na wzór polecenia .
Zasadniczo każda odwracalna akcja podejmowana przez twoje jednostki jest implementowana jako obiekt polecenia. Wszystkie te obiekty implementują co najmniej 2 metody: Execute () i Undo (), a także wszystko, czego potrzebujesz, na przykład właściwość znacznika czasu dla poprawnego pomiaru czasu.
Ilekroć twoja jednostka wykonuje odwracalną akcję, najpierw tworzysz odpowiedni obiekt polecenia. Zapisujesz go na stosie Cofnij, a następnie dodajesz do silnika gry i uruchamiasz go. Kiedy chcesz cofnąć czas, wysuwasz akcje z góry stosu i wywołujesz ich metodę Undo (), co działa odwrotnie niż w przypadku metody Execute (). Na przykład w przypadku skoku z punktu A do punktu B wykonujesz skok z B do A.
Po kliknięciu akcji zapisz ją na stosie Ponów, jeśli chcesz iść do przodu i do tyłu do woli, podobnie jak funkcja cofania / powtarzania w edytorze tekstu lub programie do malowania. Oczywiście twoje animacje muszą również obsługiwać tryb „przewijania do tyłu”, aby odtwarzać je wstecz.
Aby uzyskać więcej shenaniganów w projektowaniu gier, pozwól każdemu bytowi przechowywać swoje działania na swoim stosie, abyś mógł je cofać / ponawiać niezależnie od siebie.
Wzorzec poleceń ma inne zalety: na przykład zbudowanie rejestratora powtórek jest bardzo proste, ponieważ wystarczy zapisać wszystkie pliki na stosach do pliku, a podczas odtwarzania po prostu wprowadzić je do silnika gry jeden po drugim jeden.
źródło
Możesz rzucić okiem na Wzorzec Memento; jego głównym celem jest zaimplementowanie operacji cofania / ponawiania poprzez cofanie stanu obiektu, ale w przypadku niektórych rodzajów gier powinno wystarczyć.
W przypadku gry w pętli czasu rzeczywistego każdą klatkę operacji można uznać za zmianę stanu i zapisać ją. Jest to proste podejście do wdrożenia. Alternatywą jest pułapka, gdy zmienia się stan obiektu. Na przykład wykrywanie zmiany sił działających na sztywny korpus. Jeśli używasz właściwości do pobierania i ustawiania zmiennych, może to być również stosunkowo prosta implementacja, trudną częścią jest określenie, kiedy cofnąć stan, ponieważ nie będzie to ten sam czas dla każdego obiektu (możesz przechowywać czas cofania jako liczba klatek od początku systemu).
źródło
W konkretnym przypadku obsługa cofania przez przewijanie ruchu powinna działać poprawnie. Jeśli używasz dowolnej formy wyszukiwania ścieżek z jednostkami AI, pamiętaj, aby ponownie ją obliczyć po wycofaniu, aby uniknąć nakładania się jednostek.
Problem polega na tym, jak radzisz sobie z samym ruchem: przyzwoity silnik fizyczny (strzelanka 2D z góry na dół będzie bardzo dobra z bardzo prostym), który śledzi informacje o przeszłych krokach (w tym pozycję, siłę netto itp.) solidna podstawa. Następnie, decydując o maksymalnym wycofaniu i szczegółowości kroków wycofywania, powinieneś uzyskać pożądany wynik.
źródło
Chociaż jest to ciekawy pomysł. Odradzałbym to.
Odtwarzanie gry do przodu działa dobrze, ponieważ operacja zawsze będzie miała taki sam wpływ na stan gry. Nie oznacza to, że operacja odwrotna daje pierwotny stan. Na przykład oceń poniższe wyrażenie w dowolnym języku programowania (wyłącz optymalizację)
Przynajmniej w C i C ++ zwraca false. Chociaż różnica może być niewielka, wyobraź sobie, ile błędów może się kumulować przy 60 klatkach na sekundę w ciągu 10 sekund. Zdarzają się przypadki, w których gracz po prostu coś nie trafi, ale trafi go, gdy gra będzie odtwarzana wstecz.
Polecam przechowywanie klatek kluczowych co pół sekundy. To nie zajmie zbyt dużo pamięci. Następnie możesz albo interpolować między klatkami kluczowymi, albo jeszcze lepiej, symulować czas między dwiema klatkami kluczowymi, a następnie odtwarzać go wstecz.
Jeśli Twoja gra nie jest zbyt skomplikowana, po prostu przechowuj klatki kluczowe stanu gry 30 razy na sekundę i odtwarzaj ją wstecz. Gdybyś miał 15 obiektów z pozycją 2D, zajęłoby to dobre 1,5 minuty, aby uzyskać MB, bez kompresji. Komputery mają gigabajty pamięci.
Nie komplikuj go zbytnio, nie będzie łatwo odtworzyć gry wstecz i spowoduje wiele błędów.
źródło