Управление состоянием игры (Game, Menu, Titlescreen и т. Д.)

11

По сути, в каждой игре, которую я сделал до сих пор, у меня всегда есть переменная, такая как «current_state», которая может быть «game», «titlecreen», «gameoverscreen» и т. Д.

И тогда в моей функции обновления у меня есть огромное:

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

Однако я не чувствую, что это профессиональный / чистый способ обработки состояний. Любые идеи о том, как сделать это лучше? Или это стандартный способ?

Дэвид Гомес
источник
Какой язык, рамки и т. Д. Вы используете?
Петр Абдулин
Обычно Lua + LOVE. Я также только что обнаружил, что разные фреймворки по-разному справляются с этим. У SFML очень хороший класс Screen.
Дэвид Гомес
1
Вы смотрели в конечные автоматы?
Даркара
1
Вы также можете искать игровые состояния в строке поиска в верхнем правом углу. Должен дать некоторые результаты.
TravisG
Должна быть вторая Даркара - кажется, именно для этого и используются государственные машины.
balajeerc

Ответы:

14

Поскольку вы говорите об экранах, я чувствую, что лучше всего разделить всю эту логику на разные экраны. Что я обычно делаю:

Определите интерфейс, называемый экраном, и используйте несколько экранов для его реализации. Например, LoadScreen, MainMenuScreen, GameScreen, GameOverScreen, HighScoreScreen и т. Д. В вашей игре вы помещаете переменную, которая содержит текущий экран. Каждый цикл вы вызываете screen.update () и визуализируете текущий экран. Это сэкономит вам много «если это состояние сделает это», так как ваше состояние определяется текущим экраном.

Это очень хорошо разделит вашу логику.

Пример кода:

### 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();
    }
}

Или, в зависимости от вашей игровой настройки, у вас может быть бесконечный цикл в качестве игры.

while(true) {
    calculatetimesincelastframe()
    screen.update(time);
    screen.render(time);
}
Matsemann
источник
5

Если вы уже используете Middleclass, есть отличная библиотека конечных автоматов, которая называется Statefull . Он прост в использовании и поддерживает те же идеи, которые предлагал Матсеманн.

WuTangTan
источник
2

Если ваша current_stateпеременная является строкой, то в 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]()
Джон Калсбек
источник
1

Что я делаю примерно так:

У меня есть структура данных ориентированного ациклического графа , которая, по сути, представляет собой просто набор узлов, которые указывают друг на друга. Каждый узел представляет игровую систему. например, пользовательский интерфейс, мир, вход, рендеринг. И каждый узел указывает на другие узлы, которые идут до или после него. После того, как все узлы на месте, легко сгладить их в простой список. Настройка DAG - это первое, что я делаю при запуске игры. Каждый раз, когда я хочу добавить новую систему, скажем, AI, я могу просто написать этот код, а затем рассказать моей игре, от чего он зависит и что должно зависеть от него.

Мой основной игровой цикл идет после этого и просто запускает каждую систему по порядку. Сначала обрабатывается ввод, затем обновления мира, затем другие вещи ... Пользовательский интерфейс близок к концу, и рендеринг является последним. Когда игра только начинается, мира, физики и ИИ не существует, поэтому эти шаги, по сути, пропускаются, и отображается только титульный экран. Когда вы запускаете игру должным образом, пользовательский интерфейс отправляет сообщение мировой системе для включения, и он просто заботится о себе. Управление состоянием игры просто означает включение и выключение различных систем. Каждая система имеет свой собственный набор информации о состоянии, который обрабатывается более или менее независимо от всех других (это не совсемна самом деле, многие системы работают с одним и тем же набором данных - например, система пользовательского интерфейса берет данные из мира для отображения информации. Система ИИ также должна просматривать и отправлять сообщения объектам в мире).

Алекс Эймс
источник
Этот ответ является хорошим ответом на другой вопрос.
Мацеманн
Как же так? Он спросил, как настроить его различные игровые состояния, и мое решение состоит не в том, чтобы использовать конечный автомат, как сейчас, а в том, чтобы вместо этого разделить биты на различные системы, которые не являются конечным автоматом, а скорее DAG.
Алекс Эймс
1

Вот как я организую свои штаты в Lua + Love2d. Это позволяет избежать длинных операторов if / then.

Сначала я создаю базовый класс, который содержит методы update (dt) и render (). Вы также можете назначить ему методы обработки событий, например onKeyDown (key). Я называю этот класс Stage, но любой объект, который реализует методы, будет работать. Затем я создаю экземпляр этого класса для каждого игрового состояния, реализуя необходимые методы. Затем я создаю таблицу ключ / значение с именем состояния и экземпляром состояния. Затем следите за currentState в глобальной области видимости, чтобы состояния могли изменить его при выполнении определенного условия.

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
источник
-1

Ну, хотя это не очень хорошо, нормально обрабатывать состояния таким образом, ИМО. Вы можете сделать его намного чище, используя функции для каждого состояния, например:

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

или что-то другое мешает в этом подходе (я имею в виду, кроме того, что метод обновления очень длинный)?

Петр Абдулин
источник