studio/docs/TUTORIAL_ADD_SCRIPT_API.md
МИН 61fba4e174
Some checks failed
CI / Lint + Format (push) Failing after 32s
CI / Build (push) Failing after 37s
CI / Secret scan (push) Failing after 37s
CI / PR size check (push) Has been skipped
fix: починка билда + studio.rublox.pro инфра
Большой консолидирующий коммит после поднятия 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>
2026-05-28 05:01:13 +03:00

11 KiB
Raw Permalink Blame History

Туториал: как добавить новое 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. Протестируй вручную

  1. Запусти dev-сервер: npm run dev
  2. Открой студию: http://localhost:5174
  3. Создай новый проект (или открой существующий)
  4. Открой панель «Скрипты», создай новый скрипт с кодом:
game.onKey('e', () => {
    game.player.jump();
});
  1. Жми 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());
  1. Запусти, нажми E в игре.
  2. Игрок прыгает.

Файлы

  • 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