Загрузка...
 
Печать
Кодим 3D FPS DX9

1.1 Проектируем движок


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



Важность проектирования

Один из самых популярных способов создания компьютерной игры заключается в использовании подхода, часто называемого "хакингом" (hacking). Он включает в себя следующие шаги:1
  1. Найти классную идею для игры.
  2. Немедленно начать кодить и подгонять играбельное демо.
  3. Далее разрабатывать своё демо, добавляя "фишки" и др. функционал, пытаясь превратить всё это в полную версию игры.
  4. Пролезать сквозь дебри запутанного кода и застревать, пытаясь разобраться в горах напрограммированных "фишек" и др. функционала.
  5. Бросать работу над проектом из-за того, что это наскучило, или приходя к выводу, что это была слишком амбициозная цель.
  6. Повторить, начиная с шага 1.
Это пример создания игры без использования преимуществ проектирования. Многие следуют по этому неверному пути всякий раз, когда начинают работу над новым проектом. Этот подход очень прост и весьма распространён среди начинающих программеров, особенно занятых на небольших проектах. Ты можешь сколько угодно следовать этим шагам при разработке любительского проекта, за который возьмёшься в ближайшие выходные. Но когда попытаешься взяться за что-то действительно серьёзное, то быстро поймёшь, что без знания хотя бы самых основ проектирования тебе просто не обойтись.
Начнём с определения дизайна.
Закрыть
noteПримечание

Здесь и далее слова дизайн и дизайн-проект (англ. "design") рассматриваются как результат проектирования, схематичное представление, некий план, по которому будет вестись дальнейшая работа (в данном случае - над нашим движком). Термин дизайн движка (Engine Design) широко распространён в англоязычной литературе по игрокодингу. Хотя он не имеет ничего общего с общепринятыми в русском языке понятиями "игровой дизайн, дизайн игры, уровней и т.д.

Некоторые люди говорят, что проектирование - это деятельность или задача, которую необходимо выполнить ДО начала конструирования. И они правы. Возьмём, к примеру, строительство здания. Согласитесь, не лучшая идея: сначала построить здание, а затем разрабатывать его дизайн-проект. Это также справедливо и для разработки программного обеспечения, в том числе игр. В программировании нередки ситуации, когда стоимость и сроки проекта превышают допустимые пределы именно из-за трудностей с проектированием.
В то же время невозможно создать исчерпывающий дизайн-проект нашего движка: в нём обязательно будут появляться всё новые и новые пункты. В этом случае мы будем проектировать его постепенно, вместе с созданием основного каркаса. В программировании это называется развивающийся дизайн (evolving design). Дизайн вообще невозможно привести к законченному варианту, даже после завершения работы над проектом. Хорошим примером здесь служит послерелизная поддержка пользователей ПО. Если в приложении найден какой-нибудь баг (англ. bug - здесь "баг", "глюк"), то его дизайн-проект может быть пересмотрен с целью повышения стабильности работы данной программы. Следовательно дизайн-проектирование игрового движка это даже не деятельность и не задача. Это, скорее, процесс.
Применив всё вышесказанное к созданию дизайн-проекта игрового движка, мы получим следующий алгоритм:
  1. Найти классную идею для игры.
  2. Собрать воедино элементы основного дизайн-проекта, достаточного для началаразработки.
  3. Начать разработку демо-версии, основываясь на наброске или черновике дизайн-проекта.
  4. Переработать дизайн-проект, основываясь на созданной демо-версии и уже имеющихся наработках.
  5. Построить новую версию игры, основанную на исправленном и дополненном дизайн-проекте.
  6. Переработать дизайн-проект, основываясь на результатах и наработках предыдущей версии игры.
  7. Повторять, начиная с шага 5 до полной готовности игры.
Как можно видеть, этот подход более предпочтителен и позволяет в приемлемые сроки довести разработку игры до конца. Шаги 3 и 4 часто очень большие и занимают много времени, так как ты разрабатываешь и компилишь играбельную демо-версию, часто называемую технологическое демо (technology demo). Это демо используется, чтобы определить, какие компоненты требуются для создания полноценной игры. Например, ты можешь определить, что тебе понадобится система графического интерфейса пользователя (graphical user interface, GUI), звуковая система с поддержкой объёмного 3D-звучания и система рендеринга, включающая в себя 2D- и 3D-рендеринг. С этого момента ты входишь в цикл дизайн -разработка - дизайн - разработка и так далее, до полного завершения разработки игры. Насколько большими будут итерации этого цикла, зависит от программиста и его опыта. Если ты чувствуешь себя неуверенно, то сочтёшь необходимым делать итерации этого цикла короткими, время от времени пересматривая разрабатываемый проект. Более опытные программисты могут делать намного больше в рамках одной итерации. Просто запомни, что в дизайн-проекте невозможно всё предусмотреть, так как, каким бы опытным программистом ты ни был, всегда остаётся ещё слишком много различных факторов и неизвестных. Вот почему мы подошли к разработке дизайн-проекта с применением так называемого циклично-равивающегося процесса.
Исходя из вышеизложенного материала, делаем вывод, что без дизайн-проекта не обойтись. Наверняка ты не раз слышал, что начинать изучение игрокодинга лучше с создания небольших проектов. Например простой паззл-игры или примитивного сайд-скроллера (вроде серий Super Mario и Rockman). Не смотря на это, даже программер с многолетним опытом может не справиться с проектом, когда речь идёт о создании полноценной игры. Ключевым фактором здесь является умение применять на практике соответствующий процесс дизайн-проектирования. Ты можешь создать всё, что только подскажет твоё воображение, когда у тебя есть два ключевых компонента: правильно выбранный процесс дизайн-проектирования и большой запас терпения.
Прежде чем мы начнём дизайн-проектирование нашего движка, развеем некоторые мифы об этом процессе.

