Open-source web player for Rublox games, dual-licensed under AGPL-3.0 + Commercial. Highlights: - Babylon.js 7 + React 18 + Vite 5 stack - Self-contained engine (~46k lines): BlockManager, ModelManager, PlayerController, ScriptSandboxWorker, MultiplayerSync, 30+ GD gamemodes - Configurable backend via VITE_API_BASE and friends — works against staging (dev-api.rublox.pro) out of the box - Standalone mode (VITE_STANDALONE=true) loads a bundled sample game for first-run without any backend - Full docs: README, ARCHITECTURE, CONTRIBUTING, SECURITY, CHANGELOG - Lint + format scaffolding (ESLint + Prettier + EditorConfig) - Legal: LICENSE (AGPL-3.0), LICENSE-COMMERCIAL.md, CLA.md, COPYRIGHT.md - Issue templates: bug_report, feature_request, security_disclosure Removed before public release: - frontend_deploy.py (contained production SSH credentials) - ~27 admin endpoints (kept in private repo) - Hard-coded internal URLs and IPs - All previous git history (clean repo init)
92 lines
4.0 KiB
JavaScript
92 lines
4.0 KiB
JavaScript
/**
|
||
* LabelManager — billboard-метки (текст-плашки) над 3D-объектами.
|
||
*
|
||
* Используется для game.scene.setLabel(ref, text) — имена/HP над
|
||
* персонажами, врагами, предметами. Метка всегда повёрнута лицом к камере
|
||
* (billboardMode=7), всегда поверх геометрии (renderingGroupId=1).
|
||
*
|
||
* Метка привязывается к мешу объекта (parent) и висит над ним.
|
||
*/
|
||
import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture';
|
||
import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
|
||
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
|
||
import { Color3 } from '@babylonjs/core/Maths/math.color';
|
||
|
||
export class LabelManager {
|
||
constructor(scene) {
|
||
this.scene = scene;
|
||
// ref-строка объекта → { plane, tex, mat }
|
||
this.labels = new Map();
|
||
}
|
||
|
||
/**
|
||
* Установить/обновить метку над объектом.
|
||
* ref — ref-строка объекта (от scene.spawn / scene.find).
|
||
* anchorMesh — Babylon-меш объекта (метка крепится к нему).
|
||
* text — текст метки.
|
||
* opts — { color: '#fff', height: 2.5 (м над объектом), size: 1 }
|
||
*/
|
||
setLabel(ref, anchorMesh, text, opts = {}) {
|
||
if (!anchorMesh) return;
|
||
const color = opts.color || '#ffffff';
|
||
const heightAbove = Number.isFinite(opts.height) ? opts.height : 2.5;
|
||
const sizeMul = Number.isFinite(opts.size) ? opts.size : 1;
|
||
|
||
// Если метка уже есть — пересоздаём (текст/цвет могли измениться).
|
||
this.clearLabel(ref);
|
||
|
||
const W = 1024, H = 256;
|
||
const tex = new DynamicTexture(`lblTex_${ref}_${Date.now()}`,
|
||
{ width: W, height: H }, this.scene, true);
|
||
tex.updateSamplingMode?.(3); // TRILINEAR
|
||
tex.anisotropicFilteringLevel = 8;
|
||
const ctx = tex.getContext();
|
||
ctx.clearRect(0, 0, W, H);
|
||
ctx.font = 'bold 120px "Roboto Condensed", "Segoe UI", sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.lineWidth = 16;
|
||
ctx.lineJoin = 'round';
|
||
ctx.strokeStyle = '#000';
|
||
ctx.strokeText(String(text), W / 2, H / 2);
|
||
ctx.fillStyle = color;
|
||
ctx.fillText(String(text), W / 2, H / 2);
|
||
tex.update(true);
|
||
tex.hasAlpha = true;
|
||
|
||
const plane = MeshBuilder.CreatePlane(`lbl_${ref}`,
|
||
{ width: 2.2 * sizeMul, height: 0.55 * sizeMul }, this.scene);
|
||
const mat = new StandardMaterial(`lblMat_${ref}`, this.scene);
|
||
mat.diffuseTexture = tex;
|
||
mat.diffuseTexture.hasAlpha = true;
|
||
mat.emissiveColor = new Color3(1, 1, 1);
|
||
mat.disableLighting = true;
|
||
mat.backFaceCulling = false;
|
||
mat.disableDepthWrite = true;
|
||
plane.material = mat;
|
||
plane.billboardMode = 7; // всегда лицом к камере
|
||
plane.renderingGroupId = 1; // поверх геометрии
|
||
plane.isPickable = false;
|
||
// Крепим к объекту: метка висит над ним и двигается вместе с ним.
|
||
plane.parent = anchorMesh;
|
||
plane.position.set(0, heightAbove, 0);
|
||
|
||
this.labels.set(ref, { plane, tex, mat });
|
||
}
|
||
|
||
/** Убрать метку с объекта. */
|
||
clearLabel(ref) {
|
||
const rec = this.labels.get(ref);
|
||
if (!rec) return;
|
||
try { rec.plane.dispose(); } catch (e) { /* ignore */ }
|
||
try { rec.tex.dispose(); } catch (e) { /* ignore */ }
|
||
try { rec.mat.dispose(); } catch (e) { /* ignore */ }
|
||
this.labels.delete(ref);
|
||
}
|
||
|
||
/** Удалить все метки (при выходе из Play). */
|
||
clearAll() {
|
||
for (const ref of [...this.labels.keys()]) this.clearLabel(ref);
|
||
}
|
||
}
|