const { createApp } = Vue; // WebSocket服务类 class WebSocketService { constructor() { this.connections = new Map(); this.reconnectAttempts = new Map(); this.maxReconnectAttempts = 5; this.reconnectDelay = 1000; } connect(squareId, onMessage, onStatusChange) { const wsUrl = `ws://localhost:8501/?token=${squareId}`; try { const ws = new WebSocket(wsUrl); ws.binaryType = 'arraybuffer'; ws.onopen = () => { console.log(`WebSocket ${squareId} connected`); onStatusChange(true); this.reconnectAttempts.set(squareId, 0); // 发送进入实例消息(protobuf) protoUtils.readyPromise.then(() => { const enterInstanceBuffer = protoUtils.createEnterInstanceMessage(1); if (enterInstanceBuffer) { this.sendMessage(ws, enterInstanceBuffer); } }); }; ws.onclose = (e) => { console.log(`WebSocket ${squareId} disconnected:`, e.code, e.reason); onStatusChange(false); this.connections.delete(squareId); // 自动重连 this.scheduleReconnect(squareId, onMessage, onStatusChange); }; ws.onerror = (error) => { console.error(`WebSocket ${squareId} error:`, error); }; ws.onmessage = (event) => { try { onMessage(event.data, squareId); } catch (error) { console.error('Error processing message:', error); } }; this.connections.set(squareId, ws); } catch (error) { console.error(`WebSocket ${squareId} init error:`, error); } } sendMessage(ws, data) { if (ws.readyState === WebSocket.OPEN) { try { if (data instanceof ArrayBuffer || data instanceof Uint8Array) { ws.send(data); } else { console.error('Attempted to send non-binary data through binary channel.'); } } catch (error) { console.error('Failed to send message:', error); } } } // 发送动作消息 sendAction(squareId, actionId, x, y) { const ws = this.connections.get(squareId); if (!ws || ws.readyState !== WebSocket.OPEN) { console.warn(`Cannot send action: WebSocket ${squareId} not connected`); return false; } protoUtils.readyPromise.then(() => { const actionBuffer = protoUtils.createActionMessage(actionId, x, y); if (actionBuffer) { this.sendMessage(ws, actionBuffer); console.log(`Action sent to square ${squareId}: actionId=${actionId}, x=${x}, y=${y}`); return true; } }); return false; } // 发送方向动作消息 sendDirectionAction(squareId, directionBits) { const ws = this.connections.get(squareId); if (!ws || ws.readyState !== WebSocket.OPEN) { console.warn(`Cannot send direction action: WebSocket ${squareId} not connected`); return false; } protoUtils.readyPromise.then(() => { const actionBuffer = protoUtils.createDirectionActionMessage(directionBits); if (actionBuffer) { this.sendMessage(ws, actionBuffer); console.log(`Direction action sent to square ${squareId}: directionBits=${directionBits}`); return true; } }); return false; } scheduleReconnect(squareId, onMessage, onStatusChange) { const attempts = this.reconnectAttempts.get(squareId) || 0; if (attempts < this.maxReconnectAttempts) { const delay = this.reconnectDelay * Math.pow(2, attempts); this.reconnectAttempts.set(squareId, attempts + 1); setTimeout(() => { console.log(`Attempting to reconnect WebSocket ${squareId} (attempt ${attempts + 1})`); this.connect(squareId, onMessage, onStatusChange); }, delay); } else { console.error(`Max reconnection attempts reached for WebSocket ${squareId}`); } } disconnect(squareId) { const ws = this.connections.get(squareId); if (ws) { ws.close(); this.connections.delete(squareId); } } disconnectAll() { this.connections.forEach((ws, squareId) => { this.disconnect(squareId); }); } } // 方块组件 const SquareComponent = { props: ['squareId', 'connected', 'dots'], template: `
`, methods: { // 转换Y坐标,使左下角为原点 transformY(y) { // 假设方块高度为400px(从GameConfig获取) const height = GameConfig.game.gridSize.height; // 将y坐标从"从下到上"转换为"从上到下" return height - y; } } }; // 主应用组件 const App = { components: { 'square-component': SquareComponent }, template: `
连接状态: {{ connectedCount }}/{{ squares.length }}
总圆点数: {{ totalDots }}
当前方块: {{ activeSquareId }}
方向键: {{ directionStateText }}
`, data() { return { squares: [ { connected: false, dots: [] }, { connected: false, dots: [] }, { connected: false, dots: [] }, { connected: false, dots: [] } ], wsService: null, showStatusBar: true, activeSquareId: null, directionState: { active: false, w: false, // 1 s: false, // 2 a: false, // 4 d: false // 8 }, directionKeyCodes: { 'KeyW': 'w', 'KeyS': 's', 'KeyA': 'a', 'KeyD': 'd', // 兼容箭头键 'ArrowUp': 'w', 'ArrowDown': 's', 'ArrowLeft': 'a', 'ArrowRight': 'd' } } }, computed: { connectedCount() { return this.squares.filter(square => square.connected).length; }, totalDots() { return this.squares.reduce((total, square) => total + square.dots.length, 0); }, // 计算方向状态文本 directionStateText() { const keys = []; if (this.directionState.w) keys.push('W'); if (this.directionState.s) keys.push('S'); if (this.directionState.a) keys.push('A'); if (this.directionState.d) keys.push('D'); return keys.join('+') || '无'; }, // 计算方向位 directionBits() { let bits = 0; if (this.directionState.w) bits |= 1; // W = 1 if (this.directionState.s) bits |= 2; // S = 2 if (this.directionState.a) bits |= 4; // A = 4 if (this.directionState.d) bits |= 8; // D = 8 return bits; } }, mounted() { this.wsService = new WebSocketService(); this.initializeConnections(); // 添加键盘事件监听 window.addEventListener('keydown', this.handleKeyDown); window.addEventListener('keyup', this.handleKeyUp); // 添加键盘快捷键 this.addKeyboardShortcuts(); }, beforeUnmount() { if (this.wsService) { this.wsService.disconnectAll(); } // 移除键盘事件监听 window.removeEventListener('keydown', this.handleKeyDown); window.removeEventListener('keyup', this.handleKeyUp); }, methods: { initializeConnections() { this.squares.forEach((square, index) => { const squareId = index + 1; this.wsService.connect( squareId, this.handleMessage, (connected) => this.updateConnectionStatus(index, connected) ); }); }, updateConnectionStatus(index, connected) { this.squares[index].connected = connected; }, handleMessage(rawData, squareId) { const squareIndex = squareId - 1; try { // 仅处理二进制 protobuf 消息 if (rawData instanceof ArrayBuffer || rawData instanceof Uint8Array) { const arrayBuf = rawData instanceof Uint8Array ? rawData : new Uint8Array(rawData); const outerMsg = protoUtils.encoder.decodeMessage(arrayBuf); if (!outerMsg) return; const msgId = outerMsg.ID; const msgEnum = protoUtils.encoder.MessageID; // 根据消息ID处理不同类型的消息 switch (msgId) { case msgEnum.MESSAGE_ID_POSITION: { // 处理位置更新消息 const S2C_Position = protoUtils.encoder.root.lookupType('S2C_Position'); const positionMsg = S2C_Position.decode(outerMsg.Payload); // 处理所有位置信息 if (positionMsg.Info && positionMsg.Info.length > 0) { positionMsg.Info.forEach(info => { // 注意:坐标已经是以左下角为原点,保持原样 if (utils.validateCoordinates(info.X, info.Y)) { // 查找是否已存在该UID的点 const existingDotIndex = this.squares[squareIndex].dots.findIndex(dot => dot.uid === info.UID); if (existingDotIndex !== -1) { // 已存在,更新位置 this.squares[squareIndex].dots[existingDotIndex].x = info.X; this.squares[squareIndex].dots[existingDotIndex].y = info.Y; this.squares[squareIndex].dots[existingDotIndex].timestamp = Date.now(); console.log(`Dot updated for square ${squareId}, UID ${info.UID}: (${info.X}, ${info.Y})`); } else { // 不存在,创建新点 this.squares[squareIndex].dots.push({ x: info.X, y: info.Y, timestamp: Date.now(), uid: info.UID, // 判断是否是玩家控制的点(假设UID为当前squareId的是玩家控制的) isPlayerControlled: info.UID === squareId }); // 限制每个方块的圆点数量,避免内存泄漏 if (this.squares[squareIndex].dots.length > GameConfig.game.maxDotsPerSquare) { this.squares[squareIndex].dots.shift(); } console.log(`New dot added to square ${squareId} at (${info.X}, ${info.Y}) for UID ${info.UID}`); } } }); } break; } case msgEnum.MESSAGE_ID_ENTER_INSTANCE: { // 处理进入实例响应消息 const S2C_EnterInstance = protoUtils.encoder.root.lookupType('S2C_EnterInstance'); const enterInstanceMsg = S2C_EnterInstance.decode(outerMsg.Payload); // 如果有位置信息,添加到对应方块 if (enterInstanceMsg.Info) { const info = enterInstanceMsg.Info; // 注意:坐标已经是以左下角为原点,保持原样 if (utils.validateCoordinates(info.X, info.Y)) { // 查找是否已存在该UID的点 const existingDotIndex = this.squares[squareIndex].dots.findIndex(dot => dot.uid === info.UID); if (existingDotIndex !== -1) { // 已存在,更新位置 this.squares[squareIndex].dots[existingDotIndex].x = info.X; this.squares[squareIndex].dots[existingDotIndex].y = info.Y; this.squares[squareIndex].dots[existingDotIndex].timestamp = Date.now(); this.squares[squareIndex].dots[existingDotIndex].isInitial = true; this.squares[squareIndex].dots[existingDotIndex].isPlayerControlled = info.UID === squareId; console.log(`Initial position updated for square ${squareId}, UID ${info.UID}: (${info.X}, ${info.Y})`); } else { // 不存在,创建新点 this.squares[squareIndex].dots.push({ x: info.X, y: info.Y, timestamp: Date.now(), uid: info.UID, isInitial: true, // 标记为初始位置 // 判断是否是玩家控制的点(假设UID为当前squareId的是玩家控制的) isPlayerControlled: info.UID === squareId }); console.log(`Initial position created for square ${squareId}: (${info.X}, ${info.Y}) UID: ${info.UID}`); } } } break; } default: console.warn('Unhandled message ID:', msgId); break; } return; // 已处理protobuf } } catch (error) { console.error('Error processing message:', error); } }, // 设置当前活动方块 setActiveSquare(squareId) { if (this.squares[squareId - 1].connected) { this.activeSquareId = squareId; this.directionState.active = true; console.log(`Active square set to ${squareId}`); } }, // 清除当前活动方块 clearActiveSquare() { if (this.activeSquareId) { // 在清除之前发送一个id=0的action消息 this.sendDirectionAction(this.activeSquareId, 0); // 然后清除状态 this.activeSquareId = null; this.directionState.active = false; // 重置所有方向键状态 this.directionState.w = false; this.directionState.s = false; this.directionState.a = false; this.directionState.d = false; } }, // 处理键盘按下事件 handleKeyDown(event) { if (!this.activeSquareId) return; const key = this.directionKeyCodes[event.code]; if (key && this.directionState.hasOwnProperty(key)) { // 只有当状态真正变化时才发送 if (!this.directionState[key]) { this.directionState[key] = true; // 计算新的方向位并发送 const directionBits = this.directionBits; this.sendDirectionAction(this.activeSquareId, directionBits); } event.preventDefault(); // 防止页面滚动 } }, // 处理键盘释放事件 handleKeyUp(event) { if (!this.activeSquareId) return; const key = this.directionKeyCodes[event.code]; if (key && this.directionState.hasOwnProperty(key)) { // 只有当状态真正变化时才发送 if (this.directionState[key]) { this.directionState[key] = false; // 计算新的方向位并发送 const directionBits = this.directionBits; this.sendDirectionAction(this.activeSquareId, directionBits); } event.preventDefault(); } }, addKeyboardShortcuts() { document.addEventListener('keydown', (e) => { switch (e.key) { case 'h': this.showStatusBar = !this.showStatusBar; break; case 'c': this.clearAllDots(); break; case 'r': this.reconnectAll(); break; } }); }, clearAllDots() { this.squares.forEach(square => { square.dots = []; }); console.log('All dots cleared'); }, reconnectAll() { this.wsService.disconnectAll(); setTimeout(() => { this.initializeConnections(); }, 1000); console.log('Reconnecting all WebSockets...'); }, // 发送动作消息到服务器 sendAction(squareId, actionId, x, y) { if (this.wsService) { return this.wsService.sendAction(squareId, actionId, x, y); } return false; }, // 发送方向动作消息到服务器 sendDirectionAction(squareId, directionBits) { if (this.wsService) { return this.wsService.sendDirectionAction(squareId, directionBits); } return false; } } }; createApp(App).mount('#app');