Розробка соціального простору в метавселенні
Соціальний простір в метавселенні — це віртуальне місце для взаємодії людей: конференції, тусовки, концерти, ділові зустрічі. На відміну від ігрових метавселенн, тут фокус на людській комунікації, а не на gameplay. Технічно це real-time multiplayer 3D середовище з voice/video комунікацією та інструментами для соціальної взаємодії.
Технічна архітектура
Серверна топологія
Для соціального простору важлива фізична близькість серверів до користувачів — високий latency у голосовій комунікації неприйнятний:
Global CDN (статичні ассети, 3D моделі)
│
Regional Game Servers (AWS eu-west-1, us-east-1, ap-southeast-1)
│
├── Room Manager (створення/знищення кімнат)
├── State Sync Service (позиції аватарів, об'єктів)
├── Voice Server (SFU: Selective Forwarding Unit)
└── Chat Service (text, reactions, file sharing)
Spatial Audio та proximity chat
У реальному просторі звук тихіший на відстані. Метавселенна повинна імітувати це — інакше у кімнаті з 50 людьми всі говорять одночасно і незрозуміло хто до кого звертається.
class SpatialAudioManager {
private audioContext: AudioContext;
private panners: Map<string, PannerNode> = new Map();
addParticipant(userId: string, stream: MediaStream) {
const source = this.audioContext.createMediaStreamSource(stream);
const panner = this.audioContext.createPanner();
// Web Audio API spatial settings
panner.panningModel = 'HRTF'; // Head-Related Transfer Function - біауральний звук
panner.distanceModel = 'inverse';
panner.refDistance = 3; // повна гучність до 3 метрів
panner.maxDistance = 20; // не чути далі 20 метрів
panner.rolloffFactor = 2;
source.connect(panner);
panner.connect(this.audioContext.destination);
this.panners.set(userId, panner);
}
updateParticipantPosition(userId: string, position: Vector3) {
const panner = this.panners.get(userId);
if (panner) {
panner.positionX.value = position.x;
panner.positionY.value = position.y;
panner.positionZ.value = position.z;
}
}
updateListenerPosition(position: Vector3, orientation: Quaternion) {
const listener = this.audioContext.listener;
listener.positionX.value = position.x;
listener.positionY.value = position.y;
listener.positionZ.value = position.z;
// Напрямок погляду слухача
const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(
new THREE.Quaternion(orientation.x, orientation.y, orientation.z, orientation.w)
);
listener.forwardX.value = forward.x;
listener.forwardY.value = forward.y;
listener.forwardZ.value = forward.z;
}
}
WebRTC SFU архітектура
Для масштабованого голосу з десятками учасників потрібен SFU (Selective Forwarding Unit). Кожен клієнт відправляє потік один раз на SFU, SFU пересилає потрібним отримувачам:
[Participant A] ──send──► [SFU Server] ──forward──► [Participant B]
[Participant B] ──send──► ──forward──► [Participant C]
[Participant C] ──send──► ──forward──► [Participant A]
Рекомендовані open-source SFU: mediasoup (Node.js, висока продуктивність), livekit (Go, cloud-native), janus (C, зрілий проект).
// LiveKit client integration
import { Room, RoomEvent, Track } from 'livekit-client';
class MetaverseVoiceClient {
private room: Room;
async connect(roomToken: string) {
this.room = new Room({
adaptiveStream: true, // авто-якість по мережі
dynacast: true, // відключає video-потоки поза viewport
});
this.room.on(RoomEvent.ParticipantConnected, (participant) => {
participant.on('trackSubscribed', (track) => {
if (track.kind === Track.Kind.Audio) {
this.spatialAudio.addParticipant(participant.identity, track.mediaStream!);
}
});
});
await this.room.connect('wss://your-livekit-server.com', roomToken);
// Публікуємо мікрофон
await this.room.localParticipant.enableMicrophone();
}
}
State Synchronization
Позиції аватарів повинні синхронізуватися у реальному часі для всіх учасників кімнати:
import asyncio
from dataclasses import dataclass
import msgpack # бінарна сериалізація, швидше за JSON
@dataclass
class AvatarState:
user_id: str
position: tuple # (x, y, z)
rotation: tuple # quaternion (x, y, z, w)
animation: str # 'idle', 'walk', 'wave', etc.
timestamp: float
class StateSync:
def __init__(self, room_id: str):
self.room_id = room_id
self.states: dict[str, AvatarState] = {}
self.clients: set = set()
async def update_position(self, user_id: str, state_data: dict):
self.states[user_id] = AvatarState(**state_data)
await self.broadcast_delta(user_id, state_data)
async def broadcast_delta(self, updated_user_id: str, delta: dict):
"""Відправляємо тільки зміну, не повний стан"""
message = msgpack.packb({
'type': 'position_update',
'user_id': updated_user_id,
'state': delta
})
# Відправляємо всім крім відправника
tasks = [
client.send(message)
for client_id, client in self.clients.items()
if client_id != updated_user_id
]
await asyncio.gather(*tasks, return_exceptions=True)
Інтерполяція на клієнті: через мережевих затримок позиції приходять дискретно. Клієнт інтерполює між отриманими станами для гладкості:
class AvatarInterpolator {
private stateBuffer: Array<{time: number, state: AvatarState}> = [];
private INTERPOLATION_DELAY_MS = 100; // буфер для сглажування
update(state: AvatarState) {
this.stateBuffer.push({time: Date.now(), state});
// Зберігаємо тільки останні 10 станів
if (this.stateBuffer.length > 10) this.stateBuffer.shift();
}
getInterpolatedState(): AvatarState | null {
const renderTime = Date.now() - this.INTERPOLATION_DELAY_MS;
const before = this.stateBuffer.filter(s => s.time <= renderTime).at(-1);
const after = this.stateBuffer.find(s => s.time > renderTime);
if (!before || !after) return before?.state || null;
const t = (renderTime - before.time) / (after.time - before.time);
return this.lerp(before.state, after.state, t);
}
}
Інструменти соціальної взаємодії
Емоції та жести: набір швидких команд (wave, clap, dance). Анімації відтворюються синхронно для всіх учасників.
Collaborative whiteboard: спільна дошка для малювання, підтримує CRDT (Conflict-free Replicated Data Type) для безконфліктного спільного редагування.
Presentation mode: один учасник трансліює екран/слайди всім у кімнаті. Реалізується через WebRTC screen sharing + SFU forward.
Spatial objects: розташування 3D об'єктів, NFT artwork, вбудованого відео/аудіо в просторі. Об'єкти persisted у базі та завантажуються при вході у кімнату.
Управління доступом
interface RoomConfig {
id: string;
name: string;
maxParticipants: number;
accessControl: {
type: 'public' | 'token_gated' | 'invite_only' | 'nft_holders';
nftContract?: string; // для token-gated
minNFTBalance?: number; // скільки NFT потрібно мати
inviteList?: string[]; // адреси гаманців
};
spatialAudio: boolean;
recordingEnabled: boolean;
}
async function checkRoomAccess(userWallet: string, roomConfig: RoomConfig): Promise<boolean> {
if (roomConfig.accessControl.type === 'public') return true;
if (roomConfig.accessControl.type === 'nft_holders') {
const balance = await nftContract.balanceOf(userWallet);
return balance >= (roomConfig.accessControl.minNFTBalance || 1);
}
if (roomConfig.accessControl.type === 'token_gated') {
const tokenBalance = await erc20Contract.balanceOf(userWallet);
return tokenBalance > 0;
}
return false;
}
Token-gated соціальні простори — це потужний інструмент для DAO community calls, holder-only events та VIP networking. NFT стає не просто колекційним предметом, а ключем доступу до екслюзивних соціальних просторів.
Продуктивність: WebGL оптимізації
Рендеринг 50+ аватарів у браузері вимагає серйозних оптимізацій:
- LOD (Level of Detail): далекі аватари рендерятся з меншою кількістю полігонів
- Instanced rendering: однакові об'єкти рендерятся одним GPU draw call
- Frustum culling: не рендеримо те, що поза полем зору
- Occlusion culling: не рендеримо те, що приховано за іншими об'єктами
- Asset streaming: завантажуємо 3D ресурси при наближенні до них
Соціальний простір в метавселенні — технічно найскладніший клас real-time веб-додатків. Правильна архітектура з першого дня економить місяці переробки при масштабуванні.







