Найти в Дзене
Ржавый код

Создание небольшой игры с помощью WebAssembly и Rust

Оглавление

В этой статье я хочу поделиться своим опытом создания игры Snake для браузера с помощью WebAssembly и Rust.

Что такое WebAssembly?

WebAssembly - это независимый от платформы формат для исполняемых программ, предназначенный для компиляции языков программирования. WebAssembly был разработан для запуска в браузере и дополнения JavaScript двумя способами:

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

WebAssembly (WASM) также становится все более актуальным для серверных вычислений, и Соломон Хайкс, соучредитель Docker, даже написал:

Если бы WASM +WASI существовали в 2008 году, нам не нужно было бы создавать Docker. [...] Веб-сборка на сервере - это будущее вычислительной техники.

В этой статье мы сосредоточимся на примере создания браузерной игры.

Почему Rust?

Первоначально WebAssembly был ориентирован на C/C++, но теперь многие языки программирования могут компилироваться в WebAssembly.

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

Согласно отчету, состояние WebAssembly 2022 Rust стал наиболее часто используемым, а также наиболее желанным языком для разработки WebAssembly, что делает его еще более привлекательным по мере роста популярности экосистемы.

Инструменты

Мы использовали рекомендованные Rust модули wasm-pack и wasm-bindgen для компиляции нашего кода Rust в WebAssembly и облегчения взаимодействия с JavaScript. Эти библиотеки создают модуль WebAssembly, а также соответствующие оболочки JavaScript, которые абстрагируют низкоуровневые детали взаимодействия между кодом JavaScript и Rust. Вы можете найти отличное руководство по началу работы здесь.

web-sys модуль предоставила нам импорт на основе `wasm-bindgen` для API-интерфейсов браузера. Он поставляется с обширным списком примеров.

Для быстрых итераций мы использовали `cargo-watch` для перестройки при каждом изменении и `devserver` для горячей перезагрузки в браузере.

Структура игры

В браузере `requestAnimationFrame` является рекомендуемой основой основного “цикла” рендеринга игры в подходящее время между перерисовками.

-2

Перевод в Rust с помощью `wasm-bindgen` и `web-sys` довольно подробный, как вы можете видеть в этом примере `requestAnimationFrame` и в этом же примере `canvas`. Веб-API полагаются на динамическую типизацию и отсутствие нулевой безопасности JavaScript и, следовательно, на простые конструкции JavaScript, такие как

-3

становятся очень громоздкими при написании с использованием соответствующих безопасных для null и статически типизированных оболочек Rust, предоставляемых `web-sys`.

-4

Кроме того, концепция обратных вызовов JavaScript, разделяющая состояние сбора мусора, не переводится в Rust без некоторых трений. Обратные вызовы представлены замыканиями Rust в `web-sys`. Но концепция владения Rust затрудняет разделение состояния игры между различными замыканиями, используемыми, например, для перерисовки и обработки пользовательского ввода.

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

-5

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

-6

Графика

API браузера предоставляет различные подходы для рендеринга в HTML canvas.

Для полного использования графики с аппаратным ускорением WebGL предоставляет мощный API, и этот API может использоваться через привязки `web-sys`, но требует заботы о деталях низкого уровня.

Другой подход заключается в рисовании игровой графики с использованием примитивов, таких как прямоугольники и круги Canvas API.

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

-7

Установив для свойства рендеринга изображения `canvas` значение `pixelated`, внешний вид поддерживает ретро-стиль игры.

Игровая логика с Rust

Центральной частью состояния игры является массив screen: `[u8; SCREEN_SIZE]`, представляющий цвет каждого пикселя на экране, увеличиваемый массив координат `snake: vectdeque <Coord>`, содержащий текущее положение змеи, и вектор `direction: Coord` с текущим направлением движения.

-8

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

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

В Rust этот алгоритм может быть записан следующим образом.

-9

Вывод

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

Статья на list-site.