Игровой код, который сам себя программирует

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

Для непосвящённых: Haxe — это язык программирования и кросс-компилятор. Это значит, что можно написать игру на Haxe, и она автоматически "переводится" на другой язык программирования, в зависимости от выбранной платформы (C++ для Windows, JavaScript для Web, и т.д.), и компилируется в нативную программу для той платформы.

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

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

Условная компиляция

Это, вероятно, самый простой способ, как можно повлиять на процесс сборки. Есть возможность выборочно компилировать куски кода, используя флаги компилятора.

Например, при разработке игр я всегда пользуюсь собственным редактором уровней, который встроен в саму игру. За исключением игры Speebot, этот редактор доступен только мне, и не включён в конечную сборку, которую запускает игрок. Это достигается "заворачиванием" всего кода, что связан с редактором, в условие, которое проверяет наличие флага "dev" при компиляции. Если флага нет — редактор "стирается" из исходного кода перед нативной компиляцией игры.

Встроенный редактор уровней в игре <a href="https://api.dtf.ru/v2.8/redirect?to=https%3A%2F%2Fkircode.com%2Fru%2Fpiliepals&postId=1183884" rel="nofollow noreferrer noopener" target="_blank">Пайли</a>, который доступен только в режиме разработки.
Встроенный редактор уровней в игре Пайли, который доступен только в режиме разработки.

Эта функция также позволяет мне отделять ресурсы для "demo" и "prod" версий. Демо версии моих игр включают в себя несколько уровней игры, и я использую флаги компилятора, чтобы определить, какие уровни, файлы музыки и т.д. нужно включить в сборку. Так неиспользуемые ресурсы просто не попадают в демо версии.

Кроме того, я использую флаги компиляции для включения или выключения некоторой оптимизации в моём игровом движке. Например, объединение 3D объектов в общую модель не используется в режиме разработки, потому что оно только мешает во время редактирования уровней. Другими словами, движок оптимизируется для редактирования уровней в режиме разработки. В финальных билдах — движок оптимизирован для самого игрового процесса.

Мета данные

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

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

Не все переменные в Settings нужно сохранять в файле, так как там есть и константы, которые менять не нужно. Такие поля помечаются мета тэгом "@ignore(true)". Движок, видя эту аннотацию, не включает такое поле в файл.

Макро

Это самая сложная и самая интересная функция метапрограммирования в Haxe. Macro позволяют запускать реальный Haxe код во время компиляции, который может напрямую модифицировать исходный код игры.

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

Но самый большой плюс для меня — это возможность переместить код из run-time в compile-time.

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

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

Всего 4 мира, в каждом по 50 уровней. Процент прохождения высчитывается на основе количества пройденных уровней и собранных кристаллов в каждом уровне данного мира.
Всего 4 мира, в каждом по 50 уровней. Процент прохождения высчитывается на основе количества пройденных уровней и собранных кристаллов в каждом уровне данного мира.

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

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

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

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

6060
16 комментариев

Ты, наверное, гений.
Я тут недавно для общего развития решил попробовать в с#, до этого не имел никакого опыта программирования. Всю жизнь связан с гуманитарными науками. И то ли мой мозг уже менее пластичный стал, то ли я просто тупой. В общем нихрена не понял. А тут 94год рождения, свой движой, игры. Красавчик, по хорошему завидую)

8
Ответить

94год рожденияэто типа молодой или типа старый?

1
Ответить

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

Ответить

А расскажи почему Haxe и не один из стандартных движков? Насколько сложно работать с асетами? Много ли готовых плагинов и прочего? Можно ли игру портировать на другие платформы? Вообщем всё интересно.

3
Ответить

О причинах использования собственного движка: https://kircode.com/ru/post/why-im-using-my-own-3d-game-engine

Haxe — язык с приятным синтаксисом, к тому же кросс-платформенный, собирается под что угодно. Создан специально для разработки игр. Много игр было выпущено под консоли, но сам я пока не пробовал.

Есть всякие библиотеки и довольно активное Open Source сообщество. Сам когда-то писал уроки для Haxe.

Создатель Haxe, кстати, сам разрабатывает на нём игры. Его компания выпустила Northgard, WarTals, Dune: Spice Wars...

3
Ответить

Edit: не туда поставил коммент.

1
Ответить

Народ, а есть варианты Macro для Unity? *звучит как прикольная фишка, но в голову приходит только вынести логику в редактор, чтоб какой-то скриптбл обджект обновлял перед билдом

Ответить