Descent

Я расскажу про Descent — компьютерную игру, трехмерный шутер (стрелялку) от первого лица, выпущенный в начале 1995 года. Я испытываю к нему сильные чувства как геймер и как программист, и хочу поделиться.

Любовь и нежность

някавай же

(Някавай же! Последний босс и его ручной vulcan guy. Оба в расстроенных чувствах потому что не могут выстрелить. А выстрелить не могут потому что читом им отключена стрельба. А стрельба им отключена потому что иначе они убивают быстрее чем успеваешь выбрать ракурс для съемки)

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

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

Приятно, идя по коридору, поворачивать за угол не только влево и вправо но и вверх и вниз, входить в комнаты через потолок и выходить вертикально вдоль стены. Мне сильно не хватает этого в реальности. Анималистично выглядящие роботы-монстры вызывают симпатию и хочется завести их как домашних животных (размером например с кошку). У меня есть плюшевый какодемон из Doom, но нет никого из Descent…

Контекст

Чтобы дать понять что особенного в Descent, нужно немного рассказать о том что было до него и вокруг него. Descent был выпущен в начале 95-го, разрабатывался с 93-го. Для сравнения, Doom появился в конце 93-го, а до него популярным шутером от первого лица был Wolfenstein 3D. Через год после Descent появился Duke Nukem 3D, а еще через некоторое время Quake (тоже 96-й), а в 98-м подоспели Unreal и Half-Life, но тут у меня внезапно кончилось детство и в них я уже не играл.

Что можно сказать про Doom, культовую игру, законодатель мод и вообще главную точку отсчета на шкале? Очень много хорошего можно сказать, я обязательно в другой раз, а здесь важно что он не моделировал трехмерную физику игрового мира и даже при построении изображения использовал упрощенные (двухмерные) геометрические расчеты. Это было очевидно увлеченному игроку и особенно — дизайнеру уровней.

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

Отсутствие трехмерной геометрии проявлялось и в том, что законы перспективы действовали только в горизонтальной плоскости. Не было возможности посмотреть вверх или вниз. Duke Nukem 3D, изданный после Doom и после Descent, все еще страдал этим недостатком.

характерная проблема

(Характерная проблема тогдашней графики, реализованной без геометрически корректных трехмерных вычислений. В Doom возможность смотреть вверх была предусмотрительно отключена, в Duke Nukem 3D не была и это приводило к тому что мы видим: при взгляде наверх углы нереалистично искажаются, законы перспективы перестают действовать)

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

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

Новизна

Я люблю Descent за его трехмерность и за другие технические новшества.

Динамическое освещение (когда вдоль коридора летит снаряд, стены освещаются последовательно по мере его движения). Это было ново, это было красиво, и это было обыграно в геймплее: среди вооружения игрок имеет в том числе не поражающие, но светящиеся и втыкающиеся в стены снаряды, которыми можно ненадолго освещать темные комнаты.

Монстры в виде подвижных трехмерных моделей. Остальные шутеры тогда все еще использовали “спрайты” — плоские картинки, изображающие врагов в разных позах и разных ракурсах. Такие картинки рисовались художниками или оцифровывались с фотографий материальных макетов. В Descent спрайтами изображались игровые предметы (ключи, оружие и прочий powerup), а монстры — полноценными текстурированными объемными моделями, как это делается и сегодня.

В Descent сюжет не играет роли, поскольку уровни представляют собой совершенно абстрактные лабиринты без претензий на любую реалистичность. (Никакого объяснения нет даже для базового факта отсутствия гравитации). Главный герой отстреливает взбесившихся летающих роботов-шахтеров и подрывает подземные объекты на разных небесных телах Солнечной системы, в бесплатной версии — от Луны до Меркурия, в полной версии — от Марса до Харона. (Хотя в названиях уровней вроде “Шахта по добыче H2O на Тефии, спутнике Сатурна” есть определенный абсурдистский шарм).

трехмерность лабиринта

(Карта одного из уровней, чтобы продемонстрировать трехмерность лабиринта. В интернете я встречаю информацию, что по мнению некоторых игроков в Descent легко заблудиться, но у меня никогда такой проблемы не возникало. Для тех у кого проблемы с памятью на местность, в игре очень удобный режим карты)

Да, кое-что в игре было старомодным даже по тем временам: очки/баллы за подбитых роботов и собранные предметы; концепция нескольких “жизней” (убитый игрок, у которого были жизни в запасе, оказывался в начале уровня, но состояние уровня сохранялось как на момент смерти — монстры убиты, ключи действуют, а все подобранные powerup валяются в том месте где игрок был убит). Но ведь на это можно просто не обращать внимания.

Я люблю Descent за его геймплей, за сбалансированность, динамичность и сложность.

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

