Методы управления состоянием игры?

24

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

В качестве конкретного примера, скажем, это игра классической Battlechess. После того, как я сделал ход, чтобы взять фигуру другого игрока, начинается короткая битва. Во время этой последовательности игроку не разрешается перемещать фигуры. Итак, как бы вы отслеживали такого рода переходы состояний? Конечный автомат? Простая логическая проверка? Кажется, что последний будет хорошо работать только для игры с очень небольшим количеством изменений состояния такого рода.

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

vargonian
источник
Вы проверили gamedev.stackexchange.com/questions/1783/game-state-stack и gamedev.stackexchange.com/questions/2423/… ? Все это похоже на одну и ту же концепцию, но я не могу придумать ничего лучше, чем конечный автомат для игрового состояния.
michael.bartnett
Возможный дубликат: gamedev.stackexchange.com/questions/12664/...
Тетрада

Ответы:

18

Однажды я наткнулся на статью, которая довольно элегантно решает вашу проблему. Это базовая реализация FSM, которая вызывается в вашем основном цикле. Я изложил основное краткое изложение статьи в оставшейся части этого ответа.

Ваше основное игровое состояние выглядит так:

class CGameState
{
    public:
        // Setup and destroy the state
        void Init();
        void Cleanup();

        // Used when temporarily transitioning to another state
        void Pause();
        void Resume();

        // The three important actions within a game loop
        void HandleEvents();
        void Update();
        void Draw();
};

Каждое игровое состояние представлено реализацией этого интерфейса. Для вашего примера Battlechess это может означать следующие состояния:

  • вводная анимация
  • главное меню
  • анимация настройки шахматной доски
  • ввод хода игрока
  • анимация перемещения игрока
  • анимация движения противника
  • меню паузы
  • экран эндшпиля

Состояния управляются в вашем государственном движке:

class CGameEngine
{
    public:
        // Creating and destroying the state machine
        void Init();
        void Cleanup();

        // Transit between states
        void ChangeState(CGameState* state);
        void PushState(CGameState* state);
        void PopState();

        // The three important actions within a game loop
        // (these will be handled by the top state in the stack)
        void HandleEvents();
        void Update();
        void Draw();

        // ...
};

Обратите внимание, что каждому состоянию в какой-то момент требуется указатель на CGameEngine, поэтому само состояние может решить, следует ли вводить новое состояние. В статье предлагается передать CGameEngine в качестве параметра для HandleEvents, Update и Draw.

В конце ваш основной цикл имеет дело только с движком состояния:

int main ( int argc, char *argv[] )
{
    CGameEngine game;

    // initialize the engine
    game.Init( "Engine Test v1.0" );

    // load the intro
    game.ChangeState( CIntroState::Instance() );

    // main loop
    while ( game.Running() )
    {
        game.HandleEvents();
        game.Update();
        game.Draw();
    }

    // cleanup the engine
    game.Cleanup();
    return 0;
}
призрак
источник
17
С для класса? Еа. Впрочем, это хорошая статья - +1.
Коммунистическая утка
Из того, что я могу понять, это вопрос, о котором вопрос явно не задается. Это не значит, что вы не могли бы справиться с этим таким способом, как вы, конечно, могли бы, но если все, что вы хотели сделать, это временно отключить ввод, я думаю, что это и избыточно, и плохо для обслуживания, чтобы вывести новый подкласс CGameState, который собирается быть на 99% идентичным другому подклассу.
Kylotan
Я думаю, это сильно зависит от того, как код соединен вместе. Я могу представить себе четкое разделение между выбором фигуры и назначением (в основном, индикаторами пользовательского интерфейса и обработкой ввода) и анимацией шахматной фигуры по направлению к этому назначению (анимация всей доски, когда другие фигуры смещаются, взаимодействуют с движущимися фигурами). и т. д.), делая государства далеко не идентичными. Это разделяет ответственность, позволяя легко обслуживать и даже использовать повторно (вводная демонстрация, режим воспроизведения). Я думаю, что это также отвечает на вопрос, показывая, что использование FSM не должно быть проблемой.
призрак
Это действительно здорово, спасибо. Ключевой момент, который вы указали, был в вашем последнем комментарии: «Использование FSM не должно быть проблемой». Я ошибочно полагал, что использование FSM потребует использования операторов switch, что не всегда верно. Другое ключевое подтверждение состоит в том, что каждому состоянию нужна ссылка на игровой движок; Я задавался вопросом, как это будет работать иначе.
Варгонский
2

