Большой консолидирующий коммит после поднятия studio.rublox.pro (28 мая 2026). Содержит изменения которые делались в процессе подготовки прод-окружения: Фиксы импортов после выноса из minecraftia: - Массовая замена путей ../../components → ../components (40+ файлов в src/community/, src/admin-preview/) - Замена ../KubikonEditor/ → ../editor/, ../KubikonStudio/ → ../community/, ../AdminPreview/ → ../admin-preview/ - API.js скопирован из минки целиком (было 8 экспортов, стало 312) - Добавлены PLAYER_URL, MyButton_1, недостающие компоненты - Заменены require() на статические ES-imports в BabylonScene, PrimitiveManager, GameRuntime (Vite не поддерживает CJS require) Структура ассетов: - public/kubikon-templates/ → public/assets/kubikon-templates/ - public/kubikon-learn/ → public/assets/kubikon-learn/ - (код искал в /assets/, файлы лежали без /assets/) Навигация роутов внутри студии: - /kubikon-studio/docs → /docs (90+ навигационных вызовов sed-replaced) - /kubikon-editor/X → /edit/X, /kubikon/play/X → /play/X, /kubikon/gd/X → /gd/X UI: - Новый компонент StudioHeader (61px, как в минке) + копия favicon - WithHeader wrapper в App.jsx для всех страниц кроме fullscreen-редактора/плеера - SSO ticket-flow в AuthContext (auto-redeem #ticket= при загрузке) - Тёмная тема карточек игр в ВИКИ (фон #1c2231 вместо #fff, картинка впритык) Документация: - docs/ONBOARDING.md — путь нового контрибьютора от нуля до PR - docs/TUTORIAL_ADD_SCRIPT_API.md — как добавить game.* API - API_USAGE.md — список эндпоинтов backend - README в подпапках engine/, engine/terrain/, engine/voxel/, engine/robloxterrain/, engine/types/ .gitignore: - public/wiki/ исключён (73МБ PNG, будут на CDN отдельной задачей) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
11 KiB
Туториал: как добавить новое scripting API
Скрипты пользователей в Рублоксе работают через песочницу game.*. Все вызовы вида game.player.damage(10), game.scene.spawn(...), game.ui.set(...) — это методы которые ты можешь расширять.
Этот туториал — как добавить новый метод в game.*. На примере: добавим game.player.jump() чтобы скрипт мог программно прыгнуть.
Архитектура (важно понять до кода)
┌──────────────────────────────────────────────────────┐
│ Скрипт юзера (его JS-код) │
│ game.player.jump(); │
└────────────────────┬─────────────────────────────────┘
│ postMessage({cmd:'player.jump'})
▼
┌──────────────────────────────────────────────────────┐
│ ScriptSandboxWorker.js (Web Worker, изолирован) │
│ Прокси-объект game.* — обёртки которые шлют │
│ postMessage в основной поток │
└────────────────────┬─────────────────────────────────┘
│ Worker.onmessage
▼
┌──────────────────────────────────────────────────────┐
│ GameRuntime.js (главный поток) │
│ Обработчик _handleScriptCommand(cmd, payload): │
│ if (cmd === 'player.jump') { ... } │
│ Здесь — реальный вызов BabylonScene / PlayerCtrl │
└────────────────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ PlayerController + BabylonScene │
│ Изменение velocity, проигрывание анимации, и т.д. │
└──────────────────────────────────────────────────────┘
Почему так сложно: скрипты юзеров — недоверенный код. Они в Worker'е без доступа к DOM, window, fetch. Единственный канал коммуникации — postMessage. Это безопасность.
Шаг 1. Реализуй фичу в движке
Открой src/editor/engine/PlayerController.js. Найди где обрабатывается прыжок (по клавише Space):
// Где-то в PlayerController.js
_handleInput() {
if (this._keys.space && this._isGrounded) {
this._jump();
}
}
_jump() {
this._velocity.y = this.jumpForce;
this._isGrounded = false;
}
Если у тебя есть метод _jump() — отлично. Если нет — добавь его на основе того кода что обрабатывает Space.
Сделай его публичным (без подчёркивания):
jump() {
if (!this._isGrounded) return; // нельзя прыгнуть в воздухе
this._velocity.y = this.jumpForce;
this._isGrounded = false;
// опционально: проиграть звук
this.scene3d.audio?.play('jump');
}
Шаг 2. Добавь обработчик в GameRuntime
Открой src/editor/engine/GameRuntime.js. Найди функцию _handleScriptCommand(cmd, payload) — это длинный switch по cmd.
Добавь свой блок (рядом с другими player.*):
if (cmd === 'player.jump') {
try {
this.playerCtrl?.jump();
} catch (e) {
console.warn('[script] player.jump failed:', e);
}
return;
}
Соглашение:
cmdстрокой через точку:<namespace>.<action>- Всё оборачивай в
try/catch— скрипт юзера не должен крашить движок - Если что-то возвращаешь — отвечай через
this._postBack(msgId, result)
Шаг 3. Добавь прокси в ScriptSandboxWorker
Открой src/editor/engine/ScriptSandboxWorker.js. Найди объект game.player:
const game = {
player: {
damage(amount) { postCmd('player.damage', { amount }); },
heal(amount) { postCmd('player.heal', { amount }); },
kill() { postCmd('player.kill'); },
// ...
},
};
Добавь свой метод:
player: {
damage(amount) { postCmd('player.damage', { amount }); },
heal(amount) { postCmd('player.heal', { amount }); },
kill() { postCmd('player.kill'); },
jump() { postCmd('player.jump'); }, // ← новое
},
Готово — скрипт юзера теперь может звать game.player.jump().
Шаг 4. Добавь TypeScript-декларацию (для Monaco autocomplete)
Открой src/editor/engine/types/player.d.ts. Найди где описаны другие методы игрока:
declare namespace game.player {
/** Снять HP игроку. */
function damage(amount: number): void;
/** Восстановить HP. */
function heal(amount: number): void;
// ...
}
Добавь свой метод со строгой типизацией и JSDoc-комментарием:
/**
* Заставить игрока прыгнуть программно (как нажатие Space).
* Игнорируется если игрок в воздухе.
*
* @example
* game.player.jump(); // прыжок
*/
function jump(): void;
JSDoc-комментарий важен — Monaco показывает его как подсказку когда юзер пишет game.player.jump(.
Шаг 5. Пересобери Monaco-бандл деклараций
.d.ts файлы склеиваются в один bundle.js, который Monaco грузит при старте:
cd src/editor/engine/types
python _build_bundle.py
Скрипт пройдёт по всем *.d.ts, склеит, обновит bundle.js. Закоммить и .d.ts и bundle.js — иначе у других контрибьюторов автокомплит будет показывать твою новую функцию, а в runtime получит undefined.
Шаг 6. Протестируй вручную
- Запусти dev-сервер:
npm run dev - Открой студию: http://localhost:5174
- Создай новый проект (или открой существующий)
- Открой панель «Скрипты», создай новый скрипт с кодом:
game.onKey('e', () => {
game.player.jump();
});
- Жми Play. В игре нажми
E— игрок должен прыгнуть.
Проверки на отказ:
- Не падает если игрок в воздухе (просто игнорится)
- Не крашит движок если PlayerController ещё не создан
- Не крашит если скрипт юзера зовёт
jump()100 раз/сек
Шаг 7. Запиши в CHANGELOG.md
В секции [Unreleased]:
## [Unreleased]
### Added
- `game.player.jump()` — программный прыжок игрока ([#XX](https://git.rublox.pro/rublox/studio/pulls/XX))
Шаг 8. PR
Открой PR с описанием:
## Что сделано
Новое scripting API: `game.player.jump()` — программный прыжок игрока.
## Зачем
Юзеры часто хотят сделать «авто-прыжок» в определённой зоне (платформер,
GD-уровни). Раньше нужно было хак через эмуляцию нажатия Space.
## Как протестировать
1. Создай проект, добавь скрипт:
```js
game.onKey('e', () => game.player.jump());
- Запусти, нажми E в игре.
- Игрок прыгает.
Файлы
src/editor/engine/PlayerController.js— публичный методjump()src/editor/engine/GameRuntime.js— обработчикcmd === 'player.jump'src/editor/engine/ScriptSandboxWorker.js— проксиgame.player.jumpsrc/editor/engine/types/player.d.ts— TypeScript-декларацияsrc/editor/engine/types/bundle.js— пересобранCHANGELOG.md— запись в Unreleased
## Best practices
### ✅ Делай
- **Try/catch на всё в `GameRuntime`** — скрипт юзера не должен крашить
- **Throttle/debounce на UI-обновления** — `game.ui.set` 60 раз/сек убьёт FPS
- **JSDoc с `@example`** — Monaco покажет пример
- **Имя через точку**: `namespace.action` (`player.jump`, `scene.spawn`, `ui.set`)
- **Возвращай примитивы**: number, string, boolean. Объекты — только простые `{x,y,z}`
### ❌ Не делай
- **Не возвращай Babylon-объекты в Worker** — они не сериализуются через postMessage
- **Не создавай новые Worker'ы внутри скрипта** — у нас один Worker на скрипт
- **Не давай доступ к `fetch`, `XMLHttpRequest`, `WebSocket`** — это XSS-вектор
- **Не позволяй вечный цикл** в синхронной обработке `_handleScriptCommand` — повесит главный поток
- **Не добавляй deprecated API в `.d.ts`** — пусть устаревшие методы пропадут с автокомплита
## Что почитать дальше
- [src/editor/engine/README.md](../src/editor/engine/README.md) — обзор архитектуры движка
- [src/editor/engine/types/README.md](../src/editor/engine/types/README.md) — система типов для скриптов
- `ScriptSandboxWorker.js` — изучи как сделаны существующие 100+ методов
## Вопросы
Канал `#разработка` на https://team.rublox.pro