Миф 1. Дизайн не важен. В мировая индустрии по разработке программного обеспечения дизайн-проектирование является обычной практикой. По дизайн-проектированию ПО существуют целые стандарты и написано множество мануалов. К сожалению, индустрия компьютерных игр довольно медленно внедряет эти наработки. Как результат мы видим множество игр, где уровень применения дизайн-проектирования крайне низок. Отсутствие нормального дизайн-документа - одна из основных причин провала множества игровых проектов. Часто это происходит просто от неопределённости или незаконченности образа будущей игры. Если ты хочешь повысить свои шансы на успех, тебе необходимо строго следовать принципам игрового дизайн-проектирования. На эту тему написано множество книг. Они охватывают все аспекты разработки ПО, а некоторые из них посвящены именно игрокодингу. В то время как игры становятся всё более сложными и многоплановыми, становится очевидным, что дизайн-проектирование очень важно.

Миф 2. Дизайн более важен, чем его воплощение в виде исходного кода. Часто так думают из-за того, что дизайн-проектирование - это наиболее длительная фаза. Это совсем не так. В связи с тем, что большинство игр, при создании которых применялось дизайн-проектирование, итеративны (то есть созданы по определённому шаблону), на деле получается, что дизайн идёт бок о бок с программированием. Они дополняют друг друга. Дизайн никогда не является важнее своего воплощения в виде исходного кода. Конечно, без дизайн-проекта у тебя будет сложная разработка, но без программирования проекта просто не будет.

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

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

Наш подход к дизайн-проектированию движка

Как мы собираемся дизайнить наш движок? Конечно же, с использованием циклического (итеративного), развивающегося процесса дизайн-проектирования.
В этом разделе мы обсудим дизайн нашего движка на высоком уровне (то есть в общих чертах, не вдаваясь в подробности). Как только мы определились с тем, что будет входить в наш движок, мы можем приступать к выполнению первой итерации цикла дизайн-разработка, часто именуемую "версия 1", "ревизия 1" или "билд 1". Начнём с простого - создания карандашного наброска на листе бумаги. Это поможет нам наглядно представить структуру будущего движка. Далее приступим к следующей итерации, более детально описав наш проект. Мы повторим этот процесс для каждой итерации до тех пор, пока не будет ясно, что наш движок готов и соответствует своему назначению.
После прочтения Части 1 данного курса, наш движок будет полностью готов для создания на его основе игры. Но это вовсе не значит, что работа над ним будет полностью завершена. Вообще, какая-либо разработка не может быть доведена до логического завершения. После прочтения курса, ты можешь бесконечно долго продолжать дизайнить движок, добавляя в него новые "фишки", улучшая быстродействие и приводя его в соответствии с современными технологиями. Но мы немного забежали вперёд.
Первый шаг, на котором следует сконцентрироваться, это исследование. Да, всё верно: твой процесс дизайн-проектирования всегда должен начинаться с исследования.
Итак, что же именно нам необходимо исследовать? Чтобы начать, определимся, что собой представляет жанр 3D FPS, игру в котором мы будем создавать. Многие без труда назовут основные атрибуты этого жанра:
  • яркая графика, взрывы, куча оружия, массивные уровни; Представляют собой желания (desirables). Это те "фичи" (англ. features - преимущества, особенности, "фишки"), которые мы хотели бы видеть в игре, но они не являются обязательными. Являются фичами, специфичными для ИГРЫ.
  • рендеринг, сетевая игра, звук, система ввода и др. Являются фичами, специфичными для ИГРОВОГО ДВИЖКА. О них мы и будем говорить далее и выделим наиболее важные из них.
Самый простой способ сделать это - играть в игры жанра 3D FPS. Также помогут интернет-источники и литература по теме. Неплохо также просмотреть другие движки, заточенные под игры этого жанра. Обрати внимание как они сдизайнены и какими фичами обладают.
Мы обязательно рассмотрим основные фичи и компоненты для нашего движка чуть позднее в этой главе. А сейчас рассмотрим ещё пару интересных аспектов процесса дизайн-проектирования.

Определяем цели дизайн-проекта

Всякий раз, когда ты что-либо дизайнишь, ты намечаешь цели, которые намереваешься достичь через данный дизайн-проект. То же самое ты каждый день подсознательно проделываешь и в реальной жизни, причём часто даже не подозревая об этом. Рассмотрим пример, когда ты управляешь своим автомобилем, намереваясь добраться из пункта А в пункт Б. В начале поездки ты уже намечаешь свою первую цель - добраться в пункт Б. Тебе также может быть необходимо добраться в пункт Б к определённому времени. Тем самым ты ставишь ещё одну цель - добраться в пункт Б к определённому времени. Эта цель оказывает влияние на те решения, которые ты принимаешь во время вождения. Например, ты можешь продумать объездные пути, зная, что на основной дороге ведутся дорожные работы. Осознаёшь ты это или нет, но ты делаешь это, чтобы достичь свою вторую цель (добраться в пункт Б к определённому времени). Мы можем продолжить, утверждая, что существует также и третья цель - достичь пункта Б безопасно. А теперь, отодвинув правила дорожного движения на второй план, представим: ты подъезжаешь к перекрёстку и у него висит знак "STOP" (движение без остановки запрещено). Но ты продолжаешь двигаться дальше, игнорируя этот знак и двигаясь без совершения полной остановки. В любом случае, твоя третья цель не позволит тебе нарушить ПДД, так как это совсем небезопасно.
Как видишь, целями мы часто пользуемся в повседневной жизни. В настоящий момент твоя цель - учиться. Именно поэтому ты сейчас читаешь этот курс. Всякий раз, когда ты дизайнишь новое программное приложение, ты выбираешь несколько целей, и затем применяешь их в своих дизайнерских наработках. Чтобы лучше понять, о каких целях идёт речь, взгляни на Таблицу 1, в которой перечислены общие цели, наиболее часто встречающиеся при дизайн-проектировании программного обеспечения.

