Войти
ПрограммированиеФорумИгровая логика и ИИ

Как использовать стек состояний игры?

#0
16:52, 1 фев 2010

Начал писать каркас игры разбивая его на состояния.
В книге Джима Адамса "Программирование ролевых игр с DirectX" прочитал:
Выбор действий, которые ваше приложение должно выполнить для каждого кадра, может привести к появлению такого уродливого кода, как показанный ниже:

switch(CurrentState) {
    case STATE_TITLESCREEN:
        DoTitleScreen();
        break;

    case STATE_MAINMENU:
        DoMainMenu();
        break;

    case STATE_INGAME:
        DoGameFrame();
        break;
}
Очень похоже на то, что написал я.
Далее Джим Адамс пишет:
Вместо этого лучше использовать технику, которую я называю программированием на основании состояний (state-based programming или, для краткости, SBP). Основу этой техники составляет перенаправление потока исполнения на основе стека состояний. Каждое состояние представляется объектом или набором функций. Если вам потребовались функции, вы добавляете их в стек. Когда работа с функциями завершена, они удаляются из стека.
cStateManager SM;
Вот его пример:

// Макрос для простого вызова функции MessageBox
#define MB(s) MessageBox(NULL, s, s, MB_OK);

// Прототипы функций состояний - следуйте этим прототипам!
void Func1() { MB("1"); SM.Pop(); }
void Func2() { MB("2"); SM.Pop(); }
void Func3() { MB("3"); SM.Pop(); }

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,
                  LPSTR szCmdLine, int nCmdShow)
{
    SM.Push(Func1);
    SM.Push(Func2);
    SM.Push(Func3);

    while(SM.Process() == TRUE);
}


Как работает стек состояния я понимаю. Может кто нибудь объяснить мне в каком порядке и когда  укладывать туда мои функции, ну например вот такие?
Game_Init();
Game_Menu();
Game_Menu_Draw();
Game_Start();
Game_Start_Draw();
Game_Main();
Game_Main_Draw();
Game_Restar();
Game_Exit();

#1
17:37, 1 фев 2010

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

Вот если заменить стек на очередь, то в моём представлении всё складывается в нормальную картину.

#2
19:02, 1 фев 2010

Game_Init // возможно имелось ввиду Prog_init
Game_Exit // мэби это выход из програмы.

Game_Menu // вероятно здесь Обновление.
Game_Menu_Draw // рисование того что наобновлялось

Game_Start // вероятно это меню выбора этапа, где будем играть.

Game_Main // вероятно это Обновление интитей игры.

Game_Restart // это перезапуск програмы или просто Загрузка нового этапа в игре ?


// --------
От себя рекомендую обойтись более класическими способами.
Не понимаю почему первый вариант с кейсами является Уродливым.

#3
20:25, 1 фев 2010

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

#4
21:49, 1 фев 2010

class IGameState
{
public:
virtual void Update() = 0;
virtual void Render() = 0;
};

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

#5
23:49, 1 фев 2010

ITALY
++

#6
19:38, 2 мар 2010

Также в помощь тебе паттерн проектирования "State": http://en.wikipedia.org/wiki/State_pattern

#7
15:08, 3 мар 2010

Sergio10 и ITALY  спасибо! Действительно очень похоже на то что я искал. Начал читать книгу "Приемы объектно-ориентированного проектирования. Паттерны проектирования".

#8
19:58, 3 мар 2010

У меня тоже на подобии того что предложил ITALY:

class ClientScreen{
public:

  SCREEN_DECL(ClientScreen);

  ClientScreen( std::vector<ClientScreen*>* screenList );
  virtual ~ClientScreen();

  ClientScreen* find( const char* name );

  virtual bool init() = 0;
  virtual void free() = 0;
  virtual void render( float ifps ) = 0;

  virtual void enable();
  virtual void disable();
  bool isEnabled();
protected:
  bool enabled;
  std::vector<ClientScreen*>* screenList;
};

От этого наследуются MainMenuScreen,GameScreen,etc.

Прошло более 1 года
#9
18:53, 8 мая 2011

ITALY
> class IGameState
> {
> public:
> virtual void Update() = 0;
> virtual void Render() = 0;
> };
>
> дальше наследуешь от него все игровые состояния и храниш их в стеке. каждое
> состояние может либо положить на верх стека еще какое-либо состояние, либо
> снять. игровой цикл работает с верхним на стеке

FUUUUUUUUUUUUuuuuuuuuuuuuuu!!!
Зачем только с верхним работать? Можно работать со всеми - к примеру одно состояние - MainMenu, второе - BackGroundMainMenu. Если переходим в экран опций - MainMenu удаляем, BackGroundMainMenu оставляем. Ввод также надо инжектировать по слоям до тех пор, пока в ответку не придет "поймано". Можно еще ставить флаги для принудительного инжекта в любом случае.

ПрограммированиеФорумИгровая логика и ИИ

Тема в архиве.