Большой консолидирующий коммит после поднятия 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>
138 lines
5.5 KiB
JavaScript
138 lines
5.5 KiB
JavaScript
/**
|
||
* GdPlayerModeSkin — подмена текстуры кубика игрока на "обложку режима" в ship/ball/ufo/...
|
||
*
|
||
* Концепция (как в GD): куб остаётся кубом, но его текстура подменяется на 2D-обложку
|
||
* корабля/НЛО/шара когда игрок в соответствующем гейммоде.
|
||
*
|
||
* Сейчас реализован SHIP-режим. Остальные (ufo/ball/wave/robot) — в будущем
|
||
* через те же фабрики (ufoSkinFactories, ballSkinFactories, …).
|
||
*
|
||
* Работа:
|
||
* 1. attach(scene, scene3d, userId) — подгружает выбор из gd_ship_skin namespace.
|
||
* 2. Рисует ship-текстуру на OffscreenCanvas → DynamicTexture.
|
||
* 3. onBeforeRender следит за scene3d.player._shipMode:
|
||
* - true → сохраняет mesh.material.diffuseTexture, ставит свою.
|
||
* - false → восстанавливает оригинальную.
|
||
*
|
||
* При смене режима куб мгновенно "одевается" в корабль и обратно.
|
||
*/
|
||
import { DynamicTexture } from '@babylonjs/core';
|
||
import { SHIP_SKIN_CATALOG } from '../../admin-preview/gdShipSkins/shipSkinFactories';
|
||
import { STORYS_addres as STORYS_BASE } from '../../api/API';
|
||
|
||
const PLAYER_PRIM_ID = 10001;
|
||
const SHIP_SKIN_PID = 295;
|
||
const SHIP_SKIN_NS = 'gd_ship_skin';
|
||
const DEFAULT_SHIP_SKIN = 'ss_v1';
|
||
const TEX_SIZE = 256;
|
||
|
||
export class GdPlayerModeSkin {
|
||
constructor() {
|
||
this.scene = null;
|
||
this._scene3d = null;
|
||
this._cubeMesh = null;
|
||
this._origTexture = null;
|
||
this._shipTexture = null;
|
||
this._onBeforeRender = null;
|
||
this._wasShip = false;
|
||
}
|
||
|
||
async attach(scene, scene3d, userId = null) {
|
||
if (!scene || !scene3d) return;
|
||
this.scene = scene;
|
||
this._scene3d = scene3d;
|
||
|
||
// Подгружаем выбор
|
||
let skinId = DEFAULT_SHIP_SKIN;
|
||
if (userId) {
|
||
try {
|
||
const url = `${STORYS_BASE}/kubikon3d/savegame/${SHIP_SKIN_PID}/${userId}/${SHIP_SKIN_NS}`;
|
||
const r = await fetch(url, {
|
||
headers: { Authorization: localStorage.getItem('Authorization') || '' },
|
||
});
|
||
if (r.ok) {
|
||
const j = await r.json();
|
||
if (j?.data?.equipped) skinId = j.data.equipped;
|
||
}
|
||
} catch (e) {}
|
||
}
|
||
|
||
const skin = SHIP_SKIN_CATALOG.find(s => s.id === skinId) || SHIP_SKIN_CATALOG[0];
|
||
|
||
// Рисуем DynamicTexture с обложкой корабля
|
||
try {
|
||
this._shipTexture = new DynamicTexture(`ship_skin_${skinId}`, { width: TEX_SIZE, height: TEX_SIZE }, scene, true);
|
||
const ctx = this._shipTexture.getContext();
|
||
ctx.imageSmoothingEnabled = false;
|
||
skin.draw(ctx, '#66ff66', '#44aa44', 0);
|
||
this._shipTexture.hasAlpha = false;
|
||
this._shipTexture.update();
|
||
} catch (e) {
|
||
console.warn('[GdPlayerModeSkin] не удалось создать текстуру:', e);
|
||
return;
|
||
}
|
||
|
||
// Ждём REF_BODY
|
||
let attempts = 0;
|
||
const tryAttach = () => {
|
||
attempts++;
|
||
const pm = scene3d.primitiveManager;
|
||
const data = pm?.instances?.get(PLAYER_PRIM_ID);
|
||
const mesh = data?.mesh;
|
||
if (!mesh) {
|
||
if (attempts < 10) setTimeout(tryAttach, 200);
|
||
return;
|
||
}
|
||
this._cubeMesh = mesh;
|
||
this._setupWatch();
|
||
console.log('[GdPlayerModeSkin] прикреплён, ship-skin=', skinId);
|
||
};
|
||
tryAttach();
|
||
}
|
||
|
||
_setupWatch() {
|
||
const player = this._scene3d?.player;
|
||
if (!player || !this._cubeMesh) return;
|
||
this._onBeforeRender = () => {
|
||
const isShip = !!player._shipMode;
|
||
if (isShip !== this._wasShip) {
|
||
const mat = this._cubeMesh.material;
|
||
if (!mat) { this._wasShip = isShip; return; }
|
||
if (isShip) {
|
||
// Сохраняем оригинальную и накладываем ship
|
||
this._origTexture = mat.diffuseTexture;
|
||
mat.diffuseTexture = this._shipTexture;
|
||
} else {
|
||
// Восстанавливаем оригинальную
|
||
if (this._origTexture !== null) {
|
||
mat.diffuseTexture = this._origTexture;
|
||
}
|
||
}
|
||
this._wasShip = isShip;
|
||
}
|
||
};
|
||
this.scene.onBeforeRenderObservable.add(this._onBeforeRender);
|
||
}
|
||
|
||
dispose() {
|
||
if (!this.scene) return;
|
||
try {
|
||
if (this._onBeforeRender) {
|
||
this.scene.onBeforeRenderObservable.removeCallback(this._onBeforeRender);
|
||
this._onBeforeRender = null;
|
||
}
|
||
} catch (e) {}
|
||
try {
|
||
// Восстановим оригинальную, если что
|
||
if (this._cubeMesh?.material && this._origTexture !== null && this._wasShip) {
|
||
this._cubeMesh.material.diffuseTexture = this._origTexture;
|
||
}
|
||
} catch (e) {}
|
||
try { this._shipTexture && this._shipTexture.dispose(); } catch (e) {}
|
||
this._shipTexture = null;
|
||
this._cubeMesh = null;
|
||
this._scene3d = null;
|
||
this.scene = null;
|
||
}
|
||
}
|