Таблица 1.1 Общие цели, наиболее часто встречающиеся при дизайн-проектировании программного обеспечения
ЦЕЛЬ ОПИСАНИЕ
Возможности (Features) Приложение должно поддерживать как можно больше возможностей и технологий.
Пригодность к обслуживанию и внесению изменений (Maintainability) Исходный код приложения должен быть легко читаем, понятен и хорошо документирован.
Быстродействие (Perfomance) Приложение должно запускаться и работать так быстро, насколько это возможно.
Переносимость (Portability) Приложение должно иметь возможность функционировать в различных окружениях и/или операционных системах.
Надёжность (Reliability) Приложение должно работать как можно аккуратнее, с наименьшим потреблением системных ресурсов и предоставляя пользователю только конкретные релевантные (максимально подходящие) данные.
Возможность повторного использования исх. кода (Reusability) Исходный код приложения должен быть пригоден для повторного использования в будущих проектах.
Стабильность (Stability) Приложение (или исх. код) должно одинаково эффективно работать в разных условиях.
Простота использования (Usability) Интерфейс приложения должен быть понятным и простым в использовании для конечного пользователя.

Как ты можешь видеть, существует несколько целей, из которых ты выбираешь наиболее важные. В то же время, ты также можешь ставить свои собственные цели и задачи. Для дизайн-проектирования нашего движка мы выберем всего 3 основных цели:
  • Пригодность к обслуживанию и внесению измемнений (Maintainability);
  • Возможность повторного использования исх. кода (Reusability);
  • Простота использования (Usability).
Это совсем не значит, что другие цели не важны. Но при разработке нашего движка мы сфокусируемся на этих трёх самых важных целях. Напомним, что наш движок предназначен, главным образом, для обучения, что также повлияло на выбор целей. Когда мы завершим изучение данного курса, ты можешь начать проектировать свой собственный движок. Тогда ты можешь выбирать для него и другие цели, как например быстродействие (Perfomance), переносимость (Portability) и стабильность (Stability).
Наилучший способ реализовать выбранные цели - это записать их на листе бумаги и повесить на видном месте у рабочего стола или приклеить листок к лицевой панели компьютерного монитора. Всякий раз, когда потребуется принимать какие-либо решения по дизайн-проектированию движка, эти цели всегда будут перед глазами. И уже исходя из этого списка, ты будешь выстраивать свою работу определённым образом. Например, тебе необходимо выбрать между двумя методами поддержки и рендеринга шрифтов. Метод А может иметь лучшее быстродействие, но меньшие возможности по настройке шрифтов. В то же время метод Б обладает расширенными возможностями (Features) по работе со шрифтами, но меньшим быстродействием (Perfomance). Если цель Быстродействие (Perfomance) является одной из приоритетных (и присутствует в списке выбранных), а расширенные возможности (Features) не так важны, тогда очевидно, что метод А более предпочтителен.
Разберём, как выбранные цели повлияют на наш дизайн-проект. Здесь всё просто: мы будем выбирать те методы и техники, которые наилучшим образом удовлетворяют нашим целям. Поставив цель Пригодность к обслуживанию и внесению измемнений (Maintainability), мы будем создавать легко читаемый, разборчивый код. Также стоит обратить внимание на подробное комментирование кода.
Всякий раз, когда ты проектируешь игровой движок, важно определить, будет ли он в будущем использоваться в других проектах или навсегда останется движком для одной игры. Возможность повторного использования исх. кода (Reusability) означает, что мы будем пытаться создать игровой движок, который можно позднее взять и применить в других проектах (собственно для этого движки и нужны). Имея под рукой такой reusable-движок, можно значительно сократить время на разработку игр. Мы попытаемся достичь этой цели, используя объектно-ориентированный дизайн, специфичный для нашего жанра. Так мы сможем намного проще и быстрее разрабатывать новые 3D FPS игры на основе нашего движка.
Наша третья цель, простота использования (Usability), тесно связана с двумя предыдущими. Если движок будет удовлетворять двум первым целям, то сделать его простым в использовании будет намного проще. В нашем случае, юзабилити (от англ. use - пользоваться) показывает, насколько легко и просто работать с нашим движком и создавать на его основе компьютерные игры. Движок, исходный код которого трудно поддерживать, плохо документирован и без возможности повторного использования кода, фактически является непригодным для создания игр. Мы нацелены на создание такого движка, который позволит пользователю (т.е. программисту, использующему наш движок) вызывать из него необходимые функции с минимальными усилиями и временными затратами. Один из способов достичь этого - создать наш движок с, так называемой, единой точкой контакта (single point of contact; в программировании эту технику ещё называют "единая точка входа") и чётко продуманным интерфейсом. Термин "единая точка контакта" означает, что программист использует только один класс, например, для доступа к главным функциям движка.

Средства визуального дизайн-проектирования

