第28节:网络同步与多人在线3D场景 - 详解
第28节:网络同步与多人在线3D场景

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,可能分享一下给大家。点击跳转到网站。
https://www.captainbed.cn/ccc

概述
现代Web应用的重要发展方向,涉及实时网络通信、状态同步、冲突检测等复杂技术。本节将深入探索WebSocket通信架构、权威服务器模式、预测与调和算法,构建稳定可靠的多人交互体验。就是多人在线3D场景
多人同步系统架构:
核心原理深度解析
网络同步模型
多人游戏常用的同步架构对比:
| 模型类型 | 架构特点 | 适用场景 | 延迟处理 |
|---|---|---|---|
| 权威服务器 | 服务器验证所有操控 | 竞技游戏、MMO | 客户端预测+服务器调和 |
| P2P对等 | 节点间直接通信 | 小规模联机 | 锁步同步、帧同步 |
| 混合模式 | 区域服务器+中继 | 大型开放世界 | 分区分层同步 |
同步策略选择
根据应用需求选择合适的同步粒度:
状态同步
- 全量状态定期同步
- 增量状态实时同步
- 关键事件立即同步
输入同步
- 只同步用户输入
- 服务器计算确定结果
- 客户端预测显示
完整代码实现
多人在线3D场景框架
{{ connectionText }}
Ping: {{ currentPing }}ms
在线玩家 ({{ playerCount }})
{{ player.name }}
{{ player.ping }}ms
连接设置
同步设置
网络统计
上行: {{ formatBytes(uploadRate) }}/s
下行: {{ formatBytes(downloadRate) }}/s
丢包率: {{ packetLoss }}%
抖动: {{ networkJitter }}ms
<script>
import { onMounted, onUnmounted, ref, reactive, computed } from 'vue';
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 网络连接管理器
class NetworkManager {
constructor() {
this.socket = null;
this.isConnected = false;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectInterval = 2000;
this.messageHandlers = new Map();
this.pendingMessages = new Map();
this.setupMessageHandlers();
}
// 连接到服务器
async connect(serverUrl) {
return new Promise((resolve, reject) => {
try {
this.socket = new WebSocket(serverUrl);
this.socket.onopen = () => {
this.isConnected = true;
this.reconnectAttempts = 0;
console.log('WebSocket连接已建立');
resolve();
};
this.socket.onmessage = (event) => {
this.handleMessage(JSON.parse(event.data));
};
this.socket.onclose = () => {
this.isConnected = false;
console.log('WebSocket连接已关闭');
this.handleDisconnection();
};
this.socket.onerror = (error) => {
console.error('WebSocket错误:', error);
reject(error);
};
} catch (error) {
reject(error);
}
});
}
// 处理断开连接
handleDisconnection() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
setTimeout(() => {
this.reconnectAttempts++;
this.connect(this.socket.url);
}, this.reconnectInterval);
}
}
// 发送消息
send(messageType, data, reliable = true) {
if (!this.isConnected) return false;
const message = {
type: messageType,
data: data,
timestamp: Date.now(),
sequence: this.generateSequenceId()
};
if (reliable) {
this.pendingMessages.set(message.sequence, message);
}
this.socket.send(JSON.stringify(message));
return true;
}
// 注册消息处理器
on(messageType, handler) {
if (!this.messageHandlers.has(messageType)) {
this.messageHandlers.set(messageType, []);
}
this.messageHandlers.get(messageType).push(handler);
}
// 处理接收到的消息
handleMessage(message) {
const handlers = this.messageHandlers.get(message.type) || [];
handlers.forEach(handler => handler(message.data));
// 确认可靠消息
if (message.ack) {
this.pendingMessages.delete(message.ack);
}
}
// 生成序列ID
generateSequenceId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// 断开连接
disconnect() {
if (this.socket) {
this.socket.close();
this.socket = null;
}
this.isConnected = false;
}
}
// 实体同步管理器
class EntitySyncManager {
constructor(networkManager, scene) {
this.networkManager = networkManager;
this.scene = scene;
this.entities = new Map();
this.localEntities = new Map();
this.predictionBuffer = new Map();
this.setupNetworkHandlers();
}
// 设置网络处理器
setupNetworkHandlers() {
this.networkManager.on('entityCreate', this.handleEntityCreate.bind(this));
this.networkManager.on('entityUpdate', this.handleEntityUpdate.bind(this));
this.networkManager.on('entityDestroy', this.handleEntityDestroy.bind(this));
this.networkManager.on('worldState', this.handleWorldState.bind(this));
}
// 处理实体创建
handleEntityCreate(entityData) {
const entity = this.createEntity(entityData);
this.entities.set(entityData.id, entity);
if (entityData.owner === this.networkManager.clientId) {
this.localEntities.set(entityData.id, entity);
}
}
// 处理实体更新
handleEntityUpdate(updateData) {
const entity = this.entities.get(updateData.id);
if (!entity) return;
// 如果是本地实体,进行预测调和
if (this.localEntities.has(updateData.id)) {
this.reconcileEntity(entity, updateData);
} else {
this.applyEntityUpdate(entity, updateData);
}
}
// 预测调和
reconcileEntity(entity, serverState) {
const predictedStates = this.predictionBuffer.get(entity.userData.id) || [];
// 找到对应的预测状态
const matchingStateIndex = predictedStates.findIndex(
state => state.sequence === serverState.sequence
);
if (matchingStateIndex !== -1) {
// 移除已确认的状态
predictedStates.splice(0, matchingStateIndex + 1);
// 如果有未确认的状态,重新应用
if (predictedStates.length > 0) {
this.reapplyPredictedStates(entity, predictedStates);
}
} else {
// 没有匹配的预测状态,强制同步到服务器状态
this.applyEntityUpdate(entity, serverState);
}
}
// 重新应用预测状态
reapplyPredictedStates(entity, predictedStates) {
predictedStates.forEach(state => {
this.applyEntityUpdate(entity, state, true);
});
}
// 应用实体更新
applyEntityUpdate(entity, updateData, isPrediction = false) {
if (updateData.position) {
if (isPrediction) {
entity.position.lerp(
new THREE.Vector3().fromArray(updateData.position),
0.3
);
} else {
entity.position.fromArray(updateData.position);
}
}
if (updateData.rotation) {
entity.rotation.fromArray(updateData.rotation);
}
if (updateData.animation) {
this.updateEntityAnimation(entity, updateData.animation);
}
// 保存预测状态
if (isPrediction && this.localEntities.has(entity.userData.id)) {
this.savePredictionState(entity, updateData.sequence);
}
}
// 创建实体
createEntity(entityData) {
let mesh;
switch (entityData.type) {
case 'player':
mesh = this.createPlayerEntity(entityData);
break;
case 'npc':
mesh = this.createNPCEntity(entityData);
break;
case 'item':
mesh = this.createItemEntity(entityData);
break;
default:
mesh = this.createDefaultEntity(entityData);
}
mesh.userData = {
id: entityData.id,
type: entityData.type,
owner: entityData.owner,
lastUpdate: Date.now()
};
this.scene.add(mesh);
return mesh;
}
// 创建玩家实体
createPlayerEntity(entityData) {
const geometry = new THREE.CapsuleGeometry(0.5, 1, 4, 8);
const material = new THREE.MeshStandardMaterial({
color: entityData.color || 0x00ff00,
roughness: 0.7,
metalness: 0.3
});
const mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
// 添加玩家标签
const nameLabel = this.createNameLabel(entityData.name);
mesh.add(nameLabel);
return mesh;
}
// 创建名称标签
createNameLabel(name) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = 256;
canvas.height = 64;
context.fillStyle = 'rgba(0, 0, 0, 0.7)';
context.fillRect(0, 0, canvas.width, canvas.height);
context.font = '24px Arial';
context.fillStyle = 'white';
context.textAlign = 'center';
context.fillText(name, canvas.width / 2, canvas.height / 2 + 8);
const texture = new THREE.CanvasTexture(canvas);
const material = new THREE.SpriteMaterial({ map: texture });
const sprite = new THREE.Sprite(material);
sprite.scale.set(2, 0.5, 1);
sprite.position.y = 2;
return sprite;
}
// 更新实体动画
updateEntityAnimation(entity, animationData) {
// 实现动画状态同步
if (entity.userData.animationMixer) {
entity.userData.animationMixer.update(animationData.deltaTime);
}
}
// 保存预测状态
savePredictionState(entity, sequence) {
if (!this.predictionBuffer.has(entity.userData.id)) {
this.predictionBuffer.set(entity.userData.id, []);
}
const buffer = this.predictionBuffer.get(entity.userData.id);
buffer.push({
sequence: sequence,
position: entity.position.toArray(),
rotation: entity.rotation.toArray(),
timestamp: Date.now()
});
// 限制缓冲区大小
if (buffer.length > 60) { // 保持1秒的预测数据
buffer.shift();
}
}
}
// 输入预测系统
class InputPredictionSystem {
constructor(networkManager, entitySyncManager) {
this.networkManager = networkManager;
this.entitySyncManager = entitySyncManager;
this.inputBuffer = [];
this.lastProcessedInput = 0;
this.setupInputHandlers();
}
// 设置输入处理器
setupInputHandlers() {
document.addEventListener('keydown', this.handleKeyDown.bind(this));
document.addEventListener('keyup', this.handleKeyUp.bind(this));
document.addEventListener('mousemove', this.handleMouseMove.bind(this));
}
// 处理按键按下
handleKeyDown(event) {
if (!this.shouldProcessInput(event)) return;
const input = {
type: 'keydown',
key: event.key,
code: event.code,
timestamp: Date.now(),
sequence: this.networkManager.generateSequenceId()
};
this.processInput(input);
}
// 处理按键释放
handleKeyUp(event) {
if (!this.shouldProcessInput(event)) return;
const input = {
type: 'keyup',
key: event.key,
code: event.code,
timestamp: Date.now(),
sequence: this.networkManager.generateSequenceId()
};
this.processInput(input);
}
// 处理鼠标移动
handleMouseMove(event) {
const input = {
type: 'mousemove',
movementX: event.movementX,
movementY: event.movementY,
timestamp: Date.now(),
sequence: this.networkManager.generateSequenceId()
};
this.processInput(input);
}
// 处理输入
processInput(input) {
// 本地预测
this.applyInputPrediction(input);
// 发送到服务器
this.networkManager.send('playerInput', input);
// 保存到缓冲区
this.inputBuffer.push(input);
// 限制缓冲区大小
if (this.inputBuffer.length > 120) { // 保持2秒的输入数据
this.inputBuffer.shift();
}
}
// 应用输入预测
applyInputPrediction(input) {
// 根据输入类型更新本地实体状态
const localEntities = Array.from(this.entitySyncManager.localEntities.values());
localEntities.forEach(entity => {
this.updateEntityFromInput(entity, input);
});
}
// 根据输入更新实体
updateEntityFromInput(entity, input) {
const speed = 0.1;
const rotationSpeed = 0.02;
switch (input.type) {
case 'keydown':
switch (input.code) {
case 'KeyW':
entity.position.z -= speed;
break;
case 'KeyS':
entity.position.z += speed;
break;
case 'KeyA':
entity.position.x -= speed;
break;
case 'KeyD':
entity.position.x += speed;
break;
}
break;
case 'mousemove':
entity.rotation.y -= input.movementX * rotationSpeed;
break;
}
// 保存预测状态
this.entitySyncManager.savePredictionState(entity, input.sequence);
}
// 检查是否应该处理输入
shouldProcessInput(event) {
// 忽略组合键和系统快捷键
if (event.ctrlKey || event.altKey || event.metaKey) return false;
// 忽略输入框中的输入
if (event.target.tagName === 'INPUT') return false;
return true;
}
}
export default {
name: 'MultiplayerScene',
setup() {
const renderCanvas = ref(null);
const serverAddress = ref('ws://localhost:8080');
const isConnected = ref(false);
const connectionStatus = ref('disconnected');
const currentPing = ref(0);
const playerCount = ref(0);
const connectedPlayers = ref([]);
const chatMessages = ref([]);
const chatInput = ref('');
const showLoading = ref(false);
const loadingMessage = ref('');
const enablePrediction = ref(true);
const enableInterpolation = ref(true);
const enableReconciliation = ref(true);
const uploadRate = ref(0);
const downloadRate = ref(0);
const packetLoss = ref(0);
const networkJitter = ref(0);
let scene, camera, renderer, controls;
let networkManager, entitySyncManager, inputPredictionSystem;
let localPlayerId = null;
let pingInterval = null;
// 初始化场景
const initScene = async () => {
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
scene.fog = new THREE.Fog(0x87CEEB, 10, 100);
// 创建相机
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 10, 10);
// 创建渲染器
renderer = new THREE.WebGLRenderer({
canvas: renderCanvas.value,
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// 添加控制器
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// 创建环境
createEnvironment();
// 初始化网络系统
initNetworkSystems();
// 启动渲染循环
animate();
};
// 创建环境
const createEnvironment = () => {
// 添加环境光
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
scene.add(ambientLight);
// 添加方向光
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(50, 50, 25);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.set(2048, 2048);
scene.add(directionalLight);
// 创建地面
const groundGeometry = new THREE.PlaneGeometry(100, 100);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x90EE90,
roughness: 0.8,
metalness: 0.2
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// 添加一些障碍物
addEnvironmentObjects();
};
// 添加环境物体
const addEnvironmentObjects = () => {
const obstacleGeometry = new THREE.BoxGeometry(2, 2, 2);
const obstacleMaterial = new THREE.MeshStandardMaterial({
color: 0x8B4513,
roughness: 0.7
});
for (let i = 0; i < 10; i++) {
const obstacle = new THREE.Mesh(obstacleGeometry, obstacleMaterial);
obstacle.position.set(
(Math.random() - 0.5) * 80,
1,
(Math.random() - 0.5) * 80
);
obstacle.castShadow = true;
scene.add(obstacle);
}
};
// 初始化网络系统
const initNetworkSystems = () => {
networkManager = new NetworkManager();
entitySyncManager = new EntitySyncManager(networkManager, scene);
inputPredictionSystem = new InputPredictionSystem(networkManager, entitySyncManager);
setupNetworkEventHandlers();
};
// 设置网络事件处理器
const setupNetworkEventHandlers = () => {
networkManager.on('connectionEstablished', (data) => {
console.log('连接已建立:', data);
isConnected.value = true;
connectionStatus.value = 'connected';
localPlayerId = data.clientId;
startPingMeasurement();
});
networkManager.on('playerJoined', (playerData) => {
console.log('玩家加入:', playerData);
addOrUpdatePlayer(playerData);
});
networkManager.on('playerLeft', (playerId) => {
console.log('玩家离开:', playerId);
removePlayer(playerId);
});
networkManager.on('chatMessage', (messageData) => {
addChatMessage(messageData);
});
networkManager.on('pingResponse', (data) => {
currentPing.value = Date.now() - data.sendTime;
});
};
// 连接到服务器
const connectToServer = async () => {
showLoading.value = true;
loadingMessage.value = '正在连接服务器...';
try {
await networkManager.connect(serverAddress.value);
loadingMessage.value = '连接成功,正在初始化...';
// 模拟加载过程
setTimeout(() => {
showLoading.value = false;
}, 2000);
} catch (error) {
console.error('连接失败:', error);
loadingMessage.value = `连接失败: ${error.message}`;
setTimeout(() => {
showLoading.value = false;
}, 3000);
}
};
// 断开连接
const disconnectFromServer = () => {
if (pingInterval) {
clearInterval(pingInterval);
pingInterval = null;
}
networkManager.disconnect();
isConnected.value = false;
connectionStatus.value = 'disconnected';
connectedPlayers.value = [];
playerCount.value = 0;
};
// 开始ping测量
const startPingMeasurement = () => {
pingInterval = setInterval(() => {
networkManager.send('ping', { sendTime: Date.now() });
}, 1000);
};
// 添加或更新玩家
const addOrUpdatePlayer = (playerData) => {
const existingIndex = connectedPlayers.value.findIndex(p => p.id === playerData.id);
if (existingIndex !== -1) {
connectedPlayers.value[existingIndex] = {
...connectedPlayers.value[existingIndex],
...playerData
};
} else {
connectedPlayers.value.push({
...playerData,
isLocal: playerData.id === localPlayerId
});
}
playerCount.value = connectedPlayers.value.length;
};
// 移除玩家
const removePlayer = (playerId) => {
connectedPlayers.value = connectedPlayers.value.filter(p => p.id !== playerId);
playerCount.value = connectedPlayers.value.length;
};
// 发送聊天消息
const sendChatMessage = () => {
if (!chatInput.value.trim() || !isConnected.value) return;
const messageData = {
content: chatInput.value,
sender: '本地玩家', // 实际应该从服务器获取玩家名称
timestamp: Date.now()
};
networkManager.send('chatMessage', messageData);
chatInput.value = '';
};
// 添加聊天消息
const addChatMessage = (messageData) => {
chatMessages.value.push({
...messageData,
id: Date.now().toString(),
type: messageData.sender === '本地玩家' ? 'local' : 'remote'
});
// 限制消息数量
if (chatMessages.value.length > 50) {
chatMessages.value.shift();
}
};
// 格式化时间
const formatTime = (timestamp) => {
return new Date(timestamp).toLocaleTimeString();
};
// 格式化字节大小
const formatBytes = (bytes) => {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
// 连接状态文本
const connectionText = computed(() => {
switch (connectionStatus.value) {
case 'connected': return '已连接';
case 'connecting': return '连接中...';
case 'disconnected': return '未连接';
default: return '未知状态';
}
});
// 动画循环
const animate = () => {
requestAnimationFrame(animate);
// 更新控制器
controls.update();
// 更新网络统计(模拟数据)
updateNetworkStats();
// 渲染场景
renderer.render(scene, camera);
};
// 更新网络统计
const updateNetworkStats = () => {
// 模拟网络统计数据
if (isConnected.value) {
uploadRate.value = Math.random() * 1024 * 10;
downloadRate.value = Math.random() * 1024 * 50;
packetLoss.value = Math.random() * 2;
networkJitter.value = Math.random() * 10;
}
};
onMounted(() => {
initScene();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
if (networkManager) {
networkManager.disconnect();
}
if (pingInterval) {
clearInterval(pingInterval);
}
window.removeEventListener('resize', handleResize);
});
const handleResize = () => {
if (!camera || !renderer) return;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
return {
renderCanvas,
serverAddress,
isConnected,
connectionStatus,
currentPing,
playerCount,
connectedPlayers,
chatMessages,
chatInput,
showLoading,
loadingMessage,
enablePrediction,
enableInterpolation,
enableReconciliation,
uploadRate,
downloadRate,
packetLoss,
networkJitter,
connectionText,
connectToServer,
disconnectFromServer,
sendChatMessage,
formatTime,
formatBytes
};
}
};
</script>

浙公网安备 33010602011771号