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

249 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Туториал: как добавить новое 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