Русский
Русский
English
Статистика
Реклама

Демо

Перевод Как я написал интро 4K на Rust и оно победило

07.07.2020 12:09:02 | Автор: admin
Недавно я написал своё первое интро 4K на Rust и представил его на Nova 2020, где оно заняло первое место в конкурсе New School Intro Competition. Написать интро 4K довольно сложно. Это требует знания многих различных областей. Здесь я сосредоточусь на методах, как максимально сократить код Rust.


Можете просмотреть демо-версию на Youtube, скачать исполняемый файл на Pouet или получить исходный код с Github.

Интро 4K это демо, в которой вся программа (включая любые данные) занимает 4096 байта или меньше, поэтому важно, чтобы код был максимально эффективным. Rust имеет некоторую репутацию создания раздутых исполняемых файлов, поэтому я хотел выяснить, можно ли написать на нём эффективный и лаконичный код.

Конфигурация


Всё интро написано на комбинации Rust и glsl. Glsl используется для рендеринга, но Rust делает всё остальное: создание мира, управление камерой и объектами, создание инструментов, воспроизведение музыки и т.д.

В коде есть зависимости от некоторых функций, которые ещё не включены в стабильный Rust, поэтому я использую набор инструментов Nightly Rust. Чтобы установить и использовать этот набор по умолчанию, нужно запустить следующие команды rustup:

rustup toolchain install nightlyrustup default nightly

Я использую crinkler для сжатия объектного файла, сгенерированного компилятором Rust.

Я также использовал shader minifier для препроцессинга шейдера glsl, чтобы сделать его меньше и удобнее для crinkler. Shader minifier не поддерживает вывод в .rs, так что я брал необработанную выдачу и вручную копировал её в свой файл shader.rs (задним умом ясно, что нужно было как-то автоматизировать этот этап. Или даже написать пул-реквест для shader minifier).

Отправной точкой послужила моё прошлое интро 4K на Rust, которое тогда мне казалось довольно лаконичным. В той статье также более подробная информация о настройке файла toml и о том, как использовать xargo для компиляции крошечного бинарника.

Оптимизация дизайна программы для уменьшения кода


Многие из наиболее эффективных оптимизаций размера никак не назовёшь умными хаками. Это результат переосмысления дизайна.

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

Анализ ассемблерного кода


В какой-то момент придётся посмотреть на скомпилированный ассемблер и разобраться, во что компилируется код и какие оптимизации размера стоят того. В компиляторе Rust есть очень полезная опция --emit=asm для вывода ассемблерного кода. Следующая команда создаёт файл ассемблера .s:

xargo rustc --release --target i686-pc-windows-msvc -- --emit=asm

Не обязательно быть экспертом в ассемблере, чтобы извлечь выгоду из изучения выходных данных ассемблера, но определённо лучше иметь базовое понимание синтаксиса. Опция opt-level = "z заставляет компилятор максимально оптимизировать код для наименьшего размера. После этого немного сложнее выяснить, какая часть кода ассемблера соответствует какой части кода Rust.

Я обнаружил, что компилятор Rust может быть удивительно хорош в минимизации, удалении неиспользуемого кода и ненужных параметров. Он также делает некоторые странные вещи, поэтому очень важно время от времени изучать результат на ассемблере.

Дополнительные функции


Я работал с двумя версиями кода. Одна протоколирует процесс и позволяет зрителю манипулировать камерой для создания интересных траекторий. Rust позволяет определить функции для этих дополнительных действий. В файле toml есть раздел [features], который позволяет объявлять доступные функции и их зависимости. В toml соего интро 4K есть следующий раздел:

[features]logger = []fullscreen = []

Ни одна из дополнительных функций не имеет зависимостей, поэтому они эффективно работают как флаги условной компиляции. Условным блокам кода предшествует оператор #[cfg(feature)]. Использование функций само по себе не делает код меньше, но сильно упрощает процесс разработки, когда вы легко переключаетесь между различными наборами функций.

        #[cfg(feature = "fullscreen")]        {            // Этот код компилируется только в том случае, если выбран полноэкранный режим        }        #[cfg(not(feature = "fullscreen"))]        {            // Этот код компилируется только в том случае, если полноэкранный режим не выбран        }

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

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

Использование get_unchecked


При размещении кода внутри блока unsafe{} я вроде как предполагал, что все проверки безопасности будут отключены, но это не так. Там по-прежнему выполняются все обычные проверки, и они дорого обходятся.

По умолчанию range проверяет все обращения к массиву. Возьмите следующий код Rust:

    delay_counter = sequence[ play_pos ];

До поиска по таблице компилятор вставит код, который проверяет, что play_pos не индексируется после конца последовательности, и паникует, если это так. Это добавляет значительный размер коду, потому что таких функций может быть много.

Преобразуем код следующим образом:

    delay_counter = *sequence.get_unchecked( play_pos );

Это говорит компилятору, чтобы он не выполнял никаких проверок диапазона и просто искал по таблице. Это явно опасная операция и поэтому её можно выполнять только в пределах кода unsafe.

Более эффективные циклы


Изначально все мои циклы использовали выполнялись идиоматически как положено в Rust, используя синтаксис for x in 0..10. Я предполагал, что он будет скомпилирован в максимально плотный цикл. Удивительно, но это не так. Простейший случай:

