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

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

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

Почему не стоит писать свой игровой движок

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

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

Помимо бесплатных систем разработки игр, многие коммерческие игровые движки, полностью готовые к немедленному началу использования в игровых проектах, предлагают сразу несколько очень привлекательных схем лицензирования : полностью бесплатную (Unity 3D в бывшей Indie-редакции), смешанную схему с выплатой Royalties (Unreal Development Kit ) - 99 $ взнос за лицензию и выплаты 25 % прибыли после первых заработанных 5000 $, либо же доступную стоимость полновесной коммерческой лицензии (Unity Pro за 1500 $).

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

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

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

Подводим итог : написанием собственного игрового движка могут заниматься те, кто чётко представляет, что именно и зачем ему это нужно, видит адекватные преимущества такого подхода и способен за вменяемые сроки претворить свой план в жизнь. Всем остальным следует поискать готовое решение, благо оных в последнее время появилось достаточно - взять хотя бы те же Unity 3D и UDK.

Разработка игр на плаву, она перспективна и набирает популярность. Мы подготовили подробную инфографику о пути изучения разработки игр.

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

0. Разработка игр для детей

Многие книги ориентированы на работу с легендарной и интуитивно понятной средой разработки для детей Scratch, в том числе ScratchJr. После базиса следует информация о Python Pygame. Есть книга для пятилетних, но большая часть материалов подойдет для детей в возрасте от 8 лет.

1. Информатика

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

2. Языки программирования

Разговаривать на языке компьютера непросто, но возможно. И таких способов уйма. Например, язык C существенно повлиял на индустрию ПО, поделившись своим синтаксисом с популярными C#, C++ и Java. C++, в свою очередь, является мощным языком для создания эффективных программ и программных комплексов. Многие также пишут игры на C#: язык шустрый, удобный и позволяет быстрее стартовать разработку.

А вот Lua перенял кое-что от C++. Скрипт-язык хорош для игровой логики. Он упростит инициализацию уровня, привязку задач к объектам, смену поведения NPC в интерактивном режиме без необходимости перекомпилировать проект и многое другое.

3. Создание приложений

И если информатика – это базис теоретический, то здесь больше практики. Разработка игр – ухабистая стезя, и начать лучше с приложений. Книги с практическими заданиями, а также информацией о паттернах и UML помогут разобраться, что к чему.

4. Математика для разработки игр

Нет, здесь не будет школьного курса алгебры и геометрии. Подборка разбита на основы математики в сфере геймдева и более продвинутый уровень.

5. Игровое программирование

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

6. Разработка игрового движка

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

7. Компьютерная графика

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

Недаром этот раздел самый большой. Сюда включены основы программирования с Real-Time 3D, DirectX и OpenGL. Все дополнено информацией о рендеринге и технологиях. Отдельного внимания в подборке удостоились Direct3D и OpenGL.



8. Игровое аудио

Разработка игр касается и аудио: это звуки, издаваемые NPC, главным героем, явлениями или предметами, а также музыка. Аудио программирование обошлось всего двумя книгами, но в них доступно изложена необходимая информация.

9. Игровая физика и анимация

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

10. Игровой искусственный интеллект

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

11. Многопользовательское игровое программирование

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

В последнее время я занят тем, что пишу игровой движок на C++. Я пользуюсь им для создания небольшой мобильной игры Hop Out . Вот ролик, записанный с моего iPhone 6. (Можете включить звук!)


Your browser does not support HTML5 video.


Hop Out - та игра, в которую мне хочется играть самому: ретро-аркада с мультяшной 3D-графикой. Цель игры - перекрасить каждую из платформ, как в Q*Bert.



