Myślałem o tym, jak zaimplementować stany gry w mojej grze. Głównymi rzeczami, które chcę za to są:
Półprzezroczyste górne stany - są w stanie zobaczyć menu pauzy w grze z tyłu
Coś OO-uważam, że jest to łatwiejsze w użyciu i rozumiem leżącą u podstaw teorię, a także utrzymuję porządek i dodaje więcej.
Planowałem użyć połączonej listy i potraktować ją jako stos. Oznacza to, że mogłem uzyskać dostęp do poniższego stanu dla półprzezroczystości.
Plan: Niech stos stanu będzie połączoną listą wskaźników do IGameStates. Stan najwyższy obsługuje własne komendy aktualizacji i wprowadzania, a następnie ma element transparentny, który decyduje, czy należy narysować stan poniżej.
Wtedy mógłbym zrobić:
states.push_back(new MainMenuState());
states.push_back(new OptionsMenuState());
states.pop_front();
Aby przedstawić ładowanie odtwarzacza, przejdź do opcji, a następnie menu głównego.
Czy to dobry pomysł, czy ...? Czy powinienem spojrzeć na coś innego?
Dzięki.
źródło
new
w sposób pokazany w przykładowym kodzie, po prostu prosi o wycieki pamięci lub inne, poważniejsze błędy.Odpowiedzi:
Pracowałem na tym samym silniku co koderanger. Mam inny punkt widzenia. :)
Po pierwsze, nie mieliśmy stosu FSM - mieliśmy stos stanów. Stos stanów tworzy pojedynczy FSM. Nie wiem, jak wyglądałby stos FSM. Prawdopodobnie zbyt skomplikowane, aby zrobić cokolwiek praktycznego.
Moim największym problemem z naszą Globalną Maszyną Stanową było to, że był to stos stanów, a nie zbiór stanów. Oznacza to np. ... / MainMenu / Ładowanie było inne niż ... / Ładowanie / Menu główne, w zależności od tego, czy menu główne pojawiło się przed czy po ekranie ładowania (gra jest asynchroniczna, a ładowanie odbywa się głównie na serwerze ).
Jako dwa przykłady rzeczy uczyniło to brzydkim:
Mimo nazwy nie był zbyt „globalny”. Większość wewnętrznych systemów gier nie używała go do śledzenia swoich stanów wewnętrznych, ponieważ nie chciały, aby ich stany rżały się z innymi systemami. Inni, np. System interfejsu użytkownika, mogą go używać, ale tylko do kopiowania stanu do własnych lokalnych systemów stanu. (Chciałbym szczególnie ostrzec system przed stanami interfejsu użytkownika. Stan interfejsu użytkownika nie jest stosem, to naprawdę DAG, a próba wymuszenia na nim jakiejkolwiek innej struktury spowoduje, że korzystanie z interfejsów będzie frustrujące.)
To, co było dobre, to izolowanie zadań związanych z integracją kodu od programistów infrastruktury, którzy nie wiedzieli, jak właściwie przebieg gry jest zorganizowany, abyś mógł powiedzieć facetowi piszącemu „wstaw swój kod w Client_Patch_Update”, a facetowi piszącemu grafikę ładowanie „umieść kod w Client_MapTransfer_OnEnter”, a my możemy bez problemu zamienić niektóre przepływy logiczne.
W przypadku pobocznego projektu miałem więcej szczęścia z zestawem stanów niż ze stosem , nie boję się tworzyć wielu maszyn dla niepowiązanych systemów i nie pozwalam sobie wpaść w pułapkę posiadania „stanu globalnego”, który jest naprawdę to po prostu skomplikowany sposób synchronizacji za pomocą zmiennych globalnych - Jasne, skończysz na tym, że zbliża się termin, ale nie projektuj tego z myślą o swoim celu . Zasadniczo stan w grze nie jest stosem, a wszystkie stany w grze nie są ze sobą powiązane.
GSM, podobnie jak wskaźniki funkcji i zachowanie nielokalne, utrudniało debugowanie, chociaż debugowanie tego rodzaju dużych przejść między stanami nie było zbyt zabawne, zanim je mieliśmy. Zestawy stanów zamiast stosów stanów tak naprawdę nie pomagają, ale powinieneś o tym wiedzieć. Funkcje wirtualne zamiast wskaźników funkcji mogą to nieco złagodzić.
źródło
Oto przykładowa implementacja stosu gamestate, który okazał się bardzo przydatny: http://creators.xna.com/en-US/samples/gamestatemanagement
Jest napisany w C # i aby go skompilować potrzebujesz frameworka XNA, jednak możesz po prostu sprawdzić kod, dokumentację i wideo, aby uzyskać pomysł.
Może obsługiwać przejścia stanów, stany przezroczyste (takie jak modalne skrzynki komunikatów) i stany ładowania (które zarządzają rozładowaniem istniejących stanów i załadowaniem następnego stanu).
Używam teraz tych samych pojęć w moich projektach hobbystycznych (nie w języku C #) (oczywiście, może nie być odpowiedni dla większych projektów), a dla małych / hobbystycznych zdecydowanie mogę polecić takie podejście.
źródło
Jest to podobne do stosowanego przez nas stosu FSM. Zasadniczo po prostu daj każdemu stanowi funkcję enter, exit i tick i wywoływaj je w kolejności. Działa bardzo dobrze do obsługi rzeczy takich jak ładowanie.
źródło
Jeden z tomów „Gem Programming Gems” miał implementację automatu stanów przeznaczoną dla stanów gry; http://emergent.net/Global/Documents/textbook/Chapter1_GameAppFramework.pdf zawiera przykład wykorzystania go w małej grze i nie powinien być zbyt specyficzny dla Gamebryo, aby był czytelny.
źródło
Aby dodać trochę standaryzacji do dyskusji, klasycznym terminem CS dla tego rodzaju struktur danych jest automat pushdown .
źródło
Nie jestem pewien, czy stos jest całkowicie niezbędny, a także ogranicza funkcjonalność systemu państwowego. Używając stosu, nie można „wyjść” ze stanu do jednej z kilku możliwości. Załóżmy, że zaczynasz w „Menu głównym”, a następnie w „Załaduj grę”. Po pomyślnym załadowaniu zapisanej gry możesz przejść do stanu „Wstrzymaj” i powrócić do „Menu głównego”, jeśli użytkownik anuluje ładowanie.
Chciałbym tylko, aby stan określił stan, który ma być przestrzegany po wyjściu.
W przypadkach, w których chcesz powrócić do stanu poprzedzającego bieżący stan, na przykład „Menu główne-> Opcje-> Menu główne” i „Pauza-> Opcje-> Pauza”, po prostu przekaż jako parametr startowy do stanu stan, aby wrócić do.
źródło
Innym rozwiązaniem dla przejść i innych takich rzeczy jest zapewnienie stanu docelowego i źródłowego wraz z maszyną stanu, która może być powiązana z „silnikiem”, cokolwiek to może być. Prawda jest taka, że większość maszyn stanowych prawdopodobnie będzie musiała być dostosowana do danego projektu. Jedno rozwiązanie może przynieść korzyść tej lub innej grze, inne rozwiązania mogą to utrudnić.
Stany są wypychane z bieżącym stanem i maszyną jako parametrami.
Stany pojawiają się w ten sam sposób. To, czy zadzwonisz
Enter()
na niższyState
numer, jest kwestią dotyczącą implementacji.Wchodząc, aktualizując lub wychodząc,
State
dostaje wszystkie potrzebne informacje.źródło
Użyłem bardzo podobnego systemu w kilku grach i odkryłem, że z kilkoma wyjątkami, służy on jako doskonały model interfejsu użytkownika.
Jedynymi problemami, jakie napotkaliśmy, były przypadki, w których w niektórych przypadkach pożądane jest wycofanie wielu stanów przed wypchnięciem nowego stanu (przepuszczaliśmy interfejs użytkownika, aby usunąć wymaganie, ponieważ zwykle był to znak złego interfejsu użytkownika) i tworząc styl w stylu czarodzieja przepływy liniowe (rozwiązane łatwo poprzez przekazanie danych do następnego stanu).
Zastosowana implementacja owinęła stos i obsłużyła logikę aktualizacji i renderowania, a także operacje na stosie. Każda operacja na stosie wyzwalała zdarzenia w stanach, aby powiadomić ich o wystąpieniu operacji.
Dodano również kilka funkcji pomocniczych, aby uprościć typowe zadania, takie jak Zamień (Pop & Push, dla przepływów liniowych) i Resetuj (aby wrócić do menu głównego lub zakończyć przepływ).
źródło
Takie podejście podchodzę do prawie wszystkich moich projektów, ponieważ działa niesamowicie dobrze i jest niezwykle proste.
Mój najnowszy projekt, Sharplike , dokładnie kontroluje przepływ kontroli. Wszystkie nasze stany są połączone zestawem funkcji zdarzeń, które są wywoływane, gdy zmieniają się stany, i zawiera koncepcję „nazwanego stosu”, w której można mieć wiele stosów stanów w ramach tej samej maszyny stanów i rozgałęzić się między nimi - konceptualna narzędzie i nie jest konieczne, ale przydatne.
Ostrzegałbym przed paradygmatem „powiedz kontrolerowi, który stan powinien podążać za tym, kiedy się kończy” sugerowany przez Skizz: nie jest strukturalnie zdrowy i tworzy takie rzeczy jak okna dialogowe (które w standardowym paradygmacie stanu stosu wymagają jedynie utworzenia nowego podać podklasę z nowymi członkami, a następnie odczytać ją po powrocie do stanu wywołującego) o wiele trudniej niż to musi być.
źródło
Zasadniczo użyłem tego dokładnego systemu w kilku układach ortogonalnie; na przykład stany interfejsu i menu gry (inaczej „pauza”) miały własne stosy stanów. Interfejs użytkownika w grze również używał czegoś takiego, chociaż miał aspekty „globalne” (takie jak pasek zdrowia i mapa / radar), które zmiana koloru może zabarwić, ale które zostały zaktualizowane we wspólny sposób w różnych stanach.
Menu w grze może być „lepiej” reprezentowane przez DAG, ale z domyślną maszyną stanu (każda opcja menu, która przechodzi do innego ekranu, wie, jak tam przejść, a naciśnięcie przycisku Wstecz zawsze wyświetlało stan najwyższy) efekt był dokładnie to samo.
Niektóre z tych innych systemów również miały funkcję „zastępowania stanu najwyższego”, ale zazwyczaj była ona wdrażana w
StatePop()
późniejszym terminieStatePush(x);
.Obsługa kart pamięci była podobna, ponieważ faktycznie wepchnąłem tonę „operacji” do kolejki operacji (która funkcjonalnie zrobiła to samo co stos, podobnie jak FIFO, a nie LIFO); kiedy zaczniesz korzystać z tego rodzaju struktury („teraz dzieje się jedna rzecz, a kiedy jest zrobiona, wyskakuje sama”), zaczyna infekować każdy obszar kodu. Nawet AI zaczęła używać czegoś takiego; AI było „nieświadome”, a następnie zmieniło się w „ostrożne”, gdy gracz wydawał dźwięki, ale nie było go widać, a następnie ostatecznie wzrosło do „aktywnego”, gdy zobaczyło gracza (i w przeciwieństwie do mniejszych gier tamtych czasów, nie można było się ukryć w kartonie i spraw, aby wróg zapomniał o tobie! Nie, że jestem zgorzkniały ...).
GameState.h:
GameState.cpp:
źródło