Как гласит известная поговорка, лучше один раз увидеть, чем сто раз услышать. Изображения позволяют намного быстрее и нагляднее представить любые процессы. Это особенно актуально, когда пытаешься визуализировать что-то большое и комплексное. Самое простое и быстрое решение - взять карандаш и бумагу, и нарисовать схему. Схемы рисуют все. В то же время нередки случаи, когда схема понятна автору, но частично непонятна другим людям, которые пытаются в ней разобраться. При разработке проекта, в котором участвует команда из нескольких человек (программисты, дизайнеры, сценаристы и т.д.), это может стать большой проблемой. Чтобы этого избежать, было разработано несколько стандартов, устанавливающих специальные правила для составление моделей, схем, алгоритмов и других концепт-документов. Одним из самых известных и общепризнанных стандартов в сфере разработки программного обеспечения (ПО) является Унифицированный язык моделирования (Unified Modeling Language), более известный как язык UML(external link).
UML определяет набор часто используемых форм диаграмм, получивших наибольшее распространение во всех отраслях мировой индустрии. В него включены диаграммы для наглядного изображения отношений между пользователями, классами, видами деятельности и т.д. Ты можешь использовать UML так, как тебе нравится, и создавать на его основе сколь угодно сложные схемы. Ты даже можешь использовать его для визуализации дизайн-проектов уже существующих систем (например исходного кода готового проекта), причём для этого даже не потребуется ни единой строки кода. Но самое главное, что UML может быть изучен абсолютно всеми. Это означает, что составленная тобой UML-диаграмма будет предельно ясна и понятна другим участникам команды.
Знание основ UML помогает значительно улучшить навыки составления и чтения проектной документации. В будущем это может здорово пригодиться при разработке дизайн-проектов больших и комплексных приложений.
В данном курсе мы не будем составлять UML-диаграммы. В данный момент у нас не стоит цель разработать безотказное приложение. Поэтому будем экспериментировать. Возникающие ошибки лишь улучшат твои навыки дизайн-проектирования естественным эволюционным путём. Поэтому здесь не обязательно строго следовать какому-либо стандарту. Если ты способен изложить свою идею на клочке бумаги и сделать так, чтобы она была понятна узкому кругу людей, вовлечённых в процесс, - тогда действуй!
Единственное, на что следует обратить внимание, это тот факт, что визуальное дизайн-проектирование очень важно и намного эффективнее, чем словесное описание. Во всех случаях, когда формулируешь какую-либо идею или концепцию, не используй длинные "текстовки". Лучше нарисуй схему.

Первые наброски движка

На данном этапе мы изучили довольно приличный объём теории по дизайн-проектированию. Настало время применить наши знания на практике, продолжив дизайнить наш движок. В этой главе мы определим, что наш движок должен делать и из каких компонентов он будет состоять. Этот процесс часто называют сбор необходимых требований (gathering of requirements).
Для начала определимся с тем, что наш движок должен делать и какие функции выполнять. Очевидно, что он должен служить для простого и быстрого создания игр 3D FPS. А что же должна делать игра, выполненная в жанре 3D FPS? Это, в свою очередь, порождает следующую дилемму. Что должно входить в движок, а что в игру? Если необходимо добавить новую функцию в игровой процесс, куда её лучше добавлять: в сам движок или в код игры? Поначалу на этот вопрос трудно ответить, так как это зависит от многих факторов. Во время разработки нашего упрощённого дизайн-проекта ты станешь лучше понимать различия между кодом, специфичным для движка, и кодом, специфичным для игры.
В нашем случае мы создаём простой движок, специально разработанный для игр жанра 3D FPS. Исходя из этого, мы можем сделать ряд предположений о тех играх, которые будут создаваться на его основе. Мы допускаем, что все эти игры будут в жанре 3D FPS и все они будут иметь схожую архитектуру, реализованную в коде движка. Помня об этом, перечислим некоторые задачи, которые будет выполнять именно игровой движок:
  • Движок будет предоставлять базовый фреймворк (от англ. framework - каркас, структура), к которому будут подключаться все остальные компоненты.
  • В движке будет реализована система управления ресурсами (resource management system), как например текстуры, сетки, звуки и т.д.
  • Движок будет предоставлять различные формы управления, как для самого себя, так и для пользователя.
  • Весь рендеринг (т.е. прорисовка графики) будет реализован в движке.
  • Система поддержки звука (sound system) движка будет управлять загрузкой и проигрыванием звуков.
  • Наконец, движок будет иметь кое-какие сетевые возможности.
