Procedurální vykreslování 3D bytů v prohlížeči: Next.js, Three.js a WebSocket multiplayer
V projektu Prikolnya je implementováno vykreslování pixelových 3D bytů pro multiplayerové arkádové hry bez externích modelů. Používá se Next.js 16 s React Three Fiber, procedurální generování nábytku z primitiv, Canvas textury pro obsah a Socket.io pro synchronizaci pozic. Scény s 300–800 objekty běží plynule na moderních zařízeních.
Architektura scén a šablony bytů
Byt se skládá z oblastí (místností) se společnými stěnami a dveřmi. Šablony jsou definovány daty bez pevného kódu:
type RoomDef = {
id: string;
name: string;
x: number; z: number;
width: number; depth: number;
isSecret?: boolean;
};
type WallDef = {
x1: number; z1: number;
x2: number; z2: number;
hasDoor?: boolean;
doorOffset?: number;
};
const LOFT: ApartmentTemplate = {
id: "loft",
rooms: [
{ id: "lounge", name: "Obývák", x: 0, z: 0, width: 12, depth: 8 },
{ id: "gaming", name: "Herní místnost", x: 12, z: 0, width: 10, depth: 8 },
{ id: "bar", name: "Bar", x: 0, z: 8, width: 10, depth: 6 },
{ id: "studio", name: "Studio", x: 10, z: 8, width: 12, depth: 6 },
],
walls: [
{ x1: 0, z1: 0, x2: 22, z2: 0 },
{ x1: 22, z1: 0, x2: 22, z2: 14 },
{ x1: 12, z1: 0, x2: 12, z2: 8, hasDoor: true },
],
};
Šablony se liší od garsonky (12x10) až po penthouse (24x22). Vykreslování je deklarativní: data se převádějí na Three.js objekty.
Osvětlení: vrstvy a přednastavení nálady
Scéna je osvětlena třemi vrstvami:
- Ambientní – základní, závisí na denní době (ráno: #ffd4a0, intenzita 1.1; noc: #1a2244, 0.4).
- Směrové – hlavní se stíny + vyplňovací.
- Bodová světla – jedno na místnost ve výšce 2.3 jednotek.
Přidáno je 8 mood přednastavení. Party: stropní #ff2288 (1.8), ambientní #110022 (0.2), akcent #00ffff (3.0). Úplná transformace bez změny geometrie.
Procedurální nábytek z primitiv
Nábytek je sestaven z boxGeometry a cylinderGeometry. Výhody: žádné načítání, okamžitá generace, jednoduchost AI popisů.
Příklad arkádového automatu:
function ArcadeModel({ color, accent }: { color: string; accent: string }) {
return (
<group>
<Block args={[0.7, 1.6, 0.7]} position={[0, 0.8, 0]} color={color} />
<Block args={[0.5, 0.35, 0.05]} position={[0, 1.2, 0.33]}
color="#0a0a2a" emissive={accent} emissiveIntensity={0.3} />
<Block args={[0.5, 0.05, 0.25]} position={[0, 0.85, 0.25]}
color="#1a1a2e" rotation={[-0.3, 0, 0]} />
<mesh position={[-0.1, 0.92, 0.25]}>
<cylinderGeometry args={[0.02, 0.02, 0.12]} />
<meshStandardMaterial color="#ff2e63" />
</mesh>
{[0.05, 0.15].map((x) => (
<mesh key={x} position={[x, 0.9, 0.2]}>
<cylinderGeometry args={[0.025, 0.025, 0.02]} />
<meshStandardMaterial color={accent} emissive={accent}
emissiveIntensity={0.5} />
</mesh>
))}
</group>
);
}
Scéna: 300–800 objektů. AI generuje JSON s primitivy pro brandové výkladní skříně.
Obsah na stěnách přes CanvasTexture
Plakáty jsou vykreslovány dynamicky:
- Canvas 256x170.
- Pozadí #1a1a2e, rámeček barvou autora, word wrap textu.
THREE.CanvasTexturesNearestFilter.meshStandardMaterialsemissiveIntensity: 0.05.
const canvas = document.createElement("canvas");
canvas.width = 256;
canvas.height = 170;
const ctx = canvas.getContext("2d")!;
ctx.fillStyle = "#1a1a2e";
ctx.strokeStyle = authorColor;
// word wrap, vykreslení
const texture = new THREE.CanvasTexture(canvas);
texture.minFilter = THREE.NearestFilter;
3 sloty na stěnu (0.25, 0.5, 0.75), výška 1.3 jednotek. Podpora obrázků, videa, audia.
Pixelové avatary a mazlíčci
Avatary – Canvas textura na billboardu. Vrstvy: tělo (5 tónů), oči, kalhoty (6), oblečení (10), vlasy (12), doplňky (10). Měřítko 16x24 → 128x192, NearestFilter.
Mazlíčci jsou interpolováni s lerp 0.05 (blízko) / 0.15 (daleko).
Multiplayer s interpolací
Socket.io vysílá pozice (~20 FPS):
useFrame(() => {
const now = Date.now();
if (now - lastRef.current.t < 50) return;
const dx = Math.abs(pos.x - lastRef.current.x);
const dz = Math.abs(pos.z - lastRef.current.z);
if (dx < 0.05 && dz < 0.05) return;
socketRef.current?.volatile.emit("position", { x: pos.x, z: pos.z });
});
Interpolace: position.x += (target.x - position.x) * 0.15 (15% za snímek).
volatile.emit zahazuje zastaralé pakety.
Počasí a efekty
Partikl systémy na BufferGeometry:
- Déšť: 600 částic,
lineSegments. - Sníh: 400,
pointsse snosem. - Hvězdy: 200, blikání po Y.
useFrame(() => {
for (let i = 0; i < count; i++) {
positions[i * 3 + 1] -= velocities[i];
positions[i * 3] += Math.sin(Date.now() * 0.0008 + i * 0.7) * 0.004;
if (positions[i * 3 + 1] < -0.5) {
positions[i * 3 + 1] = 7 + Math.random() * 2;
}
}
posAttr.needsUpdate = true;
});
Dveře a animace
Otevírání na 81° (π*0.45) podle dot product normály stěny:
const wallNormal = { x: -(z2 - z1), z: x2 - x1 };
const toPlayer = { x: playerX - doorX, z: playerZ - doorZ };
const dot = wallNormal.x * toPlayer.x + wallNormal.z * toPlayer.z;
const direction = dot > 0 ? 1 : -1;
currentAngle += (targetAngle * direction - currentAngle) * 0.1;
Optimalizace výkonu
| Řešení | Efekt |
|---------|--------|
| dynamic(() => import("./Apartment3D"), { ssr: false }) | Three.js lazy, ~150 KB gzip |
| antialias: false | -15-20% fillrate |
| dpr: Math.min(devicePixelRatio, 2) | Úspora 2-4x na HiDPI |
| NearestFilter | Bez trilineární filtrace |
| castShadow pouze nábytek | Stíny selektivně |
| receiveShadow pouze podlaha | Jeden receiver |
Bundle 3D: ~600 KB raw, tree-shaken Three.js.
Gamifikace a plány
- Ekonomika: mince za příspěvky, limit 100/den.
- Úrovně: 10, až 3500 XP.
- Rostoucí budovy: 1-6 pater podle obsahu.
- Eventy: 8/rok, x3 XP.
- Tajné místnosti: 10 podle kódů.
Dále: AI nábytek, trofeje, instancing/LOD.
Co je důležité
- Procedurální nábytek ze 4-15 primitiv eliminuje načítání glTF.
- Socket.io s
volatile.emita 15% lerp zajišťuje plynulý multiplayer. - CanvasTexture + NearestFilter pro pixelový styl obsahu a avatarů.
- Optimalizace snižují bundle na 150 KB gzip při 300-800 objektech.
- Deklarativní šablony umožňují 5 variant bytů bez kódu.
— Editorial Team
Zatím žádné komentáře.