studio/src/editor/engine/GdPlayerCube.js
МИН 31adbf151b Initial public release: Студия Рублокса v1.0
Open-source веб-студия для создания игр Рублокса, двойная лицензия
AGPL-3.0 + Коммерческая.

Главное:
- Vite 5 + React 18 + Babylon 7.54.3 + Monaco Editor + Colyseus 0.16
- Самодостаточный движок ~28к строк (66 файлов): BlockManager,
  TerrainVoxelBuilder, ModelManager, DecoManager, PlayerController,
  ScriptSandboxWorker, MultiplayerSync, 30+ GD-гейммодов
- Главный редактор KubikonEditor (~37к строк) + панели, ScriptEditor (Monaco)
- Витрина игр (KubikonFeed, KubikonStudio, KubikonDocs, KubikonLearn)
- Geometry Dash sub-app (GdMenu, GdShop, GdRules, GdCoverArt)
- 10 admin-preview каталогов для дизайнеров (скины, музыка, порталы и т.д.)
- Конфигурируемый бэкенд через VITE_API_BASE — работает со staging
  (dev-api.rublox.pro) без настройки
- Standalone-режим (VITE_STANDALONE=true) — открыть пустой редактор без бэка
- Полная документация (на русском): README, ARCHITECTURE, CONTRIBUTING,
  SECURITY, CHANGELOG
- ESLint + Prettier + EditorConfig
- Legal: LICENSE (AGPL-3.0), LICENSE-COMMERCIAL.md, CLA.md, COPYRIGHT.md
- Issue templates: bug_report, feature_request, security_disclosure

Перед публикацией:
- Все импорты из minecraftia заменены на локальные
- Все хардкоды URL (minecraftia-school.ru) и внутренних IP убраны → env
- Admin-эндпоинты Kubikon3DService вырезаны (остаются в приватном репо)
- AdminKubikonModeration не публикуется (модерация — в team.rublox.pro)
- 93 МБ ассетов public/kubikon-assets вынесены в .gitignore
  (раздаются через release artifact)
2026-05-27 23:41:10 +03:00

165 lines
6.7 KiB
JavaScript
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.

/**
* GdPlayerCube — визуальные эффекты на куб игрока (этап G7).
*
* Находит primitive с id=10001 (REF_BODY в скрипте уровня) и навешивает:
* 1. Чёрный outline толщиной 0.04 (renderOutline).
* 2. Бледный glow в primary-цвете скина (через GlowLayer.referenceMeshToUseItsOwnMaterial).
* 3. Squash & stretch: при увеличении скорости падения сжимается по Y,
* при отскоке от земли — растягивается.
*
* Использование:
* const pc = new GdPlayerCube();
* pc.attach(scene, scene3d);
* pc.dispose();
*/
import { Color3, GlowLayer } from '@babylonjs/core';
const PLAYER_PRIM_ID = 10001;
export class GdPlayerCube {
constructor() {
this.scene = null;
this._scene3d = null;
this._cubeMesh = null;
this._glow = null;
this._onBeforeRender = null;
this._prevPlayerY = null;
this._prevVy = 0;
this._baseScaleY = 1;
}
attach(scene, scene3d) {
if (!scene || !scene3d) return;
this.scene = scene;
this._scene3d = scene3d;
// Откладываем — primitive может быть не создан в момент enterPlayMode
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);
else console.warn('[GdPlayerCube] куб игрока не найден');
return;
}
this._cubeMesh = mesh;
this._baseScaleY = mesh.scaling?.y || 1;
this._setupGlow();
this._setupSquash();
console.log('[GdPlayerCube] прикреплён, mesh=', mesh.name);
};
tryAttach();
}
/** Чёрная обводка по силуэту куба. */
_setupOutline() {
const m = this._cubeMesh;
try {
m.renderOutline = true;
m.outlineWidth = 0.04;
m.outlineColor = new Color3(0, 0, 0);
} catch (e) { console.warn('[GdPlayerCube] outline failed', e); }
}
/** GlowLayer — куб светится своим primary цветом скина.
* GlowLayer создаётся один раз на сцену; если уже есть — используем его. */
_setupGlow() {
try {
// Не создаём GlowLayer если он уже есть на сцене (другой код мог)
let glow = this.scene.effectLayers?.find?.((l) => l.name === 'gd_glow');
if (!glow) {
glow = new GlowLayer('gd_glow', this.scene, { mainTextureFixedSize: 256, blurKernelSize: 24 });
glow.intensity = 0.6;
this._glow = glow;
} else {
this._glow = null; // не наш, не disposить
}
glow.addIncludedOnlyMesh(this._cubeMesh);
} catch (e) { console.warn('[GdPlayerCube] glow failed', e); }
}
/** Squash при падении / stretch при взлёте.
* Считаем vy = (y_now - y_prev) / dt. При большой |vy| меняем scaling.
* При приземлении (vy резко 0) — короткий «бум» сжатия по Y. */
_setupSquash() {
this._prevPlayerY = null;
this._prevVy = 0;
this._landSquashLeft = 0; // секунд осталось эффекта squash после удара
this._onBeforeRender = () => {
const m = this._cubeMesh;
const pp = this._scene3d?.player?._pos;
if (!m || !pp || !m.scaling) return;
const dt = this.scene.getEngine().getDeltaTime() / 1000;
if (dt <= 0 || dt > 0.2) return;
const y = pp.y;
if (this._prevPlayerY == null) {
this._prevPlayerY = y;
return;
}
const vy = (y - this._prevPlayerY) / dt;
// Детект приземления: была отрицательная vy, стала ≈ 0
if (this._prevVy < -8 && Math.abs(vy) < 2) {
this._landSquashLeft = 0.18;
}
this._prevPlayerY = y;
this._prevVy = vy;
// Целевой масштаб
let sy = this._baseScaleY;
let sxz = 1;
if (this._landSquashLeft > 0) {
// Сжатие после удара: y 0.7, xz 1.2 → линейный возврат
const t = this._landSquashLeft / 0.18;
const squashAmt = Math.sin(t * Math.PI) * 0.3;
sy = this._baseScaleY * (1 - squashAmt);
sxz = 1 + squashAmt * 0.5;
this._landSquashLeft -= dt;
if (this._landSquashLeft < 0) this._landSquashLeft = 0;
} else if (vy > 5) {
// Взлёт — растягивание по y
const k = Math.min(1, vy / 15);
sy = this._baseScaleY * (1 + 0.18 * k);
sxz = 1 - 0.08 * k;
} else if (vy < -5) {
// Падение — лёгкое сжатие по y (минимальное, чтобы не дёргалось)
const k = Math.min(1, -vy / 15);
sy = this._baseScaleY * (1 - 0.10 * k);
sxz = 1 + 0.05 * k;
}
// Плавно интерполируем к целевому
const lerp = (a, b, t) => a + (b - a) * t;
const speed = 12;
m.scaling.y = lerp(m.scaling.y, sy, Math.min(1, dt * speed));
m.scaling.x = lerp(m.scaling.x, sxz, Math.min(1, dt * speed));
m.scaling.z = lerp(m.scaling.z, sxz, Math.min(1, dt * speed));
};
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) {}
if (this._cubeMesh) {
try {
this._cubeMesh.renderOutline = false;
this._cubeMesh.scaling.set(1, 1, 1);
} catch (e) {}
}
if (this._glow) {
try { this._glow.dispose(); } catch (e) {}
this._glow = null;
}
this._cubeMesh = null;
this._scene3d = null;
this.scene = null;
}
}