Я начинаю с обработки такого рода вещей самым простым способом.

bool isPieceMoving;

Затем я добавлю проверки против этого логического флага в соответствующих местах.

Если позже я обнаружу, что мне нужно больше особых случаев, чем этот - и только он, - я снова превращаюсь во что-то лучшее. Я обычно использую 3 подхода:

  • Рефакторинг любых исключительных флагов, представляющих подсостояние, в перечисления. например. enum { PRE_MOVE, MOVE, POST_MOVE }и добавьте переходы, где это необходимо. Затем я могу проверить это перечисление, где я использовал проверку на логический флаг. Это простое изменение, но оно уменьшает количество вещей, с которыми вам приходится проверяться, позволяет вам использовать операторы switch для эффективного управления поведением и т. Д.
  • Отключите отдельные подсистемы, как вам нужно. Если единственное различие во время битвы состоит в том, что вы не можете перемещать фигуры, вы можете коллировать pieceSelectionManager->disable()или подобное в начале последовательности, и pieceSelectionManager->enable(). У вас все еще есть флаги, но теперь они хранятся ближе к объекту, которым они управляют, и вам не нужно поддерживать какое-либо дополнительное состояние в коде игры.
  • Предыдущая часть подразумевает существование PieceSelectionManager: в общем, вы можете разделить части состояния и поведения вашей игры на более мелкие объекты, которые обрабатывают подмножество общего состояния согласованным образом. Каждый из этих объектов будет иметь свое собственное состояние, которое определяет его поведение, но им легко управлять, поскольку он изолирован от других объектов. Не поддавайтесь желанию позволить вашему игровому объекту или основному циклу стать дампом для псевдоглобальных объектов и учитывать это!

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

Kylotan
источник
1
Да, я полагаю, что есть грань между полным использованием состояний и использованием при необходимости bool / enums. Но, зная мои педантичные тенденции, я, вероятно, в конечном итоге сделаю почти каждый штат своим классом.
Варгониан
Вы говорите так, будто класс более правильный, чем альтернативы, но помните, что это субъективно. Если вы начнете создавать слишком много небольших классов для вещей, которые легче представить другими языковыми конструкциями, это может затенить смысл кода.
Килотан
1

http://www.ai-junkie.com/architecture/state_driven/tut_state1.html - прекрасное руководство по управлению состоянием игры! Вы можете использовать его как для игровых объектов, так и для системы меню, как указано выше.

Он начинает учить о Государственном шаблоне проектирования , а затем приступает к реализации State Machineи последовательно расширяет его. Это очень хорошее чтение! Даст вам четкое понимание того, как работает вся концепция и как применять ее к новым типам проблем!

Zolomon
источник
1

Я стараюсь не использовать конечный автомат и логические значения для этой цели, потому что оба они не масштабируемы. Оба превращаются в беспорядок, когда число государств растет.

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

Например, в вашем случае с отключением ввода игрока: у вас есть какой-то пользовательский обработчик ввода и какая-то визуальная индикация того, что ввод отключен, вы должны сделать их одним объектом или компонентом, поэтому для отключения ввода вы просто отключаете весь объект, не нужно синхронизировать их в каком-либо автомате или реагировать на какой-либо логический индикатор.

Филипп Кекс
источник