С чего бы кому-то хотеть написать игровой движок? Возможных причин много:

  • Вы - ремесленник. Вам нравится строить системы с нуля и видеть, как они оживают.
  • Вы хотите узнать больше о разработке игр. Я в игровой индустрии 14 лет и всё ещё пытаюсь в ней разобраться. Я даже не был уверен, что смогу написать движок с чистого листа, ведь это так сильно отличается от повседневных рабочих обязанностей программиста в большой студии. Я хотел проверить.
  • Вам нравится ощущение контроля. Организовать код именно так, как вам хочется, и всегда знать, где что находится - это приносит удовольствие.
  • Вас вдохновляют классические игровые движки, такие как AGI (1984), id Tech 1 (1993), Build (1995), и гиганты индустрии вроде Unity и Unreal.
  • Вы верите, что мы, индустрия игр, должны сбросить покров таинственности с процесса разработки движков. Мы пока не очень-то освоили искусство разработки игр - куда там! Чем тщательнее мы рассмотрим этот процесс, тем выше наши шансы усовершенствовать его.

Игровые платформы в 2017-ом - мобильные, консоли и ПК - очень мощные и во многом похожи друг на друга. Разработка игрового движка перестала быть борьбой со слабым и редким железом, как это было в прошлом. По-моему, теперь это скорее борьба со сложностью вашего собственного произведения . Запросто можно сотворить монстра! Вот почему все советы в этой статье вращаются вокруг того, как сохранить код управляемым. Я объединил их в три группы:

  1. Осознайте, что сериализация - обширная тема.

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

Используйте итеративный подход

Мой первый совет - не задерживаясь заставьте что-нибудь (что угодно!) работать, затем повторите.


По возможности, начните с образца приложения, которое инициализирует устройство и рисует что-нибудь на экране. В данном случае я скачал SDL , открыл Xcode-iOS/Test/TestiPhoneOS.xcodeproj , затем запустил на своём iPhone пример testgles2 .



Вуаля! У меня появился замечательный вращающийся кубик, использующий OpenGL ES 2.0.


Моим следующим шагом было скачивание сделанной кем-то 3D-модели Марио. Я быстро написал черновой загрузчик OBJ-файлов - этот формат не так уж сложен - и подправил пример, чтобы он отрисовывал Марио вместо кубика. Ещё я интегрировал SDL_Image , чтобы загружать текстуры.



Затем я реализовал управление двумя стиками, чтобы перемещать Марио. (Поначалу я рассматривал идею создания dual-stick шутера. Впрочем, не с Марио).



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



К тому моменту я отказался от формата OBJ и написал скрипт на Python для экспорта собственных JSON-файлов из Blender. Эти JSON-файлы описывали заскиненный меш, скелет и данные анимации. Я загружал эти файлы в игру с помощью библиотеки C++ JSON .



Как только всё заработало, я вернулся в Blender и создал более проработанного персонажа (Это был первый сделанный и зариганный мной трёхмерный человек. Я им весьма гордился.)



В течение следующих нескольких месяцев я сделал такие шаги:

  • Начал выделять функции работы с векторами и матрицами в собственную библиотеку трёхмерной математики.
  • Заменил.xcodeproj на проект CMake
  • Заставил движок запускаться и на Windows, и на iOS, потому что мне нравится работать в Visual Studio.
  • Начал перемещать код в отдельные библиотеки "engine" и "game". Со временем, я разделил их на ещё более мелкие библиотеки.
  • Написал отдельное приложение, чтобы конвертировать мои JSON-файлы в бинарные данные, которые игра может загружать напрямую.
  • В какой-то момент убрал все библиотеки SDL из iOS-сборки. (Cборка для Windows всё ещё использует SDL.)

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




Может показаться, что при таком подходе много времени теряется впустую, ведь вы всегда пишете плохой код, который потом требуется переписывать начисто. Но большая часть изменений представляет собой перемещение кода из одного.cpp -файла в другой, извлечение определений функций в.h -файлы или другие не менее простые действия. Определить, где что должно лежать - сложная задача, и решить её проще, когда код уже существует.