Это предельно сжатый список, но, тем не менее, он включает в себя большую часть того, что должен делать движок. Но мы ещё не закончили. Сейчас мы входим в так называемую "серую зону", то есть зону, где функционал условно равномерно распределён между кодом, специфичным для движка и кодом, специфичным для игры. Нам необходимо извлечь из этой "серой зоны", те возможности (features), которые будут относиться к коду, специфичному для движка. И первое, на что мы обратим внимание в данном вопросе - это управление движком (engine control). Не вдаваясь в детали, мы знаем, что игра должна реагировать на нажатие клавиш, чтобы определять, что игрок хочет сделать в данный момент времени. Очевидно, что это относится к коду, специфичному для игры. Тем не менее, мы знаем, что процесс считывания нажатия клавиши является общим (generic; то есть инкапсулирован в библиотеках MS Direct Input), поэтому этот процесс будет всё-таки специфичным для кода движка. Исходя из вышеприведённого примера, мы доверим движку обрабатывать пользовательский ввод, но дадим игре возможность выбирать, какой ввод относится к игре (релевантен ей). Это лишь один пример применения фичи из всеобъемлющей "серой зоны".
В свою очередь, скриптинг (scripting) является той фичей, где в игре реализуется лишь специфичный (для игры же) функционал. Мы дадим движку возможность предоставлять базовую поддержку скриптов, а игра может пользоваться этой фичей на своё усмотрение (точно также, как мы это продемонстрировали на примере с пользовательским вводом).
Игра также будет содержать различные объекты, например: игроки и оружие. Эти объекты должны быть определены (defined) и обработаны (processed). Ты позволишь игре определять свои собственные объекты и предоставлять их с уже готовой инфраструктурой для интегрирования в процессинг движка (engine's processing). Раз мы знаем, что наша игра будет в жанре 3D FPS, то мы можем сделать ещё один шаг вперёд и просто определить базовые объекты в движке. В то же время мы сделаем эти объекты достаточно гибкими, чтобы их свойства могли быть расширены кодом игры, либо полностью игнорированы им, если это необходимо в данной ситуации. Наконец, зная, что движок создаётся для игр жанра 3D FPS, мы можем позволить себе имплементировать менеджмент сцен (scene management) в самом движке, чтобы потом легко и просто рендерить всё, начиная от карты и заканчивая всеми объектами в сцене.
Рассмотрим подробнее каждый из наиболее важных компонентов нашего движка.

Фреймворк (Framework; каркас)

Англоязычное слово "фреймворк" (от англ. framework - каркас, структура) является узкоспециализированным программистским термином, а потому на русский язык не переводится. Оно означает базовую, неизменяемую часть нашего движка, которая позднее будет "обрастать" различными компонентами.
Фреймворк очень важная часть движка. Представь себе каркас автомобиля. Если он будет неправильно спроектирован, в будущем в нём могут быть обнаружены различные проблемы (непрочность некоторых узлов, или недостаточный объём пространства для размещения двигателя и других компонентов). Подобно каркасу автомобиля, фреймворк игрового движка является своеобразным скелетом, который держит на себе все другие компоненты, предоставляя им достаточное пространство и поддержку для связи друг с другом. Фреймворк также определяет, как в движке будет протекать основной процессинг (general processing). Мы уже упомянули единую точку контакта для движка, и это как раз то место, где она будет представлена. Фреймворк просто позволит всем компонентам движка взаимодействовать друг с другом, в то же время позволяя игре (созданной на данном движке) взаимодействовать с каждым из этих компонентов. И всё это будет осуществляться через ОДИН базовый класс. Фреймворк также будет осуществлять взаимодействие с операционной системой и предоставлять другие виды функциональности как например linked list (англ. - "связный список"; является общепринятым в программировании термином, поэтому на русский язык обычно не переводится) и менеджмент ресурсов (resource management).
Фреймворк должен также обладать некоторой степенью гибкости. Мы хотим дать возможность программисту некоторую свободу действий в плане предварительной настройке движка и его дальнейшему использованию. Прямо сейчас невозможно определить все необходимые "гибкие" зоны. Тем не менее мы можем предложить саму концепцию гибкости, которая позднее будет интегрирована в движок при необходимости. Другими словами, нам необходимо предоставить программисту метод для начальной инициализации движка, чтобы затем он функционировал определённым образом. Более подробно мы коснёмся этого в Главе 1.2, когда начнём создавать наш фреймворк.

Ресурсы


Image
Рис.1 Все персонажи находятся в едином 3D-пространстве, на одной карте уровня (map level); Quake II (1997, iD Software)


Иногда в игрокодинге их ещё называют ассетами (англ. asset - материальный актив, имущество, ценность). Ресурсы представляют собой всевозможные массивы данных (изображения, звуки, модели и т.д.; не всегда они представлены в виде отдельных файлов), которые придают игре большую реалистичность. Например текстура - это ресурс, также как звуковой эффект и полигональная сетка (3D-меш). Правильнее их назвать примитивными ресурсами. Игре также могут понадобиться комплексные ресурсы, которые представляют собой комбинации примитивных ресурсов. Например, игровой уровень (или карта) может состоять из набора 3D-мешей (которые покрыты текстурами), звуковых эффектов и скриптов, оперирующих звуками на этом уровне. Такой игровой уровень (или карта) является комплексным ресурсом, состоящим из нескольких примитивных (простых) ресурсов. Как игра использует комплексные ресурсы, зависит от множества факторов. Обработкой комплексных ресурсов, как правило, занимается код, специфичный для игры (game-specific code), в то время как движок предоставляет поддержку примитивных ресурсов.
К счастью, в нашем случае, мы уже предположили, что все игры, построенные на нашем движке, будут 3D FPS-играми. Таким образом, допускаем, что в них будут использоваться комплексные ресурсы, например такие как карты уровней (map levels).
Закрыть
noteПримечание

Картой игры (карта уровня, игровой уровень) обычно называют отдельную 3D-сцену, или 3D-пространство, на котором разворачивается действие 3D FPS-игры)

Исходя из этого, мы наделим наш движок всеми необходимыми функциями для поддержки различных комплексных ресурсов, например таких как карты уровней. Позднее мы расскажем и о других комплексных ресурсах, по мере продвижения работы над нашим движком. Мы пока даже не начинали думать об игре; мы всё ещё дизайн-проектируем игровой движок. Поэтому сейчас мы рассмотрим различные примитивные ресурсы, которые будет поддерживать наш движок.
Мы знаем, что движок точно должен поддерживать такие ресурсы, как 3D-меши и звуковые эффекты. Но есть ещё один интересный вид ресурсов, поддержку которого мы хотим включить в наш движок: текстуры (в общем случае, изображения, которые рендерятся на экране или на поверхности полигональной 3D-модели). DirectX уже предоставляет нам базовый функционал по работе с текстурами, но мы на этом не остановимся. Представь, как будет здорово, если мы сможем так определить текстуру (например травы), что когда ты идёшь по ней, она звучит как трава, а когда стреляешь по ней, куски грязи подлетают в воздух. Это, как раз, то, что мы собираемся сделать. Мы создадим новый ресурс - материал (material). Материал будет иметь текстуру, назначенную ему, а также другие свойства, например то, как на него будет влиять свет и то, как он будет обрабатываться движком. Мы можем добавить к нашему материалу всё, что угодно, например звуковой эффект при хождении по нему. Более подробно мы рассмотрим материалы в Главе 1.8, когда будем создавать нашу систему материалов (Material system).
Сейчас мы должны усвоить лишь одно: все эти ресурсы обязательно обладают схожими друг с другом чертами либо имеют несколько общих свойств. Каждый из этих ресурсов физически расположен на жёстком диске ПК и прежде всего их необходимо считать и загрузить (например в ОЗУ). Также это означает, что все эти ресурсы будут иметь имя файла (filename) и путь (path), указывающий, где конкретно (в какой поддиректории) расположен файл на жёстком диске. Когда ты загружаешь ресурс, он занимает определённый объём памяти. Например, если тебе необходимо загрузить 3D-меш (т.н. полигональную сетку, модель) автомобиля, и затем выяснится, что тебе нужен ещё один инстанс (от англ. instance -экземпляр) этой же модели (например, чтобы разместить её где-нибудь ещё в игровом мире), то повторная загрузка этого ресурса с жёсткого диска будет недопустимым расточительством процессорного времени и памяти.
Гораздо лучше (и профессиональнее) загрузить 3D-меш один раз и затем разрешить любому экземпляру автомобиля использовать этот ресурс. В этом случае нам достаточно хранить в памяти всего одну копию, загруженного ранее, 3D-меша. Здесь за дело берётся менеджер ресурсов (Resource manager). Мы будем использовать специальный класс, который способен загружать ресурсы любого типа и гарантирует, что только одна копия загруженного ранее ресурса присутствует в памяти в данный момент времени. Даже если мы даём команду загрузить этот ресурс дважды. Более подробно мы рассмотрим менеджер ресурсов в Главе 1.2

