Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > разработка игр, графический дизайн и моделирование > Gamedev - cоздание игр: Unity, OpenGL, DirectX
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 29.06.2014, 15:54   #81
Гром
Старожил
 
Аватар для Гром
 
Регистрация: 21.03.2009
Сообщений: 2,193
По умолчанию Менеджер игровых состояний и конечные автоматы

Применение автоматов к построению менеджера состояний

Теперь, рассмотрев такое математическое понятие, как КА, можно начать проводить параллели с нашей реальной прикладной задачей создания менеджера состояний.

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

Не менее важным, однако, является вопрос об эффективной реализации процесса перехода из одного состояния в другое. Самый примитивный вариант – выбирать следующее состояние на основе текущего, а также управляющего сигнала конструкциями типа if/switch – обладает рядом серьезных недостатков. В их числе громоздкость кода, а также трудности при его изменении, добавлении новых состояний или удалении старых. Попробуйте-ка по несколько раз поменять структуру КА с десятью состояниями и пятью управляющими символами, и при этом ничего не забыть и не перепутать! Особенно если логика размазана по нескольким участкам кода, между которыми очень неудобно то и дело скакать.

Однако при работе с конечными автоматами естественным образом возникает такая система, как таблица переходов, лишенная все этих недостатков. С математической точки зрения она представляет собой табличное задание функции δ: Q*Σ → Q. С программистской – двумерный массив, индексами которого являются номер текущего состояния и управляющий символ.

Так, КА для отслеживания текущего состояния какого-нибудь боевого юнита в игре, можно реализовать примерно таким кодом (оставляем за кадром все вопросы реализации, не связанные с альтернативой «if/switch» vs «таблица переходов»):
Код:
enum State { stIdle = 0, stWalk, stShoot, stShootWalking, stDead };
enum Command { comGo = 0, comStand, comShoot, comDie };
 
State Transition[5][4] =
	{
	{stWalk, stIdle, stShoot, stDead},
	{stWalk, stIdle, stShootWalking, stDead},
	{stShootWalking, stShoot, stShoot, stDead},
	{stShootWalking, stShoot, stShootWalking, stDead},
	{stDead, stDead, stDead, stDead}
	};
State NextState(State currState, Command receivedCommand)
	{
	returnTransition[currState][receivedCommand];
	}
Ради интереса вы можете попробовать реализовать ту же логику с помощью обычных условных переходов. Затем для более полного эффекта добавьте одно-два состояния на ваш выбор. А потом – удалите какое-то одно.

При работе с таблицей перехода естественным образом возникает возможность использовать data-driven подход (когда данные управляют бизнес-логикой). Если при использовании if/switch мы получаем напрочь захардкоженную логику, то помещение правил перехода в массив позволяет легко настраивать их под текущие задачи, менять прямо во время работы приложения, загружать из файла и т.п.

Итак, семантика конечного автомата вполне подходит для управления игровыми состояниями, а таблица переходов предоставляет удобную и эффективную реализацию этой идеи в виде кода.
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта
Тема на форуме, посвященная ему же
Гром вне форума Ответить с цитированием
Старый 29.06.2014, 15:55   #82
Гром
Старожил
 
Аватар для Гром
 
Регистрация: 21.03.2009
Сообщений: 2,193
По умолчанию Менеджер игровых состояний и конечные автоматы

Классические КА – не наш выбор

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

Сразу оговорюсь, что это вовсе не повод отказываться от автоматов вовсе – нужно лишь адаптировать их под нашу задачу. КА используются во многих приложениях, но очень редко – в первозданном виде. Немного отвлекусь от темы и приведу простой пример.

Самая естественная область для конечных автоматов – работа со строками, одна из самых частых задач в алгоритмах на строках – поиск вхождения подстроки (с указанием позиции). Один из алгоритмов, Ахо-Корасик, заключается в построении специального ПДКА (т.н. бор, англ. trie), который распознает необходимые подстроки; затем целевой текст скармливается этому автомату, и мы за чистое время O(n) (не считая времени на построение бора) находим все вхождения всех строк (их может быть не одна, а сразу очень много) в текст.

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

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

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

Так какие же все-таки моменты не дают нам использовать КА в нашей задаче?

Первое, что бросается в глаза – это то, что в мат.логике состояния автомата являются некими атомарными объектами без внутренней структуры. Это просто элементы некоторого множества, с которыми можно сопоставить разве что некий номер. В игровом же приложении состояние – это сразу и логика, выраженная в коде, и данные, описывающие игровой мир или загруженные к настоящему моменту ресурсы.

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

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

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

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

