Maszyny stanów Programowanie gier bez Unity, cz. 3 Piotr Korgul Koło Naukowe Twórców Gier Polygon 3 grudnia 2014 r.
Cykl Programowanie gier bez Unity 1 Jak zorganizować prace nad grą? 2 Jak działa gra? 3 Maszyny stanów 4 Zarządzanie zasobami 5 Wstęp do AI
Plan prezentacji 1 Problem 2 Próby rozwiązania 3 Maszyna stanów 4 Inne zastosowania 5 Podsumowanie
Problem
Menu główne [Esc] wyjście, [1] wybór poziomu, [2] creditsy
Credits [Esc] powrót do menu głównego
Wybór poziomu [1] poziom pierwszy
Rozgrywka [Esc] wyjście z gry
Próby rozwiązania
Rozwiązanie nr 1 Zajmujemy się handleinput() w pętli głównej. void handleinput ( Input input ) { if ( input == KEY_ESC ) { exit (); Na razie wszystko w porządku.
Rozwiązanie nr 1 Dodajmy creditsy puszczenie filmu: void handleinput ( Input input ) { if ( input == KEY_ESC ) { exit (); else if (input == KEY 2) { startdisplayingcredits(); Kto widzi buga?
Rozwiązanie nr 1 Naprawiamy buga: void handleinput ( Input input ) { if ( input == KEY_ESC ) { exit (); else if ( input == KEY_2 ) { if (!displayingcredits) { displayingcredits = true; startdisplayingcredits ();
Rozwiązanie nr 1 Dodajmy możliwość przedwczesnego wyjścia z creditsów: void handleinput ( Input input ) { if ( input == KEY_ESC ) { if (!displayingcredits) { exit (); else { displayingcredits = false; stopdisplayingcredits(); else if ( input == KEY_2 ) { if (! displayingcredits ) { displayingcredits = true ; startdisplayingcredits ();
Rozwiązanie nr 1 Dodajmy możliwość wybrania poziomu: void handleinput ( Input input ) { if ( input == KEY_ESC ) { //... else if (input == KEY 1) { showlevelstochoose(); else if ( input == KEY_2 ) { //... Kto widzi buga?
Rozwiązanie nr 1 Naprawiamy buga: void handleinput ( Input input ) { if ( input == KEY_ESC ) { //... else if ( input == KEY_1 ) { if (!displayinglevels) { displayinglevels = true; showlevelstochoose (); else if ( input == KEY_2 ) { //... Kto widzi kolejnego buga?
Rozwiązanie nr 1 Naprawiamy buga: void handleinput ( Input input ) { if ( input == KEY_ESC ) { //... else if ( input == KEY_1 ) { if (! displayinglevels &&!displayingcredits) { displayinglevels = true ; showlevelstochoose (); else if ( input == KEY_2 ) { //... Mamy jeszcze jednego buga. Jakiego?
Rozwiązanie nr 1 Naprawiamy buga: void handleinput ( Input input ) { if ( input == KEY_ESC ) { if (! displayingcredits &&!displayinglevels) { exit (); else if (!displayinglevels) { displayingcredits = false ; stopdisplayingcredits (); else if ( input == KEY_1 ) { //... else if ( input == KEY_2 ) { //... To nie koniec bugów! Kto wie, jakiego jeszcze mamy?
Rozwiązanie nr 1 Naprawiamy buga: void handleinput ( Input input ) { if ( input == KEY_ESC ) { //... else if ( input == KEY_1 ) { //... else if ( input == KEY_2 ) { if (! displayingcredits &&!displayinglevels) { displayingcredits = true ; startdisplayingcredits (); I tak dalej...
Rozwiązanie nr 1 Ostatecznie (1/3): void handleinput ( Input input ) { if ( input == KEY_ESC ) { if (! displayingcredits &&! displayinglevels ) { exit (); else if (! displayinglevels &&! runninggame ) { displayingcredits = false ; stopdisplayingcredits ();
Rozwiązanie nr 1 Ostatecznie (2/3): else if ( input == KEY_1 ) { if (! displayinglevels &&! displayingcredits &&! runninggame ) { displayinglevels = true ; showlevelstochoose (); else if ( displayinglevels ) { runninggame = true ; rungame (1) ;
Rozwiązanie nr 1 Ostatecznie (3/3): else if ( input == KEY_2 ) { if (! displayingcredits &&! displayinglevels &&! runninggame ) { displayingcredits = true ; startdisplayingcredits ();
Problemy z tym rozwiązaniem Właściwie same problemy, brak zalet
Inne ujęcie problemu
Inne ujęcie problemu
Inne ujęcie problemu
Inne ujęcie problemu
Inne ujęcie problemu
Inne ujęcie problemu Obserwacja: możemy się znajdować tylko w jednym stanie jednocześnie. Zatem flagi displayinglevels, displayingcredits są bez sensu. Wystarczy jedna zmienna. Może enum?
Rozwiązanie nr 2 void handleinput ( Input input ) { switch ( currentstate ) { case MAIN_MENU : if ( input == KEY_ESC ) { exit (); else if ( input == KEY_1 ) { showlevelstochoose (); currentstate = LEVEL_CHOICE ; else if ( input == KEY_2 ) { startdisplayingcredits (); currentstate = CREDITS ; break ;
Rozwiązanie nr 2 case CREDITS : if ( input == KEY_ESC ) { stopdisplayingcredits (); currentstate = MAIN_MENU ; break ; case LEVEL_CHOICE : if ( input == KEY_1 ) { rungame (1) ; currentstate = GAME ; break ; case GAME : if ( input == KEY_ESC ) { exit (); break ;
Wady i zalety tego rozwiązania Dużo prostsze i bardziej eleganckie Łatwe do zrozumienia Dużo mniej podatne na błędy Uciążliwe dodawanie kolejnych scen Bardzo długie funkcje Trudno zrobić animowane przejścia Tak naprawdę to już jest maszyna stanów, ale jej implementacja jeszcze nas w pełni nie satysfakcjonuje
FSM
Inne ujęcie problemu
Inne ujęcie problemu
Inne ujęcie problemu
Polimorfizm przypomnienie class Zwierze { public : virtual void dajglos () = 0; ; class Pies : public Zwierze { public : void dajglos () { cout << " Hau hau " << endl ; ; class Kot : public Zwierze { public : void dajglos () { cout << " Miau miau " << endl ; ; class Krowa : public Zwierze { public : void dajglos () { cout << " Muuuu " << endl ; ;
Polimorfizm przypomnienie Zwierze * zwierzeta [3] = { new Pies, new Kot, new Krowa ; for ( int i = 0; i < 3; i ++) { zwierzeta [i]-> dajglos (); Wynik działania programu: Hau hau Miau miau Muuuu
Implementacja class State { // lub Screen, Scene,... public : virtual void handleinput ( Input ) = 0; virtual void update ( float ) = 0; virtual void render () = 0; virtual void show () = 0; virtual void hide () = 0; ;
Implementacja class StateMachine { // lub ScreenManager, Director,... private : State * currentstate ; public : void handleinput ( Input ); void update ( float ) void render (); void changestate ( State *); ;
Implementacja void StateMachine :: handleinput ( Input input ) { currentstate -> handleinput ( input ); void StateMachine :: update ( float deltatime ) { currentstate -> update ( deltatime ); void StateMachine :: render () { currentstate -> render (); void StateMachine :: changestate ( State * newstate ) { currentstate -> hide (); delete currentstate ; currentstate = newstate ; currentstate -> show ();
Implementacja class MainMenu : public State { public : void handleinput ( Input ); void update ( float ) void render (); void show (); void hide (); ; void MainMenu :: handleinput ( Input input ) { if ( input == KEY_ESC ) { exit (); else if ( input == KEY_1 ) { // showlevelstochoose (); statemachine - > changestate ( new LevelChoice ); else if ( input == KEY_2 ) { // startdisplayingcredits (); statemachine - > changestate ( new Credits );
Implementacja class Credits : public State { // handleinput, update, render, show, hide ; void Credits :: handleinput ( Input input ) { if ( input == KEY_ESC ) { // stopdisplayingcredits (); statemachine - > changestate ( new MainMenu ); void Credits :: show () { startdisplayingcredits (); void Credits :: hide () { stopdisplayingcredits ();
Implementacja class LevelChoice : public State { // handleinput, update, render, show, hide ; void LevelChoice :: handleinput ( Input input ) { if ( input == KEY_1 ) { // rungame (1) ; statemachine -> changestate ( new Game (1) ); void LevelChoice :: show () { showlevelstochoose ();
Implementacja class Game : public State { private : int level ; public : Game ( int ); // handleinput, update, render, show, hide ; Game :: Game ( int lvl ) : level ( lvl ) { void Game :: handleinput ( Input input ) { if ( input == KEY_ESC ) { exit (); void Game :: show () { rungame ( level );
Zalety Zgodne z zasadami OOP i BHP Bardzo eleganckie i mało podatne na błędy Wydzielenie funkcjonalności każdej sceny do osobnej klasy Dodawanie kolejnych scen jest proste Krótkie funkcje show(), hide()
Inne zastosowania
Przejścia pomiędzy scenami
Stany głównego bohatera
Sztuczna inteligencja
Podsumowanie
Podsumowanie uproszczenie wielu problemów lepsza organizacja kodu mniejsza podatność na błędy możliwość zastosowania w wielu sytuacjach...ale czy zawsze warto?
Literatura uzupełniająca Robert Nystrom Game Programming Patterns gameprogrammingpatterns.com
Literatura uzupełniająca Banda Czworga Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku en.wikipedia.org/wiki/design Patterns