Управление движком (Engine control)

Процесс управления движком можно условно разделить на две части:
  • контролирование процессинга движка (engine processing);
  • пользовательский ввод (user input).

Контроль процессинга движка (Engine processing control)


Image
Рис.2 Главный цикл игрового приложения (Game loop)


В двух словах, процессинг движка - это всё, что происходит в движке за то время, пока готовится к прорисовке каждый кадр выводимого изображения. Мы научимся полностью контролировать этот процесс. Например, в движок может быть интегрирована система игры по сети (Networking system), звуковая система (Sound system), система рендеринга (rendering system) и игровая логика (Game logic), которые требуют определённое время на обработку (processing time), которое, в свою очередь, в каждом кадре строго ограничено. Для того, чтобы дать всем системам без задержек выполнить свою работу, необходимо разработать нечто вроде контроля процессинга (processing control). Для этого сделаем следующее:
  1. Используем то, что называют стейтами (от англ. state - состояние).
  2. Применим, так называемый, тайминг (timing), чтобы заставить компоненты вернуть результат выполнения по истечении лимита времени.
При обработке каждого кадра движок будет проделывать ряд операций по подготовке всего необходимого для данного кадра.
Этот процесс называется игровой цикл (game loop). На Рис.2 представлен пример простого игрового цикла, каждая итерация (от англ. iteration - одно полное прохождение какого-либо цикла, однократное выполнение алгоритма или функции) которого занимает какое-то время, зависящее от множества факторов (насколько быстрый процессор, какая видеокарта, и как хорошо оптимизирован код для запуска на данном "железе").

Стейты (States)

Типичная игра может состоять из нескольких стейтов. Например, в ней может быть стейт меню (menu state), стейт видеовставки (cutscene state) и стейт игрового процесса (in-game state). Для каждого кадра движок должен решить, какой из стейтов должен быть обработан (processed). Ключевым здесь является тот факт, что далеко не для всех стейтов требуется обязательная обработка всех компонентов. Более того, каждый стейт может иметь различные параметры процессинга. Вместо того, чтобы просто вслепую обрабатывать каждый кадр, мы можем использовать стейты, для указания движку, что должно обрабатываться, а что нет. Например, нетрудно догадаться, что в стейте видеовставки (cutscene state) будет отсутствовать сетевая активность. Тогда в данном стейте движок может выделить меньше времени для системы игры по сети (network system) (или полностью пропустить её) и дать больше времени другим компонентам, как например системе рендеринга.

Пользовательский ввод (User input)

Это процесс считывания нажатий клавиш клавиатуры или перемещение мыши, + превращение этих сигналов в данные, пригодные для игры .
Суть в том, чтобы предоставить пользователю (или игрокодеру) возможность вводить определённые данные, влияющие на работу движка. Как мы ранее упоминали, мы не будем строго определять пользовательский ввод в игре, но мы предоставим поддержку пользовательского ввода и дадим возможность игре самой решать, как его интерпретировать. Мы достигнем этого через использование так называемого класса-оболочки (wrapper class), инкапсулирующий (а местами просто дублирующий) функции класса DirectInput. Об этом классе-оболочке и, ранее упомянутой, системе стейтов будет подробнее рассказано в Главе 1.3, когда мы будем добавлять эти формы контроля в наш движок.

Скриптинг (Scripting)


Image
Рис.3 Скрипт игры TES3: Morrowind. Источник: https://images.uesp.net


Скрипт - это программа. Но программа, предназначенная не для перевода в бинарный код (компиляция), а для распознавания (интерпретации) команд "на лету" и немедленного выполнения (см. Рис.3). Для выполнения скриптов необходима программа-интерпретатор. Ярким примером использования скриптов являются конфигурационные файлы (config files) в Counter Strike и других играх.
Скриптинг (или система скриптов, scripting system) является одним из самых потрясающих аспектов разработки компьютерных игр. А всё из-за того, что скрипты позволяют модифицировать значительные участки игры без необходимости повторно рекомпилировать исходный код. Скриптинг может быть полезен в двух случаях:
Во всех случаях, когда разрабатываешь игру, ты должен всегда использовать систему скриптов в том или ином виде. При этом неважно, разрабатываешь ли ты собственную простую систему скриптов либо используешь полнофункциональную коммерческую систему от сторонних разработчиков.
В нашей игре мы определённо будем использовать простую систему скриптов, так как мы можем с уверенностью назвать объекты из типичной 3D-FPS игры, которым она здорово пригодится. Например, оружие может использовать скрипты чтобы определить свойства (как часто оно стеляет, дальность стрельбы, какой ущерб наносит и так далее). Таким образом здесь мы будем использовать очень простой вид скриптов называемый скрипт свойств (property script). Скрипт свойств не использует команд, как это делают обычные скрипты. Вместо этого в нём перечисляются различные свойства и их соответственные значения, которые считываются движком. Этот тип скриптов как раз подойдёт для установки различных свойств объектов игры (например оружия). Более подробно мы расскажем о скриптах в Главе 1.4, когда будем создавать нашу систему скриптов.