Только один вид оружия из десяти поражает условно мгновенно (то что называется hitscan), все остальные снаряды метательные (ну, классический “лазер” — летающая светящаяся палка) и оставляют пространство для маневра. Что разумно, ведь в шутерах оружие мгновенного действия либо чрезмерно фатально, либо искусственно ухудшено чтобы не быть таковым и с ним было интересно играть.

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

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

Артефакт невидимости обеспечивает реальную невидимость (возможно подкрасться к роботам, оставаясь полностью незамеченным), но роботы при этом адекватно реагируют на видимые события— открытые двери, подобранные предметы или выстрелы — и начинают стрельбу по месту предполагаемого нахождения игрока.

Так я постепенно подхожу к главному поводу любви к Descent — к логике поведения управляемых компьютером врагов (“искуственному интеллекту”).

В шутерах того времени монстры предпочитали передвигаться хаотически или немного в сторону игрока или вообще умирать стоя, стреляя более-менее беспорядочно. Игры 2000-х стали развиваться в сторону мультиплеера, а в синглплеере поведение компьютерных оппонентов оказалось не в числе первых приоритетов и стало строиться на основании сценариев (скриптов) и использования расставляемых вручную меток (типа: “монстр А, в сложной ситуации стой здесь, а если игрок подходит со стороны Х, то отступай вдоль вот этой линии…”). В противоположность этому, роботы Descent принимают решения на основании общих, но детально проработанных правил, что проявляет себя в игре как осмысленное и сложное поведение в бою.

Исходный код

Бросающееся в глаза отличие поведения роботов в Descent от монстров во всех других известных мне играх, и любовь к программированию с детства, сразу подтолкнули меня к поиску и изучению исходного кода. Код написан на чистом C (не C++) и может являться образцом для подражания — что я могу сейчас уверенно утверждать как специалист.

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

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

aistruct.h, немного констант: behavior — общая модель поведения одного робота (некоторые икогда не атакуют, только убегают), mode — текущее действие робота, awareness — численная характеристика, степень текущего “беспокойства”:

#define PA_WEAPON_WALL_COLLISION 2 // Level of robot awareness after player weapon hits nearby wall //#define PA_PLAYER_VISIBLE 2 // Level of robot awareness if robot is looking towards player, and player not hidden #define PA_NEARBY_ROBOT_FIRED 1 // Level of robot awareness after nearby robot fires a weapon #define PA_PLAYER_COLLISION 3 // Level of robot awareness after player bumps into robot #define PA_WEAPON_ROBOT_COLLISION 4 // Level of robot awareness after player weapon hits nearby robot // Behaviors #define AIB_STILL 0x80 #define AIB_NORMAL 0x81 #define AIB_HIDE 0x82 #define AIB_RUN_FROM 0x83 #define AIB_FOLLOW_PATH 0x84 #define AIB_STATION 0x85 // Modes #define AIM_STILL 0 #define AIM_WANDER 1 #define AIM_FOLLOW_PATH 2 #define AIM_CHASE_OBJECT 3 #define AIM_RUN_FROM_OBJECT 4 #define AIM_HIDE 5 #define AIM_FOLLOW_PATH_2 6 #define AIM_OPEN_DOOR 7

aistruct.h, структуры данных, используемых для описания состояния AI одного робота:

// This is the stuff that is permanent for an AI object and is therefore saved to disk. typedef struct ai_static { ubyte behavior; // sbyte flags[MAX_AI_FLAGS]; // various flags, meaning defined by constants short hide_segment; // Segment to go to for hiding. short hide_index; // Index in Path_seg_points short path_length; // Length of hide path. short cur_path_index; // Current index in path. short follow_path_start_seg; // Start segment for robot which follows path. short follow_path_end_seg; // End segment for robot which follows path. int danger_laser_signature; short danger_laser_num; // byte extras[28]; // 32 extra bytes for storing stuff so we don’t have to change versions on disk } __pack__ ai_static; // This is the stuff which doesn’t need to be saved to disk. typedef struct ai_local { // These used to be bytes, changed to ints so I could set watchpoints on them. sbyte player_awareness_type; // type of awareness of player sbyte retry_count; // number of retries in physics last time this object got moved. sbyte consecutive_retries; // number of retries in consecutive frames (ie, without a retry_count of 0) sbyte mode; // current mode within behavior sbyte previous_visibility; // Visibility of player last time we checked. sbyte rapidfire_count; // number of shots fired rapidly short goal_segment; // goal segment for current path fix last_see_time, last_attack_time; // For sound effects, time at which player last seen, attacked fix wait_time; // time in seconds until something happens, mode dependent fix next_fire; // time in seconds until can fire again fix player_awareness_time; // time in seconds robot will be aware of player, 0 means not aware of player fix time_player_seen; // absolute time in seconds at which player was last seen, might cause to go into follow_path mode fix time_player_sound_attacked; // absolute time in seconds at which player was last seen with visibility of 2. fix next_misc_sound_time; // absolute time in seconds at which this robot last made an angry or lurking sound. fix time_since_processed; // time since this robot last processed in do_ai_frame vms_angvec goal_angles[MAX_SUBMODELS]; //angles for each subobject vms_angvec delta_angles[MAX_SUBMODELS]; //angles for each subobject sbyte goal_state[MAX_SUBMODELS]; // Goal state for this sub-object sbyte achieved_state[MAX_SUBMODELS]; // Last achieved state } ai_local;

