Wykorzystywanie czasu bezczynności w grach turowych (RPG) do aktualizacji

9

Jeśli weźmiesz jakąkolwiek turową grę RPG, to przez długi czas nic się nie dzieje, ponieważ gra zapętla się nad „wait_for_player_input”. Oczywiście rozsądne wydaje się wykorzystanie tego czasu na aktualizację.

Jednak to natychmiast wydaje się sugerować, że trzeba by było nawiązywać wątki. Czy tego rodzaju konstrukcja jest możliwa w jednym wątku?

loop:  
if not check_something_pressed:  
    update_a_very_small_amount  
else  
  keep going

Ale jeśli mówimy, że „a_very_small_amount” aktualizuje tylko jeden obiekt w każdej pętli, aktualizacja będzie bardzo powolna.

Jak byś to zrobił, najlepiej w jednym wątku?

EDYCJA: Oznacziłem tę zależną od języka, ponieważ wydaje się to rozsądną rzeczą, chociaż wszystko bardziej specyficzne dla Pythona byłoby świetne. ;-)

Druga edycja: Ta gra nie planuje żadnych animowanych elementów; to znaczy, obecnie uruchamiam go jako wejście oczekiwania na gracza, a następnie aktualizuję wszystko i rysuję. Więc zamiast X FPS zależy to od prędkości użytkownika.

Kaczka komunistyczna
źródło

Odpowiedzi:

7

Tak, można to zrobić w jednym wątku. Ogólnie rzecz biorąc, będziesz chciał aktualizować obiekty co klatkę, a nie tylko wtedy, gdy są wolne cykle. Twoje animacje i ruch zostaną odłączone od liczby klatek na sekundę, a jeśli nie, będą wyglądać raczej niepewnie. Jeśli mówisz więcej o aktualizacjach AI lub czymś innym, co nie musi być w czasie rzeczywistym, postawiłbym na tym czas. Powinieneś wiedzieć, jaka jest docelowa liczba klatek na sekundę, a czas bezczynności będzie taki, jaki pozostanie po zakończeniu wszystkich innych czynności.

Załóżmy, że celujesz w 60 FPS w swojej grze. Dzięki temu masz 16,667 ms na wykonanie wszystkich prac potrzebnych do wykonania każdej klatki. Na początku gry uzyskaj aktualny czas przy użyciu timera o najwyższej dostępnej rozdzielczości, dodaj 16,667 ms i zapisz go gdzieś. Myślę, że funkcją w pythonie jest time (), chociaż minęło trochę czasu, odkąd pracowałem w tym języku. Po zakończeniu przetwarzania wprowadź pętlę, która porównuje bieżącą godzinę z czasem zarejestrowanym. Jeśli bieżący czas jest krótszy niż czas zakończenia ramki, update_a_very_small_amount. Nie martwiłbym się zbytnio przetwarzaniem przechodzącym do końca ramki, ponieważ twoja mała aktualizacja powinna być szybka do przetworzenia. Byłoby to tylko niewielkie opóźnienie do rozpoczęcia następnej klatki i wydaje się, że masz wystarczająco dużo czasu bezczynności, aby sobie z tym poradzić.

Po zakończeniu przetwarzania ramki dodaj 16,667 ms do czasu, który był przechowywany dla końca ostatniej ramki, aby dowiedzieć się, gdzie powinien być koniec następnej ramki. Jeśli użyjesz bieżącego czasu + 16,667 ms, a przetwarzanie się skończy, koniec następnej ramki zostanie wypchnięty o ile czasu minęła ostatnia ramka.

Odp: Druga edycja

Aby to wyjaśnić, używam tutaj terminu liczba klatek na sekundę, aby wskazać jedną iterację przez główną pętlę. Jeśli jest to oparte na prędkości wejściowej użytkownika, wyobrażam sobie, że Twoim celem jest po prostu sprawienie, aby gra była responsywna. W przeciwnym razie możesz po prostu sprawdzić dane wejściowe i zaktualizować wszystko za każdym razem przez pętlę, nawet jeśli zajmuje to 10 sekund. Aby jednak czuł się responsywny, prawdopodobnie będziesz chciał sprawdzić dane wejściowe około 20 razy na sekundę, co daje efektywną szybkość klatek wynoszącą 20 klatek na sekundę, nawet jeśli tak naprawdę nie rysujesz tych klatek. To da ci 50 ms na aktualizację, zanim będziesz musiał ponownie sprawdzić dane wejściowe.

