Большой консолидирующий коммит после поднятия 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>
249 lines
11 KiB
Markdown
249 lines
11 KiB
Markdown
# Туториал: как добавить новое 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):
|
||
|
||
```javascript
|
||
// Где-то в PlayerController.js
|
||
_handleInput() {
|
||
if (this._keys.space && this._isGrounded) {
|
||
this._jump();
|
||
}
|
||
}
|
||
|
||
_jump() {
|
||
this._velocity.y = this.jumpForce;
|
||
this._isGrounded = false;
|
||
}
|
||
```
|
||
|
||
Если у тебя есть метод `_jump()` — отлично. Если нет — добавь его на основе того кода что обрабатывает Space.
|
||
|
||
Сделай его **публичным** (без подчёркивания):
|
||
|
||
```javascript
|
||
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.*`):
|
||
|
||
```javascript
|
||
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`:
|
||
|
||
```javascript
|
||
const game = {
|
||
player: {
|
||
damage(amount) { postCmd('player.damage', { amount }); },
|
||
heal(amount) { postCmd('player.heal', { amount }); },
|
||
kill() { postCmd('player.kill'); },
|
||
// ...
|
||
},
|
||
};
|
||
```
|
||
|
||
Добавь свой метод:
|
||
|
||
```javascript
|
||
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`. Найди где описаны другие методы игрока:
|
||
|
||
```typescript
|
||
declare namespace game.player {
|
||
/** Снять HP игроку. */
|
||
function damage(amount: number): void;
|
||
|
||
/** Восстановить HP. */
|
||
function heal(amount: number): void;
|
||
// ...
|
||
}
|
||
```
|
||
|
||
Добавь свой метод **со строгой типизацией и JSDoc-комментарием**:
|
||
|
||
```typescript
|
||
/**
|
||
* Заставить игрока прыгнуть программно (как нажатие Space).
|
||
* Игнорируется если игрок в воздухе.
|
||
*
|
||
* @example
|
||
* game.player.jump(); // прыжок
|
||
*/
|
||
function jump(): void;
|
||
```
|
||
|
||
JSDoc-комментарий важен — Monaco показывает его как подсказку когда юзер пишет `game.player.jump(`.
|
||
|
||
## Шаг 5. Пересобери Monaco-бандл деклараций
|
||
|
||
`.d.ts` файлы склеиваются в один `bundle.js`, который Monaco грузит при старте:
|
||
|
||
```bash
|
||
cd src/editor/engine/types
|
||
python _build_bundle.py
|
||
```
|
||
|
||
Скрипт пройдёт по всем `*.d.ts`, склеит, обновит `bundle.js`. **Закоммить и `.d.ts` и `bundle.js`** — иначе у других контрибьюторов автокомплит будет показывать твою новую функцию, а в runtime получит `undefined`.
|
||
|
||
## Шаг 6. Протестируй вручную
|
||
|
||
1. Запусти dev-сервер: `npm run dev`
|
||
2. Открой студию: http://localhost:5174
|
||
3. Создай новый проект (или открой существующий)
|
||
4. Открой панель «Скрипты», создай новый скрипт с кодом:
|
||
|
||
```javascript
|
||
game.onKey('e', () => {
|
||
game.player.jump();
|
||
});
|
||
```
|
||
|
||
5. Жми Play. В игре нажми `E` — игрок должен прыгнуть.
|
||
|
||
**Проверки на отказ:**
|
||
- Не падает если игрок в воздухе (просто игнорится)
|
||
- Не крашит движок если PlayerController ещё не создан
|
||
- Не крашит если скрипт юзера зовёт `jump()` 100 раз/сек
|
||
|
||
## Шаг 7. Запиши в [CHANGELOG.md](../CHANGELOG.md)
|
||
|
||
В секции `[Unreleased]`:
|
||
|
||
```markdown
|
||
## [Unreleased]
|
||
|
||
### Added
|
||
- `game.player.jump()` — программный прыжок игрока ([#XX](https://git.rublox.pro/rublox/studio/pulls/XX))
|
||
```
|
||
|
||
## Шаг 8. PR
|
||
|
||
Открой PR с описанием:
|
||
|
||
```markdown
|
||
## Что сделано
|
||
Новое scripting API: `game.player.jump()` — программный прыжок игрока.
|
||
|
||
## Зачем
|
||
Юзеры часто хотят сделать «авто-прыжок» в определённой зоне (платформер,
|
||
GD-уровни). Раньше нужно было хак через эмуляцию нажатия Space.
|
||
|
||
## Как протестировать
|
||
1. Создай проект, добавь скрипт:
|
||
```js
|
||
game.onKey('e', () => game.player.jump());
|
||
```
|
||
2. Запусти, нажми E в игре.
|
||
3. Игрок прыгает.
|
||
|
||
## Файлы
|
||
- `src/editor/engine/PlayerController.js` — публичный метод `jump()`
|
||
- `src/editor/engine/GameRuntime.js` — обработчик `cmd === 'player.jump'`
|
||
- `src/editor/engine/ScriptSandboxWorker.js` — прокси `game.player.jump`
|
||
- `src/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
|