Однако, есть и другой момент. Допустим, в главное меню мы можем попасть как при запуске игры, так и по нажатию клавиши Escво время игрового процесса. Но если находясь в этом меню мы опять нажмем Esc – то что должно произойти? Возврат в игру (если мы до этого были в главном игровом состоянии) или просто игнорирование нажатия (если игра только что запущена)?

Определить ответ на этот вопрос, исходя только из знаний о том, в котором состоянии мы сейчас находимся, и какой управляющий сигнал пришел на вход, невозможно. Так что если мы хотим оставаться в рамках подхода, основанного на конечном автомате, и не жульничаем с записыванием где-то в сторонке дополнительных данных – у нас ничего не выйдет.
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта
Тема на форуме, посвященная ему же
Гром вне форума Ответить с цитированием
Старый 29.06.2014, 15:55   #83
Гром
Старожил
 
Аватар для Гром
 
Регистрация: 21.03.2009
Сообщений: 2,193
По умолчанию Менеджер игровых состояний и конечные автоматы

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

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

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

Вообще тонкости организации работы менеджера состояний при наличии стека я рассмотрю в следующем посте, когда буду подробно описывать его реализацию с чисто практической точки зрения. Пока лишь отмечу, что сочетание ПДКА и стека состояний перекликается с т.н. автоматами с магазинной памятью (АМП). Такие автоматы уже не являются эквивалентными ДКА, и распознают более широкий класс языков (например, язык сбалансированных скобок, который не является автоматным/регулярным, то есть ПДКА не распознается).

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

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

Здесь уместно будет рассмотреть идеи, заложенные в паттерны проектирования Command или Subscriber. Припомним также систему сигналов/слотов, реализованную в Qt, Boost, etc. Конечно, точное копирование здесь будет малополезным, но это хорошая отправная точка при проектировании работы менеджера состояний.

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

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

Таковы вкратце идеи, положенные мной в основу менеджера состояний моей игры. Подробно его структуру я опишу в следующем посте, к тому моменту я также планирую провести экспериментальную их проверку и написать простую игру с использованием менеджера состояний.
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта
Тема на форуме, посвященная ему же
Гром вне форума Ответить с цитированием
Старый 12.07.2014, 15:39   #84
Гром
Старожил
 
Аватар для Гром
 
Регистрация: 21.03.2009
Сообщений: 2,193
По умолчанию Реализация менеджера игровых состояний - часть 1

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

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

Перед прочтением я также рекомендую освежить в памяти содержание следующих постов: Проектирование на уровне приложений и движков, Менеджер игровых состояний и конечные автоматы. Также по ходу пьесы, вероятно, придется довольно много читать и гуглить.

Теперь, когда я вас достаточно запугал – перейдем наконец к теме, обозначенной в заголовке этого поста.

Базовые принципы

Для начала четко сформулирую те идеи, которые я закладываю в свой менеджер состояний. В дальнейшем многие проектные решения будут приниматься именно на основе того или иного принципа (и это зачастую будет приводить к появлению в проекте и коде достаточно нетривиальных структур).
  • В каждый конкретный момент игра находится ровно в одном состоянии. По сути каждое состояние представляет собой «приложение внутри приложения». Когда игра переходит в другое состояние, ее поведение сильно меняется.
  • Состояния ничего не знают друг о друге. Их задачей является только обеспечение надлежащего функционирования игрового приложения в конкретный момент времени.
  • Поскольку состояниям друг о друге ничего не известно, то они не могут (и не должны) управлять логикой перехода между собой. Эту задачу берет на себя менеджер игровых состояний.
  • Менеджер состояний работает по принципу конечного автомата. Переход в другое состояние происходит тогда, когда менеджер получает управляющий сигнал. При этом новое состояние определяется только типом текущего состояния и типом управляющего сигнала.
  • Состояние является основой логики работы игрового приложения. Все остальные средства управления логикой (например, игровой цикл) вызываются внутри состояния, при этом их использование или неиспользование целиком находится в компетенции данного состояния.
  • Менеджер состояний в зависимости от ситуации может не только выполнять действие «удалить текущее состояние, создать новое состояние, сделать его текущим», но и помещать текущее состояние в стек, не удаляя его, и затем делать текущим новое состояние; либо после удаления текущего состояния извлекать из головы стека ранее помещенное туда состояние и делать его текущим. При этом состояние извлекается из стека в том же виде, в котором было в него помещено (затем оно продолжает работу с того места, на котором остановилось).

Обоснование этих принципов приводилось в более ранних постах, здесь я не буду его повторять.

Замечу еще, что хотя изложенные здесь идеи выглядят достаточно естественно, их реализация в коде на C++зачастую будет очень нетривиальной. Возможно, гораздо естественнее было бы сделать это на Smalltalk, но – имеем то, что имеем.
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта
Тема на форуме, посвященная ему же
Гром вне форума Ответить с цитированием
Старый 12.07.2014, 15:39   #85
Гром
Старожил
 
