Большой консолидирующий коммит после поднятия 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>
95 lines
3.6 KiB
JavaScript
95 lines
3.6 KiB
JavaScript
/**
|
||
* GdStartArch — стартовая арка в начале GD-уровня (этап G4).
|
||
*
|
||
* Берёт выбранную юзером арку (по эпохе → ARCH_CATALOG.id) или дефолтную
|
||
* для текущей эпохи, ставит её в (x=ARCH_X, y=0, z=0) лицом к камере (-Z).
|
||
*
|
||
* При желании скрипт уровня может вызвать gdStartArch.celebrate() —
|
||
* лёгкая пульсация по высоте на первые ~1.5с.
|
||
*/
|
||
import { Vector3, TransformNode } from '@babylonjs/core';
|
||
import { ARCH_CATALOG } from '../../admin-preview/gdArches/archFactories';
|
||
|
||
// Дефолтный выбор арки по эпохе (выбор юзера сохранён в gd_arch_choices).
|
||
const DEFAULT_ARCH_BY_EPOCH = {
|
||
1: 'a1_v5', // Рустик
|
||
2: 'a2_v3', // Ледяная
|
||
3: 'a3_v5',
|
||
4: 'a4_v1',
|
||
5: 'a5_v1',
|
||
6: 'a6_v5',
|
||
7: 'a7_v1',
|
||
8: 'a8_v5',
|
||
9: 'a9_v2',
|
||
10: 'a10_v1',
|
||
};
|
||
|
||
// Позиция арки — чуть до x=0, чтобы куб стартовал ВНУТРИ неё.
|
||
const ARCH_X = -2;
|
||
|
||
export class GdStartArch {
|
||
constructor() {
|
||
this.scene = null;
|
||
this._handle = null;
|
||
this._root = null;
|
||
this._t = 0;
|
||
this._onBeforeRender = null;
|
||
this._celebrating = false;
|
||
this._celebrateStart = 0;
|
||
}
|
||
|
||
attach(scene, epoch = 1, archId = null) {
|
||
if (!scene) return;
|
||
this.scene = scene;
|
||
const id = archId || DEFAULT_ARCH_BY_EPOCH[epoch] || 'a1_v4';
|
||
const factory = ARCH_CATALOG.find(a => a.id === id);
|
||
if (!factory) {
|
||
console.warn('[GdStartArch] фабрика не найдена:', id);
|
||
return;
|
||
}
|
||
const h = factory.make(scene, `gd_arch_inst`);
|
||
if (!h || !h.root) return;
|
||
this._handle = h;
|
||
this._root = h.root;
|
||
this._root.position = new Vector3(ARCH_X, 1, 0); // низ арки = верх пола (y=1)
|
||
// Поворот на 90° по Y: фабрики строят арку ширнной по X, лицом в +Z.
|
||
// Куб бежит по +X — арка должна стоять поперёк трассы, проёмом вдоль X.
|
||
// Значит колонны должны быть на ±Z, балка вдоль Z. Поворот = π/2.
|
||
this._root.rotation.y = Math.PI / 2;
|
||
this._onBeforeRender = () => {
|
||
this._t += 0.01;
|
||
// Если был празднички — лёгкая пульсация
|
||
if (this._celebrating) {
|
||
const elapsed = this._t - this._celebrateStart;
|
||
if (elapsed > 1.5) {
|
||
this._celebrating = false;
|
||
this._root.scaling.set(1, 1, 1);
|
||
} else {
|
||
const s = 1 + Math.sin(elapsed * 12) * 0.04 * (1 - elapsed / 1.5);
|
||
this._root.scaling.set(s, s, s);
|
||
}
|
||
}
|
||
};
|
||
scene.onBeforeRenderObservable.add(this._onBeforeRender);
|
||
}
|
||
|
||
celebrate() {
|
||
this._celebrating = true;
|
||
this._celebrateStart = this._t;
|
||
}
|
||
|
||
dispose() {
|
||
if (!this.scene) return;
|
||
try {
|
||
if (this._onBeforeRender) {
|
||
this.scene.onBeforeRenderObservable.removeCallback(this._onBeforeRender);
|
||
this._onBeforeRender = null;
|
||
}
|
||
} catch (e) {}
|
||
try { this._handle && this._handle.dispose && this._handle.dispose(); } catch (e) {}
|
||
this._handle = null;
|
||
this._root = null;
|
||
this.scene = null;
|
||
}
|
||
}
|