Мое задокументированное путешествие по разработке кроссплатформенных игр с использованием исключительно Rust.

Серьезно. 100% ржавчина или перебор!

Следующая статья: Нативные сенсорные события iOS с Rust

Почему я выбрал ржавчину

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

Взгляд в прошлое: что мне нравится в Rust

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

Нет ООП

Модульный код

Экосистема документации

Система сборки

Управление пакетами

Мои цели изучения Rust

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

В конечном итоге я хочу опубликовать готовый продукт в App Store, но пока моя цель — что-то компилировать. Я был бы счастлив, если бы Rust позволил мне отказаться от большей части, если не всего, программирования на Swift и Objective-C.

0. Настройка среды проекта

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

Следующий сценарий позволяет мне расширить мой .bashrc из любого каталога проекта в моей папке ~/code, перемещаясь вверх, пока не найдет папку с именем .devenv. Оттуда я могу получить сценарий bash для каждого проекта.

# get the first match from `find` while traversing upwards
function find_above {
    old_pwd="$PWD"
    while [[ "$PWD" == $HOME/code/* ]] ; do
        new_pwd=`find "$PWD"/ -maxdepth 1 "$@"`

        if [[ "$new_pwd" ]]; then
            break
        fi

        cd ..
    done

    echo "$new_pwd"
    cd "$old_pwd"
    old_pwd=""
    new_pwd=""
}

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

# see code above

ENVDIR=$(find_above -type d -name ".devenv")
if [[ "$ENVDIR" ]]; then
    source "$ENVDIR/.bashrc"
fi

Создание псевдонимов

Теперь, когда моя машина может обрабатывать среды bash для каждого проекта, я настроил несколько команд для быстрого перехода по моему коду.

# ~/code/tictactoe/.devenv/.bashrc
echo "detected local env: $PWD"

# src directory
export SRCDIR=$(find_above -name "src")

# root directory
export ROOTDIR=$(dirname $SRCDIR)

# build directory
export BUILDDIR="$ROOTDIR/target"
mkdir -p "$BUILDDIR"

# lib directory
export LIBDIR="$ROOTDIR/src"
mkdir -p "$LIBDIR"

# bin directory
export BINDIR="$ROOTDIR/.devenv/bin"
mkdir -p "$BINDIR"
export PATH="$BINDIR:$PATH"

export RUST_LOG="warn,handmade=debug"
export RUST_BACKTRACE=1

# aliases
alias c="cargo build"
alias clean="clean"
alias s="source $HOME/.zshrc"
alias root="cd $ROOTDIR"
alias r="cargo run"
alias t="cargo test"
alias i="run-ios-sim.sh"
alias cr="c && r"

1. Привет, мир ржавчины/Беви

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

Установка Беви

Естественно, я скачал первый попавшийся игровой движок. Который оказался довольно хорошим фреймворком. Bevy — это игровой движок, разработанный для приложений, которые должны быть модульными. Я использовал библиотеку в течение нескольких недель, и это здорово!

cargo add bevy

Написание кода с использованием Bevy’s Paradigm

Вот Hello world, использующий Bevy в Mac OS:

use bevy::prelude::*;

fn main() {
    App::new()
        .add_system(hello_world_system)
        .run();
}

fn hello_world_system() {
    println!("Hello Rust");
}

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

Бегущий Беви Hello World

cargo run

Где графический интерфейс?

Bevy изолировала все свои компоненты в плагины, поэтому, если вы хотите отказаться от всего «игрового движка», вы можете это сделать! Вы также можете запустить Bevy в безголовом режиме! Хотя ради этого эксперимента я включаю плагины по умолчанию.

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        // ...
        .run();
}

// ...

2. Сборка для iOS (кросс-платформенная версия)

Хочу сразу попробовать собрать hello world для iOS. Это экономит мне много времени, потому что, если я не смогу заставить iOS работать с Rust, проект провалится. Кроме того, нет ничего сложнее, чем написать проект, а затем попытаться портировать его на другую систему постфактум.

Оглядываясь назад: предостережения ведут к открытиям

На успешную компиляцию у меня ушло гораздо больше времени, чем я готов признать. Первоначально я думал, что мне нужно создать мост заголовка, используя extern "C", но это не так. Все работает с чистым кодом Rust!

Комплектация

Существует инструмент сборки для груза, который позволяет мне скомпилировать и связать для iOS, не прыгая через какие-либо обручи. Сначала установите инструмент сборки:

cargo install cargo-bundle

Добавление целей компиляции

Затем вы можете перечислить все возможные цели сборки, которые поддерживает Rust. Поскольку я ориентируюсь на iOS, я выполнил следующую команду:

rustup target list | grep ios

Если вы не указали iOS в качестве цели, вы можете добавить новую цель на основе архитектуры вашей машины.

# for production
rustup target add aarch64-apple-ios

# for development
rustup target add aarch64-apple-ios-sim

Упаковка для симулятора iOS

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

Сценарий использует dasel для запроса имени и идентификатора пакета из файла проектов Cargo.toml.

brew install dasel

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

#/usr/bin/env bash

APP_NAME="$(cat Cargo.toml | dasel -r toml '.package.name')"
BUNDLE_ID="$(cat Cargo.toml | dasel -r toml '.package.metadata.bundle.identifier')"

cargo bundle --target aarch64-apple-ios-sim
xcrun simctl boot "iPhone 12 mini"  
open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app 
xcrun simctl install booted "target/aarch64-apple-ios-sim/debug/bundle/ios/$APP_NAME.app"
xcrun simctl launch --console booted "$BUNDLE_ID"

3. Успех проекта!

Если все работает правильно, у меня должно остаться приложение для iOS, которое спамит «Hello Rust» на мой терминал и соответствует следующим критериям:

  • Написан полностью на Rust
  • Нет промежуточного кода
  • Нет X-кода
  • Модульная конструкция
  • Гибкая среда разработки

Продолжение следует

Если вы хотите задокументировать остальную часть проекта в виде серии или поделиться исходным кодом, подписывайтесь, аплодируйте и комментируйте! Это помогает мне оставаться мотивированным

Следующая статья: Нативные сенсорные события iOS с Rust

Заключение

Можно полностью написать игру на Rust и скомпилировать для iOS.