for x in 0..10 {    // do code}

будет скомпилирован в ассемблерный код, который делает следующее:

    setup loop variableloop:    проверить условие цикла        если цикл закончен, перейти в end    // выполнить код внутри цикла    безусловно перейти в loopend:

тогда как следующий код

let x = 0;loop{    // do code    x += 1;    if i == 10 {        break;    }}

непосредственно компилируется в:

    setup loop variableloop:    // выполнить код внутри цикла    проверить условие цикла        если цикл не закончен, перейти в loopend:

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

Другая, гораздо более трудная для понимания проблема с идиоматическим циклом Rust заключается в том, что в некоторых случаях компилятор добавлял некоторый дополнительный код настройки итератора, который действительно раздувал код. Я так и не понял, что вызывает эту дополнительную настройку итератора, поскольку всегда было тривиально заменить конструкции for {} конструкцией loop{}.

Использование векторных инструкций


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

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

Чтобы использовать встроенные функции, я бы преобразовал следующий код

        global_spheres[ CAMERA_ROT_IDX ][ 0 ] += camera_rot_speed[ 0 ]*camera_speed;        global_spheres[ CAMERA_ROT_IDX ][ 1 ] += camera_rot_speed[ 1 ]*camera_speed;        global_spheres[ CAMERA_ROT_IDX ][ 2 ] += camera_rot_speed[ 2 ]*camera_speed;

в такое:

        let mut dst:x86::__m128 = core::arch::x86::_mm_load_ps(global_spheres[ CAMERA_ROT_IDX ].as_mut_ptr());        let mut src:x86::__m128 = core::arch::x86::_mm_load_ps(camera_rot_speed.as_mut_ptr());        dst = core::arch::x86::_mm_add_ps( dst, src);        core::arch::x86::_mm_store_ss( (&mut global_spheres[ CAMERA_ROT_IDX ]).as_mut_ptr(), dst );

что будет немного меньше по размеру (и гораздо менее читабельно). К сожалению, по какой-то причине это сломало отладочную сборку, хотя прекрасно работало в релизной. Ясно, что здесь проблема с моим знанием внутренних средств Rust, а не с самим языком. На это стоит потратить больше времени при подготовке следующего 4K-интро, поскольку сокращение объёма кода было значительным.

Использование OpenGL


Есть множество стандартных крейтов Rust для загрузки функций OpenGL, но по умолчанию все они загружают очень большой набор функций. Каждая загруженная функция занимает некоторое пространство, потому что загрузчик должен знать её имя. Crinkler очень хорошо сжимает такого рода код, но он не в состоянии полностью избавиться от оверхеда, поэтому пришлось создать свою собственную версию gl.rs, включающую только нужные функции OpenGL.

Заключение


Главная цель состояла в том, чтобы написать конкурентоспособное корректное интро 4K и доказать, что язык Rust пригоден для демосцены и для сценариев, где каждый байт имеет значение и вам действительно нужен низкоуровневый контроль. Как правило, в этой области рассматривали только ассемблер и C. Дополнительная цель состояла в максимальном использовании идиоматического Rust.

Мне кажется, что я довольно успешно справился с первой задачей. Ни разу не возникало ощущения, что Rust каким-то образом сдерживает меня или что я жертвую производительностью или функциями, потому что использую Rust, а не C.

Со второй задачей я справился менее успешно. Слишком много небезопасного кода, который на самом деле не должен быть там. unsafe несёт разрушающий эффект; очень легко его использовать, чтобы быстро что-то выполнить (например, используя изменяемые статические переменные), но как только появляется небезопасный код, он порождает ещё более небезопасный код, и внезапно он оказывается повсюду. В будущем я буду гораздо осторожнее использовать unsafe только тогда, когда действительно нет альтернативы.
Подробнее..

Tantramantra и магия проектирования

03.04.2021 22:15:26 | Автор: admin
Доброго весеннего дня!
Во время разработки различных механик и прочего интерактива для компьютерных игр, складываются различные схемы-рецепты для реализации требуемого функционала. Большая их часть не привязана к конкретному используемому движку/языку. О некоторых из них я расскажу на примере одного из своих проектов с биомашинками.



Тантрамантра



Для начала о самом проекте. Это тестовый полигон из нескольких карт-миров, где можно покататься на машинке-биотрансформере, которая умеет прыгать, кувыркаться, стрейфиться и создавать некоторые объекты. Также подконтрольное транспортное средство умеет переключаться в различные формы, в том числе и те, которые заточены под полёт (не имеют колёс).
На картах присутствуют телепорты (синие звездообразные объекты), переносящие машинку в другие миры. Всего таких миров четыре стартовый, с водой и скалами, с ровной поверхностью и ещё один водный.
В стартовом мире игрока довольно быстро начинают преследовать два терминатора, одолеть которых можно при помощи специального оружия (после чего кататься уже в более спокойной обстановке). Если же они окажутся ловчее, то происходит game over и игра начинается заново.







Проектирование игровых механизмов



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

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

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

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

В задачи игрового проектирования как раз и входит подбор определённой модели, которая позволит создать нужный интерактив. Пути решения этих задач могут быть довольно разными, но довольно часто можно (и нужно) искать ту модель, которая просто сформирует нужную иллюзию, без лишних переусложнений. То есть вам не нужно подключать плагин вывода текста, если требуется показать всего пару надписей, и достаточно вывести картинку с ними. Не требуется подключать физическую библиотеку, если вся физика в вашей игре это упрощённая гравитация через смещение объекта вниз в каждом кадре. И так далее, и тому подобное.

Иногда это работает и в другую сторону когда уже есть какие-то встроенные инструменты, та же физика, то почему бы какие-то вещи не делать через неё. Тот же таймер может заменить падающий с определённой высоты кубик-триггер. Игрок всё равно увидит только то, что ему решили показать и воспримет те связи между объектами, которые для него сформировал разработчик.

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

Грибы

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

В итоге столкновения c грибами определяются через использование предложенного движком элемента WorldTrigger, который реагирует на пересечения с геометрией.


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


В основной части скрипта происходит следующее: в инициализации на триггер вешается функция Enter. В основном цикле Update накручивается счётчик, если гриб в состоянии притвориться мёртвым. По истечении данного счётчика гриб меняет состояние на жив и снова ждёт когда его переедут. В функции Enter, собственно, гриб временно исчезает и меняет состояние на мёртв.
Чтобы всё выглядело ещё лучше можно сначала показать недавно умерший гриб в уменьшенном состоянии, не активируя сам триггер, после чего понемногу модельку гриба увеличивать, и только потом уже включать триггер.


Если бы заготовленных инструментов в виде триггеров и лучей в движке не было, то можно было бы написать свой вариант просчёта столкновений. Например, банально считать разницу координат между грибом и машинкой. А раз особая точность не требуется, то этот процесс тоже можно различным образом оптимизировать, например, проверять расстояние только по двум осям, а на предварительном этапе смотреть всего одну, подключая проверку по второй тогда, когда это необходимо. То есть каждый гриб в цикле мог бы проверять находится ли позиция машинки по Х внутри его диапазона, и если да, то проверять диапазон по Y и принимать какое-то решение дальше.

Терминаторы

Для игры требовались какие-то элементарные враги-преследователи. Я решил не делать их колёсными, создав какие-то простые сферы, перемещающиеся упрощённо.
Таким образом нужно было спланировать следующее: реализация прямолинейного движения, движение в сторону игрока. Второстепенными задачами тут были: возможность стрельбы и какая-то дополнительная полировка движения.

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


А вот как эти враги устроены пара сфер и пустышка-прицел, в которую спавнятся выстрелы


Весь код приводить не буду, ограничимся разбором параметров. Пара счётчиков для отслеживания пауз между выстрелами и рейкастами. Хитпоинты. Флаги состояний, для определения, когда нужно двигаться прямо (потому что рейкаст не касается земли) и когда нужно на некоторое время переключиться в состояние взлёта над землёй. В инициализации рассчитывается случайная прибавка к скорости, чтоб разные враги двигались с немного отличающимися скоростями и реже наползали друг на друга.


Оружие

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

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


Точка подбора оружия тоже использует WorldTrigger для определения столкновения


А это её код. Определяется машинка и кэшируется ссылка на её скрипт. Также берётся ссылка на триггер и на этапе инициализации ему устанавливается обработчик события на вход объекта внутрь зоны триггера. Если входящим объектом является машинка, то вызывается функция newWeapon внутри её скрипта.

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


NodePet объект, изображающий пушку


Код NodePet. В качестве точки привязки, за которой он будет следовать, указана пустышка-прицел, висящая на машинке. А Rotator это другая пустышка, уже в центре самой пушки, которая копирует на себя поворот того прицела, чтобы пушка смотрела в нужную сторону (в качестве бонуса это даёт эффект вращения пушки вокруг своей оси, когда машинка двигается).
Здесь как раз реализован принцип отслеживания координат пушка начинает смещаться, если машинка удалилась от неё на определённое малое расстояние. Поначалу отслеживалось отклонение только по осям X и Y, а потом я дописал и Z для большей точности.




Выстрелы

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

Работают эти выстрелы немного по-разному.


Фрагмент скрипта вражеских выстрелов. Они появляются и начинают лететь в том направлении, куда смотрел терминатор (то есть на игрока). Производить дополнительные действия выстрел начинает не сразу, а после небольшой задержки (reactionTimer) простой способ избежать каких-то нежелательных столкновений в точке его появления.
После задержки выстрел начинает анализировать пройденные отрезки на предмет пересечений, и если там что-то нашлось, то уничтожается, проигрывая эффект.
Если у этого чего-то имелось RigidBody, то выстрел применит туда импульс.
А если это ещё и машинка, то выстрел находит на ней скрипт и вызывает там функцию потери хитпоинтов.
Так как вражеские выстрелы отслеживают пересечения с большей частью геометрии, то они часто могут попасть в какую-то визуальную часть машинки, не достигнув её ядра. Таким образом получается, что часть выстрелов словно не пробивают броню.


Демоверсия



Ниже видеонарезка игрового процесса из свежей версии прототипа 01_02



Архив с демкой весит 714Мб. Запускается она на 64-битной Windows и доступна для скачивания на страничке itch.io (используется Unigine engine, поэтому системные требования не самые малые):

https://thenonsense.itch.io/tantramantra

Подробнее..

Демо-версии Невангеров для Unigine и Godot

19.10.2020 22:04:09 | Автор: admin
Альтернативные прототипы с биомашинками (и не только био-), которые собрал за время знакомства с игровыми движками Unigine 2 и Godot 3.




Unigine engine



Начнём с версии для Unigine. Используется версия 2.11, вышедшая этой весной, начиная с которой в движке появилась бесплатная лицензия. На данный момент вышла 2.12 и скоро ожидается 2.13.

Что в общем стоит знать про Unigine это томский игровой движок, часто используемый для бенчмарков и симуляций. На нём в разные годы вышли такие игры как Oil Rush, Cradle, и вот, например, относительно недавняя ммо Dual Universe.
Внутри применяется довольно много интересных и перспективных решений, рендерит достаточно красивую картинку и может довольно сильно приглянуться художникам, особенно если те моделят в отдельном 3д-пакете, а не средствами самого движка.

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

В качестве инструмента для игрового разработчика здесь в принципе применим опыт использования C# в Unity, хотя в Unigine нет такого же многообразия готовых компонентных решений. Тем не менее, какие-то базовые вещи реализованы, а документация поможет написать остальное. С++ тоже никуда не делся.

Для мобильной платформы движок пока не подойдёт, как и для разработки без программирования (хотя планируется развивать его и в этих направлениях). Требования к железу на уровне запросов Unreal, минимальный вес файла приложения довольно большой. Зато красиво и неплохо оптимизировано.

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

Как бы то ни было, машинку я более менее собрал, ориентируясь на документацию. О чём ранее писал соответствующую статью: Как я собирал физику колёс в Unigine

Что касается геймплея, в Unigine, в отличие от Unity и Godot, например, оказалось довольно просто переключать физику машинки на лету, не только визуал, но и расположение/размер колёс. Без придумывания дополнительных трюков для того, чтобы не провалиться сквозь пол, и без пересоздания модели. Хотя, существует некоторый шанс провалиться в текстуры, но совершенно в иных ситуациях, а не в момент смены машинки.

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

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

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

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



Архив для Win64 можно скачать здесь (вес 687Мб): DROPBOX
или на страничке itch.io: NEWANGERS
в распакованном виде занимает 3Gb

Что тут есть:

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

* На уровнях есть интерактивные объекты синие звездообразные порталы, проезжая в которые машинка переносится в другие миры и можно возвращаться обратно, проехав в портал с той стороны.

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

* Машинка в основном управляется кнопками WASD. Также можно стрейфиться по Q и E. Прыгать на пробел, или, наоборот, сильнее устремляться к земле нажимая R. По Tab машинку можно крутить, чтобы, например, перевернуться и встать на колёса.
Стрейфам и прыжкам не установлены лимиты, то есть во время прыжка можно прыгать дальше и так далее.

* Кнопки 1,2,3,4,5,6,7 переключают разные машинки. Колесом мыши можно слегка зумить камеру.

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

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

* P выход из игры, L перезагрузка с откатом на стартовый уровень

Более ранние исходники этого прототипа знакомый выложил на своей страничке вместе с некоторыми правками физики колёс первоначальной версии: GITLAB

Godot engine



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

У Годо больше репутация 2д-движка, благодаря многообразию проработанных инструментов именно для 2д, но они являются дополнительным плюсом для 3д куда проще делать игровой UI. Ещё проще чем в Unity, как по мне. На текущий момент Годо в своём развитии дошёл до стабильной 3.2.3 версии (но все ждут 4 из за вулкана, оптимизаций и так далее. шаткие сборки четвёрки, кстати, уже можно пробовать хотя бы оценить картинку).

Движок не требует мощного железа для 3д-графики и выдаёт вполне приличную картинку. Готовых трёхмерных инструментов не огромное количество, но реализованы как раз одни из самых нужных, полезных и универсальных. Примерно то же касается и оптимизаций. Например, в движке реализован обычный frustrum culling, отсекающий геометрию вне зоны видимости камеры. Occlusion culling (чтобы не считать закрытые стенами объекты) реализацию придётся придумывать самостоятельно (что не так уж сложно, особенно в каких-то точечных местах, да и не в каждой игре нужно). Также из коробки в движке нет батчинга геометрии (правда для gles2 частично есть) и террейна, но это не такая уж проблема, просто потребуется что-то оптимизировать вручную сшивать какие-то меши вместе, бить геометрию на мелкие части или использовать чанки и так далее. Можно подыскать какую-то реализацию в местном небольшом сторе, например, добавить в свой проект готовое решение для террейна.

Интерфейс движка, кстати, довольно продуманный и кастомизируемый (хотя есть некоторые негибкие элементы). Пользоваться им в целом удобно. Поддерживаемых языков достаточно, для разного уровня погружения. Тут и C++ и C# и довольно удобный внутренний GDScript, который запускается прямо внутри редактора, не требуя запуска отдельной среды. Визуальный скриптинг тоже присутствует, так что без знания программирования в Годо тоже вполне можно жить какую-то минимальную логику сконструировать, что-то заанимировать (в Godot есть простой и классный инструмент для записи анимаций).

Малый вес приложения, мультиплатформенность, быстрота разработки, простота имплементации различных сторонних решений тоже немаловажные плюсы движка. Есть два варианта рендера gles2 и gles3, оба поддерживают 3д, но в первом оно попроще и в целом он больше подходит для 2д и мобилок. Gles3 даёт более продвинутый уровень графики, какая-то часть мобильных устройств его тоже поддерживает.

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

С чем возникли некоторые проблемы в запущенный физический объект не так просто вмешаться, чтобы, допустим, телепортировать его из одной точки в другую или на лету поменять расположение колёса машинки. Поэтому здесь такой трюк как в Unigine не работает и, как я понял, в Годо проще пересоздать объект, чем поменять ему на лету конфигурацию физики или что-то подобное. Но на самом деле это всё особо-то и не нужно, и в этом прототипе я просто вернулся к стандартной идее геймплея, когда катаешься за разные машинки, пересаживаясь из одной в другую где-то в специальных отдельных местах.

В Годо есть интересные инструменты, вроде CGS-объектов и мультимеша. Подробнее про особенности их использования я писал в статье: Godot, 1000 мелочей

Прототип Невангеров на этом движке получил отдельное название Wild Engines. В целом у меня получается как бы семейство сходных проектов, объединённых концепцией странных машинок путешествующих по странным мирам. И в качестве рабочего собирательного названия я их привык именовать Невангерами, пока не придумается более конкретное наименование. У Godot прототипа теперь появилось своё название, у приостановленной на версии 0.9 Unity версии (с которой всё и началось) тоже появилось другое название, но до этого дойдёт дело потом, если появится время к ней вернуться.

Особого видения о том, что хотелось бы реализовать в демке Wild Engines поначалу как-то не складывалось, просто делал наброски уровней, пытаясь понять как следует лучше реализовать террейн и какие вобще возможности хотелось бы видеть. Затем я пришёл к тому, чтобы сделать камеру двигающуюся за мышью и стало возможным реализовать более контролируемое прицеливание оружия, чем в прочих версиях. Таким образом данный прототип станет, вероятно, больше ориентирован на использование стрельбы.

Попутно стал собирать уровень из платформ, состоящих из маленьких блоков, пробуя различные подходы к оптимизации и замеряя производительность. Оптимизации оптимизациями, но в какой-то момент я уже настолько заполнил видимую на камеру область геометрией, что редактор перестал тянуть всю запущенную сцену, вываливась с ошибками. Что исправилось включением большего количества памяти в настройках. Тогда я сильнее увёл генерацию платформ в код, оставив на сцене только упрощённые габаритные блоки с идентификаторами, которые сами подгружали в себя нужную платформу при старте игры. После чего откатил количество используемой памяти к меньшему значению и уже без проблем увеличил уровень ещё в несколько раз. К тому же при такой реализации всегда остаётся вариант переиспользования уже подгруженных платформ, перемещая их в другие места уровня, когда машинка направляется в ту сторону, чтобы иметь возможность рисовать ими пространство уровня практически до бесконечности при тех же ресурсах и без переходов.
Примерно похожим образом я потом собрал наброски похожего уровня в Unigine, только там редактор сразу тянет всю сцену без надобности подгружать платформы в блоки через код (но для Unigine и компьютер используется куда более мощный).

В итоге в демо версии Wild Engines есть 4 машинки, одну из которых нужно выбрать для старта, и два небольших уровня (Level A и Level B). Пара ранних карт тоже осталась (Levels 0 и 1), но они ещё более тестовые и ландшафт там неоптимизирован.



В меню можно включить/выключить полноэкранный режим и тени.
Кнопки 1, 2 и 3 меняют позицию камеры. Мышь нацеливает и поворачивает камеру, тем сильнее, чем дальше курсор от центра.
WASD перемещение. PgDown прыжок. Q случайный импульс.
Левая кнопка мыши выстрел.
По Enter появляется подсказка об управлении и кнопка возврата в основное меню, где можно поменять машинку/уровень.

Win64 версия (42 Mb): DROPBOX wildengines_x64
Linux версия (44 Mb): DROPBOX wildengines_linux

Бонус



А вот один из недавно появившихся новых мехосов, Некромант:

На нём можно покататься в версии для Unigine.
Другой новый мехос, розовый с синей подсветкой, Дебаг, есть и там и там.

Ещё тут завалялся один старый картридж как по вашему, клоны каких восьмибитных игр могли скрываться под таким названием на китайском картридже, будь Вангеры всемирно известной франшизой в те далёкие времена?

Подробнее..

Микрокосм, демоверсия

23.10.2020 18:08:20 | Автор: admin
Всем доброго дня, в какой бы галактике вы не находились.
После череды итераций прототип космической jrpg, разрабатываемый на Godot engine, дорос, наконец, до первой демоверсии. Доступны win64 и linux варианты. Ниже подробности о том, что было, что стало и куда летает маленький звездолёт.


Каркас


Наброски базового геймплея сформировались довольно быстро, когда я перешёл от продумывания основных положений концепции к сборке игрового прототипа внутри игрового движка. Сначала появился стартовый экран, сцена глобальной карты с движением и возможностью вызова инвентаря. Тогда же была продумана некоторая начальная архитектура как именно грузить отдельные модули и обмениваться информацией между ними.
Затем появился экран сражения, запускаемый сначала по отдельной кнопке, а позднее привязанный к столкновению с врагами. Позже добавился экран планеты, у планет к тому времени появился информатор на глобальной карте, куда и была добавлена кнопка посещения. От информатора в свою очередь отпочковались прочие виды интерактивных объектов, например станция, не подразумевающая входа на свой внутренний экран, но иногда имеющая какой-то внешний функционал с возможностью что-то нажать или выбрать. Или подбираемые ресурсы космическая пыльца.
Далее требовалось реализовать максимально возможную партию героев (выбирая между 3-мя либо 4-мя, я выбрал всё-таки концепцию партии из 3-х), что и было сделано. В процессе добавилось отображение повреждений, выбор целей для атаки, немного музыки.
На этом этапе можно было уже выкладывать мини-демку, но я решил сначала доработать проект до более комплексного состояния, чтобы было лучше понятно, что это вобще такое и как оно примерно должно работать. Попутно добавив немного контента и каких-то украшательств, типа озвучки действий, анимаций.

Почитать подробнее про концепцию и базовую реализацию можно в этих статьях:
Microspace project
Итеративный геймдизайн, Godot и мир маленьких планет



Развитие прототипа


Рассмотрим различные области, в которых происходило это развитие:

Бой

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

Отображение повреждений поначалу было реализовано через 3д-представление вывод вьюпорта со строкой урона на обращённый к камере полигон. Это более менее работало, хотя реализация технически мне не очень нравилась, так как с вьюпортом всё немного заморочено нужно вешать его поближе к корню сцены, чтобы не возникало сбоев и проверять как выглядит эффект чуть сложнее, чем с просто выводимой 2д-строкой. Поэтому много вьюпортов заводить не хотелось, я обходился всего одним, перемещая выводящий его полигон куда нужно, попутно увеличивая/уменьшая. А ведь надо думать и о том, что когда-то появятся способности, наносящие массовые повреждения и потребуется выводить как минимум три значка с уроном одновременно (хотя и тут можно исхитриться, сделав как бы анимацию-волну из одного и того же объекта или уж завести ещё пару вьюпортов).
Однако, когда я добавил в прототип режим полного экрана, то там цифры повреждений стали заметно так размываться, поэтому ещё сильнее захотелось рассмотреть иные варианты отображения урона. В принципе я мог бы просто фиксировать камеру в бою, но я зачем-то продолжаю до упора сохранять возможность вертеть ею в моменты между ожиданием действия.
Поэтому в итоге я переписал вывод урона. Теперь 2д-строчки цепляются к проекции позиции 3д-объекта. Хотя тут тоже есть свои нюансы, например, если отвернуть камеру, то они продолжат показываться или если их выводить беспрерывно в каждом кадре, то есть возможность подвесить текущую камеру в одном положении, но это уже решаемо.

Для ускорения процесса тестирования давно была сделана специальная, как-бы неигровая, кнопка автоматической победы, если требовалось выйти из боя. Также долгое время не было возможности проиграть. Потом я добавил замораживание корабликов при обнулении их полоски и далее уже напрашивалась полноценная реализация проигрыша сражения. Для этого я сначала перевёл кнопку автопобеды в разряд полноценных игровых опций, с той лишь разницей, что теперь это стало экстренной кнопкой автоматического проигрыша, запускающей диалог выхода из боя. А затем дописал провырки для автоматического завершения сражения при нулевой прочности всех корабликов, также вызывающие этот диалог.
После поражения в сражении враг исчезает, а прочность каждого кораблика партии в следующем сражении будет равной 10. Кстати, верхней границы прочности пока нет и различным лечением можно повышать прочность сверх базовых значений.

Корабли и враги

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

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

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

Способности и персонажи

Если корабли являются неким аналогом главных героев, то перевозимые ими персонажи являются неким аналогом оружия этих героев. Одним из важных моментов, который хотелось отразить в проекте это использование системы идентификаторов, которые дают эффект неслучайной случайности, а при более широком использовании могут работать на генерацию псведо-сюжета. Говоря по-простому, у персонажей, кораблей и врагов есть специальные ID, позволяющие рассчитать результат взаимодействия любых этих объектов и как-то его использовать.
На данный момент использование этого результата следующее. Во-первых, у каждого персонажа и корабля возникает эффект совместимости. То есть, оказавшись на борту корабля, персонаж может, например, начать паниковать, уснуть, или не понимать как обращаться с его системами. Сейчас просто в окошке инвентаря выводится результат связи корабль-пассажир и возможный дополнительный эффект, ею накладываемый. Но на участии персонажа в бою это пока никак не отражается.
Непосредственно в сражении персонажами можно атаковать через опцию Экипаж. Это не просто совершение персонажем какого-то конкретного действа, вроде конкретного магического заклинания или выстрела из оружия. Это некая ситуация, повлекшая за собой определённое количество потерь со стороны противника. Здесь работает уже связь пассажир-враг, которая интерпретируется окружающим миром (в данном случае космическим сектором) в одно из девяти последствий: #СВЕТ, #ТЬМА, #МУЗКА, #ТЕОРИЯ и так далее. То есть игрок может ассоциативно представить, что сделал персонаж, что получилось последствие с такой формулировкой (а может и не представлять ассоциации дело необязательное, тем более они всё-равно сами по себе работают где-то в фоне). У каждого врага соответственно есть уязвимости или стойкость к определённого вида последствиям. Возможно, в дальнейшем применение системы идентификаторов ещё более разрастётся. Например, на тех же планетах могут действовать их собственные ауры смыслов, видоизменяющие таблицу интерпретаций, но сначала нужно реализовать там какие-то события, в которых участвовали бы идентификаторы.

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

Предметы

До грузов всегда дело доходило во вторую очередь, после того как уже была готова реализация для персонажей. В основном всё сводилось к тому, чтобы показывать их ячейки и перемещать заглушки предметов между ними. Но позднее появился активируемый предмет, Жива, восстанавливающая 100 прочности корабля. К тому моменту я как раз начал делать боевую опцию Трюм и требовался хотя бы один предмет с каким-то реальным эффектом.
Что касается самого лечащего предмета, то изначально планировалось, что может быть у него будут какие-то заряды и их можно будет где-то восполнять. В итоге я оставил один заряд и сделал так, что этот предмет умеет разряжаться при использовании и заряжаться снова от собранной космической пыльцы.

Экран планеты и задания

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

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

Прочее

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

В некоторые световые дни теперь появляются специальные уведомления. Сами эти дни проматываются посещением любой планеты (в дальнейшем нужно будет летать между разными, для смены дня).

Постепенно были улучшены шрифты некоторых элементов. Появилось некоторое количество звуков для боя, для экрана планет, для инвентаря.

Также была сделана заготовка для специальных зон, с реалистичным полётом и масштабом объектов. В ней можно оказаться во время игры, коснувшись специального объекта. Внутри можно немного полетать на кораблике Тринити с немного изменившимся управлением, а чтобы выйти достаточно долететь до границы этой области.

Демоверсия


Уже непосредственно перед появлением демоверсии для неё был создан режим последовательного развития, как основной вариант игры здесь игрок получает один стартовый кораблик (Скиталец) и начинает исследовать сектор, открывая новые звездолётики для присоединения к партии. Таким образом в этом режиме можно открыть ещё два кораблика (Спира и Авангард). В стартовом секторе есть три планеты, которые можно посетить. Починив специальную станцию можно переместиться во второй сектор и найти там ещё одну доступную планету.

Ранний тестовый вариант локации, где различные планеты и враги размещены более плотно, доступен в альтернативном, менее отлаженном, режиме игры all ships test. Там сразу доступен выбор из 10 существующих кораблей (в активную партию можно брать до трёх), больше героев/грузов и распределены они иначе. Могут возникать некоторые наложения внутриигровых параметров после выхода из одного режима и переключения на другой.

Нововведения последнего обновления показаны в новом ролике, где помимо геймплея добавлены элементы тизера.

Видеонарезки некоторых предыдущих обновлений








Загрузить архив демки можно здесь (dropbox):

Win64 версия (50 Mb)

Linux версия (52 Mb)

Управление:

WASD полёт, мышь вращение камеры
Enter открыть/закрыть инвентарь во время полёта в космосе


Кораблики: 1 Скиталец, 2 Спира, 3 Авангард, 4 Дева Яга, 5 Мухх, 6 Стелла, 7 Тринити, 8 Отомо, 9 Аквамарин, 10 Гиибель.

Подробнее..

Биом, демоверсия игры на Godot

18.12.2020 20:18:20 | Автор: admin
Собрал небольшую демку для win и linux. Этот экспериментальный прототип фокусируется на игре с видом сверху, и реализации системы бесконечного уровня в движке Godot. Биомашинки в комплекте.


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

Архитектура уровня



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

Пара слов о том, как это устроено (в принципе довольно базовые вещи, никаких особых откровений). Текущий игровой мир состоит из двух зон (условно зелёная и синяя), каждая внутри себя разбита на 4 участка. Когда машинка задевает триггер определённого участка, то уровень проверяет требуется ли переложить одну из зон так, чтобы все выходы из данного участка вели к прочим участкам, а не в пустоту.


Игрок касается участка, прохождение которого выведет его за внешнюю границу уровня, и вторая зона перекладывается в направлении движения игрока, чтобы его снова окружали другие доступные участки.

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

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

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


Игрок касается участка синей зоны, который в данный момент является внешним. После чего красная и зелёная зона перекладываются в новое положение.


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

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


В скрипте каждой плитки выставлен ID, по которому она подцепит нужный фрагмент.

Игра



Теперь подробнее про сам геймплей. На старте игрок вбирает одну из машинок и появляется в игровом мире. Машинки умеют прыгать, стрейфиться, и стрелять, если подберут специальное живое оружие, бегающее по карте. Если машинка уничтожена (от вражеских выстрелов или соприкосновения со взрывоопасными грибами), то на её месте остаётся душа (нематериальная бессмертная сущность), которая может вселиться в новую оболочку. Машинка перевозит условные грузы (зелёные ящички), которые можно подбирать или выкидывать.
Прыжок и стрейфы у машинок лимитированы следующим образом: за раз можно использовать не более двух таких действий, после чего данная возможность уйдёт не коротенькую перезарядку. Таким образом можно делать, например, стрейф плюс прыжок или двойной прыжок. У души и веретенолёта лимиты не настроены, поэтому при желании можно поломать перекладку уровня, взлетев выше триггеров.





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


Видеонарезка с моментами геймплея демо-версии. Используемый игровой движок Godot engine 3.2.3, рендер gles3

Скачать демо для своей ОС (windows 64 .exe, linux .x86_64) можно на страничке itch.io (вес архива около 60Мб):
https://thenonsense.itch.io/biome

Подсказки по управлению показываются в игре при нажатии кнопки Enter, внутри открывающегося инвентаря.

WASD передвижение
мышь частичное прицеливание (и влияние на автокамеру)
Пробел прыжок
Левая кнопка мыши выстрел
Q,E стрейф
1 включить/выключить автокамеру
2,3 приблизить/отдалить камеру
PgUp случайный импульс

Также оставлены специальные опции для тестирования, которых в норме не должно быть у игрока (отмечены красным во внутриигровой подсказке):

4 большая степень приближения камеры
Home превратиться в душу либо в базовую машинку
End создать врага
PgDown создать живое оружие

Бонус


Недавно появившийся в прототипе летающий транспорт веретенолёт.

Также есть видео из другого прототипа на Unigine engine, с более ранней версией этой биомашинки:
Подробнее..

Перевод Превращаем компьютер BBC Micro (1981 год) в устройство записи защищённых дисков за 40 000 долларов

24.07.2020 12:09:40 | Автор: admin

Введение


Одна из самых известных историй о защите гибких дисков связана с Dungeon Master. Эта игра, выпущенная в декабре 1987 года, сочетала в себе усложнённый формат физического диска (нечёткие биты) со скрытными проверками защиты, встроенными в сам геймплей.

Рекомендую прочитать эту статью, в которой представлен замечательный обзор гибких дисков, после которого идёт очень подробный обзор защиты нечёткими битами диска Dungeon Master для Atari ST. Есть также вот эта замечательная статья, которая более подробно рассказывает об историях, связанных с защитой Dungeon Master. В ней есть цитата одного из авторов Dungeon Master:

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

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

Компьютер BBC Micro имел процессор 6502 на 2 МГц и для выполнения его простейших инструкций требовалось два такта, то есть 1 микросекунда. Есть ли надежда, что можно будет записать нечёткие биты в условиях таких ограничений? Посмотрим, как далеко нам удастся зайти. Эта работа будет называться проектом Oiled Otter.

Чтобы вы почувствовали дух времени, вот изображение аппарата дублирования гибких дисков 3,5". Удивительно, насколько он похож на фотокопировальный аппарат, только в приёмник вместо бумаги вставляются диски! Похоже, такую машину даже могла продать вам компания Advanced World Products.


Пользовательский порт BBC Micro


BBC Micro был известен своей превосходной расширяемостью, в том числе и с помощью так называемого пользовательского порта (user port). Этот порт управляется 6522 Versatile Interface Adapter, работающим с частотой 1 МГц. Сам порт имеет 8 контактов данных и 2 контакта управления. Эти контакты обеспечивали очень высокую степень контроля. Контакты данных можно было по отдельности настраивать как вводы и выводы, а логическим уровням выводов можно было задавать высокий или низкий сигнал.

Почему же нам важен user port? Мы попытаемся управлять дисковым приводом непосредственно через него. Устранив из уравнения контроллер гибких дисков, мы, возможно сможем от него избавиться и добиться более прямого контроля над дисковым приводом и передаваемыми потоками данных.


Кабель от user port к дисковому приводу

На изображении выше показан мой кабель, подключающий user port к дисковому приводу. Разъёмы стандартны, а соединяющие их провода это просто перемычки. Я искренне стремлюсь создать нечто, что можно было изготовить в те дни, поэтому не использую никакую дополнительную электронику.

Кабель устроен следующим образом:


Главный вывод, который мне удалось извлечь из этой схемы интерфейс дискового привода, вероятно, проще, чем можно было подумать. Мы можем управлять дисковым приводом и запрашивать его важное состояние всего на 8 контактах. Всё очень просто. Допустим, нам нужно раскрутить привод, тогда достаточно подать низкий сигнал на PB0 и PB1. Если вы хотите дождаться, пока диск довращается до начала дорожки, то нужно запрашивать логический уровень на PB6, пока мы не увидим изменение уровня сигнала с высокого на низкий. Для пошагового движения достаточно задавать логический уровень step in противоположно step out, а затем выполнять пульсацию низкого сигнала на контакте step.

Пока всё отлично, у нас есть базовый контроль над приводом, но мы ещё ничего не записывали.

Проблемы с электрикой


Обязательно краткое отступление о проблемах с электрикой, потому что я с ними столкнулся. При подключении случайных пар компонентов они могут заработать, но иногда требуются ухищрения. Вот интервал напряжений, изначально наблюдавшийся на контакте W/DATA привода:


Пытаемся записывать импульсы на привод с частотой FM в 250 кГц

Напряжение логики 1 равно примерно 3,4 В, а напряжение логики 0 примерно 1,5 В. Это серьёзная проблема! Приемлемые уровни напряжения TTL чётко заданы:

Входящий сигнал TTL считается низким, если имеет напряжение от 0 В до 0,8 В относительно заземляющего вывода, и высоким при напряжении от 2 В до VCC (5 В). Если на вход элемента TTL подаётся сигнал напряжения в интервале от 0,8 В до 2,0 В, то элемент не даёт конкретного ответа, а поэтому сигнал считается неопределённым.

Напряжение логики 0, равное 1,5 В, считается неопределённым, и не вызовет никаких действий. И в самом деле, мой привод с этим сигналом ничего не записывал.

Проблему удалось устранить снятием с привода оконечного резистора кабеля гибких дисков. Вот фотография моего привода, где сборка оконечных резисторов обведена красным:


Это замечательно решает проблему уровней напряжения, после чего всё работает. Похоже, что многие порты BBC Micro кроме дискового порта не имеют достаточно сил для управления кабелем с оконечными резисторами. Но постойте-ка вероятно, для установки этого резистора была какая-то причина? Да. Его снятие имеет два хитрости:

  • Обращайте внимание на длину кабелей. Без оконечного резистора длинные кабели подвержены искажениям сигнала.
  • Следите за уровнями напряжения на неподключенных проводах. Я наблюдал напряжение 1,32 В на контакте привода S/SEL (выбор стороны). Это ненормально, потому что такое значение тоже находится в интервале неопределённости TTL. Куда привод будет записывать данные? Может быть, на верхнюю сторону, может быть, на нижнюю. А может и вообще ни на одну из них! Проблема была решена подключением каждого значимого кабеля и подачей на них высокого или низкого сигнала.

Нужно повысить пропускную способность


Проблема, которую мы пока обходили стороной: как подавать сигнал на контакт W/DATA? Это жёсткий контакт. Он имеет высокую пропускную способность и обладает точными требованиями к таймингам. Давайте на секунду перестанем мечтать о нечётких битах с наносекундной точностью, и попробуем записать на привод простые FM-импульсы.

Большинство дисков для BBC Micro имеют формат FM (они же DFM, они же одиночной плотности), кодируемый с частотой 250 кГц. Запись дорожки FM на самом деле выполняется довольно просто. Нужно проверить, вращается ли привод и открыта ли створка записи. После этого каждые 4 микросекунд или выполняем пульсацию W/DATA на низкий, а потом обратно на высокий сигнал (бит 1), или не делаем этого (бит 0). Чаще всего каждый второй бит должен быть равен 1 (синхронизирующий бит для поддержания тайминга и синхронизации).

Управление W/DATA через процессор безнадёжная задача. 4 микросекунды это 8 тактов процессора; этого определённо не хватит для загрузки байта, его сдвига, записи 0, а потом 1 в логические уровни user port. Простой цикл скорее всего займёт 12 с лишним микросекунд, а это слишком много. Так что для достаточно быстрой записи W/DATA нам придётся воспользоваться возможностями чипа 6522 VIA.

Регистр сдвига 6522 VIA


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

К сожалению, мне не удалось заставить работать эту схему. Единственный сдвиговый режим, имеющий потенциал достаточно быстрой работы это сдвиг с тактовой частотой системы. В спецификации Western Design Center 6522 есть хорошая схема:


Системный тактовый генератор VIA имеет частоту 1 МГц, поэтому тактовая частота сдвига будет сигналом 500 кГц, а разрешение выводимых битов равно 250 кГц. Этого как раз хватает. Однако я не разобрался, как заставить непрерывно и плавно работать тактовую частоту сдвига. Даже после попыток точного тайминга для перезагрузки регистра сдвига интервал контакта тактовой частоты сдвига всегда выглядела вот так:


Получается, что в единственном сдвиговом режиме, достаточно быстром для наших опытов, перезагрузка регистра сдвига вызывает задержку перед продолжением сдвига. Это нам не подходит.

Режим импульсного вывода 6522 VIA


Малоизвестной функцией 6522 является его режим импульсного вывода. Он описывается в спецификациях не всех вариаций 6522, но вот небольшая запись о нём в спецификации MOS Technology:


Наконец-то мы нашли спецификацию, точно описывающую его поведение. Этот режим очень интересен для нас, потому что одна операция записи в VIA обещает привести к двум отдельным действиям: на выходной контакт подаётся низкий логический сигнал, а спустя 1 такт (1 микросекунду) он возвращается к высокому сигналу без усилий с нашей стороны. Благодаря этому мы можем использовать его для управления выходным сигналом 250 кГц. Ресурсы процессора очень ограничены циклом решить задачу никак не получится, но линейный блок кода 6502 сможет справиться, например:

        \ &70 points to &FE60, aka. user 6522 VIA ORB register.        STA (&70),Y        \ 8 cycles, pulse output        STA (&70),Y        \ 8 cycles, pulse output        STA (&70),Y        \ 8 cycles, pulse output        LDA (&70),Y        \ 8 cycles, do not pulse output        STA (&70),Y        \ 8 cycles, pulse output        ...

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

К сожалению, такая операция чрезвычайно активно задействует память. Для каждого закодированного бита FM требуется 2 байта линейного кода 6502. Каждый значимый бит данных это два бита FM, потому что каждый второй бит является синхронизирующим. Дорожка состоит из 3125 байт, поэтому потребуется 3125 * 8 * 2 * 2 == 100 кБ линейного кода. BBC Micro имеет 32 кБ ОЗУ, так что здесь нам не повезло. Можно записывать одиночные (маленькие) секторы, в том числе и мощные новые механизмы защиты диска. Но мы не сможем записывать большие (1024 байта) секторы или полные дорожки. Для правильной записи большого количества дисков требуются обе эти операции. Более того, разрешение таймингов равно 1 микросекунде, чего недостаточно для записи множества более сложных защит и поверхностей дисков.

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

Помощь от ничего не обещающего порта вывода


К счастью, я общаюсь с умными людьми, например, с Bitshifters Collective. (Сходите оценить их последнее демо Evil Influences!) В беседе в Slack Том Седдон (автор эмулятора b2) предложил использовать вывод порта RGB (?)

Кабель-переходник с видео на диск такое не встретишь на Amazon каждый день.

Поначалу я посмеялся над этой идеей, но чем больше размышлял, тем вероятной она мне казалась. В BBC Micro использует для таймингов видеочип 6845. Как и 6522, это капризный процессор, но, по крайней мере, его особенности хорошо разобраны благодаря демо Bitshifters, в которых нещадно эксплуатируется 6845. Кроме того, я занимался реверс-инжинирингом, чтобы заставить эмулятор jsbeeb правильно эмулировать Hitachi 6845. Давайте посмотрим в этом видео на работу Oiled Otter, а потом расскажем, что увидели:


Всё работает благодаря необычному конфигурированию чипа 6845. 6845 работает с частотой 1 МГц, а тайминг кадров настроен так, чтобы на кадр приходилась одна растровая строка на 32 микросекунд/32 байта. При выводе каждого кадра регистры видеопамяти 6845 перезаписываются для получения следующих 32 байтов из потенциально другого места. То есть каждые 32 микросекунд из таблицы паттернов вывода выбирается другой паттерн вывода. Мы сконфигурировали контакты RGB на передачу 8 пикселей в микросекунду, то есть 256 на паттерн вывода. Это даёт нам огромное количество различных возможных паттернов вывода. Но так как мы записываем фрагменты по 32 микросекунд дисковых кодировок FM, подходят нам только несколько паттернов. В 32 микросекунд мы можем уместить 8 импульсов/битов FM. 4 битов будут синхронизирующими, и обычно все они равны 1. 4 бита будут битами данных, и у них существует всего 16 комбинаций.

Например, если мы записываем полубайт данных 0x5, то 32-микросекундный вывод должен выглядеть так:


Видеоданные будут иметь вид 00FFFFFFFFFFFFFF00FFFFFF00FFFFFF00FFFFFFFFFFFFFF00FFFFFF00FFFFFF. Первый, второй, четвёртый и пятый 00 это синхронизирующие биты. Между синхронизирующими битами расположен битовый паттерн данных 0101, или 0x5.

Ограничения процессора и памяти хорошо сбалансированы. В конечном итоге, эта схема похожа на ту, которую бы мы примерили с регистром сдвига VIA, если бы она сработала: какой-то небольшой сопроцессор (видеочип) занимается передачей набора битов FM, а центральный процессор свободен для загрузки и предоставления следующего паттерна. Требования к памяти вполне разумны. Таблица из необходимых выходных фрагментов на 32 микросекунд благодаря специальному линейному режиму адресации вполне помещается в 1024 байтов. Список индексов поиска для всей дорожки примерно равен 12 кБ, то есть всё отлично помещается в 32 КБ ОЗУ BBC Micro.

Особенности BBC Micro / 6845


Последний символ/столбец 6845

Разумеется, чтобы заставить эту схему работать, нельзя не столкнуться с некоторыми забавными особенностями. Первая из них это особенность 6845, из-за которой он выводит чёрный цвет для последнего символа каждой растровой строки. Это проклятие разработчиков демо, а теперь, похоже, и исследователей дисков. Вот слайд из недавнего доклада, на котором я демонстрировал эту проблему:


Слева показан эффект из демо, испорченный вертикальными чёрными полосами, вызванными проблемой последнего чёрного символа/столбца. Несколько растровых строк 6845 размещаются в одном растровом проходе, и, к сожалению, ненамеренно появляются чёрные полосы. При управлении диском эффект намного хуже: чёрные полосы заменяются на нежелательные импульсы, которые записываются на диск.

Справа показано изображение решения: передаваемая на диск волна просто инвертирована. Теперь нормально, что последний столбец всегда будет чёрным (показан оранжевым контуром), потому что там всегда требуется нулевое значение. Строго говоря, это нарушает требования к таймингам некоторых дисковых приводов к пульсации низких сигналов W/DATA. Вот схема таймингов привода той эпохи, Mitsubishi M4852/M4853:


Согласно этой схеме, логический 0 должен удерживаться до 2100 нс. При инвертированной форме сигнала следует ожидать от 3000 нс и более. Однако приводы, которые у меня есть, волнуют только спадающие импульсы данных, а не их длительность. Это неудивительно. Можно было бы проделать пару трюков, чтобы избежать особенностей 6845 и обеспечить время длительности в соответствии со спецификацией, но это оказалось необязательным, а потому я этим не занимался.

Порча DRAM

Порча DRAM (DRAM decay) это кошмар. Она происходит, когда нам не удаётся вовремя обновить DRAM. Цитата из статьи Википедии про обновление памяти:

Этот процесс выполняется автоматически в фоновом режиме электронной схемой памяти и невидим для пользователя.

Это справедливо для современных систем, но не для BBC Micro. На BBC Micro обновление DRAM является побочным эффектом подсистемы видео. При нём используется то свойство, что стандартные экранные режимы итеративно обходят все строки DRAM за короткий промежуток времени. Вероятно, вы уже догадались, к чему всё идёт наш особый видеорежим, используемый для вывода кадров на 32 микросекунды, не является стандартным экранным режимом. Он не гарантирует, что обойдёт все строки DRAM, поэтому происходит порча DRAM! Порча DRAM это не шутки. Из-за незапланированной порчи DRAM я терял различные программы и содержимое дисков. Покажу вам ради смеха программу на BASIC, вызывающую порчу DRAM для самой себя всего за долю секунды:


Печально в порче DRAM то, что если она вас постигла, то вы запросто можете потерять данные.

С другой стороны, если вы ожидаете порчу DRAM, то обычно можете с лёгкостью её обойти. В случае Oiled Otter есть различные критически важные циклы, при которых видеочип 6845 находится в необычном состоянии. Для сохранения обновления DRAM при каждом из таких циклов работает ручной инкремент получения данных из памяти.

Открывшиеся возможности


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

Но в этом исследовании нам очень повезло. Благодаря недостаткам регистра сдвига VIA нам пришлось искать решение с контактами видеовыхода и мы получили доступ к гораздо более мелкому разрешению таймингов на контакте W/DATA. Мы используем MODE4 компьютера BBC Micro, задействующий пиксельную тактовую частоту 8 МГц. Это означает, что можно каждые 125 нс выводить чёрные или белые пиксели, переключая импульсы записи с разрешением 125 нс. Если бы мы хотели потратить чуть больше дополнительной памяти (которая у нас есть) на таблицы побольше, то могли бы использовать MODE0, задействующий пиксельную тактовую частоту 16 МГц, что обеспечивает разрешение 62,5 нс. Я убедился, что 125 нс вполне достаточно для всех протестированных дисковых защит, но здорово, что у нас осталось пространство для манёвра.

Защита длинными дорожками

Моя любимая дисковая защита защита длинными дорожками. Она была популярна во времена Amiga. Мне не кажется, что её когда-нибудь использовали на BBC Micro. Мне нравится защита длинными дорожками потому, что она очень фундаментальна: контроллер гибких дисков имеет большой допуск по изменению скоростей записи (потому что дисковые приводы вращаются с разными скоростями), но записывает он только на одной правильной скорости.

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

Может ли Oiled Otter записать такую дорожку? Да, и довольно легко. Учитывая разрешение вывода в 125 нс, можно легко создать несколько элементов таблицы вывода, похожими на обычные, но с вырезанными из каждой 1 микросекунды 125 наносекундами. Вот видео создания защиты длинными дорожками и проверки считывания диска:



Защита нечёткими битами

Наверно, нам давно пора вернуться к тому, с чего мы начали: к защите нечёткими битами. Может ли Oiled Otter создавать нечёткие биты на оборудовании 1981 года? Давайте попробуем. Вот изображение результатов пары считываний сектора после его записи командой FUZZ системы Oiled Otter.


Команда FUZZ записывает полубайт 0x8, а бит данных поступательно откладывается на инкременты по 125 нс. Это похоже на описание того, как записывались нечёткие биты Dungeon Master. Как видно из скриншота, байты данных 0x88 вскоре начинают считываться неверно и недетерминированным образом. Но дисперсия не на 100% случайна, как слабые биты (weak bit) дисперсия заключается в том, достаточно ли поздно записан бит 0x8, чтобы иметь вероятность быть пропущенным. Если он пропущен, мы всё равно можем увидеть, что в этом безумии есть паттерны и логика.

Представленные выше результаты являются применением принципов нечётких битов к данным, закодированным FM. В кодировке FM каждый бит данных перемежается синхронизирующим битом. Это приводит к тому, что иногда тактовые биты проникают в поток данных (см. байты 0xFF в первом прогоне скорее всего, это тактовые биты). Защита Dungeon Master использует нечёткие биты в сочетании с MFM. Это приводит к более простой ситуации, где нечёткие биты перемещаются между двумя валидными кодировками битов данных и не задевают синхронизирующие биты! Разумеется, Oiled Otter может записывать MFM, GCR и любую другую кодировку, которую только можно придумать. Всё это просто разные протоколы одного фундаментального примитива возможности передачи импульса приводу в любой момент времени с хорошим разрешением.

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


Миссия выполнена

Мы получили возможность записи дисковых импульсов с разрешением 125 нс. Этого совершенно достаточно для создания сложных дисковых защит, в том числе длинных дорожек, слабых битов и нечётких битов. Совсем неплохо для железа 1981 года со скоростью выполнения самой быстрой команды в 1 микросекунду!
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru