Разработка собственного движка, напутствие

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

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

АТЕНШОН, МНОГА ТЕКСТА НЕТ ПИКЧ

Предостережение

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

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

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

А как писать?

Для начала нужно подвести черту между двумя кардинально разными подходами в написании этих ваших "движков". Я обозвал их так...

  • "Динамические движи", это те в которых вся игровая логика содержится внутри объектов, которые можно передать в другие бинарные модули. (Прим - .dll библиотеки)
  • "Статические движки", это те в которых вся логика содержится внутри самого движка и определяется на этапе компиляции.

Какие нюансы я обнаружил при такой классификации?
"Статические движки"

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

"Динамические движки"

  • Требуют глубокого понимания матчасти, из-за виртуализации объектов.
  • Код можно разбить на независимые модули и подключать буквально на лету. В некоторых играх есть моды которые реализуют "движок в движке", которые используют другие подключаемые модули.
  • Большинство инструментов для отладки придётся писать самому, если это не встроено в сам язык. То есть, если ты не пишешь на .NET/JAVA/Lua?
  • "Скрипты" могут быть реализованы в виде скомпилированных бинарников обёрнутого в объекты, от чего "потанцевал" при добавлении контента может быть больше (в плане производительности).

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

Кроссплатформенность

Это боль. (вся глава моё нытьё...)
Первая мысль которая должна посетить твою голову — как распространить игру на максимально широкую аудиторию.
Ты можешь использовать кроссплатформенные библиотеки, но их функционал крайне ограничен, так как они пытаются создать абстракцию которая "будет жить на любой платформе". Ты конечно можете написать свою библиотеку... Но вот в чомъ мем
Проблема в том, что платформы достаточно сильно отличаются даже в самых базовых вещах. Как пример - Windows при старте программы не имеет потоков ввода-вывода и для этого ей нужно создавать консоль, но консоль можно создать 2 различными способами и у каждого способа есть куча параметров, для лююююбых потребностей. В Linux же всё проще, у тебя со старта приложения есть потоки ввода-вывода и что бы "увидеть их" нужно просто перенаправить их в файл или создать окно-терминал. КАК ты даже ТАКУЮ простую проблему будешь решать, я не представляю. (я это уже сделал)
Чего уже говорить о создании графического контекста...

Вот ты пытался в OpenGL на Windows? Знаешь что на официальном сайте написано использовать GetDC() при связывании контекста окна и OpenGL? А ты знал что это DC на самом деле может быть принтером? Может быть целым монитором? Видеокартой? Просто окном? "Ресурсом менеджера окон"? А то что в Linux ты должен ручками выбрать нужный тебе монитор для запуска? Всякие драйверы-сервера запускать? И не один. И это надо как-то собрать в одну единую абстракцию и непринуждённо дёргать по воле случая...

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

И всё же... Написать(наговнокодить) хотя бы простенькое приложение на API платформы, как по мне, важно. Так, ты будешь лучше понимать, что в конкретной системе значат абстракции которые дают фреймворки\языки.

Делаем игры, наивно

В начале статьи я ввёл понятие "компонента". На самом деле я называю так любую штуку "которая делает что-то другое" )))

Любой код можно разбить на некое число компонент-модулей. Самое примитивное приложение состоит всего из 3 модулей

  • Обработка ввода
  • Вывод изображения
  • Логика (зачастую на стейт машинах)

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

  • Загружаем нужные ресурсы
  • - В цикле...
  • Рисуем: фон > картиночки
  • Обрабатываем ввод
    - Если нажата мышка, проверяем хитбоксы объектов
    - Если прошли проверку, меняем "игровое состояние"
  • (для "давилки мух") Раз в Х циклов запускаем новую муху по некоторой траектории. И обрабатываем перемещение живых мух.

И это уже можно назвать игрой. "Погоди" - скажешь ты - "Но фактически тут 4 модуля, загрузка ресурсов!!". Хах, но если ты чуть-чуть разбираешься в линковке приложения, то ты можешь вшить данные в бинарник) И тебе не нужно будет загружать никаких данных. Поведение мух, также, вшито в движок и относится к логике, потому фактически тут именно 3 модуля.

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

Я бы сказал так - "движок занимается обработкой пользовательских данных и может иметь изменяемую логику"