Аватар для Гром
 
Регистрация: 21.03.2009
Сообщений: 2,193
По умолчанию Реализация менеджера игровых состояний - часть 1

Основа архитектуры менеджера состояний

Теперь рассмотрим, как именно мы будем выражать обозначенные выше понятия в терминах языка C++.

Очевидно, что у нас будет по крайней мере три класса – абстрактный GameState (конкретные состояний будут его наследниками), GameStateManager (уместно сделать его синглтоном – кстати, это довольно редкая ситуация) и StateEvent.

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

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

На первый взгляд, все довольно очевидно – у состояний и событий есть типы, выражающиеся перечислениями StateType и EventType соответственно, в интерфейсах этих классов есть виртуальные функции, возвращающие тип конкретного класса. На основании этих данных конечный автомат, используя таблицу переходов, легко может выдать… ой!

Единственное, что мы можем пока получить – это тип следующего состояния. Но нам-то нужно создать объект определенного типа!

Именно эту задачу можно успешно решить с помощью паттерна проектирования Factory (Фабрика). Он позволяет по переданному ему идентификатору типа создать новый объект, принадлежащий соответствующему классу. Замечу, что описанный Бандой четырех (Gang of Four, GoF) паттерн Factory Method (Фабричный метод) является в лучшем случае составным элементом необходимой мне фабрики, поэтому он не подходит для решения данной задачи.

Мне известны два варианта реализации нужного паттерна. Первый из них приводится в книге Александреску «Современное проектирование на C++» в главе 8, вместе с рядом ценных соображений по построению фабрик. Второй описан в статьях «Ставим объекты на поток, паттерн фабрика объектов» и «Factory, она же — фабрика». Первая статья описывает тему более полно, вторая – более лаконична и схематична.

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

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

Преимущества и недостатки второго подхода зеркальны – нет необходимости писать лишний код, но зато и сделать что-то более сложное, чем вызов оператора new (чего нам бы хотелось – см. ниже) представляется очень нетривиальной задачей.
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта
Тема на форуме, посвященная ему же
Гром вне форума Ответить с цитированием
Старый 12.07.2014, 15:40   #86
Гром
Старожил
 
Аватар для Гром
 
Регистрация: 21.03.2009
Сообщений: 2,193
По умолчанию Реализация менеджера игровых состояний - часть 1

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

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

Если же фабричный метод генерируется на основе шаблона, то лучшее, что мы можем сделать – организовать класс StateEvent таким образом, чтобы он содержал только идентификатор своего типа и указатель на класс StateEventData. В этом случае полиморфным будет только тип с дополнительной информацией, а у событий не будет подтипов. Тогда мы сможем передавать в конструктор состояния указатель на StateEventData, который будет приводиться к нужному типу уже внутри тела конструктора.

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

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

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

Прежде чем закончить с этим пунктом, замечу, что работа со стеком состояний реализуется достаточно просто – нужно только добавить в событие информацию о том, каким образом определить следующее состояние и что делать с текущим. Возможны три варианта: старое состояние удалить, новое определяется по правилам простого ДКА; старое состояние поместить в стек, новое найти через ДКА; если стек не пуст, то удалить текущее, новым сделать состояние с вершины стека, если пуст – ничего не делать.

Могут быть также полезны другие опции (например, после установки нового состояния также очистить весь стек). Впрочем, если при наличии всего трех альтернатив можно было особенно не задумываться над реализацией надстройки, предназначенной для работы со стеком, и смело лепить короткий switch, то при увеличении количества вариантов появляются мысли о применении чего-то наподобие фабрики, стратегий или еще чего-то зубодробительного. Впрочем, этот вопрос я пока сочту неактуальным и буду изобретать очередной велосипед только если в этом возникнет необходимость.
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта
Тема на форуме, посвященная ему же
Гром вне форума Ответить с цитированием
Старый 12.07.2014, 15:40   #87
Гром
Старожил
 
Аватар для Гром
 
Регистрация: 21.03.2009
Сообщений: 2,193
По умолчанию Реализация менеджера игровых состояний - часть 1

Основы работы состояний

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

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

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

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

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

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

Когда состояние становится активным, менеджер соединяет его с системами ввода с помощью связей наподобие сигнало-слотовых, реализованных в Qt и Boost. Когда же состояние перестает быть активным – эти связи разрушаются, и состояние перестает реагировать на происходящее в окружающем мире.

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

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

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

Гвозди микроскопом?

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

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

Подводя промежуточные итоги

