Я думал о том, как внедрить игровые состояния в мою игру. Главные вещи, которые я хочу для этого:
Полупрозрачные верхние состояния - возможность видеть через меню паузы игру позади
Что-то в этом роде. Мне легче использовать и понять теорию, а также поддерживать организацию и добавлять к ней больше.
Я планировал использовать связанный список и рассматривать его как стек. Это означает, что я мог получить доступ к состоянию ниже для полупрозрачности.
План: наличие в стеке состояний связанного списка указателей на IGameStates. Верхнее состояние обрабатывает свои собственные команды обновления и ввода, а затем имеет член isTransparent, чтобы решить, должно ли быть нарисовано нижнее состояние.
Тогда я мог бы сделать:
states.push_back(new MainMenuState());
states.push_back(new OptionsMenuState());
states.pop_front();
Чтобы отобразить загрузку плеера, перейдите в раздел параметров, а затем в главное меню.
Это хорошая идея или ...? Должен ли я посмотреть на что-то еще?
Благодарю.
источник
new
способ, показанный в примере кода, он просто запрашивает утечки памяти или другие, более серьезные ошибки.Ответы:
Я работал на том же движке, что и Coderanger. У меня другая точка зрения. :)
Во-первых, у нас не было стека автоматов - у нас был стек состояний. Стек состояний составляет один FSM. Я не знаю, как будет выглядеть стек автоматов. Вероятно, слишком сложно, чтобы сделать что-нибудь практическое с.
Моя самая большая проблема с нашим Global State Machine состояла в том, что это был набор состояний, а не набор состояний. Это означает, например, ... / MainMenu / Загрузка отличалась от ... / Загрузка / MainMenu, в зависимости от того, открылось ли главное меню до или после экрана загрузки (игра асинхронная и загрузка в основном выполняется сервером ).
В качестве двух примеров вещей это сделало безобразным:
Несмотря на название, оно было не очень «глобальным». Большинство внутренних игровых систем не использовали его для отслеживания своих внутренних состояний, потому что они не хотели, чтобы их состояния были испорчены другими системами. Другие, например система пользовательского интерфейса, могут использовать ее, но только для копирования состояния в свои собственные локальные системы состояний. (Я бы особенно предостерегал против системы для состояний пользовательского интерфейса. Состояние пользовательского интерфейса - это не стек, а действительно DAG, и попытка навязать ему любую другую структуру приведет только к тому, что пользовательские интерфейсы будут разочаровывать.)
Для этого было полезно изолировать задачи для интеграции кода от инфраструктурных программистов, которые не знали, как на самом деле структурирован игровой процесс, чтобы вы могли сказать парню, пишущему патчер, «поместить свой код в Client_Patch_Update», а парню, пишущему графику. загрузка «поместите ваш код в Client_MapTransfer_OnEnter», и мы могли бы без особых проблем поменять местами определенные логические потоки.
В стороннем проекте мне больше повезло с набором состояний, а не со стеком , не боясь создавать несколько машин для несвязанных систем и не позволяя себе попасть в ловушку «глобального состояния», которое действительно просто сложный способ синхронизировать вещи через глобальные переменные - конечно, вы в конечном итоге сделаете это в какой-то крайний срок, но не замышляйте это как свою цель . По сути, состояние в игре не является стеком, и состояния в игре не все связаны между собой.
GSM также, как обычно делают указатели функций и нелокальное поведение, усложнял отладку, хотя отладка такого рода больших переходов между состояниями была не очень забавной, пока мы ее не сделали. Наборы состояний вместо стеков состояний на самом деле не помогают, но вы должны знать об этом. Виртуальные функции, а не указатели на функции могут несколько смягчить это.
источник
Вот пример реализации стека игровых состояний, который я считаю очень полезным: http://creators.xna.com/en-US/samples/gamestatemanagement
Он написан на C #, и для его компиляции вам понадобится среда XNA, однако вы можете просто проверить код, документацию и видео, чтобы получить идею.
Он может поддерживать переходы состояний, прозрачные состояния (такие как модальные окна сообщений) и состояния загрузки (которые управляют выгрузкой существующих состояний и загрузкой следующего состояния).
Я использую те же концепции в моих (не C #) проектах хобби сейчас (да, это может не подходить для больших проектов), и для небольших / хобби проектов, я определенно могу рекомендовать подход.
источник
Это похоже на то, что мы используем, стека автоматов. По сути, просто дайте каждому состоянию функции входа, выхода и отметки и вызывайте их по порядку. Работает очень хорошо для обработки вещей, таких как загрузка тоже.
источник
В одном из томов «Gem для программирования игр» была реализована машина состояний, предназначенная для состояний игры; http://emergent.net/Global/Documents/textbook/Chapter1_GameAppFramework.pdf содержит пример того, как использовать его для небольшой игры, и не должен быть слишком специфичным для Gamebryo, чтобы быть читаемым.
источник
Просто чтобы добавить немного стандартизации к обсуждению, классический термин CS для такого рода структур данных является автоматом .
источник
Я не уверен, что стек абсолютно необходим, так как он ограничивает функциональность государственной системы. Используя стек, вы не можете «выйти» из состояния с одной из нескольких возможностей. Скажем, вы начинаете в «Главном меню», затем идете в «Загрузить игру», вы можете перейти в состояние «Пауза» после успешной загрузки сохраненной игры и вернуться в «Главное меню», если пользователь отменяет загрузку.
Я бы просто попросил штат указать состояние, которому он должен следовать при выходе.
Для тех случаев, когда вы хотите вернуться в состояние, предшествующее текущему состоянию, например «Главное меню-> Параметры-> Главное меню» и «Пауза-> Параметры-> Пауза», просто передайте в качестве параметра запуска состояние состояние, чтобы вернуться.
источник
Другое решение для переходов и других подобных вещей заключается в предоставлении конечного и исходного состояния вместе с конечным автоматом, который может быть связан с «механизмом», каким бы он ни был. Правда в том, что большинство конечных автоматов, вероятно, потребуется адаптировать к конкретному проекту. Одно решение может принести пользу той или иной игре, другие решения могут помешать этому.
Состояния выдвигаются с текущим состоянием и машиной в качестве параметров.
Штаты появляются в том же духе. Называете ли вы
Enter()
низшееState
- вопрос реализации.При входе, обновлении или выходе,
State
получает всю необходимую информацию.источник
Я использовал очень похожую систему в нескольких играх и обнаружил, что за несколькими исключениями она служит отличной моделью пользовательского интерфейса.
Единственными проблемами, с которыми мы столкнулись, были случаи, когда в некоторых случаях желательно возвращать несколько состояний, прежде чем выдвигать новое состояние (мы перенаправили интерфейс для удаления требования, как это обычно было признаком плохого интерфейса) и создания стиля мастера линейные потоки (легко решаются путем передачи данных в следующее состояние).
Реализация, которую мы использовали, фактически обернула стек и обработала логику для обновления и рендеринга, а также операций со стеком. Каждая операция в стеке запускает события в состояниях, чтобы уведомить их о происходящей операции.
Также были добавлены несколько вспомогательных функций для упрощения общих задач, таких как Swap (Pop & Push, для линейных потоков) и Reset (для возврата в главное меню или завершения потока).
источник
Именно такой подход я применяю почти ко всем моим проектам, потому что он работает невероятно хорошо и чрезвычайно прост.
Мой самый последний проект, Sharplike , обрабатывает поток управления именно таким образом. Все наши состояния связаны с набором функций событий, которые вызываются при изменении состояний, и он имеет концепцию «именованного стека», в которой вы можете иметь несколько стеков состояний внутри одного и того же конечного автомата и ветвления между ними - концептуальный инструмент, и не обязательно, но удобно иметь.
Я бы предостерег от парадигмы «сообщить контроллеру, какое состояние должно следовать за этим, когда оно заканчивается», предложенной Skizz: она не структурно надёжна и делает такие вещи, как диалоговые окна (что в стандартной парадигме стекового состояния просто подразумевает создание новой создайте подкласс с новыми членами, а затем прочитайте его, когда вы вернетесь в состояние вызова), намного сложнее, чем должно быть.
источник
Я использовал в основном эту точную систему в нескольких системах ортогонально; например, состояния внешнего интерфейса и внутриигрового меню (он же «пауза») имели свои собственные стеки состояний. В игровом интерфейсе также использовалось что-то вроде этого, хотя у него были «глобальные» аспекты (например, индикатор здоровья и карта / радар), которые переключение состояний могло бы окрашивать, но которые обновлялись в разных штатах.
Внутриигровое меню может быть «лучше» представлено DAG, но с неявным конечным автоматом (каждый пункт меню, который идет на другой экран, знает, как туда попасть, и нажатие кнопки «Назад» всегда выдвигает верхнее состояние), эффект был точно так же.
Некоторые из этих других систем также имели функциональность «заменить верхнее состояние», но обычно это выполнялось следующим
StatePop()
образомStatePush(x);
.Работа с картой памяти была схожей, так как я фактически поместил тонну «операций» в очередь операций (которая функционально выполняла те же функции, что и стек, точно так же, как FIFO, а не LIFO); как только вы начинаете использовать такую структуру («теперь что-то происходит, и когда это сделано, оно само всплывает»), оно начинает заражать каждую область кода. Даже ИИ начал использовать что-то вроде этого; ИИ был «невежественным», затем переключался на «осторожный», когда игрок издавал шумы, но не был виден, а затем, наконец, повышался до «активного», когда они видели игрока (и в отличие от меньших игр того времени, вы не могли скрыть в картонной коробке и заставь врага забыть о тебе! Не то чтобы я горький ...).
GameState.h:
GameState.cpp:
источник