ai.c, ai_fire_laser_at_player(), робот стреляет по игроку, фрагмент (“So, that’s why we write games, instead of guiding missiles…”):

// If player is not moving, fire right at him! // Note: If the robot fires in the direction of its forward vector, this is bad because the weapon does not // come out from the center of the robot; it comes out from the side. So it is common for the weapon to miss // its target. Ideally, we want to point the guns at the player. For now, just fire right at the player. if ((abs(player_direction_vector.x < 0x10000)) && (abs(player_direction_vector.y < 0x10000)) && (abs(player_direction_vector.z < 0x10000))) { vm_vec_normalized_dir_quick(&fire_vec, &bpp_diff, fire_point); // Player is moving. Determine where the player will be at the end of the next frame if he doesn’t change his // behavior. Fire at exactly that point. This isn’t exactly what you want because it will probably take the laser // a different amount of time to get there, since it will probably be a different distance from the player. // So, that’s why we write games, instead of guiding missiles… } else { vm_vec_sub(&fire_vec, &bpp_diff, fire_point); vm_vec_scale(&fire_vec,fixmul(Weapon_info[Robot_info[obj->id].weapon_type].speed[Difficulty_level], FrameTime)); vm_vec_add2(&fire_vec, &player_direction_vector); vm_vec_normalize_quick(&fire_vec); }

И многое-многое другое достойное внимания: вместо вычислений с плавающей точкой введен собственный тип fix, хранящий рациональные числа как числа с фиксированной точкой по 16 бит на целую и дробную часть; собственный аллокатор памяти и сборщики мусора в пределах нескольких часто используемых массивов; нехарактерный формат хранения демо — покадровая запись состояний игры, в противоположность начальному значению генератора псевдослучайных чисел и записи пользовательских действий; встроенный редактор уровней зачем-то содержит lisp-интерфейс — то ли разработчики процедурно генерировали карты, то ли просто любили emacs?

Оригинальный код например здесь https://github.com/videogamepreservation/descent; работающий из коробки (код, но не файлы с данными) современный здесь https://github.com/dxx-rebirth/dxx-rebirth.

Демо

Существует такое вполне самостоятельное развлечение, как запись своего прохождения игр (“демо”) со спортивными или эстетическими целями. Например, Compet-N — коллекция прохождений Doom — содержит тысячи прохождений, и смотреть некоторые интересно и познавательно (в смысле новых тактик игры); для Descent я не знаю общемировых библиотек с демо, но даже на youtube выложено немало.

И есть даже более узкое направление — tool assisted demo, когда для записи используется специально подправленная версия движка, обычно упрощающая какие-нибудь детали. Так можно записать прохождение более удачное, чем возможно получить при честной игре. Разумеется, это неспортивно и такие демо не участвуют в соревнованиях, но снимаются для красоты.

У меня нет ни способностей, ни желания участвовать в киберспорте, поэтому все мои демо немножко tool assisted. Раз уж я программист, странно было бы не воспользоваться возможностью; например, пусть для меня звонит колокольчик, когда робот незаметно подкрадывается со спины. Ломает ли это геймплей? — по-моему нет; читерство ли это? — строго говоря конечно да.

Демо я записывал не столько чтобы потом просматривать (в самом деле, зачем бы это мне могло понадобиться?), сколько для того чтобы иметь более сложную цель (ср.: ставить цели в трекере когда бегаешь по утрам). Когда записываешь демо, глупо использовать save/load, и некрасиво тупить/заблужаться/упускать роботов. Прохождение всех 30 уровней в записанном виде занимает примерно 7 часов. На многие уровни потрачены десятки попыток — в конце концов, чтобы записать сплошное прохождение, уровень надо помнить целиком. Наверное только геймеры понимают, сколько нужно времени потратить, чтобы записать 7 часов демо.

улетаем победителем
улетаем победителем