Готов поспорить, что больше времени тратится при противоположном подходе: пытаться заранее продумать архитектуру, которая будет делать всё, что вам понадобится. Две моих любимых статьи про опасности чрезмерной инженерии - The Vicious Circle of Generalization Томаша Дабровски и Don’t Let Architecture Astronauts Scare You Джоэла Спольски.


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


Итеративный подход дал мне куда более элегантную архитектуру, чем я мог бы вообразить, глядя на чистый лист бумаги. iOS-сборка моего движка сегодня на 100% состоит из оригинального кода, включая собственную математическую библиотеку, шаблоны контейнеров, систему рефлексии/сериализации, фреймворк рендеринга, физику и аудио микшер. У меня были причины писать каждый из этих модулей самостоятельно, но для вас это может быть необязательным. Вместо этого есть множество отличных библиотек с открытым исходным кодом и разрешительной лицензией, которые могут оказаться подходящими вашему движку. GLM , Bullet Physics и STB headers - лишь некоторые из интересных примеров.

Дважды подумайте, прежде чем слишком обобщать

Как программисты, мы стремимся избегать дублирования кода, и нам нравится, когда код следует единому стилю. Тем не менее, я думаю, что полезно не давать этим инстинктам управлять всеми решениями.

Время от времени нарушайте принцип DRY

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

  • Owned<> для динамически выделяемых объектов, имеющих единственного владельца.
  • Reference<> использует подсчёт ссылок чтобы позволить объекту иметь несколько владельцев.
  • audio::AppOwned<> используется кодом за пределами аудио микшера. Это позволяет игровым системам владеть объектами, которые аудио микшер использует, такими как голос, который в данный момент воспроизводится.
  • audio::AudioHandle<> использует систему подсчёта ссылок, внутреннюю для аудио микшера.

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


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

Использовать разные соглашения о вызове - это нормально

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


В моём C++ движке некоторые функции принадлежат классами, а некоторые - нет. Например, каждый противник в игре - это класс, и бо́льшая часть поведения противника реализована в этом классе, как и следовало ожидать. С другой стороны, в моём движке выполняются вызовом sphereCast() , функции в пространстве имён physics . sphereCast() не принадлежит какому-либо классу - это просто часть модуля physics . У меня есть система сборки, которая управляет зависимостями между модулями, что сохраняет код достаточно (для меня) хорошо организованным. Заворачивание этой функции в произвольный класс никоим образом не улучшит организацию кода.



Как минимум, постарайтесь представить, насколько сложными будут ваши требования. Если вы делаете маленькую игру вроде Flappy Bird, с несколькими ассетами, вам скорее всего не придётся много думать о сериализации. Вероятно, вы можете загружать текстуры напрямую из PNG и этого будет достаточно. Если вам нужен компактный бинарный формат с обратной совместимостью, но вы не хотите разрабатывать свой - взгляните на сторонние библиотеки, такие как Cereal или Boost.Serialization . Не думаю, что Google Protocol Buffers идеально подходят для сериализации игровых ресурсов, но они всё равно стоят изучения.


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


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

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

Разработка игр

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

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

Создание игр. Программирование

Начать создавать шедевры компьютерного мира теоретически может каждый. Но как можно понять, программирование игр - это очень сложно. Однако стать геймдевом может практически любой. Самое главное условие — много свободного времени и просто титаническая усидчивость. Допустим, у нас это имеется. Что же делать дальше?

В первую очередь нужно освоить хотя бы несколько самых популярных языков программирования. Без этого создать качественную игру вряд ли получится. Почему же несколько языков? Неужели одного недостаточно? Дело в том, что каждый programming language имеет свою четкую область применения. Ниже мы рассмотрим самые востребованные языки и их применение при программировании игр.

Языки

Пожалуй, наиболее универсальным языком в плане программирования игр является C++. Большинство современных игр и движков для них пишутся именно на нем. В чем же особенность данного языка? Пожалуй, одно из главных достоинств C++ заключается в огромном количестве всеобъемлющих библиотек. Благодаря этому посредством данного языка можно написать все что угодно: от маленькой инди игрушки до крупного проекта ААА класса.

