Розробка метавселенної
Метавселенна—перевантажений термін. Перед проектуванням виберіть конкретну модель: це постійний 3D світ із взаємодією гравців у реальному часі (як Decentraland, The Sandbox), чи соціальний шар над різними додатками, чи віртуальні офіси для enterprise? Кожна модель—різний технологічний стек.
Я опишу архітектуру web3-native постійного світу: багатокористувацьке 3D середовище з NFT власністю земель/активів, on-chain економікою та децентралізованим управлінням. Найбільш технічно складний і найцікавіший варіант.
Архітектурні шари метавселенної
1. Шар блокчейну: власність та економіка
Усе цінне існує в блокчейні:
- LAND NFT—ділянки віртуальної землі (ERC-721)
- Avatar NFT—персонажи з атрибутами
- Wearables NFT—одяг, предмети (ERC-1155 для fungible, ERC-721 для унікальних)
- Токен управління—участь в DAO управління
- In-world валюта—ERC-20 токен для економіки
Усе іншe—off-chain (контент земельних ділянок, 3D активи, історія взаємодій).
2. Шар контенту: що розташовується на LAND
Власник LAND розміщує на ділянці контент: 3D сцени (файли GLTF/GLB), скрипти інтерактивності, портали в інші світи. Контент зберігається децентралізовано—IPFS або Arweave.
3. Real-time шар: багатокористувацький рушій
Гравці бачать один одного та взаємодіють у реальному часі—це завдання real-time мережі, а не блокчейну. Ігрові сервери (або P2P peer relay) синхронізують позиції та стани аватарів.
4. Шар додатків: dApps та ігри на LAND
Власник землі розміщує додатки—міні-ігри, галереї NFT, віртуальні магазини, концертні майданчики. Кожен додаток може мати власну on-chain логіку.
Система LAND: NFT земля та координатна сітка
Координатна система
Класичний підхід: карта як 2D координатна сітка. Decentraland використовує (-150, -150) до (150, 150). The Sandbox—408×408.
contract LandRegistry is ERC721 {
int16 public constant MIN_X = -100;
int16 public constant MAX_X = 100;
int16 public constant MIN_Y = -100;
int16 public constant MAX_Y = 100;
// Token ID кодує координати: id = (x + 100) * 201 + (y + 100)
function coordinatesToId(int16 x, int16 y) public pure returns (uint256) {
require(x >= MIN_X && x <= MAX_X, "X out of range");
require(y >= MIN_Y && y <= MAX_Y, "Y out of range");
return uint256(uint16(x - MIN_X)) * 201 + uint256(uint16(y - MIN_Y));
}
function idToCoordinates(uint256 tokenId) public pure returns (int16 x, int16 y) {
y = int16(int256(tokenId % 201)) + MIN_Y;
x = int16(int256(tokenId / 201)) + MIN_X;
}
// Перевірка сусідства: потрібна для Estate (об'єднання суміжних ділянок)
function isAdjacent(uint256 tokenId1, uint256 tokenId2) public pure returns (bool) {
(int16 x1, int16 y1) = idToCoordinates(tokenId1);
(int16 x2, int16 y2) = idToCoordinates(tokenId2);
int16 dx = x1 - x2;
int16 dy = y1 - y2;
return (dx == 0 && (dy == 1 || dy == -1)) || (dy == 0 && (dx == 1 || dx == -1));
}
}
Estate: об'єднання суміжних LAND
Власник кількох суміжних ділянок може об'єднати їх в Estate для побудови більших сцен:
contract EstateRegistry is ERC721 {
struct Estate {
uint256[] landIds; // включені LAND токени
string name;
string ipfsHash; // метаданні
}
mapping(uint256 => Estate) public estates;
function createEstate(
uint256[] calldata landIds,
string calldata name
) external returns (uint256 estateId) {
require(landIds.length >= 2, "Need at least 2 parcels");
// Перевіримо власність та сусідство
for (uint256 i = 0; i < landIds.length; i++) {
require(landRegistry.ownerOf(landIds[i]) == msg.sender, "Not land owner");
}
require(_isConnectedGraph(landIds), "Parcels not adjacent");
// Перенесемо LAND до контракту estate (заблокувати)
for (uint256 i = 0; i < landIds.length; i++) {
landRegistry.transferFrom(msg.sender, address(this), landIds[i]);
}
estateId = ++nextEstateId;
estates[estateId] = Estate({ landIds: landIds, name: name, ipfsHash: "" });
_mint(msg.sender, estateId);
emit EstateCreated(estateId, msg.sender, landIds);
}
// BFS перевірка, що всі ділянки пов'язані в один граф
function _isConnectedGraph(uint256[] calldata ids) internal view returns (bool) {
if (ids.length == 1) return true;
bool[] memory visited = new bool[](ids.length);
uint256[] memory queue = new uint256[](ids.length);
uint256 qHead = 0;
uint256 qTail = 0;
visited[0] = true;
queue[qTail++] = ids[0];
while (qHead < qTail) {
uint256 current = queue[qHead++];
for (uint256 i = 0; i < ids.length; i++) {
if (!visited[i] && landRegistry.isAdjacent(current, ids[i])) {
visited[i] = true;
queue[qTail++] = ids[i];
}
}
}
for (uint256 i = 0; i < ids.length; i++) {
if (!visited[i]) return false;
}
return true;
}
}
Система контенту: що розміщується на LAND
Дескриптор сцени
Кожен LAND має сцену—набір 3D об'єктів, скриптів, порталів. Зберігається на IPFS:
// Дескриптор сцени (IPFS JSON)
interface SceneDescriptor {
version: '2.0';
landId: number;
owner: string; // ethereum адреса
title: string;
description: string;
// 3D контент
models: Array<{
src: string; // ipfs://Qm... посилання на GLTF/GLB
position: [number, number, number];
rotation: [number, number, number];
scale: [number, number, number];
}>;
// Скрипти інтерактивності
scripts: Array<{
src: string; // ipfs://Qm... модуль JavaScript
entryPoint: string; // ім'я експортованої функції
}>;
// Точки входу для аватарів
spawnPoints: Array<{
position: [number, number, number];
cameraTarget: [number, number, number];
}>;
// Посилання на суміжні землі / портали
portals: Array<{
position: [number, number, number];
targetLandId: number;
targetUrl?: string; // або зовнішня URL
}>;
}
Публікування сцени:
contract LandContent {
// IPFS хеш сцени для кожної LAND
mapping(uint256 => string) public sceneHash;
mapping(uint256 => uint256) public sceneVersion;
function publishScene(uint256 landId, string calldata ipfsHash) external {
require(landRegistry.ownerOf(landId) == msg.sender, "Not owner");
// Базова валідація: непустий хеш
require(bytes(ipfsHash).length == 46, "Invalid IPFS hash"); // Qm... = 46 символів
sceneHash[landId] = ipfsHash;
sceneVersion[landId]++;
emit ScenePublished(landId, msg.sender, ipfsHash, sceneVersion[landId]);
}
}
SDK сценарії сцени
Розробники пишуть скрипти для інтерактивності на LAND. Децентралізована версія Unity/Unreal сценаріїв:
// SDK сценарій сцени метавселенної (виконується в sandbox iframe/WebWorker)
import { engine, Transform, MeshRenderer, OnPointerDown } from '@metaverse/sdk';
// Інтерактивні двері
const door = engine.addEntity();
engine.addComponentOrReplace(door, Transform, {
position: { x: 0, y: 0, z: 5 },
rotation: { x: 0, y: 0, z: 0, w: 1 },
scale: { x: 1, y: 1, z: 1 },
});
let isOpen = false;
engine.addComponentOrReplace(door, OnPointerDown, {
callback: async () => {
isOpen = !isOpen;
// Анімуємо відкриття/закриття
const transform = engine.getComponent(door, Transform);
transform.rotation = isOpen
? Quaternion.fromEulerDegrees(0, 90, 0)
: Quaternion.fromEulerDegrees(0, 0, 0);
},
hoverText: isOpen ? 'Close door' : 'Open door',
});
// NFT гейт: тільки власники певного NFT можуть увійти
import { checkNFTOwnership } from '@metaverse/blockchain';
engine.addComponentOrReplace(nftGate, OnPointerDown, {
callback: async () => {
const userAddress = await engine.getUserAddress();
const hasNFT = await checkNFTOwnership(userAddress, NFT_CONTRACT, TOKEN_ID);
if (!hasNFT) {
engine.showNotification('You need the Golden Pass NFT to enter');
return;
}
engine.teleportPlayer({ x: INSIDE_X, y: 0, z: INSIDE_Z });
},
});
Sandbox сценарія—ізольований WebWorker або iframe без прямого доступу до DOM. API взаємодії з блокчейном надається через postMessage, а не прямий доступ до гаманця.
Real-time мережа: синхронізація аватарів
Архітектура: Area Servers
Світ розділений на регіони (кожен регіон = N×N LAND). Кожен регіон обслуговується одним ігровим сервером. Коли гравець переходить між регіонами—передача іншому серверу.
┌─────────────────────────────┐
│ Load Balancer / Router │
│ (за координатами гравця) │
└──────────┬──────────────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Area Server │ │ Area Server │ │ Area Server │
│ Region A │ │ Region B │ │ Region C │
│ (-100,-100) │ │ (0,0) │ │ (100,100) │
│ до (0,0) │ │ до (100,100)│ │ ... │
└──────────────┘ └──────────────┘ └──────────────┘
// Area Server: керування аватарами в регіоні
import { WebSocketServer } from 'ws';
import { World } from '@dimforge/rapier3d'; // фізичний рушій
class AreaServer {
private players = new Map<string, PlayerState>();
private physicsWorld = new World({ x: 0, y: -9.81, z: 0 });
handlePlayerJoin(playerId: string, ws: WebSocket, position: Vector3) {
const state: PlayerState = {
id: playerId,
position,
rotation: Quaternion.identity(),
animation: 'idle',
ws,
};
this.players.set(playerId, state);
// Відправляємо новому гравцю стани всіх в регіоні
const snapshot = this.getRegionSnapshot();
ws.send(JSON.stringify({ type: 'region_snapshot', players: snapshot }));
// Повідомляємо інших про нового гравця
this.broadcast({ type: 'player_joined', player: state }, playerId);
}
handleMovement(playerId: string, movement: MovementPacket) {
const player = this.players.get(playerId);
if (!player) return;
// Серверна валідація руху (anti-cheat)
if (!this.isValidMovement(player, movement)) {
// Коригуємо позицію
player.ws.send(JSON.stringify({
type: 'position_correction',
position: player.position,
}));
return;
}
player.position = movement.position;
player.rotation = movement.rotation;
player.animation = movement.animation;
// Broadcast всім в регіоні (delta compression)
this.broadcastMovement(playerId, movement);
}
// 20 оновлень/сек для плавного руху
private startTickLoop() {
setInterval(() => this.tick(), 50);
}
private tick() {
this.physicsWorld.step();
// Збираємо dirty states та надсилаємо батчем
const updates = this.getDirtyPlayerStates();
if (updates.length > 0) {
this.broadcast({ type: 'batch_update', updates });
}
}
}
Proximity-based Broadcasting
Не потрібно надсилати позицію гравця всім в регіоні—тільки тим, хто рядом. Interest Management:
const VISIBILITY_RADIUS = 100; // метри
function getVisiblePlayers(playerId: string): string[] {
const player = players.get(playerId)!;
return Array.from(players.values())
.filter(p => p.id !== playerId)
.filter(p => distance(p.position, player.position) < VISIBILITY_RADIUS)
.map(p => p.id);
}
Це зменшує пропускну здатність з O(N²) до O(N × K), де K = середня кількість видимих гравців.
In-world економіка
Контракт Marketplace
Власник LAND продає віртуальні товари всередину світу:
contract InWorldMarketplace {
struct Listing {
address seller;
address nftContract;
uint256 tokenId;
uint256 price; // в in-world валюті (ERC-20)
uint256 landId; // на якій LAND виставлений товар
bool active;
}
// Royalty для власника LAND: 2.5% від продажів на його землі
uint256 public constant LAND_ROYALTY = 250; // basis points
function buy(uint256 listingId) external {
Listing storage listing = listings[listingId];
require(listing.active, "Not active");
uint256 landRoyalty = listing.price * LAND_ROYALTY / 10_000;
uint256 sellerProceeds = listing.price - landRoyalty;
// Покупець платить in-world валютою
worldToken.transferFrom(msg.sender, listing.seller, sellerProceeds);
worldToken.transferFrom(msg.sender, landRegistry.ownerOf(listing.landId), landRoyalty);
// Передаємо NFT
IERC721(listing.nftContract).transferFrom(
address(this), msg.sender, listing.tokenId
);
listing.active = false;
emit Sale(listingId, msg.sender, listing.price);
}
}
Play-to-Earn механіки
In-world активності можуть генерувати in-world валюту:
- Відвідування подій на LAND (check-in reward)
- Виконання квестів, створених власниками LAND
- Участь у міні-іграх
Важливо: темп выпуску має бути контрольованим для запобігання інфляції. Рекомендація: тижневий ліміт видачі + механізм halving як у Bitcoin.
DAO та управління
// Управління через Compound-style голосування
contract MetaverseDAO is Governor, GovernorTimelockControl {
// Тримання LAND дає право голосу
function _getVotes(
address account,
uint256 blockNumber,
bytes memory
) internal view override returns (uint256) {
// 1 LAND = 1 голос + бонус за staking токена управління
uint256 landVotes = landRegistry.balanceOf(account); // на blockNumber
uint256 tokenVotes = govToken.getPastVotes(account, blockNumber);
return landVotes + tokenVotes;
}
}
Управління вирішує: розмір світу (нові LAND), параметри економіки, whitelist форматів контенту, оновлення контрактів.
Технологічний стек
3D рендеринг
Three.js + React Three Fiber—для web-native метавселенної. Хороша підтримка GLTF, PBR матеріали, оптимізація продуктивності через instancing та LOD.
Babylon.js—альтернатива, особливо добра з WebXR (VR/AR). Decentraland використовує Babylon.js.
Unity WebGL—найкращої якості графіка, але великий bundle (50–200 MB), повільне завантаження. Добре для desktop-орієнтованого продукту.
Повний стек
| Шар | Технологія |
|---|---|
| Blockchain | Polygon PoS або Arbitrum (низький газ для LAND транзакцій) |
| LAND/NFT | Solidity ERC-721 + ERC-1155, Foundry |
| Управління | OpenZeppelin Governor + TimelockController |
| Зберігання | IPFS (Pinata/Web3.Storage) + Arweave для постійного контенту |
| Real-time | Node.js + uWebSockets.js (висока продуктивність) |
| Фізика | Rapier3D (Rust/WASM, швидше за Cannon.js) |
| 3D Web | Three.js + React Three Fiber + Drei |
| Система аватарів | ReadyPlayerMe SDK або користувацькі VRM аватари |
| Стан | Redis для стану регіону, PostgreSQL для постійних даних |
| Індексування | The Graph (события LAND transfers, scene updates) |
Етапи розробки
| Фаза | Вміст | Сроки |
|---|---|---|
| Foundation | Контракти LAND, координатна система, базовий marketplace | 4–6 тиж |
| Content system | Дескриптор сцени, IPFS зберігання, публікування сцени | 3–4 тиж |
| 3D Client | Three.js світ, завантаження сцени, базова навігація | 6–8 тиж |
| Real-time | Area servers, синхронізація аватарів, proximity система | 6–8 тиж |
| Economy | In-world токен, marketplace, play-to-earn | 4–6 тиж |
| Scripting SDK | Sandbox сценаріїв сцени, NFT gate APIs | 4–6 тиж |
| Governance | Контракти DAO, UI голосування | 3–4 тиж |
| Audit | LAND, marketplace, economy контракти | 5–8 тиж |
| Alpha launch | Приватна альфа з обмеженою картою | 2–4 тиж |
Реалістичний сроки від нуля до public alpha: 12–18 місяців для команди 8–12 людей (2–3 blockchain, 2–3 3D/frontend, 2 backend, 1 PM, 1 designer). Один з найбільш scope-складних проектів у Web3.
Головний ризик не технічний, а продуктовий: без контенту на LAND та активної спільноти світ буде пустим. Паралельно з розробкою потрібна програма для ранніх власників LAND та контент-творців.