(Вот эта ссылка может привести на заключительную часть битвы с последним боссом: http://www.youtube.com/watch?v=osZAlhNxm4w&feature=youtu.be&t=236. И там же еще несколько выбранных моментов из прохождения и ссылка на полный плейлист)

Какая в этом ценность? Что ты делал в жизни, человече? — Я играл в Descent. По-моему, не так уж плохо по сегодняшним меркам!

Что было дальше

Ну понятно что: после коммерческого успеха последовал Descent II, в который было свалено всё наскребённое по сусекам после первого. Графический и трехмерный дизайн куда-то съехал, сюжет никто даже не пытался изображать, в общем, история знакомая по Doom / Doom II и многим другим сиквелам.

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

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

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

Сложно сравнивать библию, мону лизу, эйфелеву башню и курьёзити ровер; но вот, где-то среди них и Descent (программисты Mike Kulas и Matt Toschlog, дизайнер моделей Adam Pletcher, дизайнер уровней Che-Yuan Wang — четыре человека * полтора года разработки).

Уверен что в общем всё это заслуживает упоминания.

Спасибо!

122122
46 комментариев

С удовольствием почитал. Завтра утром со свежей головой посмотрю код.
А выстрелить не могут потому что читом им отключена стрельба. А стрельба им отключена потому что иначе они убивают быстрее чем успеваешь выбрать ракурс для съемкиО да! Я помню вечно ныкался за углами и стрелял ракетами вместе с fusion cannon (?) последняя такая, пурпурная! И до блевоты крутился, чтобы занять лучшее положение.
Динамическое освещение (когда вдоль коридора летит снаряд, стены освещаются последовательно по мере его движения). Это было новоБыло уже минимум в 1992 году в РПГ Shadoworlds. :-)
Некоторые монстры в Doom могли летать, но не могли пролететь друг над другом. Игрок не мог пробежать под летящим в высоте какодемоном. Комнаты могли быть расположены повыше и пониже, но ни в коем случае не одна над другой, и вообще никакой элемент архитектуры не мог был видимым и сверху, и снизу, что исключало мосты и вообще любые арки, где было бы можно пройти и под ними, и по ним.Самое смешное, что уже в 1992, в Ultima Underworld, все это можно было сделать. А вот FPS отставали, да. :-)
  Descent, естественно, имел шесть степеней свободы в перемещении, но просто забавные замечания.

6

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

Обнимемся, бро! Первая часть заиграна была до дыр. Эффект погружения - не передать. Игра была, конечно, передовой. Жалко, популярность ее в массах была не очень высока - сложноватое управление, вестибюлярный аппарат не у всех справлялся,  да и к железу требования были высоки, особенно, если играть в режиме SVGA, если мне олдскулы не изменяют, можно было играть в 320 х 240 и 640 х 480. Во втором от четкости картинки резало глаза. 

4

программисты Mike Kulas и Matt Toschlogвот эти двое 2 года назад выпустили кстати типа продолжение - Overload

3

У Volition в офисе заметил большую картонную модель того зеленого робота с клешнями, который в ближнем бою дерется. Рассказал сопровождающим как залипал в Descent в детстве - меня тут же отвели к, чуть ли не единственному сотруднику, оставшемуся с тех времен. Кажется, это Адам был.

3

вместо вычислений с плавающей точкой введен собственный тип fix, хранящий рациональные числа как числа с фиксированной точкой по 16 бит на целую и дробную часть

Я напомню, тем кто не в курсе, что до «старших» 486-х процессоров плавающая точка реализовывалась сопроцессорами, так что это вынужденная мера. 

Графический и трехмерный дизайн куда-то съехал, сюжет никто даже не пытался изображать, в общем, история знакомая по Doom / Doom II

А вот тут я совсем не понял упоминания Дума. Вы не могли бы пояснить свою мысль? Что именно у него «съехало»? По-моему, именно вторая часть является той самой «культовой игрой», а не первая. 

2

Первый Doom имел детально проработанный сюжет (пусть и далеко не полностью реализованный, но мы знаем его историю: Tom Hall, the Doom Bible, доступные теперь альфа-версии и т.д.). А в Doom 2 попали карты, которые не вошли в Doom 1, монстры, которых не успели реализовать раньше, а "сюжет" вообще придуман в последний момент что называется "на отцепись". В Doom 1 все очень гармонично: первый эпизод - космические базы полные зомби и младших бесов, второй эпизод - космические базы сильно смешанные с адской архитектурой, третий эпизод - сплошной ад, смерть и боль. А что Doom 2? Проваливаешься в чужой мир а там технический лифт и надпись "осторожно", и "бочки веселья" расставлены. И два секретных уровня на тему Wolfenstein и Commander Keen, ну чистый фан-сервис, не могу к этому относиться серьезно, какой-то фарс. Но вообще это тема для большого текста, конечно же

1