Но к сожалению, C++ очень сложен в освоении. Новичок вряд ли сможет разобраться в этих дебрях. Именно по этой причине лучше начать свое знакомство с миром программирования с чего-нибудь попроще.

Python — это, пожалуй, лучший выбор для начинающего геймдева. Во-первых, синтаксис языка довольно прост. Для того чтобы начать программировать на Пайтоне, достаточно прочитать туториал и обладать нативным уровнем английского. Во-вторых, возможности данного языка программирования достаточно широки. Конечно, Пайтону не угнаться за C++ в плане функциональности. Тем не менее посредством Python можно создать вполне достойный софт (в том числе и игру). К примеру, на Пайтоне написаны такие игры, как "Батлфилд" (2005), "Цивилизация 4", "Симс 4" и много других проектов, которые стали настоящими хитами.

Java — еще один претендент, который определенно достоин внимания. Пожалуй, главное достоинство данного языка — полная кроссплатформенность. Это означает, что софт, написаный посредством "Ява", поддерживается всеми операционными системами ("Виндовс", "Линукс", "Андроид" и т.д.). Это позволяет быстро переделать игру под любую платформу. Помимо этого, Java дает программисту массу возможностей. За примерами далеко ходить не нужно. Вышеупомянутый "Майнкрафт" был написан именно на "Яве".

Однако что делать, если нет времени на изучение языков, но свою игру написать все-таки хочется? Тут на помощь приходят так называемые игровые конструкторы. Что это такое? Ответ на вопрос вы сможете найти ниже.

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

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

Программы для создания игр

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

Пожалуй, сама известная программа для разработки игр — Game Maker. Она предназначена для создания двухмерных проектов. Делать игры можно без навыков программирования. Вместо строчек кода пользователю предоставляют набор готовых действий. Все, что нужно сделать — создать объекты и определить правила взаимодействия между ними. Также стоит подметить, что рисовать спрайты можно прямо в Game Maker без использования посторонних программ. Поэтому софт является вполне самодостаточным. Мало того, Гейм Мейкер не обидит и продвинутых юзеров, которые обладают навыками программирования. Ведь в программе есть возможность добавлять свой исходный код. Посредством Game Maker можно создавать игры с видом сверху (РПГ, тактический шутер и т.д.) и сбоку (платформер).

Construct 2 — это еще один конструктор для разработки 2D-игр. Пожалуй, главная особенность данной программы — мультиплатформенность. Посредством "Конструкта" можно создавать игры для iOS, Android, Windows, Web и т.д. В плане функциональности Construct 2 ничем не уступает тому же "Гейм Мейкеру".

Вывод

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

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

Вот так вот я организовал структуру своего движка. Давайте я рассмотрю подробней. Начну с папок. Всего создано шесть папок. Первая - _Doc, будет содержать ссылки на используемую документацию по движку (история, лист задач и т.д.). Фишка: обратили внимание на подчеркивание? Так вот, это сделано специально чтобы папка была в самом вверху и не смешивалась с папками движка (папки сортируются по имени).
Application - это уровень приложения. Как вы видите в ней всего один проект с тем же именем. Данный проект - это динамическая библиотека (подробнее о видах библиотек можете прочитать http://ru.wikipedia.org/wiki/Библиотека_(программирование)). Пользователь в идеале должен будет работать только с этой библиотекой.
Папка Core - это основа из схемы. Она содержит 4 проекта. Каждый проект - это статическая библиотека.
Папка Engine - это ядро из схемы. Все проекты из папки, также являются статическими библиотеками.
Папка Framework пуста, потому что пока нет смысла в ней что-то делать.
И папка Test будет хранить приложения для тестирования частей движка.

То есть в итоге у нас одна динамическая библиотека, одинадцать статических которые будут линковаться к динамической. И проект.

Я не стану пояснять как я создал такой проект, так как это долго. Да и не расчитанно на новичков.

На этом подготовка завершена. Продолжим.

Любое приложение должно с чего-то начинаться. Это называется точкой старта (или входа) приложения. Обычно это или main() или WinMain(), а также все их производные... Но в движке нет точки старта, потому что движок не запускается сам по себе. Точку старта задает пользователь при создании приложения. Но я начну именно с нее проектировать и писать код. Наша точка старта будет в приложении test.

Пользователь должен при старте, создать наследника от класса Application, полиморфировать нужные методы, инициализировать наследник, запустить его на выполнение и затем очистить ресурсы.
То есть получается так:

Поясню. Start - это условный момент запуска приложения. End - это условный момент завершения приложения. WinMain и MyApplication - это уровень приложения. Данные сущности пишутся пользователем. iApplication - это интерфейс из библиотеки Application. Как вы видите, я выделил их цветом согласно схемы из предыдущей статьи. Я так буду поступать и дальше чтобы вам было легче ориентироваться.

Начнем писать код. Для начала создадим класс приложения. Находим Application и пишем:
Application.h

200?"200px":""+(this.scrollHeight+5)+"px");">
#pragma once

