Как игровые компании "создавали игры вручную" до появления зрелых движков?
Ответ на этот вопрос гораздо более суров, чем большинство людей представляют.
Сегодняшние разработчики игр открывают Unity или Unreal — и редактор уже там, физический движок уже там, конвейер рендеринга уже там, система освещения уже там. Их задача — заполнить контентом уже готовый каркас. В 1980-х годах этого просто не существовало. Не было фреймворков, не было движков, не было переиспользуемой инфраструктуры. Если ты хотел сделать игру, ты сначала должен был построить машину, способную её запустить, а затем на этой машине, на языке ассемблера, сказать ей, какого цвета должен быть каждый пиксель.
Всё начиналось с пикселей.
Графика ранних игр записывалась прямо в память. Программистам нужно было знать адрес памяти, соответствующий каждой позиции на экране. Чтобы нарисовать точку в определённом месте, ты записывал значение по этому адресу — значение обозначало цвет. Чтобы нарисовать линию, ты писал цикл, указывая CPU записывать данные через каждые N байт от этого адреса до того. Чтобы нарисовать персонажа, ты преобразовывал каждую строку пикселей в шестнадцатеричные числа, записывал их строка за строкой, находил правильное смещение на экране и помещал эти числа туда.
Сегодня этот процесс называется «программный рендеринг». Тогда у него не было названия, потому что не было альтернативы — он назывался просто «как получается изображение».
Когда Кармак работал над первым Wolfenstein, он столкнулся с конкретной проблемой: графическая архитектура IBM PC не поддерживала плавную горизонтальную прокрутку. Экран обновлялся путём полного обновления кадра за раз, что вызывало разрывы при движении изображения — оно выглядело так, будто дрожало. Он потратил огромное количество времени на изучение низкоуровневых регистров видеокарты и нашёл способ обмануть оборудование, чтобы выполнить функцию, которую оно изначально не поддерживало, симулируя её программно — и изображение стало прокручиваться плавно.
Не было никакой документации для этого, никакого предыдущего опыта. Он просто сидел, штудировал аппаратные руководства и выяснял всё шаг за шагом методом проб и ошибок.
Это было базовым условием работы программистов той эпохи. Ты имел дело не с системой с инструкцией — ты имел дело с самим оборудованием. Ты должен был знать оборудование лучше, чем оно знало себя, и находить пространство в его щелях.
Звук был проблемой совершенно другого измерения.
Звуковая система ранних ПК представляла собой зуммер — PC Speaker. Он мог издавать только один звук: писк. Чтобы воспроизвести с его помощью музыку, нужно было управлять частотой вибрации этого зуммера, переключаясь между разными частотами с чрезвычайно высокой скоростью, используя эффект последействия человеческого слуха, чтобы это звучало как мелодия. Эта техника называется ШИМ, широтно-импульсная модуляция. Принцип — акустическая физика, реализация — управление таймингом с точностью до микросекунды. Ошибёшься хоть немного — и получишь только шум.
В то время были программисты, которые专门研究 как выжать больше слоёв звука из PC Speaker. Их метод заключался в переключении между разными частотами в чрезвычайно коротких временных окнах, создавая псевдостереоэффект. Эта техника считалась чёрной магией в демосцене того времени. Ты берёшь зуммер, который умеет только «пищать», и создаёшь нечто, звучащее почти как музыка. Другие программисты, слыша это, понятия не имели, как ты это сделал — потому что принцип не был интуитивным. Требовалось чрезвычайно точное понимание управления таймингом, чтобы это осуществить.
Когда появились Adlib и Sound Blaster, они принесли с собой FM-синтез. Это была ещё одна система, которую нужно было осваивать с нуля. Принцип FM-синтеза заключается в использовании波形 одной частоты для модуляции波形 другой, создавая сложные тембры. Разные параметры модуляции давали разные звуки инструментов. Программистам приходилось самостоятельно настраивать параметры, тестировать, какие комбинации звучат как пианино, какие как скрипка, какие как взрыв. Стандартного ответа не было — всё靠 на слух и опыт. В то время у каждого звукового программиста в каждой компании были свои накопленные таблицы параметров. Это были коммерческие тайны в самом прямом смысле.
Ощущение дизайна на уровне игровой логики было делом совершенно иного рода.
Отсутствие движка означало отсутствие готового обнаружения коллизий, отсутствие готовой физической симуляции, отсутствие готового AI-фреймворка. Если ты хотел, чтобы персонаж останавливался у стены, а не проходил сквозь неё, тебе нужно было самому писать обнаружение коллизий, самому определять, что такое «коллизия», и самому решать, как изменятся скорость и позиция персонажа после коллизии.
Обнаружение коллизий в ранних играх было простым и грубым: рассматривать и персонажа, и препятствия как прямоугольники, проверять, перекрываются ли эти прямоугольники, и если да — останавливаться. Эта схема была быстрой, но проблема в том, что реальные игровые персонажи — не прямоугольники, это спрайты с формой. Между прямоугольной коллизионной коробкой и фактическим изображением всегда была погрешность. Поэтому в ранних играх часто бывало, что пули, явно пролетевшие мимо, всё равно засчитывались как попадания, или персонажи останавливались, ещё не коснувшись стены.
Игроки того времени не называли это багом. Они называли это особенностью игры. Потому что они не знали, что может быть иначе.
Ещё одна вещь, заслуживающая упоминания: ранние ПК имели крайне ограниченную память. 640 КБ были аппаратным потолком целое десятилетие. Это число сегодня не может вместить даже один скриншот. Программистам приходилось утрамбовывать всё содержимое игры — графику, звуковые эффекты, карты, логический код — в эти 640 КБ, и ещё оставлять место для данных времени выполнения.
Как? Сжатие, блочная загрузка и переиспользование.
Сжатие графических данных было отдельной дисциплиной. Программистам приходилось изобретать собственные алгоритмы сжатия, делая графические данные как можно меньше при хранении и мгновенно распаковывая их при необходимости. Если карта была слишком велика, её делили на блоки, загружали блок, в котором находился игрок, и выгружали, когда он уходил. Сегодня эта техника называется стримингом. Тогда она называлась «другого выхода нет».
Каждый байт в памяти был на вес золота. При написании кода программистам постоянно приходилось думать: могу ли я использовать для этой переменной тип данных поменьше? Могу ли я использовать меньше временных переменных для этой логики? Могу ли я уменьшить количество обращений к памяти в этом цикле? Этот образ мышления сегодняшним программистам в значительной мере не нужен. У современного оборудования достаточно запаса памяти, а компиляторы достаточно умны в оптимизации — тебе не нужно считать каждый байт.
Но программисты той эпохи носили в голове всю внутреннюю работу компьютера. От циклов инструкций CPU до расположения памяти и регистров GPU — они должны были понимать каждый слой, потому что каждый слой был щелью, которую им, возможно, придётся использовать.
Кармак однажды написал код для вычисления обратного квадратного корня с комментарием «what the fuck». Этот код использовал чрезвычайно контринтуитивный трюк с битовыми операциями для быстрого приближения обратного квадратного корня, намного быстрее стандартной математической библиотеки — всё ради экономии вычислительного времени в 3D-рендеринге. Этот фрагмент кода позже стал легендой в культуре программистов, цитируемой снова и снова, потому что он олицетворял исчезнувший образ мышления — метод выжимания производительности из математики и творчества в аппаратных ограничениях.
Программисты игр той эпохи были ближе к гибриду инженеров и учёных. Проблема, которую они решали, была не просто «как реализовать эту функцию» — а «можно ли эту функцию вообще реализовать в физических пределах этой машины».
Это два совершенно разных вопроса.
У первого вопроса есть стандартный ответ: посмотреть документацию, вызвать API, следовать инструкции. У второго вопроса стандартного ответа нет. Ты должен изобрести ответ сам. Иногда ответ существует, иногда нет. Сначала нужно было выяснить, в каком случае ты находишься, а затем решить, что делать.
Современная игровая индустрия — это огромная система сотрудничества. Компании движков, компании middleware, компании инструментария — над каждым слоем кто-то работает. Разработчики стоят на вершине этой системы, сосредоточившись на самом контенте. Это прогресс, это хорошо — это позволяет большему числу людей делать игры, а масштаб и сложность игр намного превосходят то, что было возможно в прошлом.
Но та эпоха создания игр с нуля, с пикселей, имела нечто, что больше не существует сегодня.
Ты открываешь игру и знаешь, откуда взялась каждая точка на экране, как был создан каждый звук, как была определена каждая коллизия. Потому что всё это было написано тобой, строка за строкой. Не было ничего, что кто-то другой сделал за тебя.
У этого чувства есть название. Это называется полный контроль.
Так что великие, которые создавали игры в те времена — они были по-настоящему великими. По-настоящему выдающимися.