Рендеринг (Rendering)

  • Может значительно сократить время разработки игры, например при балансировке игровой механики (например, для точной настройке баланса способностей персонажей в игре), так как позволяет производить точную настройку и немедленно видеть результат;
  • Способен значительно повысить ценность игры, дав возможность конечному пользователю (игроку) модифицировать игру и, в некоторых случаях, даже создавать собственный игровой контент (например добавлять новые игровые уровни или новые объекты, вроде оружия и скинов для персонажей).
Одна из главных систем любой игры - это система рендеринга (rendering system). В нашем случае под рендерингом понимаем вывод изображения, прорисовка игровой сцены на экране монитора, а также всех деталей на ней.
Система рендеринга представляет собой совокупность методов, отображающих различные изображения на экране монитора. Это одна из самых ресурсоёмких систем игрового движка и, как правило, является наиболее оптимизированной частью игры. Для нашей системы рендеринга мы будем использовать Direct3D (является составной частью DirectX 9.0). Точнее мы будем использовать устройство Direct3D (Direct3D device; является программным объектом DirectX) чтобы рендерить вершины на экране монитора, которые образуют полигоны для создания форм нашей 3D-сцены 3D-среда; 3D-enviroment). Мы также будем использовать текстуры, которые будут накладываться на эти полигоны, чтобы создаваемые формы различных объектов выглядели также, как и их прототипы в реальном мире. Другими словами, кирпичная стена будет выглядеть как кирпичная стена, в то время как на самом деле это лишь несколько полигонов, с наложенной на них кирпичной текстурой.
И последнее, что важно помнить, это то, что не все компьютеры одинаковы с точки зрения их физической конфигурации оборудования (hardware configuration). Различные конфигурации могут вести себя по-разному при использовании одних и тех же настроек экрана (Display settings). Одни компьютеры поддерживают более высокие разрешения изображения, выводимого на экран, чем другие. Это тоже надо учитывать. Мы построим небольшой интерфейс графических настроек (Graphics settings interface) и интегрируем его в наш движок. Это позволит конечному пользователю (игроку) настраивать графическое разрешение, глубину цвета и частоту обновления (refresh rate) экрана. Более подробно мы рассмотрим эту тему в Главе 1.5, когда будем добавлять Direct3D device в наш движок.

Система звука (Sound system)

Ни одна игра не обходится без звуковых эффектов. Будь то звуки выстрелов оружия, шагов персонажей или взрыва цистерны с топливом. К счастью процесс внедрения звуков в игру максимально прост благодаря DirectMusic (является составной частью DirectX 9.0). И пусть название не вводит тебя в заблуждение: эта клёвая библиотека способна на куда более серьёзные вещи, чем просто проигрывание музыки. Мы, конечно, могли бы разработать систему звука через использование DirectSound (является составной частью DirectX 9.0). Но DirectMusic выглядит перспективнее, так как работать с ним намного проще. Более того, он даже предоставляет возможности работы с 3D-звуком (3D sound effects) через применение всё того же DirectSound. То есть когда в игре твой друг (или враг) подкрадывается слева, это будет звучать так, как будто он действительно подкрадывается к тебе слева.
Для достижения этого мы будем использовать класс-оболочку (wrapper class), который возьмёт на себя реализацию всей необходимой функциональности и будет реализован в виде единого небольшого класса, который позволит с лёгкостью загружать и проигрывать различные звуки в игре. Елс помнишь, при рассмотрении игровых ресурсов, мы определили звуки как типичные примитивные ресурсы. Мы также упомянули о том, как мы будем использовать наш менеджер ресурсов, чтобы загружать наши ресурсы для последующего использования. И снова, к счастью для нас, DirectMusic предоставляет интерфейсы, которые как раз этим и занимаются. Это значит, что для загрузки звуков в игру нам даже не понадобится менеджер ресурсов! Жаль, что DirectX 9.0 не предоставляет подобной поддержки для других ресурсов. Более подробно мы рассмотрим систему звука в Главе 1.6, когда будем её внедрять в наш движок.

Система игры по сети (Networking)

Играть одному, конечно, весело. Но играть в игру с другими людьми - это вообще ураган! Несмотря на это, существуют игры, где система игры по сети совсем не нужна (например, настольные, карточные игры). Но это не наш случай. Система игры по сети (в нашем случае) - это всё то, что предоставляет инфраструктуру, которая позволяет двум или более компьютерам связываться через сеть для синхронизации текущего состояния игры (актуального для всех этих компьютеров). И здесь DirectX приходит на помощь, представляя ещё один специализированный компонент DirectPlay (является составной частью DirectX 9.0). DirectPlay отвечает за соединение компьютеров через локальную сеть (или через сеть Интернет, которая, на самом деле, также организована как обычная сеть, только очень большая) и позволяет им взаимодействовать друг с другом, передавая информацию. Для того, чтобы это произошло, мы вновь будем использовать класс-оболочку, который инкапсулирует в себе функциональность DirectPlay. Это поможет нам создать более простой и понятный интерфейс для создания наших многопользовательских игр. DirectPlay поддерживает работу с двумя основными видами сетей:
  • одноранговые сети ( peer-to-peer, р2р; от англ. "равный к равному"; также их называют "пиринговые" сети);
  • сети с клиент-серверной архитектурой (client-server).
В главе 1.7 (когда мы будем внедрять систему игры по сети в наш движок) ты увидишь как мы ловко скомбинируем эти две архитектуры и используем получившуюся гибридную модель, чтобы пользоваться преимуществами обоих архитектур.

Игровые объекты (Game Objects)

