Переключение между различными экранами одного и того же типа состояния?

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

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

Тем не менее, я ОЧЕНЬ начинающий кодер (стараюсь изо всех сил учиться), и я столкнулся с фундаментальной проблемой с моей программой прямо с самого первого класса...

Первый класс, который я сделал, это SplashScreenState. И основная концепция заключалась в том, чтобы иметь состояние, которое, по сути, просто показывает серию «изображений заставки» (скажем, 3, например), и каждый раз, когда пользователь нажимает клавишу, он переключается на следующее изображение и наконец (когда нет изображений заставки для циклического просмотра) переключается в следующее состояние (состояние меню).

Моя проблема в том, что мне трудно понять, как это структурировать. Первоначально я сделал ошибку, рассматривая каждое отдельное изображение экрана-заставки как экземпляр SplashScreenState. Однако я решил, что это неправильно, поскольку технически все 3 экрана-заставки являются частью одного и того же «состояния».

Итак, теперь у меня есть две основные проблемы:

  • Первая проблема заключается в том, что я не уверен, как/где хранить все изображения заставки. Если я хочу переключаться между тремя разными изображениями экрана при запуске программы, должен ли я сделать их всеми членами класса SplashScreenState? Или разумнее просто иметь один член класса для «currentImage», и каждый раз, когда пользователь нажимает клавишу, он запускает функцию load() для загрузки следующего изображения в указатель currentImage? Лучше сделать массив или вектор изображений и циклически перемещаться по ним? Я просто не уверен...

  • Моя вторая проблема связана с обработкой событий () SplashScreenState. Я знаю, что хочу, чтобы изображение на экране менялось примерно так; *image1 -> image 2 -> image 3 -> changeState(menuState).. Таким образом, каждый раз, когда пользователь нажимает клавишу на клавиатуре, он переключается на следующую заставку до последней заставки, где она затем изменится состояние в главное меню. Я не уверен, что это лучший способ сделать это. Должен ли я создавать перечисление для каждого экрана-заставки и увеличивать их (до последнего экрана, где он меняет состояния)? Я также думаю, что если бы я хранил все свои различные экраны в массиве, то я мог бы легко увеличить их, но тогда это было бы неоптимизированным, потому что все экраны должны были бы храниться в памяти все время?

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

Спасибо!


person MrKatSwordfish    schedule 12.08.2012    source источник
comment
Это действительно отличный вопрос - хорошо организованный и объясненный, красиво сделанный!   -  person Bojangles    schedule 12.08.2012


Ответы (2)


Кажется, вы разрываетесь между объектно-ориентированной и процедурной парадигмой обработки переходов между состояниями. Другой ответ, предлагающий оператор switch для обработки перечисленных изменений состояния, является хорошим процедурным способом сделать это. Недостатком этого является то, что вы можете получить монолитный игровой класс, содержащий весь код и все лишние данные, относящиеся к состоянию, для обработки вашего события/логики/рендеринга для всех возможных состояний. Объектно-ориентированный способ справиться с этим намного чище и инкапсулирует их в свои собственные отдельные объекты состояния, где их можно использовать полиморфно через общий интерфейс. Затем, вместо того, чтобы хранить все детали для обработки всех состояний в вашем игровом классе, вашему игровому классу нужно только хранить указатель состояния и не беспокоиться о деталях реализации конкретного объекта состояния. Это переносит ответственность за обработку переходов состояний из игрового класса в класс состояний, которому он принадлежит. Вы должны прочитать о шаблоне состояния/стратегии из книги шаблонов проектирования. Обработка изменения состояния должна быть обязанностью самого объекта состояния. Вот немного чтения:

http://www.codeproject.com/Articles/14325/Understanding-State-Pattern-in-C

http://sourcemaking.com/design_patterns/state

http://sourcemaking.com/design_patterns/state/cpp/1

http://codewrangler.home.comcast.net/~codewrangler/tech_info/patterns_code.html#State

http://en.wikipedia.org/wiki/State_pattern

http://www.codeproject.com/Articles/38962/State-Design-Pattern

