700.000 строк кода и 20 лет разработки Dwarf Fortress | Перевод интервью Тарна Адамса для StackOverflow

700.000 строк кода и 20 лет разработки Dwarf Fortress | Перевод интервью Тарна Адамса для StackOverflow

В июле 2021 этого года на StackOverflow выходило интервью Тарна Адамса, программиста знаменитой игры Dwarf Fortress. Я решил сделать для вас перевод со вспомогательными ссылками. Интервью может показаться весьма интересным как для интересующихся геймдевом, так и для обычных игроков. Тайн генерации мира Тарн не раскрыл, но всё равно поделился некоторыми секретами устройства Dwarf Fortress.

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

Райан Донован, Редактор stackoverflow.blog

Разработка игры началась ещё в 2002 году. Сначала это было больше как хобби, однако после выхода в 2006, Тарн решил покинуть работу преподавателя и полностью посвятить себя разработке проекта. На сегодняшний день над проектом по-прежнему работает Тарн вместе со своим братом Заком, также помощь им оказывает издатель Kitfox Games.

В: Какими языками программирования и технологиями вы пользуетесь? Изменилось ли что-нибудь за 15-20 лет разработки?

О: DF — это некая смесь С и С++, при чём не в привычном понимании стандартизированного подчинения, а скорее это некий беспорядок, который только нарастает со временем. Я использовал Microsoft Visual Studio с MSVC 6, сейчас же я использую Visual Studio Community.

В качестве движка я использую OpenGL и SDL. Они были выбраны, так как легче всего портируются на системы OSX и Linux, хотя я, конечно, не мог сделать этого сам. Я не уверен, что использовал бы что-нибудь по типу Unity или Unreal, так как не владею ими. Но поддержка своего собственного движка — это настоящая боль, особенно сейчас, когда я делаю что-то кроме ASCII графики. Для звуков я использую FMOD.

Ничего не меняется на протяжении всей разработки проекта, кроме SDL, на введение которого было затрачено несколько лет. Касательно механики, то я не использую большого количества сторонних библиотек. Но иногда нахожу некоторые для генерации случайных чисел. Сначала я работал с Mersenne Twiste, а совсем недавно добавил SplitMix64, о котором я рассказывал на прошедшем Roguelike Celebration.

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

О: Намного легче его забыть. При подсчёте количества «;», что является простым, но не самым точным методом, мы получим около 711 тысяч строк. Я стараюсь давать имена моим переменным и объектам, оставляю множество комментариев, чтобы легче было вспомнить, за что отвечает эта часть кода. Иногда это занимает несколько шагов, чтобы отыскать нужную нить, иногда приходится обращаться к тем участкам кода, которые я не трогал десять лет, что случается часто. Я бы сказал, что большинство изменений сфокусированы на частях кода, которые я уже знаю достаточно хорошо. Но до сих пор остаются участки, на которые я не обращал внимание с 2006 года.

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

В: Самый крупный рефакторинг или изменение, которое вам приходилось делать?

О: Были некоторые рефакторинги, которые длились месяцами. Я переделывал определенные структуры данных, но я бы не назвал это полноценным рефакторингом, так как всё ещё мог добавлять какие-то механики параллельно.

Добавление оси Z для преобразования игры в 3D (оставаясь при этом плоской ASCII) было одним из самых ошеломляющих изменений, что я когда-либо делал. Долгие недели на то, чтобы разобраться в логике и вызовах функций, основанных на X и Y, в которые должна была вписываться ось Z. В конечном итоге сделать систему предметов полиморфной было большой ошибкой.

В: Почему это было ошибкой?

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

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

Я думаю, что некоторые разработчики игр называют это ECS, хотя, насколько я понимаю, люди, занимающиеся оптимизацией воспринимают её как систему, где вы фактически разбиваете вещи по отдельным полям. Использование одного объекта с разными выделенными подобъектами усугубляет ситуацию промахов кеша, но преимущества в организации, гибкости и расширяемости просто нельзя игнорировать, а различные подполя «инструмента» инструмента используются не так часто, чтобы это становилось проблемой оптимизации.

В: Я видел как другие игры, похожие на DF, тормозят из-за своих алгоритмов поиска пути. Что вы используете и как поддерживаете его эффективность?

О: Да, базовый алгоритм — это только его часть. Мы используем A*, что, конечно, быстро, но само по себе недостаточно. Мы не можем воспользоваться некоторыми нововведениями в этой области (например Jump Point Search) потому как наша карта очень сильно меняется. Как правило, разработчики используют подходы, которые добавляли различные более крупные структуры поверх карты, чтобы срезать углы,
и из-за постоянно меняющейся карты их поддержание занимает слишком много времени. Таким образом, наш подход заключался в том, чтобы просто отслеживать соединённые компоненты, до которых можно добраться пешком. Такой алгоритм легко обновляется, даже если карта меняется быстро, хотя он немного использует алгоритм заливки. Например, если вода рассекает крепость пополам, ему нужно затопить с одной стороны и обновить целую половину крепости до нового индекса. Затем это позволяет нам исключить из игры почти все неудавшиеся вызовы A*: наши программные агенты просто запрашивают номера компонентов и, если номера компонентов совпадают, они знают, что вызов будет успешным.

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

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

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

В: К слову, вы моделируете множество вещей одновременно: как вам удается асинхронно управлять таким количеством акторов (и вы вообще делаете это)?

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

В: Пробовали ли вы делать другие проекты помимо DF?

О: Конечно! Папка сторонних проектов, которая перемещалась между компьютерами за последние десять лет или около того, содержит около 90 проектов. Некоторые из них длились несколько дней, другие — несколько лет. В основном это другие игры, почти всегда других жанров, но есть также несколько дополнительных проектов к DF, например прототип генератора мифов. Они все далеки от завершения, но разрабатывать их достаточно интересно.

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

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

В: У вас очень интересные описания к обновлениям. Какой ваш любимый баг и чем он был вызван?

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

272

Комментарии

Это только часть? Как будто концовки не хватает

Это только часть? Как будто концовки не хватает
Panda Shiren
Это только часть? Как будто концовки не хватает

Panda Shiren, не, просто достаточно резкий конец получился >_<

морали не будет.

Panda Shiren, не, просто достаточно резкий конец получился >_<морали не будет.
AllexKzk
Panda Shiren, не, просто достаточно резкий конец получился >_<морали не будет.

AllexKzk, хороший кончание.

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

это опечатка или какая-то постметаироничная отсылка?

это опечатка или какая-то постметаироничная отсылка?
Kai
это опечатка или какая-то постметаироничная отсылка?

Kai, когда-нибудь я сделаю обложку без ошибок

(#><)

Kai, когда-нибудь я сделаю обложку без ошибок (#><)
AllexKzk
Kai, когда-нибудь я сделаю обложку без ошибок (#><)
это опечатка или какая-то постметаироничная отсылка?
Kai
это опечатка или какая-то постметаироничная отсылка?

Kai, поменял, спасибо.

Интересно, спасибо :)

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