Новый взгляд на старые игры. Часть 7. Эпилог. Armor Alley (1991). Веб-прототип
- воскресенье, 18 мая 2025 г. в 00:00:05
Во время обдумывания чем можно было бы завершить цикл “Новый взгляд на старые игры” в памяти всплыл специфический, технический текст, перевод которого некогда застрял на стадии полировки терминологии. Сегодня вашему вниманию предлагается, насколько то дозволила сложившаяся специфика, доработанная версия.
Об оригинальной игре я ранее рассказывал в рамках материала из другого цикла, здесь же речь пойдёт именно о ремейке и, преимущественно, его front-end специфике.
Перевод выкладывается с разрешения Scott-а Schiller-а. Характер статьи изобилует заметками / элементами монолога автора с самим собой. При переводе было решено оставить заданную подачу как есть, без радикальной стилистической коррекции или смены формата.
Осуществлял дополнительный анализ JavaScript-терминологии - oldalexi. Выступал в качестве дополнительного редактора - Newbillius.
Эволюция разных версий ремейка от самого раннего прототипа до текущих дней.
Я играл в Armor Alley в начале 1990-х на старом IBM-PC на базе 8088 процессора; здесь вы найдёте мою интерпретацию данной игры.
Прямо сейчас её можно запустить здесь.
Armor Alley — аркадный сайд-скроллер, который также можно назвать симулятором со стратегическими элементами. Основная цель — доставить фургон через поле боя к вражеской базе, но конечно, сделать это будет не так просто; фургон совершенно безоружен и крайне уязвим. Вы должны защищать фургон силами колонны, состоящей из танков, ракетных установок и других наземных единиц, а также обеспечивать воздушное прикрытие с помощью вертолёта. Силы противника идентичны вашим.
Краткая демонстрация веб-прототипа АА в обучающем режиме. Показана основная механика, объяснены элементы обороны и наступательной тактики.
- ... Затем.
Любое приложение, которое может быть написано на JavaScript, в конечном итоге будет написано на JavaScript. -- Закон Этвуда (2007).
Написание браузерной игры с использованием HTML, CSS и JavaScript - отличный способ учиться, переучиваться и экспериментировать с подходами к программированию на клиенте. Игры требуют глубины логической мысли, планирования, усилий для построения, а также поощряют эксперименты по части архитектуры и самого стиля разработки.
Производительность и масштабируемость являются важными факторами при создании игр и оба предоставляют возможность для обучения написанию производительного кода, совместимого с чередой платформ и устройств. Независимо от точности соответствия оригиналу, работающий прототип игры может быть интересен как с образовательной стороны, так и с развлекательной.
Создавайте и защищайте колонны, состоящие из танков, ракетных установок, пехоты, фургонов и инженеров, по мере того, как они пересекают поле боя, используйте свой вертолет как для нападения, так и для защиты. Конечная цель состоит в том, чтобы доставить свой фургон до вражеской базы на другой стороне поля боя.
PC, версия для MS-DOS, год выхода - 1990, порт оригинальной версии для компьютеров Macintosh.
Сайд-скроллер, высота фиксированная, 16 цветов, паттерны, которые можно переиспользовать (автомобили, шрапнель, стрельба, дым, взрывы), минимальная анимация, многочисленное взаимодействие между транспортными средствами и смена их поведения в окружающей среде. Варьирующаяся, но в целом неглубокая и ограниченная сложность; относительно прямолинейная реализация.
Полновесная реализация на клиентской стороне: HTML, JavaScript, CSS. Первый "уровень", стандартная техника и элементы местности, включая зенитные турели / "пушки", но без бронированных бункеров. Базовый "ИИ" противника, автоматизированное построение конвоя / отдача приказов + контроль действий вражеского вертолета / защита — вероятно будет трудно по настоящему подражать исходному поведению. Сетевой режим и мультиплеер отсутствуют.
Первоначальный прототип: базовый ландшафт, местность, транспортные средства, бункеры с воздушными шарами и передвижение транспорта. Вертолет игрока может летать над местностью.
Добавлена вражеская техника. Добавлены основы обнаружения врагов, стрельбы, бомб и обнаружения столкновений.
Пехота, бункеры, взаимодействие с транспортными средствами.
Строка состояния, посадочные площадки топливопровода + действия по ремонту / заправке / перезагрузке. Умные ракеты и радарная система. Система инвентаризации / заказа. Поиск / обнаружение близлежащих объектов (умные ракеты, ИИ).
Chrome DevTools: фреймы, память, профилирование JS / CPU.
Преобразования CSS + JS feature detection.
Горячие циклы, создание объектов / использование памяти / сборка мусора.
«Архитектура»
Ванильный JS, SoundManager 2 для аудио. Старые добрые элементы DOM для рендеринга пользовательского интерфейса против <canvas> или WebGL и т.п. Преимущества: естественный DOM createElement() для создания игровых объектов, CSS для их стилизации, манипуляции на основе className, трансформации и анимации.
JavaScript: utils (утилиты) для манипулирования именами классов CSS, событиями DOM, удаления узлов дерева, примесей объектов, клонирования и т.п.
Контроллеры, то есть gameLoop, перебирают коллекции игровых объектов, вызывая метод animate() каждого объекта. Если animate() возвращает true, объект мёртв и контроллер может удалить его из массива. Шаблон повторяется для коллекций транспортных средств, стрельбы, зданий и т.п.
MVC-подобная тема: css, data, dom, интерфейс объектов определён для основных игровых объектов. У некоторых объектов есть дочерние и / или родительские объекты, например, бункер <- цепь -> воздушный шар.
frameCount и модуль определяют интервал поведения — движение, стрельбы, скорости анимации, обнаружения врага и т.п. — то есть if (frameCount % 10 === 0) { fire(); }
Имена объектов + карта типов сопоставляются между именами массивов, паттернами конструктора, именами классов CSS (в общем случае). Например у фургона есть тип (data.type = ‘van’), CSS-свойство .van, он хранится в game-objects.vans и так далее. Каждый объект имеет предсказуемый шаблон DOM, имя класса CSS и структуру данных.
isEnemy применяется к JS, содержит класс .enemy в CSS. Пользовательский интерфейс + логика столкновения, в остальном, в основном, одинаковы.
nearbyOptions - "в кого стреляют?"
nearbyObject() - "is an X (то есть, вертолёт) в зоне досягаемости?"
Object targeting - "двигаться в сторону вертолёта"
Если есть перекрытие объектов, вызвать target.hit() и предоставить “исходный” интерфейс объекта. Цель определяет взаимодействие — то есть цель может умереть, но может и убить источник.
Анимации
Комбинация style.left / top, некоторых анимаций спрайтов на основе backgroundPosition, а также анимаций и трансформаций CSS. Пошаговая анимация CSS обеспечивает удобные переходы, запускаемые className, например, взрыв танка: .tank.dying {} -> .tank.dead {} Метод animate() применяет vX + vY к x + y, обновляет свойства style.top / left (традиционный подход) или transform (ускорение GPU) для изменения положения узла DOM.
«Наследование»
Наследование данных, структур CSS и т.д. на основе миксинов. Общие имена классов CSS (состояния), атрибуты данных, такие как x, y, dead, isEnemy и т.п. Общие операции: перемещение спрайта (DOM x / y), объект находится слева в объекте верхнего уровня common, аналогичном utils.
Производительность
Используем transform: translate3d(), если поддерживается движение элементов на основе графического процессора по осям x / y, по сравнению с традиционными изменениями стиля слева / сверху. Translate позволяет избежать дорогостоящих перерисовок, вместо этого используя композитинг на основе графического процессора для движения.
JS: избегание создания чрезмерного мусора (например, клонирования объектов в стиле миксина) в горячих / дорогих циклах; уменьшение использование GC, оперативной памяти и общую текучесть кадров. Передаём объекты напрямую / по ссылке, избегая создания новых объектов или изменения исходных значений объектов в циклах.
Разрушение / очистка объекта: удаляем дерево узлов, ссылки JS / DOM и ссылку на родительский массив в случае коллекции объектов. Минимизируем “ввод-вывод” DOM: кэшируем ссылки и координаты узлов, чтобы уменьшить перекомпоновку из-за операций чтения (например offsetWidth). Обновляем клиентские координаты только при критических событиях, таких как init и window.onresize().
Память, количество узлов DOM и сборка мусора JS / DOM
Нормальный игровой процесс и распределение памяти показаны выше синим цветом, наряду с растущим числом ссылок на узлы DOM (зелёным). В этом случае пулемёт вертолета стреляет 64 раза и узлы добавляются линейным образом. Когда объекты стрельбы уничтожаются всё успокаивается и в конечном итоге происходит событие сборки мусора.
Когда узлы JS / DOM разыменовываются посредством уничтожения / очистки объекта JS, оставшиеся узлы DOM могут быть надлежащим образом удалены сборщиком мусора. Естественное событие GC отражает это в середине, за которым следуют остальные новые узлы с ручным событием GC, вызванным ближе к концу.
“Detached Dom Trees” Chrome DevTools в разделе “Heap Snapshot Profile” также может пригодиться для поиска утекших узлов DOM; отсоединенный DOM включен в представление сдерживания. На момент написания (1 ноября 2013 г.) могла существовать ошибка, связанная с тем, что узлы DOM с ускорением на GPU не подвергались сборке мусора или просто не отражались на графиках Chrome DevTools. В контексте подробностей можно ознакомиться с информацией здесь - https://code.google.com/p/chromium/issues/detail?id=304689.
«Искусственный интеллект»
На самом деле достаточно глуп. “Логика, основанная на правилах” — более подходящее описание этой реализации.
Умные ракеты: двигаются прямо к цели, плюс небольшое отклонение с изменением ускорения.
Вражеский вертолет: нацеливается на ближайшее облако, воздушный шар, танк или вертолет игрока, если он находится в пределах досягаемости. Фиксированная скорость ускорения, нормализуется до 0, когда находится "достаточно близко" к цели. Возвращается на базу, когда закончились боеприпасы + бомбы, топливо или сильно повреждён. Не уклоняется от целей и препятствий. Враг может прятаться в облаках, бомбить проходящие танки в пределах досягаемости, если подобное применимо.
“Режим воздушного боя”: стремится равняться с вертолётом игрока. Стреляет из пулемёта, когда находится в пределах досягаемости. Если игрок находится прямо под ним, то пытается взорвать его. Препятствует прямому пролёту над или под собой.
Флажки “прицеливания”: облака, танки, вертолёт, воздушные шары. Если несколько целевых вариантов, логика определяет приоритет. (грубое предпочтение, от низкого к более высокому: облака, воздушные шары, вертолеты, танки).
Построение / сортировка конвоя противником: последовательность "MTVIE" через фиксированные промежутки времени - один раз в несколько минут, в зависимости от наличия средств. Вражеский вертолет имеет небольшое преимущество в скорости, что затрудняет погоню или бегство от него.
transform:translate начальное положение
При использовании преобразования графического процессора, перевод в Chrome: обнаружены нечётные / случайные проблемы с перерисовкой, если style.left / top или transformOrigin изначально не назначены. Логический; в противном случае браузер скажет: “преобразовать этот элемент относительно чего?”... Рекомендация: применить начальные значения top / left 0px и / или transform-origin в CSS.
Звуковые эффекты
Звук значительно улучшает впечатления от игры.
Оригинальные 8-битные звуки нельзя было повторно лицензировать; современные замены (и новые звуки) были смешаны из многочисленных Creative Commons и бесплатных источников по типу freesound.org. В частности благодаря звукам Hi-Fi стало веселее взрывать те или иные объекты.
Расстояние влияет на громкость и, в идеале, на эффекты панорамирования звуков (звуки за кадром более тихие и т.п.).
Разные примечания по эффективности и производительности
Обнаружение столкновений — это просто математика. Кэширование / аннулирование, вероятно, будет более дорогим и не стоит затраченных усилий. То же самое касается других простых проверок координат, например, объекта поблизости / на экране / прицеливания. Большая часть времени тратится на GPU / железо, выполнение операций отрисовки / разметки /рендеринга.
Вещи, которые сработали
Согласованное соглашение об именах внутри объектов, открытый интерфейс через exports = { css, data, dom }; return exports;
Общие методы: animate(), hit(), die() и т.п.
Массивы объектов (фургоны, танки, бункеры) + один контроллер верхнего уровня, цикл, который вызывает animate() для каждого элемента и соответственно удаляет “мертвые” элементы.
Столкновение содержит интерфейсы exports, стандартные свойства, как data и функция hit().
hit() принимает необязательное значение точки и исходного / целевого объекта. В некоторых случаях оба объекта могут быть повреждены или уничтожены. JS меняет имена классов CSS в зависимости от состояния: .enemy, .dying, .dead и так далее. Общие методы “создания” объекта, необязательный параметр / опция, то есть конфигурации, например, isEnemy, x, y, vX, vY.
Интервалы на основе frameCount, устанавливающие анимацию + скорость поведения, например, move() для каждого кадра, fire() только два раза в секунду, обнаружение врагов один раз в секунду и т.п.
Конфигурация поблизости и “столкновение” - легко определить “по кому стреляют”, например, танки -> пехота. “Упреждающий просмотр” по умолчанию влияет на способность транспортного средства “видеть” перед собой.
“utils” для базовых событий DOM, манипулирования именами классов CSS, удаления дерева узлов.
Пакетные изменения DOM, в частности постановка в очередь и удаление узлов в результате деструкции объектов (т.е. гибели множества экземпляров объектов типа GunFire).
Пересмотр наследование объектов, данных и функций — могут ли почти все игровые объекты наследоваться от каких-либо “спрайтов”, основанных на сортировке.
Могут быть исследованы и реализованы более интеллектуальные алгоритмы обнаружения столкновений.
Трансляция событий? Будет ли это разумно использовать с точки зрения абстракции? (До сих пор - вопрос).
Различные “экспорты” / API для каждого объекта? Больше абстракции, меньше предположений о css, data, dom? Улучшенная абстракция “спрайта” для каждого объекта. Проще манипулировать DOM?
Спрайты в CSS ранее на / SASS / compass для автоматической оптимизации.
Избегание написания каких-либо вызовов setInterval() / setTimeout(). В настоящее время используется для задержек после взрыва перед уничтожением объекта (удаление узла DOM, очистка объекта). Более умное решение: использовать существующий цикл анимации, чтобы применить действие после заданного количества кадров и таким образом уничтожить объект. Частично реализовано.
Удалить вызовы setTimeout(), используемые для уничтожения объектов, вместо этого перейти к использованию animate() + frameCount для синхронизации (некоторые из них реализованы — см. FrameTimeout() в коде для справки).
Пересмотреть создание объектов, выделение памяти и сборку мусора. В настоящее время не так уж плохо, но всегда есть возможности для улучшения. Объединение объектов можно использовать для обычных объектов, таких как стрельба и т.п.
Дальнейшие соображения по оптимизации: спрайты изображений и звуковые спрайты, где применимо. Удалить анимированные .GIF, сместить акцент на анимацию спрайтов + CSS, если это будет быстрее / плавнее.
Особенности, которых нет в оригинальной игре
Режим обучения: вводное руководство по игровой механике, задачам и основным стратегиям.
Возможность спрятаться в облаках / замаскироваться от радара (возможно было реализовано в оригинальном многопользовательском режиме, здесь не уверен).
Вражеский вертолёт может прятаться в облаках и может бомбить проходящие танки (“режим скрытой облачной бомбардировки”). С другой стороны, игрок может незаметно проходить над вражескими зенитными орудиями, ракетными установками и вражеской базой, а также над вражеским вертолетом. Добавляет в игру элементы веселья и стелса.
Дополнительные звуковые эффекты для вертолёта, пехоты с парашютом, стрельбы со стороны последней, заклинивания фургона и попадания осколков по объектам.
Вещи, до которых я никогда не доберусь
Мультиплеер. Не из-за отсутствия интереса; думаю это было бы здорово, только времени нет.
Послесловие. 17.05.25.
С момента, когда мы общались последний раз, я успел выпустить множество обновлений для моего "ремастерированного ремейка" Armor Alley. Отныне игра содержит все части оригинальной кампании, а также баталии в мультиплеере, оружие, регулировку уровня сложности, подсчёт очков и прочее. Сегодня она может запускаться в режиме 60 к/с, вариант 30 к/с был оставлен в качестве бэкапа, также есть опции, связанные с различного рода дополнительными эффектами и деталями, которые можно отключить.
Я планирую резюмировать обновление в своём блоге в этом году, с указанием множества деталей и того пути, что игра проделала, начиная с 2013 года.
У меня имеется список изменений относительно большинство деталей и возможностей, что пополняется по мере поступления. Я довольно плотно работал над игрой последние несколько месяцев, последний раз список изменений обновлялся в феврале, думаю скоро обновлю его ещё раз.
https://github.com/scottschiller/ArmorAlley/blob/master/CHANGELOG.txt
Также планируется большое обзорное видео на YouTube канале, связанные с обновлением 2025 года, думаю разберусь с этим вопросом в районе лета.
Здесь можно найти краткое представление о том, как игра смотрелась в конце 2024 года:
https://youtube.com/watch?v=ahO7opaW28g
Игра должна быть вполне играбельной в Safari на iOS, а также в Chrome на устройствах, работающих на базе Android, планшетах и сенсорных экранах. Недавно была добавлена поддержка геймпадов.
Также имеется базовая поддержка PvP и кооперативного режима, ведутся работы по улучшению опыта с использованием "отката сетевого кода" вместо "ввода на основе задержки".
Наконец я добавил дополнительную "тему" в библиотеку звуков игры, полностью опциональную, где задействованы Beavis и Butt-Head с MTV. Они по очереди "отыгрывают" свои роли, пока вы управляете вертолетом и комментируют ваш прогресс.
Ниже более подробный обзор за декабрь 2023 года, включающий всё перечисленное:
https://youtube.com/watch?v=4FyuEamvTTQ
Я получаю письма от людей, нашедших игру, из разных уголков мира, множество благодарностей, в частности от тех, кто помнит, как они играли в "AA" или "Rescue Raiders" (1984) на Apple ][ и они говорят, что в моей версии имеется множество деталей, она возвращает воспоминания о былом веселье оригинальной игры. Приятно слышать, что есть и другие лица, которым нравится полученный результат и которые всё ещё помнят эту игру, а также приятно, что я могу помочь им снова пережить этот опыт и испытать чувство ностальгии.
- Scott
Фотоальбом (заметки, скриншоты процесса разработки, отладка и т.п.)
Armor Alley (веб-прототип) на GitHub
Титры + благодарности (веб-прототип)