Zarządzanie stanem gry (gra, menu, ekran tytułowy itp.)

11

Zasadniczo w każdej grze, którą do tej pory stworzyłem, zawsze mam zmienną, taką jak „aktualny stan”, która może być „grą”, „ekranem tytułów”, „ekranem gry” itp.

A potem na mojej funkcji aktualizacji mam ogromny:

if current_state == "game" 
  game stuf
  ...
else if current_state == "titlescreen"
  ...

Nie wydaje mi się jednak, aby był to profesjonalny / czysty sposób radzenia sobie ze stanami. Wszelkie pomysły, jak to zrobić w lepszy sposób? Czy jest to standardowy sposób?

David Gomes
źródło
Z jakiego języka, frameworku itp. Korzystasz?
Petr Abdulin,
Zwykle Lua + LOVE. Właśnie odkryłem, że różne frameworki mają różne sposoby radzenia sobie z tym. SFML wydaje się mieć bardzo fajną klasę Screen.
David Gomes,
1
Czy zajrzałeś do maszyn stanowych?
Darcara,
1
Możesz także poszukać gamestates na pasku wyszukiwania w prawym górnym rogu. Powinien dać jakieś wyniki.
TravisG,
Musi zająć drugie miejsce w Darcara - wydaje się, że właśnie do tego służą maszyny stanu.
balajeerc,

Odpowiedzi:

14

Ponieważ mówisz o ekranach, uważam, że najlepiej jest rozdzielić całą logikę na różne ekrany. Co zwykle robię:

Zdefiniuj interfejs o nazwie screen i zaimplementuj go na wielu ekranach. Podobnie jak LoadingScreen, MainMenuScreen, GameScreen, GameOverScreen, HighScoreScreen itp. W grze umieszczasz zmienną, która przechowuje bieżący ekran. W każdej pętli wywołujesz screen.update () i renderujesz bieżący ekran. Zaoszczędzi ci to dużo „jeśli ten stan to zrobi”, ponieważ stan jest określony przez bieżący ekran.

To bardzo ładnie oddzieli Twoją logikę.

Przykładowy kod:

### Screen interface ###
public interface Screen {

    public void show();

    public void update(float delta);

    public void render(float delta);

    public void hide ();
}

### An implementation of screen ###
public class MainMenuScreen implements Screen {

    private Game game;

    public MainMenuScreen(Game game) {
        this.game = game;
    }

    public void show() {
        // init stuff
    }

    public void update(float delta) {
        // react to clicks, update animations etc.
        if (buttonwasclicked) {
            game.setScreen(new GameScreen(game)); // change the screen
        }
    }

    public void render(float delta) {
        // draw everything
    }

    public void hide() {
        // release all resources, as the screen is being hidden
    }
}

### Game, drawing the appropriate screen ###
public class Game {

    public Screen screen;

    public void update() {
        screen.update(getDeltaTime);
        screen.render();
    }

    public void setScreen(Screen screen) {
        this.screen.hide();

        this.screen = screen;
        this.screen.show();
    }
}

Lub w zależności od konfiguracji gry, być może masz nieskończoną pętlę jako grę.

while(true) {
    calculatetimesincelastframe()
    screen.update(time);
    screen.render(time);
}
Matsemann
źródło
5

Jeśli już używasz klasy Middleclass, istnieje znakomita biblioteka automatów stanów, która nazywa się Statefull . Jest łatwy w użyciu i zawiera te same pomysły, które zaproponował Matsemann.

WuTangTan
źródło
2

Jeśli twoja current_statezmienna jest łańcuchem, to jest to bardzo łatwe w Lua:

game_states = {}
function game_states.game()
    -- game stuff
end
function game_states.titlescreen()
    -- title screen stuff
end

-- then, inside the Update function:
game_states[current_state]()
John Calsbeek
źródło
1

To, co robię, jest z grubsza następujące:

Mam ukierunkowaną strukturę danych wykresu acyklicznego , która jest w zasadzie tylko grupą węzłów, które wskazują na siebie. Każdy węzeł reprezentuje system gry. np. interfejs użytkownika, świat, dane wejściowe, rendering. I każdy węzeł wskazuje na inne węzły, które pojawiają się przed nim lub po nim. Po umieszczeniu wszystkich węzłów łatwo jest spłaszczyć je na prostej liście. Konfigurowanie tego DAG to pierwsza rzecz, którą robię podczas uruchamiania gry. Za każdym razem, gdy chcę dodać nowy system, powiedzmy AI, mogę po prostu napisać ten kod, a następnie powiedzieć mojej grze, od czego zależy i od czego powinien zależeć.

Moja główna pętla gry pojawia się później i po prostu uruchamia kolejno każdy system. Obsługiwane są pierwsze dane wejściowe, później aktualizacje świata, a następnie inne rzeczy ... Interfejs użytkownika jest na końcu, a renderowanie jest ostatnie. Gdy gra zaczyna się po raz pierwszy, nie ma świata, fizyki ani sztucznej inteligencji, więc te kroki są w zasadzie pomijane i wyświetla się tylko ekran tytułowy. Po uruchomieniu właściwej gry interfejs użytkownika wysyła komunikat do systemu światowego, aby się włączył, a on sam się o to zadba. Zarządzanie stanem gry oznacza po prostu włączanie i wyłączanie różnych systemów. Każdy system ma swój własny zestaw informacji o stanie, który jest obsługiwany mniej lub bardziej niezależnie od wszystkich innych (to nie jest całkowicieto prawda, że ​​wiele systemów działa na tym samym zestawie danych - na przykład system interfejsu użytkownika pobiera dane ze świata, aby na przykład wyświetlać informacje. System AI musi także patrzeć i wysyłać wiadomości do podmiotów na świecie).

Alex Ames
źródło
Ta odpowiedź jest dobrą odpowiedzią na inne pytanie.
Matsemann,
Jak to? Zapytał, jak ustawić różne stany gry, a moim rozwiązaniem nie jest użycie automatu stanowego, jaki ma teraz, ale podzielenie bitów na różne systemy, które nie są maszyną stanową, ale raczej DAG.
Alex Ames,
1

Oto jak organizuję swoje stany w Lua + Love2d. Pozwala to uniknąć długich instrukcji if / then.

Najpierw tworzę podstawową klasę zawierającą metody update (dt) i render (). Możesz także nadać mu metody obsługi zdarzeń, takie jak onKeyDown (klucz). Nazywam tę klasę Stage, ale każdy obiekt implementujący metody będzie działał. Następnie tworzę instancję tej klasy dla każdego stanu gry, wdrażając niezbędne metody. Następnie tworzę tabelę kluczy / wartości z nazwą stanu i jego instancją. Następnie śledź bieżący stan w zasięgu globalnym, aby stany mogły go zmienić, gdy spełniony zostanie określony warunek.

states = {}
states["title"] = title   -- Where title implements Stage class.
states["game"] = game     -- You could create the instance of 'game' lazily too.
currentState = "title"

function love.update(dt)
    if states[currentState] ~= nil then
       states[currentState]:update(dt) 
    end
end
h4tch
źródło
-1

Cóż, chociaż nie jest to całkiem w porządku, obsługa stanów w ten sposób, IMO. Możesz uczynić go o wiele czystszym, używając funkcji dla każdego stanu, takich jak:

if current_state == "game" 
  game()
else if current_state == "titlescreen"
  titlescreen()

czy coś innego przeszkadza ci w tym podejściu (mam na myśli, że metoda aktualizacji jest bardzo długa)?

Petr Abdulin
źródło