Joel Baker
źródło
2

W przypadku Pythona możesz w szczególności użyć Coroutines do wykonania obliczeń dla wielu wywołań aktualizacji.

... coroutines to komponenty programu, które uogólniają podprogramy, aby umożliwić wiele punktów wejścia do zawieszania i wznawiania wykonywania w określonych lokalizacjach ...

PEP-0342 szczegółowo opisuje implementację coroutine w Pythonie.

Możesz stworzyć własny harmonogram i zadania, które mogą symulować wielowątkowość w jednym wątku. Jest to ten sam sposób, w jaki frameworki JavaScript pozwalają na przetwarzanie wielowątkowe, takie jak przetwarzanie, nawet gdy Javascript jest uruchamiany w jednym procesie.

David Young
źródło
1

Tak, to jest możliwe. Twoja gra będzie miała jakąś główną pętlę. Coś takiego:

while(gameRunning)
{
  checkUserInput()
  updateGame()
  renderGame()
}

W updateGame możesz iterować obiekty gry i aktualizować je „trochę”. Jeśli wykonujesz ciężkie obliczenia w tej metodzie, gra po prostu się zawiesi. Potrzebujesz więc sposobu na podzielenie tych obliczeń, aby przejść przez kilka iteracji pętli gry.

Jak je podzielić, zależy od rodzaju budowanej gry. Załóżmy, że masz procedurę wyszukiwania ścieżki, która wykorzystuje A * do obliczenia ścieżki przez labirynt. Trzeba będzie zatrzymać algorytm po pewnym czasie lub po ustalonej liczbie iteracji, zachować obliczoną ścieżkę do tej pory i przywrócić kontrolę do pętli gry. Wyszukiwanie ścieżki będzie kontynuowane przy następnym wywołaniu updateGame. Możesz nawet przesunąć postać wzdłuż ścieżki częściowej, gdy jest ona obliczana.

W większości przypadków nie musisz się martwić, że połączenie z grą trwa zbyt długo.

„Przedwczesna optymalizacja jest źródłem wszelkiego zła!”
Jeśli napotkasz wąskie gardło wydajności w procedurze aktualizacji, nadszedł czas na zbadanie i optymalizację. Ponieważ nie możesz z góry wiedzieć, które części gry zajmą najwięcej czasu, a może zmarnować czas na optymalizację niewłaściwych elementów.

Stephen
źródło
1

Rozumiem twoje pytanie: w zasadzie pytasz o wielozadaniowość kooperacyjną.

Podstawową infrastrukturą byłaby lista wskaźników funkcji, wszystkie wywoływane w sposób okrężny (chyba że wymagany jest bardziej zaawansowany priorytet), a wszystkie te funkcje wykonałyby „odrobinę pracy”, wystarczająco małe, aby nie spowodować ospać. Zrobiłem to w DOSie, zanim wątki były gorące, a także na urządzeniach mobilnych, które nie obsługiwały wątków.

Moim zdaniem lepszym pytaniem jest, co robić, czekając na ruch gracza. Jakie rzeczy byłyby tak intensywne pod względem procesora, że ​​i tak nie mogą być wykonywane w locie, a jednocześnie są tak opcjonalne, że jeśli gracz po prostu wbije jakiś klawisz ruchu wystarczająco szybko, gra nie ma czas je przetworzyć. Tak więc nie ma rzeczy takich jak obliczanie A * dla kilku tysięcy AI.

Moim zdaniem lepszym rozwiązaniem jest po prostu poddanie się / uśpienie i pozwolenie, aby zarządzanie energią komputera wykonało swoją pracę. Użytkownicy laptopów polubią Cię za to.

Jari Komppa
źródło