[Перевод] Автор Dwarf Fortress Тарн Адамс рассказывает о разработке игры
Одним из лучших способов применения мощных процессоров для развлечений долгое время была Dwarf Fortress — игра, в которой весь мир состоит из символов ASCII, и которая с радостью съест гигабайт памяти и большую долю процессорного времени.
Но в отличие от некоторых других игр, в случае DF игрок чувствует, что ей действительно нужно всё то, что она требует. Её подробные вычисления создают целый мир со зданиями, городами, торговцами, реками, вулканами, монстрами и, разумеется, гномами. Если бы один человек создал всё это, то это было бы потрясающим достижением; Dwarf Fortress — программа, создающая все эти объекты самостоятельно.
Автор игры Тарн Адамс согласился ответить на наши вопросы о своём творении, которое, несмотря на существование множества имитаций, до сих пор остаётся совершенно уникальной игрой.
Gamasutra: мы слышали, что Dwarf Fortress выходит в Steam и itch.io в платной версии с упрощённым интерфейсом. Говорят, что это вызвано вашими грядущими тратами на лечение. Сложно ли оставаться на плаву инди-разработчику?
Адамс: Да, много усилий приходится прикладывать, чтобы не уходить из бизнеса, и почти все разработчики вынуждены выдумывать творческие способы, чтобы иметь возможность оплачивать аренду жилья и прочие первостепенные нужды. И иногда ты сталкиваешься с проблемами, с которыми не можешь справиться. Нам пока везло, но со временем приходится меняться: увольняться и устраиваться на другое место работы, переходить на Patreon, а теперь и в Steam с itch, или рисовать карандашные рисунки, что очень далеко от создания видеоигр.
Как выглядит ваша повседневная работа по созданию кода для Dwarf Fortress? Какие языки вы используете? Какие библиотеки? Какие IDE, если пользуетесь ими? На каком компьютере вы разрабатываете игру?
Я работаю на обычном, ничем не примечательном ноутбуке Toshiba с Windows 10. Пишу код на ужасной смеси C/C++ в MSVC Community. Для legacy-версии я пользуюсь OpenGL, для основной — SDL, а для звука использую FMod. Больше ничем другим в Windows я не пользуюсь, за исключением части заголовков из MSVC (и до него), которые я использую уже десятки лет. Я не совсем в курсе, что происходит в версиях для Linux/Mac, потому что не разрабатываю их на регулярной основе.
Dwarf Fortress разрабатывается уже почти 17 лет, и её кодовая база должно быть достигла гигантских размеров. На моей машине для создания мира при стандартных настройках требуется больше 1,2 гигабайта ОЗУ. Проблема таких мегапроектов заключается в том, что они становятся слишком большими и не помещаются целиком в мозгу. Какими стратегиями вы пользуетесь, чтобы проект оставался доходчивым и понимаемым для работы?
У меня есть строгая система наименований, и я не экономлю на длинных названиях переменных и функций, чтобы всё было читаемым даже спустя несколько лет. В целом я стараюсь заботиться о будущем себе. Все мои комментарии в коде нацелены на это. Я активно пользуюсь функцией «найти в файлах». Но бывают ситуации, когда мне заново приходится разбираться в том, что происходит, например, при расширении старой системы или устранении бага; в таком случае просто на исследование может уйти час или больше. Это позволяет мне оставлять дополнительные полезные комментарии, о которых я изначально не подумал.
Я помню, что Threetoe (партнёр Тарна в разработке игры) пишет истории, а вы потом пытаетесь создать игровой движок, в котором они могут произойти. Мне до сих пор кажется, что это вдохновляющий способ работы. Были ли истории, которые вы отклонили, как слишком сложные в реализации? Бывало ли так, что сюжет одной из них полностью повторялся в игре?
Ха, я думаю, что в каком-то смысле все они слишком сложны. Мотивации персонажей, задание целей и т.д. продолжают надолго отставать от того, как они происходят в историях. Тем не менее, это всё равно полезный процесс, потому что всегда есть более лёгкие элементы генерирования истории; кроме того, мы можем приближаться к базовой механике персонажей, пусть даже никогда её и не достигнем.
В Википедии написано, что номер версии игры (сейчас это .44) показывает, насколько далеко вы от завершения (то есть на 44%). Что ждёт Dwarf Fortress в дальнейшем? Есть ли у вас предчувствия о том, что произойдёт? Какие серьёзные аспекты вам осталось реализовать?
Я завершаю релиз со злодеями, который выйдет в ближайшие несколько месяцев. Он должен быть довольно любопытным. Затем мы реализуем графику и повысим удобство игры для версий в Steam/itch. Потом мы улучшим процесс осад и выполним ещё кое-какую работу, и после этого перейдём к Big Wait. Это крупнейшая реструктуризация и расширение за всю историю DF. Она позволит нам генерировать мифы о творении и создавать полностью процедурные системы магии, а также открывать несколько окон обзора разных частей мира, и т.д. Это будет отличное дополнение. Потом будет релиз с собственностью/законами/таможней. После этого порядок пока не определён, но мы будем работать над экономикой, кораблями и другими важными компонентами, которых пока нет. Нам ещё многое предстоит сделать! Мы даже не прошли ещё полпути до версии 1.0. А ведь версией 1.0 разработка игры на самом деле не закончится… возможно, к моменту её выпуска у нас просто останется не так много времени.
В играх существует баланс между сюжетом и симуляцией, между заранее написанной историей, которая есть в большинстве игр, и созданием глубокого мира с набором правил, позволяющим возникать множеству различных историй. Я бы сказал, что Dwarf Fortress — один из самых серьёзных аргументов в пользу симуляции. Делают ли персонажи на этапе генерации мира или во время игры нечто такое, что удивляет даже вас? Можете привести какие-нибудь интересные/запоминающиеся примеры?
Да такое происходит постоянно! Частично это вызвано тем, что когда играешь, сложно держать все правила в голове. Однако все мои запоминающиеся истории — это баги, потому что я редко имею возможность достаточно долго играть в игру, поэтому с точки зрения самостоятельно возникающего геймплея они мало интересны (даже если удивляют меня). Хорошие истории можно найти на форумах, у стримеров, и так далее.
Граница между «микро» и «макро»: почему вы сделали её именно такой, между тем, что гномы могут делать сами, и что игрок приказывает крепости?
Это сложный баланс, и его не всегда легко соблюсти, но в данный момент концепция заключается в том, что игрок является «официальным выразителем воли крепости», а гномы проявляют автономность, которая должна присутствовать за пределами их официальных обязанностей. Это позволяет им быть актёрами в собственных историях, которые и являются основным источником возникающего (эмерджентного) повествования. В то же время игрок должен иметь возможность управлять основным потоком своей части игры (на самом деле это не критично, но часто более интересно, чем наблюдать за симуляцией).
Эти две цели могут конфликтовать, и часто это связано с тем, чтобы игрок продолжал получать от игры удовольствие — например, если аварийный рычаг действительно нужно опустить, то система приоритетов задач может практически заставить гнома сделать это, автономно или нет, в зависимости от того, должен ли он «знать» об этом, или нет. В прошлом у нас были проблемы с добавлением слишком сильной бюрократии, когда у нас, например, был гном-квартирмейстер, занимающийся выдачей оборудования. Но эта система была слишком медленной, подверженной багам и сбивала игроков с толку. Очень важно думать о том, как каждая отдельная игровая механика может добавлять потенциальные истории, а квартирмейстер почти не играл в этом никакой роли.
Какие шаги выполняет программа при построении мира?
Она выделяет память для карты. Затем она выбирает, какой полюс у неё будет (например, север, юг) (или учитывает параметры, переданные игроком). Seed генератора случайных чисел задаёт базовые значения полей карты (высота, осадки, температура, водоотвод, вулканическая активность, дикая природа) для сетки переменного размера с учётом различных параметров (океанов, размеров островов, другой вариативности), а затем программа фрактально заполняет их. От полюсов зависит изменение температуры, и программа выбирает точки для высочайших пиков. Здесь она делает первый проход, чтобы посмотреть, как выполняется процесс, и пытается изменить высоты, чтобы карта укладывалась в желательные параметры. На этом этапе, если мир нельзя исправить, он отбрасывается, и всё начинается заново.
Затем задаётся первое производное поле — растительность — зависящее от высоты, количества осадков, температуры и т.д. Программа проверяет, соответствуют ли биомы заданным в параметрах интервалам. На этом этапе сглаживаются высоты середины уровня, чтобы создать больше равнинных областей, а также размещаются вулканы в соответствии с полем вулканической активности.
Затем мы переходим к этапу эрозии и рек. Мелкие океаны осушаются, программа находит края склонов гор, с которых она может пустить тестовые реки. Кроме того, она располагает камеру на одной из них, чтобы игрок мог следить за процессом. Множество фальшивых рек течёт вниз из этих точек, прорывая каналы, если они не могут найти путь к морю. Слишком большие высоты иногда сглаживаются, чтобы вся карта не превратилась в каньоны. В идеале нужно бы использовать для этого виды минеральных веществ, но пока мы их не применяем. В нескольких точках рек выращиваются озёра.
Высоты снова сглаживаются от гор вниз к морю, а для пиков и вулканов выполняются локальные корректировки. После завершения создания высот программа вносит корректировки в величину осадков на основании дождевых теней и атмосферных осадков в горных областях. На основании высоты и осадков, а также сдерживающего эффекта лесов заново устанавливаются температуры, после чего программа использует новые значения для окончательного задания уровня растительности. Для океана и соседних с ним тайлов задаются значения солёности.
Определившись со всем этим, мы теперь можем найти границы окончательных областей биомов, чтобы дать им названия и индивидуальность. Здесь мы также добавляем геологические и подземные слои, хоть, как и сказано выше, геологическую информацию следовало бы добавлять раньше. Далее проводится финальный процесс верификации на соответствие параметрам, чтобы убедиться, что мы не слишком отклонились от того, что хочет игрок. Закончив с этим, прогрмма генерирует для каждого региона изначальные популяции животного мира и задаёт переменные погоды.
Сама история начинается с этого момента. Затем располагаются цивилизации и пещеры. Довольно сложно объяснить, что происходит дальше, но основная идея заключается в том, что симулируется огромная стратегическая игра с нулём игроков с довольно произвольными правилами ходов и плохим ИИ (но с тысячами агентов), на основании чего записывается история. Процедурная генерация историй при помощи ведения журнала симуляции — это вполне рабочий подход, но он имеет и свои недостатки. Это большой объём работы, необходимо выполнять постобработку и изучение, чтобы найти хорошие моменты, которые вы хотите подчеркнуть, и если динамики и механизмов будет недостаточно, то результат может оказаться скучным.
В DF мне больше всего нравится то, что игра не содержит никакой явной системы хитпоинтов, всё связанное с силой и уроном персонажа является частью сложной модели частей тела. Какие преимущества даёт подобная система? Если кто-то решит создать нечто подобное, то какие препятствия его могут ожидать?
Эта модель создаёт более подробные сюжетные моменты, долговременные последствия и обеспечивает бОльшую связь систем. Здесь легко перегнуть палку, и мы действительно кое-где её перегнули, по крайней мере, на текущем этапе — некоторые свойства материалов больше нигде не используются. Кроме того, существуют способы смягчения системы, примеры которых можно увидеть в играх, где, например, есть общий пул выдержки/энергии/хитпоинтов, но конкретные раны возникают или вследствие критических ударов, или как последствия достижения нулевых или близких к нулевым значений в пуле. Правильный выбор системы зависит от игры. Мы стремимся как можно меньше использовать числа, потому что числа обычно плохо вяжутся с историями.
Как выглядит основной цикл игры?
Возьмём для примера режим гномов. Он начинается с проверки объявлений и считывания автосохранений, и т.п. БОльшая часть остального цикла происходит не в каждом такте. Например, через каждую сотню тактов программа проверяет назначенные задачи и «странные настроения». Армии перемещаются по карте мира. Через каждую сотню тактов программа обрабатывает выдаваемые гномам задачи. Это своего рода невидимый аукцион, который используется для управления разными конфликтующими приоритетами. Каждые десять тактов меняются времена года, что влияет на погоду и карту (и локально, и во всём мире), а также выполняется проверка развития элементов сюжета (дипломаты, осады и т.д.) и проверка того, что форт всё ещё жив.
Затем программа берётся за то, что делается в каждом такте. Движутся жидкости и меняется другая информация тайлов карты (однако есть различные оптимизации, чтобы в каждом ходе не обязательно проверялся каждый тайл; кроме того, существуют флаги, которые позволяют пропускать целые области карты, если в них ничего не произошло.) Обновляется перемещение хищников и обрабатываются другие «события» карты, например, активные пожары.
Если флаг задан, то раненые/испытывающие жажду/голодные гномы, которые не могут позаботиться о себе, получают обновление. Мёртвые гномы «думают» о своих ритуалах погребения, чтобы можно было назначить другим эти задачи. Заключённые в клетки и цепи существа периодически обновляют свои мысли и ситуацию.
Затем, если существа пересекли края карты, они с неё убираются.
Каждые пятьдесят тактов обновляется информация всех таверн, храмов, библиотек и т.д, зависящих от других тактов. Припасы, тоже зависящие от других тактов, работают аналогично. Аналогично выполняется создание заданий на хранение. Несмотря на то, что этот процесс дополнен различными оптимизациями, он всё равно достаточно медленный и на определённом моменте 50 с лишним тысяч камней может вызвать проблемы.
Каждую тысячу тактов объекты, помеченные на удаление из игры, на самом деле удаляются, и освобождается выделенная под них память. С предметами это происходит чаще, раз в пятьдесят тактов, как и проверка использования зданий (в основном это обновления для колодцев и некоторые другие флаги, которые нужно проверять часто.)
Далее мы выполняем ещё одно обновление, работающее в каждом такте. Удаляются выпущенные из оружия снаряды. Действия (от танцев и обучения боевым искусствам до рассказа историй) обновляются при необходимости. Гномы и другие существа принимают решения и выполняют свои мгновенные действия (движение на соседний тайл, работа в мастерской и т.д.) — основная часть их ИИ (за исключением выбора заданий) выполняется здесь.
Каждую сотню тактов портятся предметы. Каждый такт выполняется рост растительности (хоть здесь есть много зависимостей и флагов.) При необходимости в каждом такте обновляются состояния зданий и перемещаются вагонетки. Выполняется продвижение по маршрутам перевозки. Обновляется температура (здесь есть множество флагов оптимизации, но пока это по-прежнему довольно медленный процесс.)
Наконец, обновляется камера, следующая за существом, за которым наблюдает игрок.
В Dwarf Fortress для отображения мира используется тайловая карта на основе сетки — простой и эффективный способ представления. Я заметил, что существует множество способов отрисовки тайлов, и это зависит от того, что делает или как себя чувствует существо, сколько элементов на тайле, есть ли что-нибудь над ним, является ли тайл текущей водой или он погребён под камнем. Когда DF принимает решение о том, как нужно отображать тайл, то что она делает? Как вы оптимизировали этот процесс?
Это просто символ (байт) с ещё несколькими байтами цвета, поэтому система совсем не затратна и мы можем просто заменить выбранное решение перед выводом на экран, а не пытаться решить всё одновременно, а большинству тайлов всё равно достаточно одного символа земли/стены. Программа идёт снизу вверх, в каком-то смысле используя «высоту» (существа находятся выше предметов, предметы — выше земли), время от времени меняя решение. Тем не менее, несмотря на наличие различных флагов и вспомогательных массивов, можно сделать ещё многое. Существует маленький массив возможных юнитов, которые можно вывести в каждый тайл, поэтому программа может реализовать покадровую анимацию, позволяющую увидеть каждый элемент на тайле, даже когда игра поставлена на паузу.