Цитата из книг «Шаблоны проектирования» и «Архитектура программного обеспечения, ориентированная на шаблоны»: Подход, основанный на шаблонах, использует код вместо структур данных для определения переходов состояний, но хорошо справляется с действиями переходов состояний. Шаблон состояния не указывает, где должны быть определены переходы между состояниями. Это можно сделать в объекте контекста или в каждом отдельном производном классе состояния. Как правило, более гибко и уместно позволить подклассам состояния указывать свое последующее состояние и время выполнения перехода.

У вас есть возможность создавать объекты состояния заранее и никогда не уничтожать их, что может быть полезно, когда изменения состояния происходят быстро, и вы хотите избежать уничтожения состояний, которые могут вскоре снова понадобиться. С другой стороны, это может быть неудобно, поскольку контекст должен содержать ссылки на все состояния, в которые можно войти.

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

person derpface    schedule 12.08.2012
comment
Отличная информация. Большое спасибо за подробный ответ и за все ссылки! Я определенно заинтересован в том, чтобы научиться работать более объектно-ориентированным способом. Прямо сейчас мой код представляет собой настоящий беспорядок, мои классы, кажется, с трудом знают о других классах, у меня есть тонна глобальных переменных, и у меня есть странная смесь процедурного и объектно-ориентированного, что, я думаю, просто сбивает меня с толку (С++ — мой первый язык). -- Одна вещь все еще остается для меня немного неясной: если моя программа имеет 3-4 разных логотипа заставки, считается ли КАЖДЫЙ логотип состоянием, экземпляром класса SplashScreenState или членом класса?? - person MrKatSwordfish; 13.08.2012
comment
Я думаю, что ваша идея просто перебирать их в массиве или другом контейнере была хорошей. Логика кажется довольно простой. Вы переходите только после получения определенного типа ввода, верно? Затем этот ввод может вызвать изменение изображения, и как только вы окажетесь на последнем изображении, он может вызвать переход. Вы могли бы переусложнить это, включив шаблон состояния внутри вашего шаблона состояния, но очевидно, что это немного чрезмерно. Это достаточно просто, чтобы ограничиться одним объектом состояния. - person derpface; 13.08.2012
comment
Ок, отлично! Спасибо за продолжение! :) Я думаю, что у меня есть четкое понимание того, как я должен подходить к этому с этого момента! - person MrKatSwordfish; 13.08.2012

Отличное объяснение вашей проблемы, прежде всего.

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

Получить ввод; установить следующее состояние.

Итак, примеры:

 STATE_SPLASH1
 STATE_SPLASH2
 STATE_SPLASH3
 STATE_TITLE
 STATE_GAME_INIT
 STATE_GAME
 STATE_EXIT

псевдокод:

state = STATE_SPLASH1

while (state != STATE_EXIT) 
  ... receive input ...
  ... process events to responders ...
  ... update ...
  ... yadda yadda ...
  switch (state) {
    case STATE_SPLASH1:
      show_splash1()
    case STATE_SPLASH2:
      show_splash2()
    case ..:

    case STATE_TITLE:
      show_title()
    case STATE_GAME_INIT:
      show_loading()
      setup_level()
      setup_characters()
    case STATE_GAME:
      game_update()
    case STATE_EXIT:
      cleanup_and_quit()

Другой способ — управлять заставкой «состоянием игры», а затем состоянием заставки как внутренним состоянием. и когда у заставки больше нет логики для запуска, установите состояние игры на следующее. Когда я учился, я обнаружил, что источник DOOM является ценным ресурсом и человеком, это не что иное, как сотни конечных автоматов. :)

person dans3itz    schedule 12.08.2012
comment
Отличный ответ! Спасибо! Этот метод относительно прост, и он имеет большой смысл для меня. Хотя я хотел бы привыкнуть к изучению подхода ООП (я изо всех сил пытался изучить ООП). Если бы я создал класс StateManager, было бы возможно (или разумно) иметь класс менеджера состояний, содержащий подобную структуру? Спасибо еще раз за помощь! :) - person MrKatSwordfish; 13.08.2012