diff --git a/client/assets/scripts/App/AppStatus/AppStatusBoot.ts b/client/assets/scripts/App/AppStatus/AppStatusBoot.ts index 5b01f7d..96818b6 100644 --- a/client/assets/scripts/App/AppStatus/AppStatusBoot.ts +++ b/client/assets/scripts/App/AppStatus/AppStatusBoot.ts @@ -1,7 +1,10 @@ import { find } from "cc"; import { BaseState } from "../../Framework/FSM/BaseState"; +import { NetConfig, NetProtocolType } from "../../Framework/Net/NetConfig"; +import { NetEvent } from "../../Framework/Net/NetEvent"; import { NetManager } from "../../Framework/Net/NetManager"; import { UIMgr } from "../../Framework/UI/UIMgr"; +import { serviceProto } from "../../Shared/protocols/serviceProto"; /** * 应用启动状态 @@ -15,15 +18,15 @@ export class AppStatusBoot extends BaseState { constructor(fsm: any) { super(fsm, "Boot"); } - + /** * 进入启动状态 */ async onEnter(params?: any): Promise { super.onEnter(params); - + console.log("[AppStatusBoot] 开始初始化应用..."); - + try { // 初始化UI console.log("[AppStatusBoot] 初始化UI管理器..."); @@ -31,32 +34,71 @@ export class AppStatusBoot extends BaseState { // 1. 初始化并连接网络 await this.initAndConnectNet(); - + // 2. 初始化完成,切换到登录状态 console.log("[AppStatusBoot] 启动完成,切换到登录状态"); this._fsm.changeState("Login"); - + } catch (error) { console.error("[AppStatusBoot] 初始化失败:", error); } } - + /** * 初始化并连接网络 */ private async initAndConnectNet(): Promise { console.log("[AppStatusBoot] 初始化网络管理器..."); - - // TODO: 从配置文件读取服务器地址 - // import { serviceProto } from '../../Shared/protocols/serviceProto'; - // const netManager = NetManager.getInstance(); - // netManager.setServiceProto(serviceProto); - // netManager.init({ serverUrl: 'http://localhost:3000' }); - // await netManager.connect(); - - console.log("[AppStatusBoot] 网络连接完成(待配置)"); + + // 1. 获取网络管理器实例 + const netManager = NetManager.getInstance(); + + // 2. 设置服务协议 (必须在 init 之前调用) + netManager.setServiceProto(serviceProto); + + // 3. 监听网络事件 + netManager.on(NetEvent.Connected, () => { + console.log('✅ 网络已连接'); + this.onConnected(); + }); + + netManager.on(NetEvent.Disconnected, () => { + console.log('❌ 网络已断开'); + }); + + netManager.on(NetEvent.Reconnecting, (count: number) => { + console.log(`🔄 正在重连... (${count})`); + }); + + netManager.on(NetEvent.Error, (error: any) => { + console.error('⚠️ 网络错误:', error); + }); + + const config: NetConfig = { + serverUrl: 'http://localhost:3000', // TODO: 替换为实际服务器地址 + protocolType: NetProtocolType.WebSocket, + timeout: 30000, + autoReconnect: true, + reconnectInterval: 3000, + maxReconnectTimes: 5 + }; + + // 5. 初始化 + netManager.init(config); + // 6. 连接服务器 (HttpClient 创建即可用) + const success = await netManager.connect(); + + if (success) { + console.log('[AppStatusBoot]✅ 网络初始化成功'); + } else { + console.error('[AppStatusBoot]❌ 网络初始化失败'); + } } - + + private onConnected() { + + } + /** * 退出启动状态 */ diff --git a/client/assets/scripts/App/AppStatus/AppStatusGame.ts b/client/assets/scripts/App/AppStatus/AppStatusGame.ts index 7cc142a..5f5a898 100644 --- a/client/assets/scripts/App/AppStatus/AppStatusGame.ts +++ b/client/assets/scripts/App/AppStatus/AppStatusGame.ts @@ -1,8 +1,9 @@ +import { find } from "cc"; import { BaseState } from "../../Framework/FSM/BaseState"; import { UIMgr } from "../../Framework/UI/UIMgr"; +import { PlayerInfo } from "../../Shared/protocols/MsgResLogin"; import { UIGame } from "../Game/UIGame"; import { World } from "../Game/World"; -import { PlayerInfo } from "../../Shared/protocols/PtlLogin"; /** * 应用游戏状态 @@ -16,19 +17,19 @@ export class AppStatusGame extends BaseState { private _player: PlayerInfo = null; private _isNewPlayer: boolean = false; private _uiGame: UIGame = null; - + constructor(fsm: any) { super(fsm, "Game"); } - + /** * 进入游戏状态 */ async onEnter(params?: any): Promise { super.onEnter(params); - + console.log("[AppStatusGame] 进入游戏世界"); - + // 保存玩家信息 if (params) { this._player = params.player || null; @@ -36,82 +37,82 @@ export class AppStatusGame extends BaseState { console.log(`[AppStatusGame] 玩家信息:`, this._player); console.log(`[AppStatusGame] 是否新玩家: ${this._isNewPlayer}`); } - + try { // 1. 加载游戏场景 await this.loadGameScene(); - + // 2. 初始化游戏 await this.initGame(); - + // 3. 开始监听服务器广播 this.listenServerMessages(); - + // 4. 开始游戏 this.startGame(); - + } catch (error) { console.error("[AppStatusGame] 进入游戏失败:", error); // 返回登录 this._fsm.changeState("Login"); } } - + /** * 加载游戏场景 */ private async loadGameScene(): Promise { console.log("[AppStatusGame] 加载游戏场景..."); - + // 加载游戏UI this._uiGame = await UIMgr.getInstance().load(UIGame); - + console.log("[AppStatusGame] 游戏场景加载完成"); } - + /** * 初始化游戏 */ private async initGame(): Promise { console.log("[AppStatusGame] 初始化游戏..."); - + if (!this._uiGame) { throw new Error("UIGame 未加载"); } // 获取世界根节点 - const worldRoot = this._uiGame.getWorldRoot(); + const worldRoot = find("Game") if (!worldRoot) { throw new Error("世界根节点未找到"); } // 初始化世界,传入本地玩家信息 await World.getInstance().init(worldRoot, this._player); - + console.log("[AppStatusGame] 游戏初始化完成"); } - + /** * 监听服务器广播消息 */ private listenServerMessages(): void { console.log("[AppStatusGame] 开始监听服务器广播..."); - + // 网络消息监听已在 World 中注册 // World 会自动处理 MsgPlayerJoin 和 MsgPlayerMove - + console.log("[AppStatusGame] 服务器广播监听已设置"); } - + /** * 开始游戏 */ private startGame(): void { console.log("[AppStatusGame] 游戏开始!"); - + // 游戏已启动,玩家可以通过 WASD 控制角色移动 } - + /** * 更新游戏状态(每帧调用) */ @@ -122,7 +123,7 @@ export class AppStatusGame extends BaseState { // - 更新敌人AI // - 同步网络状态 } - + /** * 暂停游戏 */ @@ -132,7 +133,7 @@ export class AppStatusGame extends BaseState { // - 停止游戏更新 // - 显示暂停菜单 } - + /** * 恢复游戏 */ @@ -142,60 +143,60 @@ export class AppStatusGame extends BaseState { // - 继续游戏更新 // - 隐藏暂停菜单 } - + /** * 玩家死亡 */ onPlayerDeath(): void { console.log("[AppStatusGame] 玩家死亡"); - + // TODO: 处理玩家死亡 // - 显示死亡界面 // - 显示复活选项或返回登录 - + // 延迟后返回登录 setTimeout(() => { this._fsm.changeState("Login"); }, 3000); } - + /** * 退出游戏(返回登录) */ quitGame(): void { console.log("[AppStatusGame] 退出游戏"); - + // TODO: 断开连接或通知服务器 // const netManager = NetManager.getInstance(); // await netManager.disconnect(); - + // 返回登录 this._fsm.changeState("Login"); } - + /** * 延迟辅助函数 */ private delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } - + /** * 退出游戏状态 */ onExit(): void { super.onExit(); console.log("[AppStatusGame] 离开游戏状态"); - + // 清理世界 World.clear(); - + // 卸载游戏UI if (this._uiGame) { UIMgr.getInstance().unload(UIGame); this._uiGame = null; } - + this._player = null; } } diff --git a/client/assets/scripts/App/Game/World.ts b/client/assets/scripts/App/Game/World.ts index e37f3ef..503419c 100644 --- a/client/assets/scripts/App/Game/World.ts +++ b/client/assets/scripts/App/Game/World.ts @@ -1,9 +1,10 @@ -import { Node, Vec3, instantiate, Prefab } from 'cc'; +import { instantiate, Node, Prefab } from 'cc'; import { NetManager } from '../../Framework/Net/NetManager'; import { ResMgr } from '../../Framework/ResMgr/ResMgr'; import { MsgPlayerJoin } from '../../Shared/protocols/MsgPlayerJoin'; import { MsgPlayerMove } from '../../Shared/protocols/MsgPlayerMove'; -import { PlayerInfo } from '../../Shared/protocols/PtlLogin'; +import { PlayerInfo } from '../../Shared/protocols/MsgResLogin'; +import { CameraController } from './CameraController'; import { PlayerController } from './PlayerController'; import { RemotePlayer } from './RemotePlayer'; @@ -33,7 +34,10 @@ export class World { /** 玩家模型预制体 */ private playerPrefab: Prefab = null; - private constructor() {} + /** 摄像机控制器 */ + private cameraController: CameraController = null; + + private constructor() { } public static getInstance(): World { if (!World.instance) { @@ -68,7 +72,7 @@ export class World { */ private async loadPlayerPrefab(): Promise { try { - this.playerPrefab = await ResMgr.getInstance().load('resources', 'res://Actor/M1/M1', Prefab); + this.playerPrefab = await ResMgr.getInstance().load('res', 'Actor/M1/M1', Prefab); console.log('[World] 玩家模型预制体加载成功'); } catch (error) { console.error('[World] 加载玩家模型预制体失败:', error); @@ -113,6 +117,10 @@ export class World { this.localPlayerController = this.localPlayerNode.addComponent(PlayerController); this.localPlayerController.init(this.localPlayer); + // 创建并绑定摄像机控制器(只有本地玩家需要) + this.cameraController = this.worldRoot.addComponent(CameraController) as CameraController; + this.cameraController.setTarget(this.localPlayerNode); + console.log('[World] 本地玩家创建完成:', this.localPlayer.name); } @@ -198,6 +206,12 @@ export class World { } this.localPlayerController = null; + // 销毁摄像机控制器 + if (this.cameraController) { + this.cameraController.node.removeComponent(CameraController); + this.cameraController = null; + } + // 销毁所有远程玩家 this.remotePlayers.forEach((remotePlayer) => { remotePlayer.destroy(); diff --git a/client/assets/scripts/App/Msg.meta b/client/assets/scripts/App/Msg.meta new file mode 100644 index 0000000..16cb3a6 --- /dev/null +++ b/client/assets/scripts/App/Msg.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "9f8d42c2-cee9-4156-b0ee-992cb5d66317", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/App/Msg/MessagePairBase.ts b/client/assets/scripts/App/Msg/MessagePairBase.ts new file mode 100644 index 0000000..0ef900b --- /dev/null +++ b/client/assets/scripts/App/Msg/MessagePairBase.ts @@ -0,0 +1,76 @@ +/** + * 消息对定义基类 + * 用于定义成对出现的请求和响应消息 + */ +export abstract class MessagePair { + /** 请求消息名称 */ + abstract readonly requestName: string; + + /** 响应消息名称 */ + abstract readonly responseName: string; + + /** 请求消息类型检查 */ + abstract isValidRequest(msg: any): msg is TReq; + + /** 响应消息类型检查 */ + abstract isValidResponse(msg: any): msg is TRes; +} + +/** + * 消息对注册表 + * 用于管理所有的消息对 + */ +export class MessagePairRegistry { + private static _instance: MessagePairRegistry; + private _pairs: Map> = new Map(); + + static getInstance(): MessagePairRegistry { + if (!this._instance) { + this._instance = new MessagePairRegistry(); + } + return this._instance; + } + + /** + * 注册消息对 + * @param pair 消息对实例 + */ + register(pair: MessagePair): void { + this._pairs.set(pair.requestName, pair); + console.log(`[MessagePairRegistry] Registered message pair: ${pair.requestName} -> ${pair.responseName}`); + } + + /** + * 根据请求消息名获取响应消息名 + * @param requestName 请求消息名 + * @returns 响应消息名,如果未找到返回null + */ + getResponseName(requestName: string): string | null { + const pair = this._pairs.get(requestName); + return pair ? pair.responseName : null; + } + + /** + * 根据请求消息名获取消息对 + * @param requestName 请求消息名 + * @returns 消息对实例,如果未找到返回null + */ + getPair(requestName: string): MessagePair | null { + return this._pairs.get(requestName) || null; + } + + /** + * 检查是否为已注册的请求消息 + * @param msgName 消息名 + */ + isRegisteredRequest(msgName: string): boolean { + return this._pairs.has(msgName); + } + + /** + * 获取所有已注册的消息对 + */ + getAllPairs(): MessagePair[] { + return Array.from(this._pairs.values()); + } +} \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/PtlLogin.ts.meta b/client/assets/scripts/App/Msg/MessagePairBase.ts.meta similarity index 70% rename from client/assets/scripts/Shared/protocols/PtlLogin.ts.meta rename to client/assets/scripts/App/Msg/MessagePairBase.ts.meta index 8bd8f29..f0dbaa4 100644 --- a/client/assets/scripts/Shared/protocols/PtlLogin.ts.meta +++ b/client/assets/scripts/App/Msg/MessagePairBase.ts.meta @@ -2,7 +2,7 @@ "ver": "4.0.24", "importer": "typescript", "imported": true, - "uuid": "db7f0511-7e71-4088-9822-186f68082db6", + "uuid": "3ba46d91-073d-44d6-8157-05f1af758121", "files": [], "subMetas": {}, "userData": {} diff --git a/client/assets/scripts/App/Msg/MessagePairInit.ts b/client/assets/scripts/App/Msg/MessagePairInit.ts new file mode 100644 index 0000000..e610a89 --- /dev/null +++ b/client/assets/scripts/App/Msg/MessagePairInit.ts @@ -0,0 +1,23 @@ +import { MessagePairRegistry } from './MessagePairBase'; +import { LoginMessagePair } from './Pair/LoginMessagePair'; +import { MoveMessagePair } from './Pair/MoveMessagePair'; +import { SendMessagePair } from './Pair/SendMessagePair'; + +/** + * 消息对初始化 + * 在应用启动时调用此函数注册所有消息对 + */ +export function initMessagePairs(): void { + const registry = MessagePairRegistry.getInstance(); + + // 注册登录消息对 + registry.register(new LoginMessagePair()); + + // 注册移动消息对 + registry.register(new MoveMessagePair()); + + // 注册发送消息对 + registry.register(new SendMessagePair()); + + console.log('[MessagePairs] All message pairs registered successfully'); +} \ No newline at end of file diff --git a/client/assets/scripts/Framework/Net/LoginProtocol.ts.meta b/client/assets/scripts/App/Msg/MessagePairInit.ts.meta similarity index 70% rename from client/assets/scripts/Framework/Net/LoginProtocol.ts.meta rename to client/assets/scripts/App/Msg/MessagePairInit.ts.meta index fdebbf8..122f681 100644 --- a/client/assets/scripts/Framework/Net/LoginProtocol.ts.meta +++ b/client/assets/scripts/App/Msg/MessagePairInit.ts.meta @@ -2,7 +2,7 @@ "ver": "4.0.24", "importer": "typescript", "imported": true, - "uuid": "b5331a2b-20fa-4653-8013-deb54bad8d2e", + "uuid": "9b552413-09a9-4ec6-b389-ea371b734a41", "files": [], "subMetas": {}, "userData": {} diff --git a/client/assets/scripts/App/Msg/MsgExample.ts b/client/assets/scripts/App/Msg/MsgExample.ts new file mode 100644 index 0000000..38f6bc4 --- /dev/null +++ b/client/assets/scripts/App/Msg/MsgExample.ts @@ -0,0 +1,84 @@ +import { initMessagePairs } from './MessagePairInit'; +import { MsgManager } from './MsgManager'; + +/** + * 消息系统使用示例 + */ +export class MsgExample { + + /** + * 初始化消息系统 + * 应在游戏启动时调用 + */ + static init(): void { + // 初始化消息对注册表 + initMessagePairs(); + + console.log('[MsgExample] Message system initialized'); + } + + /** + * 登录示例 + */ + static async loginExample(): Promise { + const msgMgr = MsgManager.getInstance(); + + try { + const response = await msgMgr.login({ + playerId: 'player123', + playerName: 'TestPlayer' + }); + + if (response && response.success) { + console.log('登录成功:', response.player); + } else { + console.log('登录失败:', response?.message); + } + } catch (error) { + console.error('登录请求异常:', error); + } + } + + /** + * 移动示例 + */ + static async moveExample(): Promise { + const msgMgr = MsgManager.getInstance(); + + try { + const response = await msgMgr.move({ + x: 100, + y: 200 + }); + + if (response && response.success) { + console.log('移动成功:', response.position); + } else { + console.log('移动失败:', response?.message); + } + } catch (error) { + console.error('移动请求异常:', error); + } + } + + /** + * 发送消息示例 + */ + static async sendMessageExample(): Promise { + const msgMgr = MsgManager.getInstance(); + + try { + const response = await msgMgr.send({ + content: 'Hello, World!' + }); + + if (response) { + console.log('消息发送成功:', response.time); + } else { + console.log('消息发送失败'); + } + } catch (error) { + console.error('发送消息异常:', error); + } + } +} \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/PtlMove.ts.meta b/client/assets/scripts/App/Msg/MsgExample.ts.meta similarity index 70% rename from client/assets/scripts/Shared/protocols/PtlMove.ts.meta rename to client/assets/scripts/App/Msg/MsgExample.ts.meta index e30337e..0fb57fe 100644 --- a/client/assets/scripts/Shared/protocols/PtlMove.ts.meta +++ b/client/assets/scripts/App/Msg/MsgExample.ts.meta @@ -2,7 +2,7 @@ "ver": "4.0.24", "importer": "typescript", "imported": true, - "uuid": "9fdf30ed-1223-4112-81f6-75339229fd1b", + "uuid": "4c559445-88e3-4fe2-a87c-8c9ef390aa60", "files": [], "subMetas": {}, "userData": {} diff --git a/client/assets/scripts/App/Msg/MsgManager.ts b/client/assets/scripts/App/Msg/MsgManager.ts new file mode 100644 index 0000000..979e08e --- /dev/null +++ b/client/assets/scripts/App/Msg/MsgManager.ts @@ -0,0 +1,55 @@ +import { NetManager } from '../../Framework/Net/NetManager'; +import { MsgReqLogin } from '../../Shared/protocols/MsgReqLogin'; +import { MsgReqMove } from '../../Shared/protocols/MsgReqMove'; +import { MsgReqSend } from '../../Shared/protocols/MsgReqSend'; +import { MsgResLogin } from '../../Shared/protocols/MsgResLogin'; +import { MsgResMove } from '../../Shared/protocols/MsgResMove'; +import { MsgResSend } from '../../Shared/protocols/MsgResSend'; +import { LoginMessagePair } from './Pair/LoginMessagePair'; +import { MoveMessagePair } from './Pair/MoveMessagePair'; +import { SendMessagePair } from './Pair/SendMessagePair'; + +/** + * 消息管理器 - 提供类型安全的消息发送方法 + */ +export class MsgManager { + private static _instance: MsgManager; + + static getInstance(): MsgManager { + if (!this._instance) { + this._instance = new MsgManager(); + } + return this._instance; + } + + private get netManager(): NetManager { + return NetManager.getInstance(); + } + + /** + * 发送登录请求 + * @param loginData 登录数据 + * @returns 登录响应 + */ + async login(loginData: MsgReqLogin): Promise { + return this.netManager.callMsg(new LoginMessagePair(), loginData); + } + + /** + * 发送移动请求 + * @param moveData 移动数据 + * @returns 移动响应 + */ + async move(moveData: MsgReqMove): Promise { + return this.netManager.callMsg(new MoveMessagePair(), moveData); + } + + /** + * 发送消息请求 + * @param sendData 发送数据 + * @returns 发送响应 + */ + async send(sendData: MsgReqSend): Promise { + return this.netManager.callMsg(new SendMessagePair(), sendData); + } +} \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/PtlSend.ts.meta b/client/assets/scripts/App/Msg/MsgManager.ts.meta similarity index 70% rename from client/assets/scripts/Shared/protocols/PtlSend.ts.meta rename to client/assets/scripts/App/Msg/MsgManager.ts.meta index ce2e4d2..cf59db4 100644 --- a/client/assets/scripts/Shared/protocols/PtlSend.ts.meta +++ b/client/assets/scripts/App/Msg/MsgManager.ts.meta @@ -2,7 +2,7 @@ "ver": "4.0.24", "importer": "typescript", "imported": true, - "uuid": "4414c606-7995-4f83-b83c-30c9fe840a6b", + "uuid": "871b5daf-492f-4ff7-adac-a13bbf6b82ce", "files": [], "subMetas": {}, "userData": {} diff --git a/client/assets/scripts/App/Msg/Pair.meta b/client/assets/scripts/App/Msg/Pair.meta new file mode 100644 index 0000000..f78c8f8 --- /dev/null +++ b/client/assets/scripts/App/Msg/Pair.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "6f3ea388-fd67-45a4-9bfc-cae858378b7a", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/App/Msg/Pair/LoginMessagePair.ts b/client/assets/scripts/App/Msg/Pair/LoginMessagePair.ts new file mode 100644 index 0000000..fcd4e07 --- /dev/null +++ b/client/assets/scripts/App/Msg/Pair/LoginMessagePair.ts @@ -0,0 +1,21 @@ +import { MsgReqLogin } from '../../../Shared/protocols/MsgReqLogin'; +import { MsgResLogin } from '../../../Shared/protocols/MsgResLogin'; +import { MessagePair } from '../MessagePairBase'; + +/** + * 登录消息对实现 + */ +export class LoginMessagePair extends MessagePair { + readonly requestName = 'ReqLogin'; + readonly responseName = 'ResLogin'; + + isValidRequest(msg: any): msg is MsgReqLogin { + return msg && typeof msg.playerId === 'string'; + } + + isValidResponse(msg: any): msg is MsgResLogin { + return msg && + typeof msg.success === 'boolean' && + typeof msg.message === 'string'; + } +} \ No newline at end of file diff --git a/client/assets/scripts/App/Msg/Pair/LoginMessagePair.ts.meta b/client/assets/scripts/App/Msg/Pair/LoginMessagePair.ts.meta new file mode 100644 index 0000000..6bd7d04 --- /dev/null +++ b/client/assets/scripts/App/Msg/Pair/LoginMessagePair.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "8a0f8893-e078-4885-8407-70944f67d185", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/App/Msg/Pair/MoveMessagePair.ts b/client/assets/scripts/App/Msg/Pair/MoveMessagePair.ts new file mode 100644 index 0000000..b7140a6 --- /dev/null +++ b/client/assets/scripts/App/Msg/Pair/MoveMessagePair.ts @@ -0,0 +1,23 @@ +import { MsgReqMove } from '../../../Shared/protocols/MsgReqMove'; +import { MsgResMove } from '../../../Shared/protocols/MsgResMove'; +import { MessagePair } from '../MessagePairBase'; + +/** + * 移动消息对实现 + */ +export class MoveMessagePair extends MessagePair { + readonly requestName = 'ReqMove'; + readonly responseName = 'ResMove'; + + isValidRequest(msg: any): msg is MsgReqMove { + return msg && + typeof msg.x === 'number' && + typeof msg.y === 'number'; + } + + isValidResponse(msg: any): msg is MsgResMove { + return msg && + typeof msg.success === 'boolean' && + typeof msg.message === 'string'; + } +} \ No newline at end of file diff --git a/client/assets/scripts/App/Msg/Pair/MoveMessagePair.ts.meta b/client/assets/scripts/App/Msg/Pair/MoveMessagePair.ts.meta new file mode 100644 index 0000000..18884d7 --- /dev/null +++ b/client/assets/scripts/App/Msg/Pair/MoveMessagePair.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "f95aa765-9371-4644-9694-8eb8dd7d8930", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/App/Msg/Pair/SendMessagePair.ts b/client/assets/scripts/App/Msg/Pair/SendMessagePair.ts new file mode 100644 index 0000000..a7f1ffb --- /dev/null +++ b/client/assets/scripts/App/Msg/Pair/SendMessagePair.ts @@ -0,0 +1,19 @@ +import { MsgReqSend } from '../../../Shared/protocols/MsgReqSend'; +import { MsgResSend } from '../../../Shared/protocols/MsgResSend'; +import { MessagePair } from '../MessagePairBase'; + +/** + * 发送消息对实现 + */ +export class SendMessagePair extends MessagePair { + readonly requestName = 'ReqSend'; + readonly responseName = 'ResSend'; + + isValidRequest(msg: any): msg is MsgReqSend { + return msg && typeof msg.content === 'string'; + } + + isValidResponse(msg: any): msg is MsgResSend { + return msg && msg.time instanceof Date; + } +} \ No newline at end of file diff --git a/client/assets/scripts/App/Msg/Pair/SendMessagePair.ts.meta b/client/assets/scripts/App/Msg/Pair/SendMessagePair.ts.meta new file mode 100644 index 0000000..d7e369e --- /dev/null +++ b/client/assets/scripts/App/Msg/Pair/SendMessagePair.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "d9ef0dad-c949-4a46-9306-d4a060a40a3b", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/Net/LoginProtocol.ts b/client/assets/scripts/Framework/Net/LoginProtocol.ts deleted file mode 100644 index 1ac98fb..0000000 --- a/client/assets/scripts/Framework/Net/LoginProtocol.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * 登录协议类型定义 - * - * 注意: 这是临时定义,实际项目中应该通过 npm run sync-shared - * 从服务端同步完整的协议定义到 Shared 目录 - */ - -/** - * 登录请求 - */ -export interface ReqLogin { - /** 账号 */ - account: string; - /** 密码 (可选) */ - password?: string; -} - -/** - * 登录响应 - */ -export interface ResLogin { - /** 是否成功 */ - success: boolean; - /** 消息 */ - message?: string; - /** 玩家信息 */ - player?: { - /** 玩家ID */ - id: string; - /** 玩家名称 */ - name: string; - /** 位置 */ - position: { x: number; y: number; z: number }; - /** 出生点 */ - spawnPoint: { x: number; y: number; z: number }; - /** 生命值 */ - hp: number; - /** 最大生命值 */ - maxHp: number; - /** 是否存活 */ - isAlive: boolean; - /** 创建时间 */ - createdAt: number; - /** 最后登录时间 */ - lastLoginAt: number; - }; - /** 是否新玩家 */ - isNewPlayer?: boolean; -} diff --git a/client/assets/scripts/Framework/Net/NetConfig.ts b/client/assets/scripts/Framework/Net/NetConfig.ts index 9c2c468..34b202d 100644 --- a/client/assets/scripts/Framework/Net/NetConfig.ts +++ b/client/assets/scripts/Framework/Net/NetConfig.ts @@ -1,29 +1,48 @@ +/** + * 网络协议类型 + */ +export enum NetProtocolType { + /** HTTP/HTTPS 协议 */ + Http = "http", + + /** WebSocket 协议 */ + WebSocket = "websocket" +} + /** * 网络配置接口 */ export interface NetConfig { /** 服务器地址 */ serverUrl: string; - + + /** 网络协议类型 默认 Http */ + protocolType?: NetProtocolType; + /** 超时时间(ms) 默认 30000 */ timeout?: number; - + /** 是否自动重连 默认 true */ autoReconnect?: boolean; - + /** 重连间隔(ms) 默认 3000 */ reconnectInterval?: number; - + /** 最大重连次数 默认 5 */ maxReconnectTimes?: number; + + /** 是否使用JSON格式 默认 true */ + json?: boolean; } /** * 默认网络配置 */ export const DefaultNetConfig: Partial = { + protocolType: NetProtocolType.Http, timeout: 30000, autoReconnect: true, reconnectInterval: 3000, - maxReconnectTimes: 5 + maxReconnectTimes: 5, + json: true }; diff --git a/client/assets/scripts/Framework/Net/NetEvent.ts b/client/assets/scripts/Framework/Net/NetEvent.ts index 8ec7842..cc8bb3c 100644 --- a/client/assets/scripts/Framework/Net/NetEvent.ts +++ b/client/assets/scripts/Framework/Net/NetEvent.ts @@ -2,24 +2,27 @@ * 网络事件定义 */ export enum NetEvent { + /** 正在连接 */ + Connecting = "net_connecting", + /** 连接成功 */ Connected = "net_connected", - + /** 连接断开 */ Disconnected = "net_disconnected", - + /** 正在重连 */ Reconnecting = "net_reconnecting", - + /** 重连成功 */ ReconnectSuccess = "net_reconnect_success", - + /** 重连失败 */ ReconnectFailed = "net_reconnect_failed", - + /** 网络错误 */ Error = "net_error", - + /** 连接超时 */ Timeout = "net_timeout" } diff --git a/client/assets/scripts/Framework/Net/NetManager.ts b/client/assets/scripts/Framework/Net/NetManager.ts index 12bfec2..af64a9c 100644 --- a/client/assets/scripts/Framework/Net/NetManager.ts +++ b/client/assets/scripts/Framework/Net/NetManager.ts @@ -1,33 +1,36 @@ -import { NetConfig, DefaultNetConfig } from './NetConfig'; +import { MessagePair, MessagePairRegistry } from '../../App/Msg/MessagePairBase'; +import { DefaultNetConfig, NetConfig, NetProtocolType } from './NetConfig'; import { NetEvent } from './NetEvent'; -import { PlatformAdapter, ClientConfig } from './PlatformAdapter'; +import { ClientConfig, PlatformAdapter } from './PlatformAdapter'; +import { INetClient } from './WebSocketClient'; /** * 网络管理器单例 * 负责管理网络连接和消息通信 + * 支持 HTTP 和 WebSocket 两种协议 */ export class NetManager { private static _instance: NetManager | null = null; - - /** TSRPC Client 实例 (HttpClient) */ - private _client: any = null; - + + /** TSRPC Client 实例 (HttpClient 或 WsClient) */ + private _client: INetClient | null = null; + /** 是否已连接 */ private _isConnected: boolean = false; - + /** 网络配置 */ private _config: NetConfig | null = null; - + /** 重连计数 */ private _reconnectCount: number = 0; - + /** 重连定时器 */ private _reconnectTimer: any = null; - + /** 事件监听器 */ private _eventListeners: Map = new Map(); - private constructor() {} + private constructor() { } /** * 获取单例实例 @@ -58,6 +61,7 @@ export class NetManager { } as NetConfig; console.log('[NetManager] Initialized with config:', this._config); + console.log('[NetManager] Protocol:', this._config.protocolType); console.log('[NetManager] Platform:', PlatformAdapter.getPlatformInfo()); } @@ -66,46 +70,56 @@ export class NetManager { */ async connect(): Promise { try { + if (!this._config) { + console.error('[NetManager] Config not set, please call init() first'); + return false; + } + console.log('[NetManager] Connecting to server:', this._config.serverUrl); - + // 创建客户端配置 const clientConfig: ClientConfig = { server: this._config.serverUrl, - json: true, - timeout: this._config.timeout + protocolType: this._config.protocolType, + json: this._config.json, + timeout: this._config.timeout, }; - // 根据平台创建对应的客户端 + // 根据平台和协议类型创建对应的客户端 this._client = PlatformAdapter.createClient(clientConfig); - // HttpClient 不需要显式连接,创建即可使用 - // 如果未来需要 WebSocket 支持,可以在这里添加 connect() 调用 - + // 如果是 WebSocket 客户端,需要显式连接 + if (this._config.protocolType === NetProtocolType.WebSocket && this._client.connect) { + this.emit(NetEvent.Connecting); + + const result = await this._client.connect(); + if (!result.isSucc) { + console.error('[NetManager] WebSocket connection failed:', result.errMsg); + this.emit(NetEvent.Error, result.errMsg); + + // 尝试重连 + if (this._config.autoReconnect) { + this.scheduleReconnect(); + } + return false; + } + } + this._isConnected = true; this._reconnectCount = 0; - - this.emit(NetEvent.Connected); - console.log('[NetManager] Client created successfully'); - - // client 可能不需要显式连接 - // 对于 WsClient 需要调用 connect() - - this._isConnected = true; - this._reconnectCount = 0; - + this.emit(NetEvent.Connected); console.log('[NetManager] Connected successfully'); - return true; } catch (error) { console.error('[NetManager] Connection failed:', error); this.emit(NetEvent.Error, error); - + // 尝试重连 - if (this._config.autoReconnect) { + if (this._config?.autoReconnect) { this.scheduleReconnect(); } - + return false; } } @@ -114,26 +128,26 @@ export class NetManager { * 断开连接 */ disconnect(): void { - if (!this._isConnected) { + if (!this._isConnected || !this._client) { return; } - // HttpClient 无需显式断开连接 - // 如果使用 WebSocket,可以在这里调用 disconnect() - - this._isConnected = false; - this._client = null; - if(this._reconnectTimer){ + + console.log('[NetManager] Disconnecting...'); + + // 清除重连定时器 + if (this._reconnectTimer) { + clearTimeout(this._reconnectTimer); this._reconnectTimer = null; } - // TODO: 调用客户端的断开连接方法 - if (this._client && typeof this._client.disconnect === 'function') { + // 如果是 WebSocket 客户端,调用断开连接方法 + if (this._client.disconnect) { this._client.disconnect(); } this._isConnected = false; this._client = null; - + this.emit(NetEvent.Disconnected); console.log('[NetManager] Disconnected'); } @@ -151,13 +165,12 @@ export class NetManager { try { console.log(`[NetManager] Calling API: ${apiName}`, req); - - // TODO: 根据实际的协议定义调用 API + const result = await this._client.callApi(apiName, req); - + if (result.isSucc) { console.log(`[NetManager] API ${apiName} success:`, result.res); - return result.res; + return result.res as Res; } else { console.error(`[NetManager] API ${apiName} failed:`, result.err); return null; @@ -170,7 +183,7 @@ export class NetManager { } /** - * 监听消息 + * 监听消息 (仅支持 WebSocket) * @param msgName 消息名称 * @param handler 处理函数 */ @@ -180,16 +193,91 @@ export class NetManager { return; } - console.log(`[NetManager] Listening message: ${msgName}`); - - // TODO: 根据实际的 TSRPC 客户端实现监听消息 - if (typeof this._client.listenMsg === 'function') { - this._client.listenMsg(msgName, handler); + if (!this._client.listenMsg) { + console.warn('[NetManager] Message listening not supported for HTTP client'); + return; } + + console.log(`[NetManager] Listening message: ${msgName}`); + this._client.listenMsg(msgName, handler); } /** - * 发送消息 + * 取消监听消息 (仅支持 WebSocket) + * @param msgName 消息名称 + * @param handler 处理函数(可选,不传则取消所有该消息的监听) + */ + unlistenMsg(msgName: string, handler?: Function): void { + if (!this._client) { + console.error('[NetManager] Client not initialized'); + return; + } + + if (!this._client.unlistenMsg) { + console.warn('[NetManager] Message listening not supported for HTTP client'); + return; + } + + console.log(`[NetManager] Unlisten message: ${msgName}`); + this._client.unlistenMsg(msgName, handler); + } + + /** + * 发送消息并等待响应 (基于消息对系统) + * @param messagePair 消息对实例 + * @param requestData 请求消息内容 + * @returns Promise<响应消息> + */ + async callMsg(messagePair: MessagePair, requestData: TReq): Promise { + if (!this._isConnected || !this._client) { + console.error('[NetManager] Not connected'); + return null; + } + + const requestName = messagePair.requestName; + const responseName = messagePair.responseName; + + // 验证请求消息格式 + if (!messagePair.isValidRequest(requestData)) { + console.error(`[NetManager] Invalid request data for ${requestName}:`, requestData); + return null; + } + + console.log(`[NetManager] Calling message: ${requestName} -> ${responseName}`, requestData); + + return new Promise((resolve, reject) => { + // 创建一次性响应处理器 + const responseHandler = (response: TRes) => { + // 验证响应消息格式 + if (!messagePair.isValidResponse(response)) { + return; + } + + // 取消监听 + this.unlistenMsg(responseName, responseHandler); + // 返回响应 + console.log(`[NetManager] Received response for ${requestName}:`, response); + resolve(response); + }; + + // 监听响应 + this.listenMsg(responseName, responseHandler); + + // 发送请求消息 + this.sendMsg(requestName, requestData); + + // 设置超时处理 + const timeout = this._config?.timeout || 30000; + setTimeout(() => { + this.unlistenMsg(responseName, responseHandler); + console.error(`[NetManager] Request timeout for ${requestName}`); + resolve(null); + }, timeout); + }); + } + + /** + * 发送消息 (仅支持 WebSocket) * @param msgName 消息名称 * @param msg 消息内容 */ @@ -199,12 +287,13 @@ export class NetManager { return; } - console.log(`[NetManager] Sending message: ${msgName}`, msg); - - // TODO: 根据实际的 TSRPC 客户端实现发送消息 - if (typeof this._client.sendMsg === 'function') { - this._client.sendMsg(msgName, msg); + if (!this._client.sendMsg) { + console.warn('[NetManager] Message sending not supported for HTTP client'); + return; } + + console.log(`[NetManager] Sending message: ${msgName}`, msg); + this._client.sendMsg(msgName, msg); } /** @@ -223,9 +312,9 @@ export class NetManager { this._reconnectCount++; console.log(`[NetManager] Scheduling reconnect (${this._reconnectCount}/${this._config.maxReconnectTimes})...`); - + this.emit(NetEvent.Reconnecting, this._reconnectCount); - + this._reconnectTimer = setTimeout(() => { this.reconnect(); }, this._config.reconnectInterval || 3000); @@ -236,9 +325,9 @@ export class NetManager { */ private async reconnect(): Promise { console.log('[NetManager] Reconnecting...'); - + const success = await this.connect(); - + if (success) { this.emit(NetEvent.ReconnectSuccess); } else if (this._config?.autoReconnect) { @@ -301,7 +390,41 @@ export class NetManager { /** * 获取客户端实例 */ - get client(): any { + get client(): INetClient | null { return this._client; } + + /** + * 获取协议类型 + */ + get protocolType(): NetProtocolType | undefined { + return this._config?.protocolType; + } + + /** + * 判断是否为 WebSocket 连接 + */ + get isWebSocket(): boolean { + return this._config?.protocolType === NetProtocolType.WebSocket; + } + + /** + * 判断是否为 HTTP 连接 + */ + get isHttp(): boolean { + return this._config?.protocolType === NetProtocolType.Http; + } + + /** + * 获取所有已注册的消息对信息 + * @returns 消息对列表 + */ + getMessagePairs(): { requestName: string; responseName: string }[] { + const registry = MessagePairRegistry.getInstance(); + return registry.getAllPairs().map(pair => ({ + requestName: pair.requestName, + responseName: pair.responseName + })); + } } + diff --git a/client/assets/scripts/Framework/Net/PlatformAdapter.ts b/client/assets/scripts/Framework/Net/PlatformAdapter.ts index 3dd6ca0..24aa85a 100644 --- a/client/assets/scripts/Framework/Net/PlatformAdapter.ts +++ b/client/assets/scripts/Framework/Net/PlatformAdapter.ts @@ -1,7 +1,9 @@ import { sys } from 'cc'; // 使用别名导入避免命名冲突 -import { BaseServiceType, HttpClient as HttpClientBrowser } from 'tsrpc-browser'; -import { HttpClient as HttpClientMiniapp } from 'tsrpc-miniapp'; +import { BaseServiceType, HttpClient as HttpClientBrowser, WsClient as WsClientBrowser } from 'tsrpc-browser'; +import { HttpClient as HttpClientMiniapp, WsClient as WsClientMiniapp } from 'tsrpc-miniapp'; +import { NetProtocolType } from './NetConfig'; +import { INetClient, WebSocketClientWrapper } from './WebSocketClient'; /** * 平台类型枚举 @@ -9,10 +11,10 @@ import { HttpClient as HttpClientMiniapp } from 'tsrpc-miniapp'; export enum PlatformType { /** 浏览器平台 */ Browser = "browser", - + /** 小程序平台 (微信、抖音、QQ等) */ MiniApp = "miniapp", - + /** 未知平台 */ Unknown = "unknown" } @@ -23,22 +25,29 @@ export enum PlatformType { export interface ClientConfig { /** 服务器地址 */ server: string; - + + /** 网络协议类型 */ + protocolType?: NetProtocolType; + /** 是否使用 JSON 格式 (默认 true) */ json?: boolean; - + /** 超时时间(ms) */ timeout?: number; - + /** 其他配置 */ [key: string]: any; } /** * 平台适配器 - * 根据当前平台返回对应的 TSRPC 客户端实现 + * 根据当前平台和协议类型返回对应的 TSRPC 客户端实现 * - * 注意: TSRPC 不同平台的库中 API 是重名的,所以使用别名导入 + * 支持协议: + * - HTTP/HTTPS: 用于无状态的API调用 + * - WebSocket: 用于实时双向通信 + * + * 支持平台: * - tsrpc-browser: 用于浏览器和 XMLHttpRequest 兼容的环境 * - tsrpc-miniapp: 用于微信、抖音、QQ 等小程序环境 */ @@ -94,11 +103,12 @@ export class PlatformAdapter { } /** - * 创建对应平台的 TSRPC 客户端实例 + * 创建对应平台和协议的 TSRPC 客户端实例 * @param config 客户端配置 */ - static createClient(config: ClientConfig): HttpClientBrowser | HttpClientMiniapp { + static createClient(config: ClientConfig): INetClient { const platform = this.detectPlatform(); + const protocolType = config.protocolType || NetProtocolType.Http; // 默认配置 const defaultConfig: ClientConfig = { @@ -115,19 +125,50 @@ export class PlatformAdapter { console.warn('[PlatformAdapter] Service protocol not set, please call setServiceProto() first'); } + console.log(`[PlatformAdapter] Creating ${protocolType} client for ${platform}:`, defaultConfig.server); + + // 根据协议类型和平台创建对应的客户端 + if (protocolType === NetProtocolType.WebSocket) { + return this.createWebSocketClient(platform, defaultConfig); + } else { + return this.createHttpClient(platform, defaultConfig); + } + } + + /** + * 创建 HTTP 客户端 + */ + private static createHttpClient(platform: PlatformType, config: ClientConfig): INetClient { let client: HttpClientBrowser | HttpClientMiniapp; - // 根据平台创建对应的客户端 if (platform === PlatformType.MiniApp) { - console.log('[PlatformAdapter] Creating MiniApp client:', defaultConfig.server); - client = new HttpClientMiniapp(this._serviceProto, defaultConfig) as HttpClientMiniapp; + client = new HttpClientMiniapp(this._serviceProto, config) as HttpClientMiniapp; } else { - // 浏览器和其他 XMLHttpRequest 兼容环境使用 Browser 客户端 - console.log('[PlatformAdapter] Creating Browser client:', defaultConfig.server); - client = new HttpClientBrowser(this._serviceProto, defaultConfig) as HttpClientBrowser; + client = new HttpClientBrowser(this._serviceProto, config) as HttpClientBrowser; } - return client; + // 包装 HTTP 客户端使其符合 INetClient 接口 + return { + callApi: async (apiName: string, req: Req) => { + return await client.callApi(apiName, req); + } + } as INetClient; + } + + /** + * 创建 WebSocket 客户端 + */ + private static createWebSocketClient(platform: PlatformType, config: ClientConfig): INetClient { + let wsClient: WsClientBrowser | WsClientMiniapp; + + if (platform === PlatformType.MiniApp) { + wsClient = new WsClientMiniapp(this._serviceProto, config) as WsClientMiniapp; + } else { + wsClient = new WsClientBrowser(this._serviceProto, config) as WsClientBrowser; + } + + // 使用包装器统一接口 + return new WebSocketClientWrapper(wsClient); } /** diff --git a/client/assets/scripts/Framework/Net/README.md b/client/assets/scripts/Framework/Net/README.md deleted file mode 100644 index df52098..0000000 --- a/client/assets/scripts/Framework/Net/README.md +++ /dev/null @@ -1,303 +0,0 @@ -# 网络通信模块 (Framework/Net) - -## 📋 模块概述 -基于 TSRPC 的网络通信层,支持多平台(浏览器、小程序),提供 API 调用和服务器消息监听功能。 - -## 🎯 核心特性 -- ✅ 跨平台支持(浏览器/小程序) -- ✅ 自动平台检测和适配 -- ✅ 服务协议动态配置 -- ✅ API 调用和消息监听 -- ✅ 自动重连机制 -- ✅ 完整的事件系统 - -## 📦 依赖包 - -| 平台 | NPM 包 | -|------|--------| -| 浏览器 (Web) | `tsrpc-browser` | -| 小程序 (微信/抖音/QQ) | `tsrpc-miniapp` | - -## 🗂️ 文件结构 - -``` -Framework/Net/ -├── NetManager.ts # 网络管理器(核心) -├── PlatformAdapter.ts # 平台适配器 -├── NetConfig.ts # 网络配置 -├── NetEvent.ts # 网络事件 -├── LoginProtocol.ts # 登录协议(临时) -└── NetExample.ts # 使用示例 -``` - -## 📘 核心类详解 - -### NetManager - 网络管理器 - -**职责**: 网络连接管理、消息收发、重连机制 - -**核心方法**: - -```typescript -class NetManager { - // 获取单例 - static getInstance(): NetManager; - - // 设置服务协议(必须在 init 之前调用) - setServiceProto(serviceProto: ServiceProto): void; - - // 初始化网络配置 - init(config: NetConfig): void; - - // 创建客户端实例并连接 - connect(): Promise; - - // 断开连接并清理资源 - disconnect(): void; - - // 调用 API - callApi(apiName: string, req: Req): Promise; - - // 监听服务器消息 - listenMsg(msgName: string, handler: (msg: T) => void): void; - - // 取消监听服务器消息 - unlistenMsg(msgName: string, handler?: Function): void; - - // 发送消息到服务器 - sendMsg(msgName: string, msg: T): Promise; - - // 监听网络事件 - on(event: NetEvent, callback: Function): void; - - // 取消监听网络事件 - off(event: NetEvent, callback: Function): void; -} -``` - -### PlatformAdapter - 平台适配器 - -**职责**: 根据运行平台创建对应的 TSRPC 客户端 - -**技术实现**: -- 使用别名导入: `HttpClient as HttpClientBrowser` 和 `HttpClient as HttpClientMiniapp` -- 自动检测 Cocos 平台类型 (`sys.platform`) -- 根据平台实例化对应的客户端 - -**核心方法**: - -```typescript -class PlatformAdapter { - // 设置服务协议 - static setServiceProto(serviceProto: ServiceProto): void; - - // 检测当前运行平台 - static detectPlatform(): string; - - // 创建对应平台的客户端实例 - static createClient(config: NetConfig): HttpClient | null; - - // 获取当前平台 - static getCurrentPlatform(): string; - - // 平台判断 - static isMiniApp(): boolean; - static isBrowser(): boolean; - - // 获取平台详细信息 - static getPlatformInfo(): object; -} -``` - -### NetConfig - 网络配置 - -**配置接口**: - -```typescript -interface NetConfig { - serverUrl: string; // 服务器地址 - timeout?: number; // 超时时间(ms) 默认 30000 - autoReconnect?: boolean; // 是否自动重连 默认 true - reconnectInterval?: number; // 重连间隔(ms) 默认 3000 - maxReconnectTimes?: number; // 最大重连次数 默认 5 -} - -// 默认配置 -const DefaultNetConfig: Partial; -``` - -### NetEvent - 网络事件 - -**事件类型**: - -```typescript -enum NetEvent { - Connected = "net_connected", // 连接成功 - Disconnected = "net_disconnected", // 连接断开 - Reconnecting = "net_reconnecting", // 正在重连 - ReconnectSuccess = "net_reconnect_success", // 重连成功 - ReconnectFailed = "net_reconnect_failed", // 重连失败 - Error = "net_error", // 网络错误 - Timeout = "net_timeout" // 连接超时 -} -``` - -## 📝 使用指南 - -### 1. 基础使用流程 - -```typescript -import { NetManager } from './Framework/Net/NetManager'; -import { NetConfig } from './Framework/Net/NetConfig'; -import { NetEvent } from './Framework/Net/NetEvent'; -import { serviceProto } from '../Shared/protocols/serviceProto'; - -// 1. 获取实例并设置协议 -const netManager = NetManager.getInstance(); -netManager.setServiceProto(serviceProto); // 必须在 init 之前 - -// 2. 监听网络事件 -netManager.on(NetEvent.Connected, () => { - console.log('✅ 网络已连接'); -}); - -netManager.on(NetEvent.Disconnected, () => { - console.log('❌ 网络已断开'); -}); - -netManager.on(NetEvent.Error, (error: any) => { - console.error('⚠️ 网络错误:', error); -}); - -// 3. 初始化配置 -const config: NetConfig = { - serverUrl: 'http://localhost:3000', - timeout: 30000, - autoReconnect: true, - reconnectInterval: 3000, - maxReconnectTimes: 5 -}; -netManager.init(config); - -// 4. 连接服务器 -const success = await netManager.connect(); -if (success) { - console.log('✅ 网络初始化成功'); -} -``` - -### 2. 调用 API - -```typescript -import { ReqLogin, ResLogin } from '../Shared/protocols/PtlLogin'; - -// 调用登录 API -const result = await netManager.callApi('Login', { - username: 'testUser', - password: '123456' -}); - -if (result) { - console.log('登录成功:', result); -} -``` - -### 3. 监听服务器消息 - -```typescript -import { MsgUserJoin } from '../Shared/protocols/MsgUserJoin'; - -// 监听用户加入消息 -netManager.listenMsg('UserJoin', (msg) => { - console.log('有新用户加入:', msg); -}); - -// 取消监听 -netManager.unlistenMsg('UserJoin'); -``` - -### 4. 发送消息到服务器 - -```typescript -import { MsgChat } from '../Shared/protocols/MsgChat'; - -// 发送聊天消息 -await netManager.sendMsg('Chat', { - content: 'Hello World!' -}); -``` - -## 🔄 协议同步 - -### 同步脚本配置 - -**文件**: 项目根目录的 `sync-shared.js` - -```javascript -// 服务端共享目录 -const serverSharedDir = path.join(__dirname, '../server/src/shared'); - -// 客户端目标目录 -const clientSharedDir = path.join(__dirname, 'assets/scripts/Shared'); -``` - -### 使用步骤 - -1. **确保服务端项目位置**: 服务端项目应该在 `../server` 目录 -2. **运行同步命令**: - ```bash - npm run sync-shared - ``` -3. **导入协议**: - ```typescript - import { serviceProto } from '../Shared/protocols/serviceProto'; - import { ReqLogin, ResLogin } from '../Shared/protocols/PtlLogin'; - ``` - -## ⚠️ 注意事项 - -1. **协议必须先设置**: 在调用 `init()` 之前,必须先调用 `setServiceProto()` -2. **连接状态检查**: 调用 API 前确保已连接,否则会返回 null -3. **错误处理**: 建议监听 `NetEvent.Error` 事件处理网络错误 -4. **资源清理**: 应用退出时调用 `disconnect()` 清理资源 -5. **平台兼容**: PlatformAdapter 会自动处理平台差异,无需手动判断 - -## 🔍 调试技巧 - -### 启用详细日志 - -```typescript -// NetManager 内部已有详细的 console.log -// 可以根据日志前缀过滤: -// [NetManager] - 网络管理器日志 -// [PlatformAdapter] - 平台适配器日志 -``` - -### 常见问题 - -**问题1**: `协议未设置` 错误 -```typescript -// 解决: 确保在 init 之前调用 setServiceProto -netManager.setServiceProto(serviceProto); -netManager.init(config); -``` - -**问题2**: `API 调用返回 null` -```typescript -// 解决: 检查网络连接状态 -netManager.on(NetEvent.Connected, async () => { - // 在连接成功后再调用 API - const result = await netManager.callApi('Login', data); -}); -``` - -**问题3**: 小程序平台客户端创建失败 -```typescript -// 解决: 确保已安装 tsrpc-miniapp -npm install tsrpc-miniapp -``` - -## 📚 参考资料 - -- [TSRPC 官方文档](https://tsrpc.cn/) -- [Cocos Creator 官方文档](https://docs.cocos.com/creator/manual/zh/) diff --git a/client/assets/scripts/Framework/Net/README.md.meta b/client/assets/scripts/Framework/Net/README.md.meta deleted file mode 100644 index 3704508..0000000 --- a/client/assets/scripts/Framework/Net/README.md.meta +++ /dev/null @@ -1,11 +0,0 @@ -{ - "ver": "1.0.1", - "importer": "text", - "imported": true, - "uuid": "6eb67b95-26c3-410a-a9ee-441d4fa23371", - "files": [ - ".json" - ], - "subMetas": {}, - "userData": {} -} diff --git a/client/assets/scripts/Shared/protocols/MsgReqLogin.ts b/client/assets/scripts/Shared/protocols/MsgReqLogin.ts new file mode 100644 index 0000000..14411ee --- /dev/null +++ b/client/assets/scripts/Shared/protocols/MsgReqLogin.ts @@ -0,0 +1,11 @@ + +/** + * 登录请求消息 + */ +export interface MsgReqLogin { + /** 玩家ID(用于识别玩家) */ + playerId: string; + + /** 玩家昵称(可选,新玩家时使用) */ + playerName?: string; +} \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/MsgReqLogin.ts.meta b/client/assets/scripts/Shared/protocols/MsgReqLogin.ts.meta new file mode 100644 index 0000000..157031a --- /dev/null +++ b/client/assets/scripts/Shared/protocols/MsgReqLogin.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "24fdb58b-b594-4bcf-8695-2669bd9bf9ca", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Shared/protocols/MsgReqMove.ts b/client/assets/scripts/Shared/protocols/MsgReqMove.ts new file mode 100644 index 0000000..fbe70b7 --- /dev/null +++ b/client/assets/scripts/Shared/protocols/MsgReqMove.ts @@ -0,0 +1,10 @@ +/** + * 移动请求消息 + */ +export interface MsgReqMove { + /** X坐标 */ + x: number; + + /** Y坐标 */ + y: number; +} \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/MsgReqMove.ts.meta b/client/assets/scripts/Shared/protocols/MsgReqMove.ts.meta new file mode 100644 index 0000000..0f6112a --- /dev/null +++ b/client/assets/scripts/Shared/protocols/MsgReqMove.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "b244e6e0-857f-41f2-ac85-c6130e61218b", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Shared/protocols/MsgReqSend.ts b/client/assets/scripts/Shared/protocols/MsgReqSend.ts new file mode 100644 index 0000000..fe0984d --- /dev/null +++ b/client/assets/scripts/Shared/protocols/MsgReqSend.ts @@ -0,0 +1,7 @@ +/** + * 发送消息请求 + */ +export interface MsgReqSend { + /** 消息内容 */ + content: string; +} \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/MsgReqSend.ts.meta b/client/assets/scripts/Shared/protocols/MsgReqSend.ts.meta new file mode 100644 index 0000000..f58fa43 --- /dev/null +++ b/client/assets/scripts/Shared/protocols/MsgReqSend.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "b6e9ef16-0098-4050-a133-721d339159e5", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Shared/protocols/PtlLogin.ts b/client/assets/scripts/Shared/protocols/MsgResLogin.ts similarity index 69% rename from client/assets/scripts/Shared/protocols/PtlLogin.ts rename to client/assets/scripts/Shared/protocols/MsgResLogin.ts index f8a3ab2..12d6819 100644 --- a/client/assets/scripts/Shared/protocols/PtlLogin.ts +++ b/client/assets/scripts/Shared/protocols/MsgResLogin.ts @@ -1,61 +1,50 @@ -import { Position } from './base'; - -/** - * 登录请求 - */ -export interface ReqLogin { - /** 玩家ID(用于识别玩家) */ - playerId: string; - - /** 玩家昵称(可选,新玩家时使用) */ - playerName?: string; -} - -/** - * 玩家角色信息 - */ -export interface PlayerInfo { - /** 玩家ID */ - id: string; - - /** 玩家昵称 */ - name: string; - - /** 当前位置 */ - position: Position; - - /** 出生点 */ - spawnPoint: Position; - - /** 当前生命值 */ - hp: number; - - /** 最大生命值 */ - maxHp: number; - - /** 是否存活 */ - isAlive: boolean; - - /** 创建时间 */ - createdAt: number; - - /** 最后登录时间 */ - lastLoginAt: number; -} - -/** - * 登录响应 - */ -export interface ResLogin { - /** 是否成功 */ - success: boolean; - - /** 消息 */ - message: string; - - /** 玩家信息 */ - player?: PlayerInfo; - - /** 是否新玩家 */ - isNewPlayer?: boolean; -} +import { Position } from './base'; + +/** + * 玩家角色信息 + */ +export interface PlayerInfo { + /** 玩家ID */ + id: string; + + /** 玩家昵称 */ + name: string; + + /** 当前位置 */ + position: Position; + + /** 出生点 */ + spawnPoint: Position; + + /** 当前生命值 */ + hp: number; + + /** 最大生命值 */ + maxHp: number; + + /** 是否存活 */ + isAlive: boolean; + + /** 创建时间 */ + createdAt: number; + + /** 最后登录时间 */ + lastLoginAt: number; +} + +/** + * 登录响应消息 + */ +export interface MsgResLogin { + /** 是否成功 */ + success: boolean; + + /** 消息 */ + message: string; + + /** 玩家信息 */ + player?: PlayerInfo; + + /** 是否新玩家 */ + isNewPlayer?: boolean; +} \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/MsgResLogin.ts.meta b/client/assets/scripts/Shared/protocols/MsgResLogin.ts.meta new file mode 100644 index 0000000..dd551f5 --- /dev/null +++ b/client/assets/scripts/Shared/protocols/MsgResLogin.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "dcefce3e-1a92-45ac-8d63-94fb1370edae", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Shared/protocols/MsgResMove.ts b/client/assets/scripts/Shared/protocols/MsgResMove.ts new file mode 100644 index 0000000..3b24251 --- /dev/null +++ b/client/assets/scripts/Shared/protocols/MsgResMove.ts @@ -0,0 +1,15 @@ +import { Position } from './base'; + +/** + * 移动响应消息 + */ +export interface MsgResMove { + /** 是否成功 */ + success: boolean; + + /** 消息 */ + message: string; + + /** 新位置(成功时返回) */ + position?: Position; +} \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/MsgResMove.ts.meta b/client/assets/scripts/Shared/protocols/MsgResMove.ts.meta new file mode 100644 index 0000000..f50bc3a --- /dev/null +++ b/client/assets/scripts/Shared/protocols/MsgResMove.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "e7ba8c78-ba4b-4dc6-a3de-352a5444421a", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Shared/protocols/MsgResSend.ts b/client/assets/scripts/Shared/protocols/MsgResSend.ts new file mode 100644 index 0000000..19f883c --- /dev/null +++ b/client/assets/scripts/Shared/protocols/MsgResSend.ts @@ -0,0 +1,7 @@ +/** + * 发送消息响应 + */ +export interface MsgResSend { + /** 发送时间 */ + time: Date; +} \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/MsgResSend.ts.meta b/client/assets/scripts/Shared/protocols/MsgResSend.ts.meta new file mode 100644 index 0000000..af5d07e --- /dev/null +++ b/client/assets/scripts/Shared/protocols/MsgResSend.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "1fecff7f-ad47-4829-b229-e89e6e2e301e", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Shared/protocols/PtlMove.ts b/client/assets/scripts/Shared/protocols/PtlMove.ts deleted file mode 100644 index 7fe831c..0000000 --- a/client/assets/scripts/Shared/protocols/PtlMove.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Position } from './base'; - -/** - * 移动请求 - */ -export interface ReqMove { - /** 目标位置 X 坐标 */ - x: number; - - /** 目标位置 Y 坐标 */ - y: number; -} - -/** - * 移动响应 - */ -export interface ResMove { - /** 是否成功 */ - success: boolean; - - /** 消息 */ - message?: string; - - /** 实际移动后的位置 */ - position?: Position; -} diff --git a/client/assets/scripts/Shared/protocols/PtlSend.ts b/client/assets/scripts/Shared/protocols/PtlSend.ts deleted file mode 100644 index a732699..0000000 --- a/client/assets/scripts/Shared/protocols/PtlSend.ts +++ /dev/null @@ -1,10 +0,0 @@ -// This is a demo code file -// Feel free to delete it - -export interface ReqSend { - content: string -} - -export interface ResSend { - time: Date -} \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/serviceProto.ts b/client/assets/scripts/Shared/protocols/serviceProto.ts index 53be2c6..2ad665d 100644 --- a/client/assets/scripts/Shared/protocols/serviceProto.ts +++ b/client/assets/scripts/Shared/protocols/serviceProto.ts @@ -2,34 +2,32 @@ import { ServiceProto } from 'tsrpc-proto'; import { MsgChat } from './MsgChat'; import { MsgPlayerJoin } from './MsgPlayerJoin'; import { MsgPlayerMove } from './MsgPlayerMove'; -import { ReqLogin, ResLogin } from './PtlLogin'; -import { ReqMove, ResMove } from './PtlMove'; -import { ReqSend, ResSend } from './PtlSend'; +import { MsgReqLogin } from './MsgReqLogin'; +import { MsgReqMove } from './MsgReqMove'; +import { MsgReqSend } from './MsgReqSend'; +import { MsgResLogin } from './MsgResLogin'; +import { MsgResMove } from './MsgResMove'; +import { MsgResSend } from './MsgResSend'; export interface ServiceType { api: { - "Login": { - req: ReqLogin, - res: ResLogin - }, - "Move": { - req: ReqMove, - res: ResMove - }, - "Send": { - req: ReqSend, - res: ResSend - } + }, msg: { "Chat": MsgChat, "PlayerJoin": MsgPlayerJoin, - "PlayerMove": MsgPlayerMove + "PlayerMove": MsgPlayerMove, + "ReqLogin": MsgReqLogin, + "ReqMove": MsgReqMove, + "ReqSend": MsgReqSend, + "ResLogin": MsgResLogin, + "ResMove": MsgResMove, + "ResSend": MsgResSend } } export const serviceProto: ServiceProto = { - "version": 3, + "version": 5, "services": [ { "id": 0, @@ -47,19 +45,34 @@ export const serviceProto: ServiceProto = { "type": "msg" }, { - "id": 2, - "name": "Login", - "type": "api" + "id": 6, + "name": "ReqLogin", + "type": "msg" }, { - "id": 5, - "name": "Move", - "type": "api" + "id": 7, + "name": "ReqMove", + "type": "msg" }, { - "id": 1, - "name": "Send", - "type": "api" + "id": 8, + "name": "ReqSend", + "type": "msg" + }, + { + "id": 9, + "name": "ResLogin", + "type": "msg" + }, + { + "id": 10, + "name": "ResMove", + "type": "msg" + }, + { + "id": 11, + "name": "ResSend", + "type": "msg" } ], "types": { @@ -176,18 +189,18 @@ export const serviceProto: ServiceProto = { } ] }, - "PtlLogin/ReqLogin": { + "MsgReqLogin/MsgReqLogin": { "type": "Interface", "properties": [ { - "id": 1, + "id": 0, "name": "playerId", "type": { "type": "String" } }, { - "id": 2, + "id": 1, "name": "playerName", "type": { "type": "String" @@ -196,7 +209,38 @@ export const serviceProto: ServiceProto = { } ] }, - "PtlLogin/ResLogin": { + "MsgReqMove/MsgReqMove": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "x", + "type": { + "type": "Number" + } + }, + { + "id": 1, + "name": "y", + "type": { + "type": "Number" + } + } + ] + }, + "MsgReqSend/MsgReqSend": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "content", + "type": { + "type": "String" + } + } + ] + }, + "MsgResLogin/MsgResLogin": { "type": "Interface", "properties": [ { @@ -214,16 +258,16 @@ export const serviceProto: ServiceProto = { } }, { - "id": 4, + "id": 2, "name": "player", "type": { "type": "Reference", - "target": "PtlLogin/PlayerInfo" + "target": "MsgResLogin/PlayerInfo" }, "optional": true }, { - "id": 5, + "id": 3, "name": "isNewPlayer", "type": { "type": "Boolean" @@ -232,7 +276,7 @@ export const serviceProto: ServiceProto = { } ] }, - "PtlLogin/PlayerInfo": { + "MsgResLogin/PlayerInfo": { "type": "Interface", "properties": [ { @@ -302,26 +346,7 @@ export const serviceProto: ServiceProto = { } ] }, - "PtlMove/ReqMove": { - "type": "Interface", - "properties": [ - { - "id": 0, - "name": "x", - "type": { - "type": "Number" - } - }, - { - "id": 1, - "name": "y", - "type": { - "type": "Number" - } - } - ] - }, - "PtlMove/ResMove": { + "MsgResMove/MsgResMove": { "type": "Interface", "properties": [ { @@ -336,8 +361,7 @@ export const serviceProto: ServiceProto = { "name": "message", "type": { "type": "String" - }, - "optional": true + } }, { "id": 2, @@ -350,19 +374,7 @@ export const serviceProto: ServiceProto = { } ] }, - "PtlSend/ReqSend": { - "type": "Interface", - "properties": [ - { - "id": 0, - "name": "content", - "type": { - "type": "String" - } - } - ] - }, - "PtlSend/ResSend": { + "MsgResSend/MsgResSend": { "type": "Interface", "properties": [ {