Игровое состояние 'Stack'?

Я думал о том, как реализовать игровые состояния в моей игре. Главное, что я хочу для этого:

  • Полупрозрачные верхние состояния - возможность видеть через меню паузы в игре позади

  • Что-то OO-я считаю, что это проще в использовании и понимании теории позади, а также в сохранении orgranised и добавлении больше.



Я планировал использовать связанный список и рассматривать его как стек. Это означает, что я мог бы получить доступ к состоянию ниже для полупрозрачности.
План. Содержит ли стек состояний связанный список указателей на IGameStates. Верхнее состояние обрабатывает свои собственные команды обновления и ввода, а затем имеет член, являющийся прозрачным, чтобы решить, должно ли отображаться состояние под ним.
Тогда я мог бы сделать:

states.push_back(new MainMenuState());
states.push_back(new OptionsMenuState());
states.pop_front();

Чтобы представить загрузку проигрывателя, перейдите к опциям, а затем в главное меню.
Это хорошая идея, или ...? Должен ли я взглянуть на что-то еще?

Спасибо.

52 голоса | спросил The Communist Duck 28 J000000Wednesday10 2010, 22:05:50

10 ответов


43

Я работал над тем же движком, что и coderanger. У меня другая точка зрения. :)

Во-первых, у нас не было пакета FSM - у нас был стек состояний. Стек штатов делает один FSM. Я не знаю, как выглядит стек FSM. Возможно, слишком сложно сделать что-то практическое.

Моя самая большая проблема с нашей глобальной машиной состояния состояла в том, что это был стек состояний, а не набор состояний. Это означает, например, ... /MainMenu /Загрузка отличается от ... /Loading /MainMenu, в зависимости от того, было ли у вас главное меню до или после экрана загрузки (игра асинхронная, а загрузка в основном зависит от сервера ).

В качестве двух примеров вещей это сделало уродливым:

  • Это привело к, например, состояние LoadGameplay, поэтому у вас есть Base /Loading и Base /Gameplay /LoadingGameplay для загрузки в состоянии Gameplay, который должен был повторить большую часть кода в нормальном состоянии загрузки (но не все и добавить еще немного).
  • У нас было несколько функций, таких как «если в создателе персонажа перейти в игровой процесс, если в игровом процессе перейдите к выбору персонажа, если в выборе символа вернитесь к логину», потому что мы хотели показать одни и те же окна интерфейса в разных состояниях, но сделать Кнопки «Назад /Вперед» все еще работают.

Несмотря на название, это было не очень «глобально». Большинство внутренних систем game не использовали его для отслеживания своих внутренних состояний, потому что они не хотели, чтобы их состояния сливались с другими системами. Другие, например. система пользовательского интерфейса, может использовать ее, но только для копирования состояния в свои локальные государственные системы. (Я бы особо предостерег от системы для состояний пользовательского интерфейса. Состояние пользовательского интерфейса - это не стек, это действительно DAG, и попытка заставить любую другую структуру на нем будет только создавать пользовательские интерфейсы, которые разочаровывают.)

Для этого было нужно изолировать задачи по интеграции кода с программистов инфраструктуры, которые не знали, как игровой поток действительно структурирован, поэтому вы могли бы сказать, что парень пишет патчер «поместите свой код в Client_Patch_Update», а парень записывая графическую загрузку, «поместите свой код в Client_MapTransfer_OnEnter», и мы могли бы без особых проблем поменять некоторые логические потоки.

В побочном проекте мне повезло с состоянием set , а не стек , не боясь создавать несколько машин для несвязанных систем и отказываться от позвольте себе попасть в ловушку наличия «глобального состояния», что на самом деле является просто сложным способом синхронизации вещей через глобальные переменные. Конечно, вы собираетесь сделать это в некоторый крайний срок, но не планируете с этим как ваша цель . По сути, состояние в игре - это не стек, а состояния в игре не все связаны.

GSM также, как правило, как указатели функций и нелокальное поведение, затрудняет отладку вещей, хотя отладка таких крупных переходов состояния была не очень забавной, прежде чем мы ее получили. Государственные наборы вместо государственных стеков на самом деле не помогают этому, но вы должны знать об этом. Виртуальные функции, а не указатели на функции могут немного облегчить это.

ответил 28 J000000Wednesday10 2010, 22:42:03
11

Вот пример реализации стека gamestate, который я нашел очень полезным: http: //creators.xna .com /EN-US /образцы /gamestatemanagement

Это написано на C # и для его компиляции вам нужна инфраструктура XNA, однако вы можете просто проверить код, документацию и видео, чтобы получить эту идею.

Он может поддерживать переходы состояний, прозрачные состояния (такие как модальные окна сообщений) и состояния загрузки (которые управляют разгрузкой существующих состояний и загрузкой следующего состояния).