#include "export.h"

Class ENGINE_DLL Application
{
public:
Application();
virtual ~Application();

// Инициализация приложения.
bool Create();

// выполнение одного кадра
bool RunOneFrame();

// Выполнение приложения.
void Run();

// Завершение приложения.
void Shutdown();

Bool IsInit(){return _isinit;}
bool IsExit() {return _exit;}

Protected:
virtual bool _init() = 0;
// пользовательский кадр
virtual bool _frame() = 0;
// пользовательская очистка
virtual void _close() = 0;

Private:
bool _isinit;
bool _exit;
};


Application.cpp

200?"200px":""+(this.scrollHeight+5)+"px");">
#include "Application.h"

Application::Application() :
_isinit(false),
_exit(false)
{
}

Application::~Application()
{
// чистим за нерадивым программистом
if (_isinit==true)
Shutdown();
}

Bool Application::Create()
{
/*
Инициализация всех систем движка
*/

// пользовательская инициализация
if (_init() == false)
return false;
// сообщаем что приложение инициализировано
_isinit=true;
// сбрасываем флаг сообщающий о выходе
_exit = false;
return true;
}

Bool Application::RunOneFrame()
{
// следим чтобы не было попытки рисовать при неудачно инициализации
if (_isinit == false)
return false;

Return _frame();
}

Void Application::Run()
{
// Инициализируем
if(_isinit == false)
Create();

// выполняем бесконечный цикл
while (_exit==false && _isinit==true)
{
_exit = !(RunOneFrame());
}

// очищаем ресурсы
Shutdown();
}

Void Application::Shutdown()
{
_isinit = false;
}

Если вам нужен файл export.h, то он такой:

200?"200px":""+(this.scrollHeight+5)+"px");">
#pragma once

#ifdef WIN32
#ifdef _BUILD_ENGINE
#define ENGINE_DLL __declspec(dllexport)
#else
#define ENGINE_DLL __declspec(dllimport)
#endif
#else
#define ENGINE_DLL
#endif

При этом, где-то в Application должен быть объявлен _BUILD_ENGINE. В проекте на который я дал ссылку выше, все это уже есть.

Теперь вернемся к приложению:
main.cpp

200?"200px":""+(this.scrollHeight+5)+"px");">
#include
#include "../Application/Application.h"

Class MyApp: public Application
{
private:
protected:
// пишем пользовательские классы
virtual bool _init() { return true; }
virtual bool _frame() { return true; }
virtual void _close() {}
};

Int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow)
{
MyApp app;

Return 0;
}


Собственно вы уже можете собрать (но не забудьте прилинковать Application). Но запускать не советую - уйдет в бесконечность:) Мы ведь еще условий выхода не написали.

