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

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

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

在这里插入图片描述

概述

现代Web应用的重要发展方向,涉及实时网络通信、状态同步、冲突检测等复杂技术。本节将深入探索WebSocket通信架构、权威服务器模式、预测与调和算法,构建稳定可靠的多人交互体验。就是多人在线3D场景

多人同步系统架构:

多人在线系统
网络层
同步层
表现层
WebSocket连接
数据传输
连接管理
状态同步
预测算法
冲突解决
实体渲染
动画同步
特效管理
心跳检测
插值计算
视觉平滑

核心原理深度解析

网络同步模型

多人游戏常用的同步架构对比:

模型类型架构特点适用场景延迟处理
权威服务器服务器验证所有操控竞技游戏、MMO客户端预测+服务器调和
P2P对等节点间直接通信小规模联机锁步同步、帧同步
混合模式区域服务器+中继大型开放世界分区分层同步

同步策略选择

根据应用需求选择合适的同步粒度:

  1. 状态同步

    • 全量状态定期同步
    • 增量状态实时同步
    • 关键事件立即同步
  2. 输入同步

    • 只同步用户输入
    • 服务器计算确定结果
    • 客户端预测显示

完整代码实现

多人在线3D场景框架


<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>
posted @ 2025-11-06 20:30  gccbuaa  阅读(7)  评论(0)    收藏  举报