- Подтягивание на уступы выше ~2 м не работало: цель ставила центр капсулы на уровень поверхности, тело оказывалось наполовину в уступе, и физика выталкивала назад. Теперь центр на полвысоты капсулы выше поверхности (проверено ботом: уступы 1,2–2,7 м, тонкие и сплошные, коробка и меш, ходьба и спринт — везде успешно; раньше от 2,0 м не работало) - Откачено сальто камеры при двойном прыжке (тошнит, не видно куда прыгать) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WxZo3ekR5m1af437Qzqwc1
My World
Мультиплеерная игра от первого лица в браузере (Godot 4.7.2, Web). Игроки собираются в стартовой комнате-хабе, заходят через порталы на разные карты и убегают там от преследователей — камня и Верити. Паркур-управление: спринт, рывок, подкат, присед, бег по стенам, подтягивание на уступы.
- Движок: Godot 4.7.2, рендерер GL Compatibility (нужен для Web), физика Jolt
- Платформа: Web (WebAssembly), без потоков (
thread_support=false), без GDExtension (extensions_support=false) - Язык комментариев в коде, интерфейса и документации: русский
- Репозитории (соседние папки в
/home/aibrash/Projects/My_World/):my-world— этот проект;grid-world— готовый веб-экспорт, который CI выкладывает на сайт;Blender models— исходники моделей;source-assets— исходные файлы вне игры (например, видео, из которого сделан звук Верити)
Состояние: что есть и чего нет
| Что | |
|---|---|
| ✅ | Хаб с порталами; карты Песочница и Backrooms; выход из карт обратно в хаб |
| ✅ | Карты качаются отдельными пакетами .pck при первом входе в портал (экран загрузки с прогрессом) |
| ✅ | Мультиплеер: выделенный сервер, игроки видят друг друга, комнаты по картам (в разных картах не пересекаются) |
| ✅ | Преследователи: камень и Верити; общие для всех игроков комнаты, ходят по навмешу, «видят» только вблизи или в поле зрения, могут потерять игрока |
| ✅ | Смерть при поимке и возрождение, красный экран «ПОЙМАЛ!» |
| ✅ | Паркур: спринт, рывок, подкат, присед (C), бег по стенам, прыжок от стены, двойной прыжок, подтягивание на уступ, зум (ЛКМ) |
| ✅ | Управление не зависит от раскладки (русская и любая другая): действия InputMap на физические клавиши |
| ✅ | Деплой: CI из grid-world на сайт; скрипты экспорта, dev-сервера, тестов без окна, выкладки сервера |
| ⏳ | Телефон: пока не поддерживается (нет сенсорного управления, не проверена производительность) — план в разделе «Дорожная карта» |
| ❌ | Чата, ников, настроек (чувствительность мыши, громкость), меню паузы |
| ❌ | Серверных проверок (антиспуфинг позиций, античит): сервер только пересылает |
| ❌ | Сохранения прогресса, аккаунтов |
| ❌ | Автоматических тестов в CI (есть боты для ручного прогона, см. «Тесты без окна») |
| ✅ | Чужой игрок — модель киберниндзя со скелетом (player/character/character.glb, 752 треугольника, 17 костей, 14 анимаций); свечение красится в цвет игрока от его id. Своё тело — та же модель, спрятанная от своей камеры |
Быстрый старт
# запуск игры локально (стартует в хабе; без сервера — одиночная игра, клиент переподключается каждые 3 с)
godot --path . --windowed --resolution 960x540
# с локальным сервером (в другом терминале)
tools/test/make_test_server.sh /tmp/test-server && godot --headless --path /tmp/test-server
godot --path . # можно запустить несколько окон
Карты в редакторе запускаются отдельно (F6 на сцене карты): maps/sandbox/sandbox.tscn,
maps/backrooms/backrooms.tscn. В редакторе и при запуске с диска порталы ведут на сцены напрямую (пакеты не качаются).
Управление
Клавиши привязаны к физическому расположению (так работает и на русской раскладке). Действия лежат в
project.godot → [input]: move_forward/back/left/right, jump, sprint, crouch, dash.
| Клавиша | Действие |
|---|---|
| W A S D, стрелки | движение |
| Shift | спринт |
| Пробел | прыжок; держать у уступа в воздухе — подтянуться; у стены — прыжок от стены; в воздухе — двойной прыжок |
| C (держать) | присед (скорость 3,5, капсула вдвое ниже, пролезает под низкое) |
| Shift + C на бегу | подкат |
| Q | рывок |
| ЛКМ (зажать) | зум |
| Esc / клик | отпустить / захватить мышь |
Бег по стене — в воздухе у стены с зажатым «вперёд» на скорости. Ctrl не используется: в браузере Ctrl+W закрывает вкладку.
Структура проекта
project.godot, export_presets.cfg, icon.svg
net/ сеть. Net — автолоад: и клиент, и сервер (один скрипт)
net.gd состояние комнат, преследователей, RPC (см. «Сеть»)
ghost.gd чужой игрок: модель, выбор анимации по флагам и скорости, цвет свечения от id, ник
player/character/ character.glb — модель и анимации (исходник и скрипт сборки: `../Blender models/character_01/`)
server.tscn пустая сцена для запуска выделенного сервера
core/
map_loader.gd автолоад MapLoader: скачивает пакет карты и подключает его (см. «Загрузка карт»)
player/
player.tscn, player.gd игрок от первого лица: всё паркур-управление, смерть и возрождение
objects/
portal.gd портал на другую карту (Area3D, рисуется кодом)
chasers/
chaser.gd общий «мозг» преследователя: навмеш, зрение, погоня, блуждание, поимка, синхронизация
rock/ камень: rock.tscn, Rock.glb (+ извлечённая текстура), Guitar.ogg
verity/ Верити: verity.tscn, verity2.glb (+ текстура), verity.ogg (звук из видео)
maps/ у каждой карты своя папка — она самодостаточна и экспортируется отдельным пакетом
hub/ стартовая комната: hub.tscn, hub.gd (порталы строятся из списка PORTALS)
sandbox/ песочница: sandbox.tscn, forest.gd, birds.gd, levitate.gd, nav_bake.gd,
floor/ sky/ audio/ models/ (здания, башня, обелиск, деревья, Эмпайр-стейт…)
backrooms/ backrooms.tscn, backrooms.gd, backrooms_nav.res (запечённый навмеш),
model/ (готовая модель «Backrooms VR» + 27 извлечённых текстур + CREDITS.md)
deploy/ боевой конфиг nginx, systemd-юнит, project.godot выделенного сервера
tools/ export_all.sh, serve_web.py, deploy_server.sh, test/ (боты)
Правила, которые стоит соблюдать
- Папки и файлы — в
snake_case. Каждая карта — вmaps/<имя>/. Всё, что нужно только ей, лежит внутри. Общее (игрок, преследователи, порталы, сеть) — внеmaps/: оно входит в основной пакет игры. - Файлы рядом с
.glbне переименовывать и не удалять. Это текстуры, которые Godot вынул из модели при импорте (gltf/embedded_image_handling=Extract), их имена зависят от имени.glb. Переименуешь.glb— импорт создаст дубли. Папки с моделями переносить целиком можно (я так и делал). .importи.uidкоммитятся и переносятся вместе с файлом (git mv). Кэш.godot/не коммитится; после переноса файлов вне редактора его удаляют и делаютgodot --headless --import --path ..- Пути в сценах — через
res://; ссылки на карты (PORTALSвhub.gd,target_sceneу порталов) — строками.
Карты и порталы
| Карта | Сцена | Пакет | Что там |
|---|---|---|---|
| Хаб | maps/hub/hub.tscn |
в основном пакете | Комната 28×18 м, строится кодом (пол-грид, стены, свет). Порталы SANDBOX и BACKROOMS в ряд по центру, влезает 6. Безопасна, преследователей нет |
| Песочница | maps/sandbox/sandbox.tscn |
world.pck |
Киберпанк-мир: пол-шахматка, здания, 60 деревьев (MultiMesh), птицы, парящий обелиск, звук tron_01. Преследователи: камень и Верити. Портал «HUB» возвращает в хаб |
| Backrooms | maps/backrooms/backrooms.tscn |
backrooms.pck |
Модель «Backrooms VR» (×1,4). Преследователь: Верити (масштаб 0,5). Выход «EXIT» в дальнем конце ведёт в хаб. Гул ламп генерируется кодом |
Добавить карту:
- Создай
maps/<имя>/<имя>.tscn: корень Node3D, внутри игрок (player/player.tscn), портал обратно (objects/portal.gd,target_map="hub",target_scene="res://maps/hub/hub.tscn"), при необходимости преследователи. В_ready()вызовиNet.enter_map("<имя>")(идемпотентно; так сервер знает, в какой ты комнате). - Если нужны преследователи — навмеш: либо
NavigationRegion3D+maps/sandbox/nav_bake.gd(запекание при старте, как в песочнице), либо заранее запечённый.res(как у Backrooms: в вебе без потоков запекать на лету долго). - В
export_presets.cfgдобавь пресет-копию «World» (export_filter="resources",export_files=["res://maps/<имя>/<имя>.tscn"],exclude_filter="player/*, objects/*, chasers/chaser.gd",export_path="../grid-world/<имя>.pck"), а в пресет «Web» —maps/<имя>/*вexclude_filter. Добавь вызов вtools/export_all.sh. - В
PORTALS(maps/hub/hub.gd) допиши запись{title, map, scene, pack, color}— портал появится сам.
Преследователи
chasers/chaser.gd — один скрипт для всех. Сцены (rock.tscn, verity.tscn) — это просто модель + звук + NavigationAgent3D.
- Поведение: гонится за ближайшим живым игроком по навмешу, ловит на
catch_distance(по горизонтали, не вышеcatch_height). После поимки игрок умирает и возрождается через 2,5 с в стартовой точке; если преследователь рядом со стартом — уходит на свой старт. - Зрение (
sight_limited = true): замечает игрока, если он ближеhearing_radius(слышит даже за стеной) или в конусеsight_fovдоsight_rangeи луч до него не упирается в стену. Потерял из виду — идёт к последнему местуlose_timeсекунд, потом блуждает (patrol_speed,wander_radius). Безsight_limitedзнает, где игрок, из любой точки. - Параметры (экспорты узла):
chaser_id(имя в сети),speed,turn_speed,catch_distance,catch_height,sight_*,hearing_radius,lose_time,patrol_speed,wander_radius,model_scale(масштаб дочернего узлаModel),emission_energy(чисто светящийся материал; у Верити 1,0). - Значения сейчас: камень в песочнице — скорость 15, зрение 45 м / 130°, слух 8 м, лимит потери 5 с. Верити в песочнице — скорость 12, зрение 55 м / 110°, слух 10 м, потеря 6 с. Верити в Backrooms — скорость 10, масштаб 0,5, зрение по умолчанию (30 м / 120° / слух 6 м / потеря 4 с).
- Модели — плоские «карточки» вдоль оси X; в сцене повёрнуты на 90° и подняты, чтобы стоять на полу. У
verity2лицо нарисовано с одной стороны (свечением), при погоне оно повёрнуто к игроку. - Добавить преследователя: новая сцена по образцу
verity.tscn(корень со скриптомchaser.gd,chaser_id="имя",NavigationAgent3D), инстанс в карту. Больше ничего: сеть синхронизирует всех по имени.
Сеть
Архитектура: выделенный сервер на Godot (WebSocket, порт 9080), клиенты — WebSocketMultiplayerPeer. Сервер не знает про уровни
и ничего не считает, только пересылает сообщения внутри «комнаты» (карты). Физика, управление, преследователи считаются на клиентах.
net/net.gd— автолоадNet.DisplayServer.get_name() == "headless"→ сервер, иначе клиент; флаг--clientделает клиентом и headless (боты). Клиент переподключается каждые 3 с. Адрес в вебе берётся изwindow.location(wss://<домен>/wsилиws://), на десктопе —ws://127.0.0.1:9080или-- --server-url=....- Комнаты по картам. Клиент при входе на карту шлёт
submit_map(имя), сервер отвечаетrecv_map_ack. Пока подтверждения нет (Net.map_ready == false), клиент игнорирует чужие сообщения — так при смене карты не просачиваются старые пакеты. Игроки разных карт друг друга не видят. - Владелец преследователей. В каждой комнате один клиент (первый вошедший, затем наименьший
peer_idиз оставшихся) симулирует всех преследователей и шлёт их позиции 20 раз/с; остальные клиенты плавно повторяют. Если владелец молчитROCK_TIMEOUT= 3 с (вкладка в фоне, лаги) или вышел — сервер передаёт роль другому; преследователь продолжает с того же места. Поимку «своего» игрока всегда решает его собственный клиент по тому, что он видит. - Смерть и возрождение шлются отдельно (
submit_life), чтобы владелец не гнался за мёртвыми и мог увести преследователя от точки возрождения.
| RPC | Кто → кому | Что |
|---|---|---|
submit_map(map) / recv_map_ack(map) |
клиент → сервер / обратно | вход в комнату |
recv_rock_owner(id) |
сервер → клиенты | кто симулирует преследователей |
submit_state(pos, yaw, pitch, flags) / recv_state(id, …) |
клиент → сервер → остальные | состояние игрока, 20 Гц, unreliable_ordered |
submit_chasers(ids, positions, yaws) / recv_chasers(…) |
владелец → сервер → остальные | позиции всех преследователей, 20 Гц |
submit_life(dead, pos) / recv_life(id, dead, pos) |
клиент → сервер → остальные | смерть/возрождение, reliable |
recv_left(id) |
сервер → комната | игрок вышел |
Флаги состояния (Net.FLAG_*): подкат, бег по стене (+ сторона стены), рывок, подтягивание, «в воздухе». Присед передаётся как подкат; призрак отличает их по скорости. Скорость и двойной прыжок призрак считает сам по смещению позиции — в протокол они не входят.
RPC требуют одинаковые путь узла и скрипт на сервере и клиенте, поэтому net.gd один. Любое изменение RPC — клиент и сервер выкладываются вместе
(старый клиент с новым сервером не работает). Имена вроде rock_owner/recv_rock_owner остались от времени, когда преследователь был один.
Загрузка карт пакетами (веб)
Основной пакет игры (index.pck) — только хаб, игрок, сеть и порталы (около 70 КБ). Движок — index.wasm (39,5 МБ, ~10 МБ с gzip).
Карты — отдельные .pck: world.pck (13,6 МБ), backrooms.pck (21,3 МБ). Портал вызывает MapLoader.enter(map, scene, pack, title):
- если сцена уже есть в проекте (редактор, десктоп, пакет уже скачан) — переход сразу;
- иначе игра встаёт на паузу (преследователь не должен убить игрока на экране загрузки), показывается «Загрузка: <имя> N / M МБ»;
- пакет качается
HTTPRequestсCache-Control: no-cache(браузер сверяет файл с сервером), записывается вuser://и подключается черезProjectSettings.load_resource_pack(path, false)(false— файлы основной игры в приоритете, устаревший пакет не затрёт код); - при ошибке на экране и в консоли пишется причина, портал можно повторить.
Грабли, на которые уже наступили:
HTTPRequestв браузере:accept_gzip = falseобязательно. Браузер сам распаковывает gzip, а движок видитContent-Encoding: gzipи пытается распаковать ещё раз (ошибка 8).download_fileв вебе не создаёт файл — тело берём в память и пишем вuser://сами.- Файлы-«не ресурсы» (
.mp4,.md) не экспортируются; потому исходное видео Верити лежит вне проекта (source-assets). - Предупреждения
invalid UID … using text path insteadпри загрузке пакета безвредны. - Кэш зависимостей редактора у недавно созданных/изменённых сцен может устареть, и экспорт пакета карты пропустит модели и звуки (пакет «слишком маленький»).
tools/export_all.shделаетtouchсцен перед экспортом. Признак беды: размер пакета не вырос после добавления ресурсов.
Экспорт, тесты и деплой
Экспорт
tools/export_all.sh # веб-клиент + backrooms.pck + world.pck в ../grid-world
Три пресета в export_presets.cfg: Web (хаб + общее; из него исключены maps/sandbox, maps/backrooms, chasers/rock, chasers/verity, tools),
World и Backrooms (export_filter=resources — сцена карты и всё, на что она ссылается, кроме общего из основного пакета). Шаблоны экспорта —
~/.local/share/godot/export_templates/4.7.2.stable/ (есть только Web). Экспорт при выходе печатает безвредную ошибку Parameter "singleton" is null.
Проверка сборки как на сервере
python3 tools/serve_web.py ../grid-world 8080 # nginx-подобно: COOP/COEP и gzip чанками
Открыть http://127.0.0.1:8080. На десктопном godot --main-pack пакеты карт качаются плоским python3 -m http.server (без gzip) через -- --pack-url=http://127.0.0.1:8777/.
Тесты без окна (боты)
Автотестов в CI нет; для проверок есть боты. tools/test/make_test_project.sh /tmp/tp делает временную копию проекта с автозагрузкой tools/test/bot.gd
(телепортирует игрока по расписанию и печатает состояние раз в секунду), tools/test/make_test_server.sh /tmp/ts — локальный сервер в боевой раскладке.
godot --headless --path /tmp/ts & # сервер
godot --headless --path /tmp/tp res://maps/sandbox/sandbox.tscn -- --client --name=A &
godot --headless --path /tmp/tp res://maps/sandbox/sandbox.tscn -- --client --name=B
# цепочка карт на реальных пакетах (портал SANDBOX в хабе на (-2.25, -7.5), BACKROOMS на (2.25, -7.5)):
/tmp/tp/tools/export_all.sh /tmp/site && (cd /tmp/site && python3 -m http.server 8777 --bind 127.0.0.1 &)
godot --headless --main-pack /tmp/site/index.pck -- --client --name=T --pack-url=http://127.0.0.1:8777/ --tp=2:-2.25,1,-7.5
Для картинок: godot --path . --resolution 1152x648 --fixed-fps 10 --write-movie /tmp/shots/a.png --quit-after 25 res://maps/hub/hub.tscn (окно мелькнёт; кадры — a00000020.png).
В браузере: Chrome (flatpak com.google.Chrome) --headless=new --enable-logging=stderr --v=0 http://127.0.0.1:8080 — вывод Godot виден в строках CONSOLE.
Осторожно: pkill -f/pgrep -f с шаблоном, который есть и в твоей же командной строке, убивает собственный шелл; не используй pkill chrome — закроет браузер владельца. Останавливай по PID или по точному признаку.
Деплой
Браузер ──https/wss──▶ Nginx Proxy Manager (SSL, домен на duckdns) ──http──▶ контейнер 192.168.10.110
nginx :80
├─ / → /opt/grid-world (статика игры)
└─ /ws → 127.0.0.1:9080 (игровой сервер)
Сервер: root@192.168.10.110, Debian 12, контейнер LXC в Proxmox, 1 ядро, 1 ГБ RAM, ssh по ключам. Нет curl, rsync, ufw; есть python3.
В Nginx Proxy Manager включено Websockets Support (без него /ws не работает). Боевой конфиг nginx: deploy/nginx.conf
(/etc/nginx/sites-enabled/godot_game); обязателен gzip для .wasm/.pck (иначе 39,5 + 21 МБ уходят как есть) и заголовки COOP/COEP.
Клиент: tools/export_all.sh, затем коммит и пуш в grid-world (main). Gitea (git.ibrash.duckdns.org, aibrash/grid-world) по пушу запускает
.gitea/workflows/deploy.yml: ssh на сервер и git pull в /opt/grid-world. Пакеты карт выкладываются вместе с index.*.
Сервер: отдельный минимальный проект в /opt/my-world-server/ (project.godot + net/ + официальный бинарник Godot 4.7.2 linux x86_64 godot; бинарник из Fedora на Debian не подходит,
берётся из github.com/godotengine/godot-builds). Служба systemd my-world.service (deploy/my-world.service, DynamicUser, Restart=always).
tools/deploy_server.sh root@192.168.10.110 # бэкап, копирование net/ и project.godot, пересборка кэша классов, перезапуск
ssh root@192.168.10.110 'journalctl -u my-world -f' # лог: «+ игрок …», «[карта] камень считает игрок …»
Трогать сервер нужно только при изменении net/*. Порядок при изменении RPC: сначала пуш grid-world, сразу следом deploy_server.sh; игрокам — Ctrl+F5.
Откат сервера: бэкап в /opt/backup-my-world-server-<дата>/.
Сейчас на сервере ещё старая раскладка (
/opt/my-world-server/Net/, заглавная).deploy_server.shпри следующем запуске переведёт её наnet/. Код сети при этом по протоколу не изменился.
Git
- Репо проекта:
git@git.ibrash.duckdns.org:aibrash/my-world.git, основная веткаmaster; коммиты на русском с трейлеромCo-Authored-By. - Пушить и выкладывать на сайт — только по прямой просьбе владельца. Коммитить локально можно.
Что выяснено по ходу работы
- Окружение: в рендерере Compatibility объёмный туман не работает (удалён из песочницы). SSAO и glow работают (неоновое свечение), оставлены.
В Backrooms включён обычный туман (
Environment.fog_*), он работает. - Память: вкладка в браузере показывала около 1,6 ГБ, хотя на десктопе сцена занимает ~115 МБ. Причина не найдена (нужны данные Chrome:
Shift+Esc). Гипотезы: рост WASM-памяти, несжатые текстуры, HDR-небо 2k,.ogg, распаковываемый целиком. Измерения с тех пор не повторялись. - Материал Верити — две поверхности: жёлтая основа и диск с лицом (текстура свечения на белой основе). Белая основа на ярком свету даёт бледность,
поэтому
emission_energyделает материал чисто светящимся (чёрная основа). - Backrooms: двери ~1,8 м, поэтому преследователь там масштабом 0,5 и навмеш запечён для агента радиусом 0,6 (иначе проход закрыт). Самый дальний зал за узкой дверью недостижим.
- Ввод:
Input.is_key_pressed(KEY_W)проверяет символ, на русской раскладке это «Ц». Только действияInputMapна физические клавиши. - Пресеты: при отключённом
extensions_support— одинindex.wasm; включение даёт ещёindex.side.wasm44 МБ.
Дорожная карта
Цель: играть с телефона
Сейчас игра рассчитана на клавиатуру и мышь. Что нужно сделать, по порядку:
- Сенсорное управление. Основа уже есть: ввод игрока сведён к действиям
InputMap, поэтому сенсорные кнопки просто вызываютInput.action_press/release("move_forward", strength)и т. д. Нужна сценаui/touch_controls.tscn(CanvasLayer): виртуальный стик слева (движение), зона перетаскивания справа (обзор), кнопки прыжка, спринта (переключатель), приседа (удержание), рывка, зума. Показывать только на сенсорных устройствах (DisplayServer.is_touchscreen_available()и feature-тегиweb_android/web_ios). Вplayer/player.gdобзор сейчас считается изInputEventMouseMotionпри захваченной мыши — добавить обработкуInputEventScreenDrag; зум по ЛКМ заменить действиемzoom. - Ориентация и экран: принудительно альбомная (
display/window/handheld/orientation), полноэкранный режим по первому касанию (браузеры требуют жест), масштаб интерфейса (надписи «ПОЙМАЛ!», экран загрузки сейчас рассчитаны на десктоп), безопасные зоны. - Звук: браузер не стартует
AudioContextбез касания. Добавить экран «Нажми, чтобы начать» перед хабом (заодно решает и предупреждение на десктопе). - Текстуры (главное узкое место): мобильные GPU не поддерживают S3TC/BPTC, которыми сейчас сжаты текстуры (
vram_texture_compression/for_desktop=true,for_mobile=false). Нужно включитьfor_mobile(ETC2/ASTC) — это удваивает размер пакетов, если держать оба формата в одном.pck. Варианты: отдельные пакеты*.mobile.pck, выбираемые по feature-тегу, и уменьшенные текстуры (27 текстур Backrooms по 2К — тяжёлые). - Производительность и память: на телефоне отключить SSAO/glow (в Compatibility они дорогие), уменьшить
Viewport.scaling_3d_scale(0,6–0,75), меньше деревьев и птиц, теней нет у деревьев уже сейчас. Мобильный Safari убивает вкладки около 1–1,5 ГБ, а на десктопе она уже занимала 1,6 ГБ — сначала разобраться с памятью (см. выше). Проверить размер.wasmи распаковку (на сотовой сети 40 МБ чувствительны;index.wasmуже жмётся gzip). - Сеть: вкладка уходит в фон и замирает — владельца преследователей сервер уже передаёт другому через 3 с; клиенту при возвращении стоит
показывать «переподключение» и заново входить в комнату (
submit_map), если соединение сброшено. - Тестирование: режим устройства в Chrome DevTools; на реальном телефоне —
tools/serve_web.pyпо локальной сети (http://<IP-компьютера>:8080, привязать сервер к0.0.0.0). Минимальная планка: 30 FPS в хабе и песочнице на среднем Android.
Другое
- Ники по выбору игрока, текстовый чат, анимация приземления, свои руки/тело от первого лица, настройки (чувствительность, громкость), меню паузы.
- Серверные проверки (антиспуфинг позиций), если игроков станет много.
- Новые карты (добавляются по инструкции выше) и новые преследователи.
- Автоматизировать прогон ботов в CI и проверку «пакет не пустой» после экспорта.
Персонаж
Модель и анимации генерирует скрипт Blender models/character_01/build_character.py (Blender 5.x: exec в окне или blender -b -P).
Он строит меш из гранёных сегментов (жёсткие веса: одна кость на сегмент), арматуру из 17 костей, считает позы и пишет 14 анимаций
(idle walk run crouch_idle crouch_walk slide jump fall air_jump dash wallrun_R wallrun_L mantle death), ноги ставятся через IK.
Экспорт: GLB, export_animation_mode=NLA_TRACKS, в player/character/character.glb. Материалы: Suit (тёмный) и Glow (свечение; в игре заменяется цветом игрока).
- Выбор анимации —
Ghost._choose(): смерть > подтягивание > рывок > бег по стене > воздух > присед/подкат > бег/ходьба/стоит. Скорость шага подгоняетсяspeed_scale(REF_SPEED), между анимациями смешивание 0,15 с. Голова повторяет взгляд вверх-вниз. - Своё тело: та же модель стоит в
player/player.tscn(видна в редакторе, отбрасывает тень), но спрятана от своей камеры слоем 2 (player.gd → _ready). Не анимируется. - Выкладка сервера:
ghost.gdлежит и на сервере, где модели нет, поэтому модель грузитсяload()внутриsetup(), а неpreload. - Изменить позы: правь функции анимаций в скрипте, перезапусти его в Blender, заново экспортируй GLB и импортируй в Godot.
Лицензии и авторство
- Модель карты Backrooms — «Backrooms VR», автор VTX (https://sketchfab.com/VTX_car), CC-BY 4.0 (
maps/backrooms/model/CREDITS.md). При публикации нужно указывать автора. - Остальные модели и звуки — материалы владельца проекта.