初次提交
This commit is contained in:
519
game.js
Normal file
519
game.js
Normal file
@@ -0,0 +1,519 @@
|
||||
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 = `wss://www.hlsq.asia/ws/?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: `
|
||||
<div class="square">
|
||||
<div class="connection-dot" :class="{ connected: connected }"></div>
|
||||
<div class="loading-indicator" v-if="!connected">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
<div
|
||||
v-for="(dot, index) in dots"
|
||||
:key="dot.uid"
|
||||
class="game-dot"
|
||||
:class="{ 'player-controlled': dot.isPlayerControlled }"
|
||||
:style="{ left: dot.x + 'px', top: transformY(dot.y) + 'px' }"
|
||||
></div>
|
||||
</div>
|
||||
`,
|
||||
methods: {
|
||||
// 转换Y坐标,使左下角为原点
|
||||
transformY(y) {
|
||||
// 假设方块高度为400px(从GameConfig获取)
|
||||
const height = GameConfig.game.gridSize.height;
|
||||
// 将y坐标从"从下到上"转换为"从上到下"
|
||||
return height - y;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 主应用组件
|
||||
const App = {
|
||||
components: {
|
||||
'square-component': SquareComponent
|
||||
},
|
||||
template: `
|
||||
<div class="app-container">
|
||||
<div class="grid-container">
|
||||
<square-component
|
||||
v-for="(square, index) in squares"
|
||||
:key="index"
|
||||
:square-id="index + 1"
|
||||
:connected="square.connected"
|
||||
:dots="square.dots"
|
||||
@mouseenter="setActiveSquare(index + 1)"
|
||||
@mouseleave="clearActiveSquare()"
|
||||
></square-component>
|
||||
</div>
|
||||
<div class="status-bar" v-if="showStatusBar">
|
||||
<div class="status-item">
|
||||
连接状态: {{ connectedCount }}/{{ squares.length }}
|
||||
</div>
|
||||
<div class="status-item">
|
||||
总圆点数: {{ totalDots }}
|
||||
</div>
|
||||
<div class="status-item" v-if="activeSquareId">
|
||||
当前方块: {{ activeSquareId }}
|
||||
</div>
|
||||
<div class="status-item" v-if="directionState.active">
|
||||
方向键: {{ directionStateText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
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');
|
||||
Reference in New Issue
Block a user