/** * 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; } }