Объекты являются строительными блоками любой динамической сцены. Всё, что не статично - это объект. Например, оружие, лежащее на земле это объект, который может быть подобран и использован. Кирпич в стене не является объектом, потому что это часть статичной геометрии игровой среды (сцены). В то же время, если бы тот же кирпич лежал на земле, он мог бы также стать объектом, так как игрок мог бы пнуть его, либо подобрать а затем бросить. Все эти объекты относятся к игровым объектам (game objects). Таковыми они и являются: они программируются как часть игры и, главным образом, никак не реализованы в игровом движке (разве что соответствующая инфраструктура для них может быть закодена в нём). В любом случае, мы сделали ряд предположений о том, для чего будет использоваться наш движок, вцелом допуская имплементацию (программную реализацию) различных игровых объектов прямо в движке.
Мы реализуем базовый объект (base object) в движке. Он будет иметь возможность перемещаться по игровой сцене, рендерить (отображать) себя на экране и сталкиваться с другими объектами игровой среды (игрового уровня). На его основе мы создадим другие объекты, применив одну из основных концепций объектно-ориентированного программирования наследованиеg (inheritance). В общих чертах этот термин означает создание, например, класса, который наследует функциональность другого, базового класса. Другими словами, мы создадим базовый объект со всеми базовыми атрибутами, необходимыми для создания других объектов. Все новые объекты, которые мы создадим позднее, будут построены на основе этого базового объекта и будут наследовать (брать на себя) его функциональность. Об этой и других концепциях объектно-ориентированного программирования (ООП) мы расскажем позднее.
Эти концепции (в частности наследование и полиморфизма1) позволяют нам наделять новые объекты более продвинутой функциональностью, как например анимированные объекты, которые обладают возможностью анимировать любой из мешей (полигональных сеток), ассоциированных с ними. Мы также можем определить так называемые спаунер-объекты (spawnerg objects; от англ. "spawn" - рождать, являть на свет), которые обладают возможностью споунить (т.е. являть на свет, генерировать в определённом месте 3D-сцены) другие различные объекты, например оружие. В то же время, все эти объекты будут достаточно гибкими, чтобы на их основе было возможно создавать другие объекты или полностью игнорировать их те или иные свойства, которые в данный момент не требуются. Когда придёт время создавать игру, ты обнаружишь, что многие базовые функции и объекты уже созданы именно на основе сделанных ранее предположений. Это существенно ускорит процесс создания игры. В Главе 1.9 мы подробнее рассмотрим различные виды объектов и, конечно же, имплементируем (интегрируем) их в наш движок.

Менеджмент сцены (Scene management)

Когда запускаешь свою любимую FPS-игру, ты наверняка замечаешь, что все игровые уровни (game levels) ограничены (то есть не бесконечны). К одной 3D-сцене (далее - сцена) может присоединяться другая и все они имеют чёткие границы. Часто такие сцены называют игровыми картами (game maps). Но в данном курсе мы будем придерживаться термина сцена. Таким образом, менеджмент сцены - это всё, что связано c управлением игровыми уровнями или картами.
Наш движок должен иметь возможность управлять всеми аспектами игровой сцены, в частности:
  • загрузка сцены и выделение памяти для всего, что в ней содержится;
  • уничтожение сцены и освобождение памяти, ранее выделенной для неё;
  • эффективный рендеринг сцены путём разбиения статичной геометрии на иерархические объекты, часть из которых в данный момент времени отсекается (не брабатывается) с применением специальных приёмов: отсечение по пирамиде видимости (view frustum culling) и отсечение объектов, скрытых (заслонённых) другими объектами (occlusion culling);
  • обновление и рендеринг всех, необходимых в данный момент, объектов сцены;
  • работа с обнаружением столкновенийg (collision detection) между сценой и объектами на ней.
Как видим, у нашего менеджера сцены будет полно работы. Вполне логично, что на эту систему будет расходоваться львиная доля процессорного времени. И именно она будет первым кандидатом для будущих оптимизаций. Мы детально рассмотрим создание менеджера сцены в Главе 1.10. Сразу после этого мы будем полностью готовы к разработке игры.

Использование библиотеки D3DX

DirectX включает в себя одну очень полезную библиотеку D3DX. Эта библиотека специально создана для того, чтобы оградить программиста от множества хитросплетений и "подводных камней", связанных с использованием Direct3D. А, так как мы планируем применять Direct3D везде, где будет рендеринг, причём наша цель сделать это как можно быстрее и проще, то без данной библиотеки нам просто не обойтись. При создании движка мы будем использовать именно её.
Библиотека D3DX предоставляет доступ к определённому числу интерфейсов, которые обладают всем необходимым функционалом. Но наилучшим образом они "заточены" под управление, анимацию и рендеринг полигональных сеток (мешей). Также, она содержит огромное количество функций, связанных с математическими вычислениями (матрицы, кватернионы, векторы, плоскости и т.д.). D3DX является настоящей находкой для начинающих игрокодеров, так как предоставляет солидный базовый функционал, необходимый для работы с 3D-пространством. Когда ты почувствуешь себя профессионалом, ты захочешь выйти за границы D3DX и, возможно, создать свою собственную библиотеку, наподобие этой. Но сейчас в этом нет необходимости.
Как только установишь DirectX Software Development Kit (SDK), загляни в раздел по D3DX в документации по DirectX (там всё на английском языке). Ты найдёшь его в секции DirectX Graphics. Все интерфейсы библиотеки D3DX хорошо документированы. В данном курсе мы используем D3DX в нашем движке и при разработке финальной игры.

Итоги Главы

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

Выбранный в данном курсе язык программирования - C++. И если ты его пока не освоил, читай статьи:
Это важно.

Источники:


1. Young V. Programming a Multiplayer FPS in DirectX 9.0. - Charles River Media, 2005


ДАЛЕЕ ==> Кодим 3D FPS DX9. 1.2 Создаём фреймворк движка

Последние изменения страницы Воскресенье 03 / Июль, 2022 10:43:59 MSK

Последние комментарии wiki

No records to display

Search Wiki Page

Точное совпадение

Категории

|--> C#
|--> C++