web client

This commit is contained in:
2025-07-04 23:42:27 +08:00
parent f0fd00d706
commit 027f582d4e
13 changed files with 19795 additions and 85 deletions

303
Client/web/proto.js Normal file
View File

@@ -0,0 +1,303 @@
// Proto.js - 游戏协议处理
// 基于 proto/action.proto 和 proto/define.proto 生成
// 协议定义(将由本地 .proto 文件动态填充枚举值)
const GameProto = {};
// 协议编码器
class ProtoEncoder {
constructor() {
this.protobuf = window.protobuf;
this.root = null;
this.readyPromise = this.initProtobuf();
}
async initProtobuf() {
try {
// 使用本地 .proto 文件,避免硬编码
const root = await this.protobuf.load([
"proto/define.proto",
"proto/action.proto"
]);
this.root = root;
// 缓存枚举,供其他模块使用
this.MessageID = this.root.lookupEnum('MessageID').values;
this.ActionID = this.root.lookupEnum('ActionID').values;
// 更新全局GameProto枚举避免硬编码
GameProto.MessageID = this.MessageID;
GameProto.ActionID = this.ActionID;
utils.info('Protobuf loaded from local files successfully');
} catch (error) {
utils.error('Failed to load protobuf files:', error);
}
}
// 编码消息
encodeMessage(messageId, payload) {
try {
if (!this.root) {
throw new Error('Protobuf not initialized');
}
const Message = this.root.lookupType('Message');
const message = {
ID: messageId,
Payload: payload
};
const buffer = Message.encode(message).finish();
return buffer;
} catch (error) {
utils.error('Failed to encode message:', error);
return null;
}
}
// 编码动作
encodeAction(actionId, payload) {
try {
if (!this.root) {
throw new Error('Protobuf not initialized');
}
const C2S_Action = this.root.lookupType('C2S_Action');
const action = {
Action: actionId,
Payload: payload
};
const buffer = C2S_Action.encode(action).finish();
return buffer;
} catch (error) {
utils.error('Failed to encode action:', error);
return null;
}
}
// 解码消息
decodeMessage(buffer) {
try {
if (!this.root) {
throw new Error('Protobuf not initialized');
}
const Message = this.root.lookupType('Message');
const message = Message.decode(buffer);
return message;
} catch (error) {
utils.error('Failed to decode message:', error);
return null;
}
}
// 解码动作
decodeAction(buffer) {
try {
if (!this.root) {
throw new Error('Protobuf not initialized');
}
const C2S_Action = this.root.lookupType('C2S_Action');
const action = C2S_Action.decode(buffer);
return action;
} catch (error) {
utils.error('Failed to decode action:', error);
return null;
}
}
}
// 协议工具类
class ProtoUtils {
constructor() {
this.encoder = new ProtoEncoder();
this.readyPromise = this.encoder.readyPromise;
}
// 创建移动指令
createMoveAction(x, y) {
const moveData = new Uint8Array([x, y]);
return this.encoder.encodeAction(this.encoder.ActionID.ACTION_ID_MOVE, moveData);
}
// 创建攻击指令
createAttackAction(targetId) {
const attackData = new Uint8Array([targetId]);
return this.encoder.encodeAction(this.encoder.ActionID.ACTION_ID_ATTACK, attackData);
}
// 创建消息
createMessage(messageId, payload) {
return this.encoder.encodeMessage(messageId, payload);
}
// 解析接收到的数据
parseReceivedData(data) {
try {
// 如果是字符串尝试解析为JSON
if (typeof data === 'string') {
const jsonData = JSON.parse(data);
return {
type: 'json',
data: jsonData
};
}
// 如果是二进制数据尝试解析为protobuf
if (data instanceof ArrayBuffer || data instanceof Uint8Array) {
const message = this.encoder.decodeMessage(data);
return {
type: 'protobuf',
data: message
};
}
return {
type: 'unknown',
data: data
};
} catch (error) {
utils.error('Failed to parse received data:', error);
return {
type: 'error',
data: null,
error: error.message
};
}
}
// 验证坐标数据
validateCoordinateData(data) {
if (!Array.isArray(data) || data.length !== 2) {
return false;
}
const [x, y] = data;
return utils.validateCoordinates(x, y);
}
// 格式化坐标数据
formatCoordinateData(data) {
if (this.validateCoordinateData(data)) {
const [x, y] = data;
return {
x: Math.round(x),
y: Math.round(y),
timestamp: Date.now()
};
}
return null;
}
// 创建进入实例消息外层Message
createEnterInstanceMessage(instanceId = 0) {
try {
if (!this.encoder.root) {
utils.warn('Protobuf root not ready');
return null;
}
const C2S_EnterInstance = this.encoder.root.lookupType('C2S_EnterInstance');
const enterInstance = {
InstanceID: instanceId
};
const innerBuffer = C2S_EnterInstance.encode(enterInstance).finish();
return this.encoder.encodeMessage(
this.encoder.MessageID.MESSAGE_ID_ENTER_INSTANCE,
innerBuffer
);
} catch (error) {
utils.error('Failed to create EnterInstance message:', error);
return null;
}
}
// 创建动作消息外层Message
createActionMessage(actionId, x, y) {
try {
if (!this.encoder.root) {
utils.warn('Protobuf root not ready');
return null;
}
// 创建移动指令的payload
let payload;
if (actionId === this.encoder.ActionID.ACTION_ID_MOVE) {
// 创建包含x,y坐标的二进制数据
// 注意这里使用Float64Array来存储双精度浮点数因为proto中X和Y是double类型
const buffer = new ArrayBuffer(16); // 8字节 * 2 = 16字节
const view = new Float64Array(buffer);
view[0] = x;
view[1] = y;
payload = new Uint8Array(buffer);
} else {
// 其他类型的动作可以在这里添加
utils.warn('Unsupported action ID:', actionId);
return null;
}
// 创建C2S_Action消息
const C2S_Action = this.encoder.root.lookupType('C2S_Action');
const action = {
Action: actionId,
Payload: payload
};
const actionBuffer = C2S_Action.encode(action).finish();
// 封装到外层Message
return this.encoder.encodeMessage(
this.encoder.MessageID.MESSAGE_ID_ACTION,
actionBuffer
);
} catch (error) {
utils.error('Failed to create Action message:', error);
return null;
}
}
// 创建WASD方向动作消息外层Message
createDirectionActionMessage(directionBits) {
try {
if (!this.encoder.root) {
utils.warn('Protobuf root not ready');
return null;
}
// 创建C2S_Action消息payload为空
const C2S_Action = this.encoder.root.lookupType('C2S_Action');
const action = {
Action: directionBits, // 使用位运算结果作为动作ID
Payload: new Uint8Array(0) // 空payload
};
const actionBuffer = C2S_Action.encode(action).finish();
// 封装到外层Message
return this.encoder.encodeMessage(
this.encoder.MessageID.MESSAGE_ID_ACTION,
actionBuffer
);
} catch (error) {
utils.error('Failed to create Direction Action message:', error);
return null;
}
}
}
// 创建全局协议工具实例
const protoUtils = new ProtoUtils();
// 导出到全局
window.GameProto = GameProto;
window.ProtoEncoder = ProtoEncoder;
window.ProtoUtils = ProtoUtils;
window.protoUtils = protoUtils;
// 兼容性处理
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
GameProto,
ProtoEncoder,
ProtoUtils,
protoUtils
};
}