Я использую те же самые концепции в моих (не-C #) проектах хобби сейчас (предоставляется, возможно, не подходит для крупных проектов), а для небольших /хобби проектов я могу определенно рекомендовать подход.

ответил Janis Kirsteins 28 J000000Wednesday10 2010, 23:23:23
5

Это похоже на то, что мы используем, стек FSM. В основном просто дайте каждому состоянию функцию enter, exit и tick и вызовите их по порядку. Работает очень хорошо для обработки вещей, таких как загрузка.

ответил coderanger 28 J000000Wednesday10 2010, 22:12:12
3

Один из томов «Game Programming Gems» имел реализацию конечного автомата в нем, предназначенную для игровых состояний; http://emergent.net/Global/Documents/textbook/Chapter1_GameAppFramework.pdf имеет пример того, как использовать его для небольшой игры, и не должен быть слишком игровым, чтобы быть читаемым.

ответил Tom Hudson 2 PM00000040000000331 2010, 16:49:03
3

Чтобы добавить небольшую стандартизацию в обсуждение, классический термин CS для таких структур данных данных pushdown automaton .

ответил munificent 11 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowSat, 11 Sep 2010 04:03:42 +0400 2010, 04:03:42
1

Я не уверен, что стек необходим, а также ограничивает функциональность государственной системы. Используя стек, вы не можете «выйти» из состояния в одну из нескольких возможностей. Скажите, что вы начинаете в «Главное меню», затем переходите в «Загрузка игры», вы можете перейти в состояние «Пауза» после успешной загрузки сохраненной игры и вернуться в «Главное меню», если пользователь отменяет загрузку.

Я просто хотел бы указать состояние, которое будет следовать, когда оно выйдет.

Для тех случаев, когда вы хотите вернуться в состояние, предшествующее текущему состоянию, например «Главное меню-> Параметры-> Главное меню» и «Пауза-> Параметры-> Пауза», просто перейдите в качестве параметр запуска для состояния, в которое возвращается состояние.

ответил Skizz 29 J000000Thursday10 2010, 15:22:18
1

Другим решением переходов и других подобных вещей является предоставление состояния назначения и источника вместе с конечным автоматом, который может быть связан с «движком», каким бы он ни был. По правде говоря, большинство государственных машин, вероятно, придется адаптировать к проекту. Одно решение может принести пользу той или иной игре, другие решения могут помешать ей.

class StateMachine
{
public:
    StateMachine(Engine *);
    void Push(State *);
    State *Pop();
    void Update();
    Engine *GetEngine();

private:
    std::stack<State *> _states;
    Engine *_engine;
};

Государства вводятся с текущим состоянием и машиной в качестве параметров.

void StateMachine::Push(State *state)
{
    State *from = 0;
    if (!_states.empty()) from = _states.top();
    _states.push(state);
    state->Enter(this, from);
}

Штаты выбиваются таким же образом. Вызов Enter() в нижнем State является вопросом реализации.

State *StateMachine::Pop()
{
    _ASSERT(!_states.empty());
    State *state = _states.top();
    State *to = 0;
    _states.pop();
    if (!_states.empty()) to = _states.top();
    state->Exit(this, to);
    return state;
}

При вводе, обновлении или выходе, State получает всю необходимую информацию.

void SomeGameState::Enter(StateMachine *sm, State *from)
{
    Engine *eng = sm->GetEngine();
    eng->GetKeyboard()->KeyDown.Bind(this, &SomeGameState::KeyDown);
    LoadLevelState *state = new LoadLevelState();
    state->SetLevel(eng->GetSaveGame()->GetLevelName());
    state->Load.Bind(this, &SomeGameState::OnLevelLoaded);
    sm->Push(state);
}

void SomeGameState::Update(StateMachine *sm)
{
    Engine *eng = sm->GetEngine();
    float time = eng->GetFrameTime();
    if (shouldExit)
        sm->Pop();
}

void SomeGameState::Exit(StateMachine *sm, State *from)
{
    Engine *eng = sm->GetEngine();
    eng->GetKeyboard()->KeyDown.UnsubscribeAll(this);
}
ответил Nick Bedford 9 Jam1000000amMon, 09 Jan 2012 04:50:20 +040012 2012, 04:50:20
0

Я использовал очень похожую систему в нескольких играх и обнаружил, что за пару исключений она служит отличной моделью пользовательского интерфейса.

Единственными проблемами, с которыми мы столкнулись, были случаи, когда в некоторых случаях было желательно всплывать несколько состояний перед нажатием нового состояния (мы повторно протестировали пользовательский интерфейс, чтобы удалить это требование, поскольку это обычно было признаком плохого пользовательского интерфейса) и создания линейных потоков в стиле мастера (легко решается путем передачи данных в следующее состояние).

Реализация, которую мы использовали, фактически завернула стек и обработала логику для обновления и рендеринга, а также операции над стеком. Каждая операция в стеке вызывала события в состояниях, чтобы уведомить их о происходящей операции.

Несколько вспомогательных функций были добавлены также для упрощения общих задач, таких как Swap (Pop & Push, для линейных потоков) и Reset (для возврата в главное меню или окончания потока).

ответил Jason Kozak 30 J000000Friday10 2010, 18:17:15
0

Это подход, который я беру для почти всех моих проектов, потому что он работает невероятно хорошо и очень просто.

Мой последний проект, Sharplike , обрабатывает поток управления именно таким образом. Наши состояния связаны с набором функций событий, которые вызывается при изменении состояний, и в нем есть концепция «названного стека», в которой вы можете иметь несколько стеков состояний внутри одного и того же конечного автомата и ветви между ними - концептуальную инструмент, и не обязательно, но удобно иметь.

Я бы предостерег от того, что «скажите контроллеру, какое состояние должно следовать за этим, когда оно заканчивается», парадигма, предложенная Skizz: она не является структурно звуковой и делает такие вещи, как диалоговые окна (которые в стандартной парадигме состояния стека просто связаны создавая новый подкласс состояний с новыми членами, а затем считывая его, когда вы возвращаетесь в вызывающее состояние) намного сложнее, чем это должно быть.

ответил Ed Ropple 30 J000000Friday10 2010, 21:00:35
0

Я использовал в основном эту точную систему в нескольких системах ортогонально; интерфейс и внутриигровое меню (например, «пауза»), например, имеют свои собственные штатные стеки. В игре пользовательский интерфейс также использовал что-то вроде этого, хотя в нем были «глобальные» аспекты (например, панель безопасности и карта /радар), которые могут переключаться на состояние, но которые обновляются обычным образом в разных состояниях.

В игровом меню может быть «лучше», представленном DAG, но с неявным автоматом состояния (каждый пункт меню, который переходит на другой экран, знает, как туда попасть, и нажатие кнопки «Назад» всегда выталкивало верхнее состояние) эффект был точно таким же.

Некоторые из этих других систем также имели функциональность «заменить верхнее состояние», но обычно это выполнялось как StatePop(), за которым следует StatePush(x);.

Работа с картами памяти была аналогичной, поскольку я фактически нажимал тонну «операций» в очередь операций (которая функционально делала то же самое, что и стек, как FIFO, а не LIFO); как только вы начнете использовать такую ​​структуру («сейчас происходит одна вещь, и когда это делается, она всплывает сама»), она начинает заражать каждую область кода. Даже ИИ начал использовать что-то вроде этого; ИИ был «невежественным», а затем переключился на «настороженно», когда игрок сделал шумы, но не был замечен, а затем, наконец, поднялся до «активного», когда увидел игрока (и в отличие от меньших игр того времени вы не могли скрыть в картонной коробке и заставлять врага забыть о вас! Не то, чтобы я горько ...).

GameState.h:

enum GameState
{
   k_frontend,
   k_gameplay,
   k_inGameMenu,
   k_moviePlayback,
   k_numStates
};

void GameStatePush(GameState);
void GameStatePop();
void GameStateUpdate();

GameState.cpp:

// k_maxNumStates could be bigger, but we don't need more than
// one of each state on the stack.
static const int k_maxNumStates = k_numStates;
static GameState s_states[k_maxNumStates] = { k_frontEnd };
static int s_numStates = 1;

static void (*s_startupFunctions)()[] =
   { FrontEndStart, GameplayStart, InGameMenuStart, MovieStart };
static void (*s_shutdownFunctions)()[] =
   { FrontEndStop, GameplayStop, InGameMenuStop, MovieStop };
static void (*s_updateFunctions)()[] =
   { FrontEndUpdate, GameplayUpdate, InGameMenuUpdate, MovieUpdate };

static void GameStateStart(GameState);
static void GameStateStop(GameState);

void GameStatePush(GameState gs)
{
   Assert(s_numStates < k_maxNumStates);
   GameStateStop(s_states[s_numStates - 1])
   s_states[s_numStates] = gs;
   s_numStates++;
   GameStateStart(gs);
}

void GameStatePop()
{
   Assert(s_numStates > 1);  // can't pop last state
   s_numStates--;
   GameStateStop(s_states[s_numStates]);
   GameStateStart(s_states[s_numStates - 1]);
}

void GameStateUpdate()
{
   GameState current = s_states[s_numStates - 1];
   s_updateFunctions[current]();
}

void GameStateStart(GameState gs)
{
   s_startupFunctions[gs]();
}

void GameStateStop(GameState gs)
{
   s_shutdownFunctions[gs]();
}
ответил dash-tom-bang 29 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowWed, 29 Sep 2010 04:25:25 +0400 2010, 04:25:25

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132