Это гостевой пост от Блажея Кустры (Błażej Kustra), инженера по работе с разработчиками в Software Mansion, который работает над Argent. Он также создал Purrkour, проект для конкурса Expo App Award.
Если вы хоть раз видели, как кот пытается втиснуться в слишком маленькую коробку, концепция Purrkour вам сразу понятна. Это уютная головоломка в стиле паркура с котиками на доске 5x5: нужно загнать каждого кота в коробку («если влел — значит,сел»), перепрыгивая по пути через других котов, растения и занятые коробки.
Вся игра рендерится с помощью WebGPU внутри приложения Expo. Здесь нет файлов изображений, 3D-моделей и аудиофайлов. Мой первый коммит был сделан 21 июля 2026 года, а версия 1.0.0 была готова к выходу в App Store уже 23 июля — с рекламой, политикой конфиденциальности, конфигурацией EAS и веб-версией, всего за 15 коммитов.
Почему я ее создал
Я работаю инженером по работе с разработчиками в Software Mansion, где занимаюсь развитием Argent — нашего инструментария, который позволяет ИИ-агентам управлять симуляторами iOS, эмуляторами Android и приложениями для Chromium. В свободное время я люблю создавать разные вещи, и Purrkour — одна из них.
Я люблю котиков и логические игры, и идея уютной головоломки с котами давно сидела у меня в голове. Толчком послужило то, что Конрад Речко (Konrad Reczko) из команды TypeGPU создал собственную игру и движок на TypeGPU. Это показалось отличным поводом наконец-то реализовать и свой проект.
Чего я хотел доказать: 3D-игра с рендерингом на GPU может быть обычным приложением Expo. Один кодовая база, запуск в App Store и веб, без игрового движка, без нативного графического кода и без конвейера ресурсов (asset pipeline).
Игра также служит проверкой в реальных условиях стека с открытым исходным кодом от Software Mansion: TypeGPU, react-native-webgpu (мы внесли в него свой вклад), react-native-audio-api, Reanimated и Gesture Handler.
Почему Expo, а не игровой движок
Очевидной альтернативой был Unity или Godot с возможностью экспорта в веб. Но я хотел, чтобы игровая логика, рендерер и интерфейс были написаны на TypeScript, разделяя код с веб-сайтом, а вокруг холста находился интерфейс React Native. Я хорошо знаком с Expo, и это самый быстрый известный мне способ запустить приложение на реальном устройстве.
TypeGPU — ключевой элемент. Те же типизированные шейдеры и код рендеринга выполняются на WebGPU в браузере и на react-native-webgpu (Dawn) на устройстве. Expo упростила нативную сторону: пребилд (prebuild), плагины конфигурации и сборочные версии для разработки (dev builds).
Вот вся модель освещения. Это функция TypeScript, которую TypeGPU компилирует в WGSL, причем тот же код работает и в Chrome, и на iPhone. Директива «use gpu» помечает его как код шейдера.
Expo SDK 57 на новой архитектуре (React Native 0.86) тоже сыграл свою роль. react-native-webgpu и react-native-audio-api — оба являются нативными модулями новой архитектуры, а Reanimated 4 и Worklets требуют ее наличия. В SDK 57 они все заработали вместе без каких-либо обходных путей.
Один движок, две тонкие оболочки
Вся игра находится в packages/engine: правила, решатель (solver), уровни, меши, рендерер, аудио и игровая сессия. Приложение Expo и сайт на Vite представляют собой тонкие оболочки вокруг него, поэтому баг, исправленный один раз, исчезает на обеих платформах.
День за днем приложение Expo рендерит <Canvas> из react-native-webgpu, получает его контекст WebGPU и передает в createSession(...) движка. Движок не знает, на какой платформе он запущен. Доступ к платформе внедряется извне: контекст холста GPU, фабрика AudioContext и хранилище наподобие localStorage (MMKV на мобильных устройствах, localStorage в вебе).
Все, что вы видите, — это процедурная низкополигональная геометрия, состоящая из коробок и пирамид на TypeScript: котики четырех расцветок, такса и корги, комнатные растения, клубки ниток и картонные коробки. Саундтрек и эффекты мяуканья воспроизводятся в реальном времени с помощью осцилляторов через react-native-audio-api.
Вокруг холста приложение Expo использует expo-font со шрифтом Baloo 2 из Expo Google Fonts, expo-splash-screen, expo-system-ui, expo-keep-awake, expo-tracking-transparency и expo-build-properties, а также MMKV для сохранения прогресса, react-native-pulsar для тактильной отдачи и Google Mobile Ads для опциональных подсказок за просмотр рекламы.
Уровни генерируются, а затем фильтруются решателем
Всего в игре 25 уровней. Первые 10 были расставлены вручную, и каждый обучает одной концепции (прыжки, собаки, коты как препятствия). Для более сложных рядов скрипт генерирует десятки тысяч случайных досок и оставляет только те, которые одобряет поиск в ширину (BFS-решатель):
- Мало элементов: от 2 до 3 котов, от 1 до 2 собак, несколько растений. Сложность проистекает из взаимодействия элементов друг с другом.
- Собаки должны иметь значение: уровень должен быть нерешаемым с одними лишь котами, и каждая собака должна совершать движение в оптимальном решении.
- Хитрый первый ход: множество возможных открытий и всего несколько оптимальных, поэтому начало неочевидно.
Каждому ряду присваивается целевой диапазон ходов (par range), поэтому сложность возрастает от разминки в 1 ход до финала в 20 ходов. Юнит-тест повторно решает все 25 уровней, поэтому каждый «Purrfect-результат» имеет доказанный минимум. Подсказки показывают следующее оптимальное движение (на мобильных устройствах — через опциональную рекламу с вознаграждением, в вебе — после 20-секундной паузы).
Тестирование с агентом, который не видит доску
Я создал Purrkour с помощью Claude Code, а агент использовал Argent для запуска игры на симуляторе iOS и в Chrome, прокликивая уровни после каждого изменения.
Это породило проблему, о которой я не подумал. Доска представляет собой единый холст WebGPU, поэтому дерева доступности (accessibility tree) здесь нет. Для агента вся игра выглядит как одно изображение. Он мог угадывать положение кота по скриншоту, но это было ненадежно. Поэтому я добавил небольшой отладочный мост. В версиях для разработки он размещает объект __purrkour в глобальной области видимости как в браузере, так и в приложении React Native:
cellScreen выполняет ту же проекцию камеры, которую использует рендерер, поэтому он возвращает точные координаты на экране для любой ячейки доски. Цикл выглядит следующим образом:
- Запросить у BFS-решателя следующий оптимальный ход
- Запросить у моста, где эта ячейка находится на экране
- Кликнуть туда через Argent: симулятор на мобильном устройстве, Chrome в вебе
- Прочитать state(), чтобы убедиться, что кот действительно сдвинулся с места, и сделать скриншот для проверки визуальной корректности
Решатель гарантирует, что каждый уровень имеет решение, а Argent гарантирует, что в каждый уровень можно играть. Вместе они составили весь мой отдел контроля качества.
Результаты
С точки зрения производительности, поскольку игра работает на GPU, она держит 120 кадров в секунду. Игра не слишком требовательна к вычислениям, так что это скорее не бенчмарк, а доказательство того, что стек не путается под ногами.
Purrkour ощущается завершенной маленькой и уютной игрой. Если я что-то и добавлю, то это будет редактор уровней или ежедневная головоломка, поскольку решатель уже умеет проверять любую доску и рассчитывать целевое число ходов. В моем списке пожеланий к Expo есть один пункт: WebGPU в Expo Go. Сканирование QR-кода и запуск игры на TypeGPU без сборки для разработки значительно упростили бы тестирование WebGPU на React Native.
Создавать игры на Expo еще никогда не было так просто. Холст WebGPU — это просто еще один компонент, Expo берет на себя все сопутствующие задачи, а тот же движок TypeScript бесплатно работает и в веб-версии. Путь от первого коммита до App Store занял у меня три дня.
Если вы думали, что 3D-игра на мобильном устройстве обязательно означает Unity, попробуйте поместить холст в сборку для разработки и посмотрите, как далеко вас заведет TypeScript. Сыграйте в Purrkour в App Store или в браузере, а код заберите на GitHub.