Итак, концепция менеджера состояний оказалась недостаточно проработана в прошлых постах, чтобы можно было легко реализовать ее в коде. Именно поэтому мне пришлось углубленно рассмотреть вопрос проектирования менеджера состояний. На очереди – изучение механизма обмена сообщениями и написание-таки прототипа упомянутого менеджера.
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта
Тема на форуме, посвященная ему же
Гром вне форума Ответить с цитированием
Старый 29.07.2014, 14:44   #88
Kinaris
Новичок
Джуниор
 
Регистрация: 29.07.2014
Сообщений: 2
По умолчанию

есть парочка идей игр,но сам программировать не умею,игровые приложения,кто сможет помочь?
Kinaris вне форума Ответить с цитированием
Старый 29.07.2014, 20:04   #89
Гром
Старожил
 
Аватар для Гром
 
Регистрация: 21.03.2009
Сообщений: 2,193
По умолчанию Реализация менеджера игровых состояний - часть 2. Создание кода

Несколько последних постов я излагал вам свои умозаключения по поводу архитектуры игры. Однако, во-первых, голая теория – это не очень интересно, во-вторых, никто не гарантирует, что в реальности все будет так же радужно, как представлялось. Сегодня я наконец начну описывать свой практический опыт написания реального программного кода, а именно – простой игры, использующей тот самый менеджер состояний, о котором столько говорилось в прошлых постах.
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта
Тема на форуме, посвященная ему же
Гром вне форума Ответить с цитированием
Старый 29.07.2014, 20:04   #90
Гром
Старожил
 
Аватар для Гром
 
Регистрация: 21.03.2009
Сообщений: 2,193
По умолчанию Реализация менеджера игровых состояний - часть 2. Создание кода

Цели и задачи

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

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

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

Инструменты и методология

Первое, что я сделал – это собрал воедино все то, что упоминалось в посте «Выбор инструментов разработки», за исключением Lua, поскольку скрипты мне понадобятся еще не скоро.

Язык (С++ 11), библиотека (Qt 5.2.1) и среда разработки (QtCreator 3.0.1) у меня уже были под рукой, Git в качестве системы контроля версий (ранее я писал, что отказался от Mercurial в его пользу) поставился и был подружен с IDE без проблем. С GoogleTest все оказалось несколько сложнее.

Скомпилировать его из исходников и написать простейший «Hello, GoogleTest!» было не трудно, однако для организации адекватного процесса тестирования пришлось повозиться. Общую структуру проекта я опишу ниже, а некоторые технические детали можно будет посмотреть в коде готовой Змейки, который я выложу сразу же по готовности.

Я хотел также работать через TDD (Test Driven Development), но идея себя не оправдала. Произошло это главным образом потому, что трудно использовать TDD, когда с самого начала пишешь относительно высокоуровневый код, не дающий на выходе, однако, интуитивно понятных данных какого-либо фундаментального типа. Это в полной мере относится к рассмотренной в прошлом посте фабрике, которая еще и является только адаптацией полностью готового и описанного компонента.

Обзор программного кода

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

Структура проекта такова. В основе его лежит Qt-шный проект типа subdirs, в который входят подпроекты Build (типа app, в настоящий момент содержит только один исходный файл с пустой функцией main), Lib (типа lib, статичная библиотека; весь код, отвечающий за логику приложения находится здесь) и Test (типа app, содержит исходный файл с тестами). Сейчас почти вся работа ведется над кодом Lib, а Test просто подключает его как библиотеку. Точно так же будет поступать Build – грубо говоря, это будет одна функция main, содержащая строку World.Run();.

В проекте имеется абстрактный класс состояний GameState, имеющий чисто виртуальные функции onEnter, onExit, Run и функцию sendEvent, которая вызывает функцию onEvent менеджера состояний. Также есть несколько производных классов, но пока они представляют из себя заглушки для проверки работы менеджера.

Основная же работа ведется как раз над последним. В первую очередь я сделал его синглтоном (конкретно – синглтоном Мейерса), поскольку у игры, очевидно, может быть только один менеджер состояний; при этом синглтон позволяет объектам классов состояний не хранить у себя указатель на менеджер, а каждый раз при посылке события свободно получать его через соответствующий интерфейс.
Простые и красивые программы - коды программ + учебник C++
Создание игры - взгляд изнутри - сайт проекта
Тема на форуме, посвященная ему же
Гром вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Электронно учебное пособие gloomy_jr Общие вопросы Delphi 1 23.05.2012 14:07
Создание игры::особенности коллективной разработки флеш приложений АТИКОН Gamedev - cоздание игр: Unity, OpenGL, DirectX 9 21.08.2011 19:51
Мультимедийное учебное пособие world12_tk Помощь студентам 4 21.04.2011 17:37
статья - Может-ли ПО работать быстрее или взгляд изнутри Pblog Обсуждение статей 0 27.02.2011 23:10
Электронное учебное пособие Zeibel Помощь студентам 10 31.05.2010 10:55