Так, теперь двигаемся дальше. Вспомним для чего нам был нужен Application - для обертывания остальных сущностей. Среди которых главной была Engine. Значит теперь ее нужно создать. Далее, по текущей задаче мы должны создать окно и инициализировать рендер. Значит в модуле рендера появляются два класса - Window и Render. Но так как мы решили что сущность рендера абстрактна, то тогда нужен еще один класс RenderDX11 и WindowWin32, которые будут выполнять реализацию использования в рендере DirectX 11 в окне операционной системы Windows.

Схема усложняется:).
Начнем выполнение с конца. Находим библиотеку рендера и пишем код:
Window.h

200?"200px":""+(this.scrollHeight+5)+"px");">
#pragma once

#include

Class Window
{
public:
Window();
virtual ~Window();

/// Создать окно
virtual bool createWindow(const std::wstring &caption, unsigned int width, unsigned int height)=0;

/// Закрыть окно.
virtual void closeWindow()=0;

/** Показать/Скрыть окно.
@param
value определяет, показывать ли окно (при true) или не показывать.
*/
virtual void showWindow(bool value)=0;

/// Вернуть заголовок окна
virtual const std::wstring& getWindowName() const {return _caption;}

/// Изменить заголовок окна.
virtual void setWindowCaption(const std::wstring& _caption)=0;

Protected:
std::wstring _caption; ///< заголовок окна
unsigned int _width; ///< ширина клиентской части окна
unsigned int _height; ///< высота клиентской части окна
};


Window.cpp

200?"200px":""+(this.scrollHeight+5)+"px");">
#include "Window.h"

Window::Window()
{
_width = 800;
_height= 600;
_caption = L"Engine";
}

Window::~Window()
{
}

Теперь нам нужен класс Render. Но тут я хочу сказать вот что - наш рендер должен быть единственным. Реализуем мы это через паттерн синглтон (погуглите, кому интересны подробности, прям по запросу "паттерн синглтон"). Так что сначала надо написать сам паттерн. Переходим к библиотеке Common. и пишем:
macros.h

200?"200px":""+(this.scrollHeight+5)+"px");">#pragma once

#include

#define Assert(a, b) assert(a && b)


Singleton.h

200?"200px":""+(this.scrollHeight+5)+"px");">
#pragma once

#include "macros.h"

/** Singleton pattern implemented using templates.
@remarks
Using singleton is useful for objects such as engine
or managers which should have no more than a single
instance in the whole application.
@remarks
If you want to make object of some class singleton you
have to derive this class from Singleton class.
@remarks
If something goes wrong during singleton object creation,
deletion or on attempt to access it, assertion arises.
By "something going wrong" I also mean attempts to create
more than single object of class marked singleton.
*/
template
class Singleton
{
public:
/** Creates singleton of type T.
*/
Singleton()
{
Assert(!s_pSingleton, "Singleton class already instantiated.");

#if defined(_MSC_VER) && _MSC_VER < 1200
int offset=(int)(T*)1-(int)(Singleton *)(T*)1;
s_pSingleton=(T*)((int)this+offset);
#else
s_pSingleton=static_cast(this);
#endif
}

Virtual ~Singleton()
{
s_pSingleton=0;
}

/** Returns reference to a singleton.
*/
static T& getSingleton()
{
Assert(s_pSingleton, "Singleton class not instantiated.");
return(*s_pSingleton);
}

/** Returns pointer to a singleton.
*/
static T* getSingletonPtr()
{
return s_pSingleton;
}

/// A static pointer to an object of T type.
/// This is the core member of the singleton.
/// As it is declared as static no more than
/// one object of this type can exist at the time.
static T* s_pSingleton;
};

Template T* Singleton ::s_pSingleton=0;


Паттерн не мой, поэтому сохранен язык оригинала:-D

Вообщем, я к сожалением на сегодня на этом завершаю:-(Хотел бы написать дальше, но 12 часов - пора спать. Ждите до завтра, будет продолжение. Мы наконец-то создадим окно и инициализируем движок.