Из такого определения давай же опишем некий "сферический движок для платформера".
Для начала, тебе потребуется описать больше чем один модуль, а логика самого движка будет гораздо сложнее.

  • Конкретная загрузка ресурсов
  • Обработка ввода, который конвертируется в абстракции
  • Вывод изображения, который обходит множество объектов
  • Игровая логика, которая уже разбита на несколько частей
    - Обработка загрузки-переходов уровней
    - Информация о сцене
    - Нахождение коллизий и проверки на триггеры
    - Обработка поведения ИИ?
    - Обработка ввода от игрока
    - Скриптовый процессор

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

  • Информация о сцене (уровне)
    - Какой фон
    - Набор тайлсетов (2D матрица)
    - Предметы
    - Юниты
    *** Всё кроме фона должно иметь информацию о себе, типа - "текстура", "какие-то свойства", есть ли "триггер", "скрит" или "поведение"
  • Вшитые в движок "примитивы"
    - Триггеры (какие бывают, что проверяют, что вызывают. прим - если хибоксы пересекаются - запускает смену уровня)
    - Поведение (хардкодное поведение, что делает объект. прим - триггернутый предмет увеличивает свойство Х, у объекта который триггернул. Объект Х движется в одну сторону и случайное время меняет направление. Это можно оформить в виде "функций" для скриптового языка. )
    - Свойства (Что за свойства, определяемые скриптами свойства)
    - Процессор скриптов
  • Обработка передвижений-триггеров-физики?

Ну и сам игровой цикл будет состоять примерно из такой логики

  • Загружаем информацию об уровне (описание уровня)
  • Загружаем нужные ресурсы (текстуры и скрипты)
  • - В цикле...
  • Рисуем: фон > тайлест > предметы > юниты
  • Обрабатываем ввод
    - Абстрагируемся от кнопок и просто передаём "Идти влево"
  • Обрабатываем логику
    - Проходимся по всем единицам со скриптами
    - Проходимся по тем, у кого базовое поведение
    - Что-то делаем с игроком?
    - Проверяем коллизии-физику? (вызываем триггеры если что-то нашли)
    - Двигаем юнитов-предметы

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

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

Поясню за скрипты - для многих это будет открытием, но скрипты можно делать и в виде виртуальной машины-процессора. Твоя задача будет - написать парсер текста, описать виртуальную машину и придумать как именно получать информацию о сцене-объектах. В качестве примера, можно использовать многострадальные стековые процессоры, по типу forth. Они очень легко делаются, но к их логике нужно привыкать. По сути это единственный выход написать "быстро" ваш собственный "скриптовый процессор".
*** Обязательно сделай вывод логов при сборке скрипта, или при его работе.
*** Старайся избегать логики типа [INT a += "Lord"]. Писать нетипизрованный код опасно, но можно выделить отдельные команды для работы с конкретными типами.
*** ДА, ТЫ БУДЕШЬ ПИСАТЬ СКРИПТЫ НА АССЕМБЛЕРЕ.
*** Для написания простого ассемблера, хватит и знаний типа - Sting.indexof("ADD"); и подобного говнокода. Но что бы написать нормально, или хотя бы простенький язык, вам нужны знания о "регулярных выражениях" или "парсерных комбинаторах".
*** Не надо упарываться в "полноценный язык", посмотрите как писались языки программирования в бородатых 80х, даже тот же Pascal. Они работают просто и честно, такие реализации займут у вас в разы меньше времени, чем описание "очередного" ???C\Rust\Haskell???

Типа канец?

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

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

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

  • Как лучше работать с памятью-данными.
  • Проблемы STL библиотек. (C++)
  • Детали виртуализации объектов, микро рекомендации.
  • Как можно делать "моды".
  • Профилирование и Логирование, почему это важно.
  • Что нужно знать о многопотоке, вводные-подводные.
  • Асинхронно или параллельно? Как это работает?
  • Работа с текстом-кодировками, и почему это настоящий ад.

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

PS - Под конец получилось немного сумбурно, ибо я писал всё за один заход. В целом я описал лишь поверхностно многие вещи. Если будет спрос, могу углубится.
PPS - Мне тут говорят что бы я сделал бусти и публиковался впредь там, раз в месяцок публикуя "фри контент". Может в следующий раз? Я просто не знаю о чём там можно писать, лол.
PPPS - "Статья слишкам длинная устал читать, пиши кароче"

Так что… Ещё свидимся?

8888
121 комментарий

Выложил Лонг в то время, пока сайт спит. Ещё и картинок мало.

Статья полезная, спору нет. Надеюсь ее кто то найти сможет утром

23
Ответить

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

2
Ответить

Комментарий недоступен

1
Ответить

Этот парень прав ☝️

1
Ответить

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

6
Ответить

Ну я первый раз что-то подобное писал, потому спасибо за совет.

Я вообще по угару писал себе всякий софт. Типо "компилятор для процессора Х" или "Визуализация шумов от измерений датчиков". Всё на нативном API. Ударила в голову идея объединить всё, и как раз знакомый хочет игру сделать, типо "ууу круто свой движок, так взлетим".
В целом вся история.

А статью написал чисто попробовать. Если будет время окупать, буду ещё писать.

5
Ответить

решился написать эту статью, ради привлечения инвестиций, на время разработки своей «базовой +18 новеллы».

Перечиал статью. Он что реально пишет движок что бы прозрачные png картинки показывать по порядку?визуальной новелл?

2
Ответить