From 8f58b890be8097b47b0b16d42427817bb61c8521 Mon Sep 17 00:00:00 2001 From: janing <1175861874@qq.com> Date: Thu, 18 Dec 2025 16:04:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=85=A5ResLogin.otherPlayers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/assets/res/UI/Game/PlayerInfo.prefab | 301 ++++++++++++++++++ .../assets/res/UI/Game/PlayerInfo.prefab.meta | 13 + client/assets/res/UI/Game/UIGame.prefab | 16 +- client/assets/scenes/main.scene | 10 +- .../scripts/App/AppStatus/AppStatusGame.ts | 15 +- .../scripts/App/Game/CameraController.ts | 180 +++++++++-- .../scripts/App/Game/PlayerController.ts | 18 +- client/assets/scripts/App/Game/PlayerInfo.ts | 88 +++++ .../scripts/App/Game/PlayerInfo.ts.meta | 9 + .../assets/scripts/App/Game/RemotePlayer.ts | 6 +- client/assets/scripts/App/Game/UIGame.ts | 120 ++++++- client/assets/scripts/App/Game/World.ts | 126 +++++++- client/assets/scripts/App/Login/UILogin.ts | 5 +- .../scripts/Shared/protocols/MsgPlayerJoin.ts | 2 +- .../scripts/Shared/protocols/MsgPlayerMove.ts | 2 +- .../scripts/Shared/protocols/MsgReqMove.ts | 4 +- .../scripts/Shared/protocols/MsgResLogin.ts | 7 +- .../scripts/Shared/protocols/MsgResMove.ts | 2 +- .../assets/scripts/Shared/protocols/base.ts | 2 +- .../scripts/Shared/protocols/serviceProto.ts | 14 +- client/settings/v2/packages/project.json | 8 +- 21 files changed, 861 insertions(+), 87 deletions(-) create mode 100644 client/assets/res/UI/Game/PlayerInfo.prefab create mode 100644 client/assets/res/UI/Game/PlayerInfo.prefab.meta create mode 100644 client/assets/scripts/App/Game/PlayerInfo.ts create mode 100644 client/assets/scripts/App/Game/PlayerInfo.ts.meta diff --git a/client/assets/res/UI/Game/PlayerInfo.prefab b/client/assets/res/UI/Game/PlayerInfo.prefab new file mode 100644 index 0000000..0c21e37 --- /dev/null +++ b/client/assets/res/UI/Game/PlayerInfo.prefab @@ -0,0 +1,301 @@ +[ + { + "__type__": "cc.Prefab", + "_name": "PlayerInfo", + "_objFlags": 0, + "__editorExtras__": {}, + "_native": "", + "data": { + "__id__": 1 + }, + "optimizationPolicy": 0, + "persistent": false + }, + { + "__type__": "cc.Node", + "_name": "PlayerInfo", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": null, + "_children": [ + { + "__id__": 2 + } + ], + "_active": true, + "_components": [ + { + "__id__": 8 + }, + { + "__id__": 10 + } + ], + "_prefab": { + "__id__": 12 + }, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 8388608, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "" + }, + { + "__type__": "cc.Node", + "_name": "text", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 1 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 3 + }, + { + "__id__": 5 + } + ], + "_prefab": { + "__id__": 7 + }, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 8388608, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 2 + }, + "_enabled": true, + "__prefab": { + "__id__": 4 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 42.255859375, + "height": 50.4 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "d0A/8x7mpNx5EvjYIoaCay" + }, + { + "__type__": "cc.Label", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 2 + }, + "_enabled": true, + "__prefab": { + "__id__": 6 + }, + "_customMaterial": null, + "_srcBlendFactor": 2, + "_dstBlendFactor": 4, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_string": "label", + "_horizontalAlign": 1, + "_verticalAlign": 1, + "_actualFontSize": 20, + "_fontSize": 20, + "_fontFamily": "Arial", + "_lineHeight": 40, + "_overflow": 0, + "_enableWrapText": true, + "_font": null, + "_isSystemFontUsed": true, + "_spacingX": 0, + "_isItalic": false, + "_isBold": false, + "_isUnderline": false, + "_underlineHeight": 2, + "_cacheMode": 0, + "_enableOutline": false, + "_outlineColor": { + "__type__": "cc.Color", + "r": 0, + "g": 0, + "b": 0, + "a": 255 + }, + "_outlineWidth": 2, + "_enableShadow": false, + "_shadowColor": { + "__type__": "cc.Color", + "r": 0, + "g": 0, + "b": 0, + "a": 255 + }, + "_shadowOffset": { + "__type__": "cc.Vec2", + "x": 2, + "y": 2 + }, + "_shadowBlur": 2, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "e3hL8BSnhFGJfx1WiaCfWA" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "1bkkKqEyFO7LsTGxIKuStm", + "instance": null, + "targetOverrides": null, + "nestedPrefabInstanceRoots": null + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 1 + }, + "_enabled": true, + "__prefab": { + "__id__": 9 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 100, + "height": 100 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "f4PBh8n8NKX506YXxKc3Tp" + }, + { + "__type__": "cc.Widget", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 1 + }, + "_enabled": true, + "__prefab": { + "__id__": 11 + }, + "_alignFlags": 18, + "_target": null, + "_left": 0, + "_right": 0, + "_top": 0, + "_bottom": 0, + "_horizontalCenter": 0, + "_verticalCenter": 0, + "_isAbsLeft": true, + "_isAbsRight": true, + "_isAbsTop": true, + "_isAbsBottom": true, + "_isAbsHorizontalCenter": true, + "_isAbsVerticalCenter": true, + "_originalWidth": 0, + "_originalHeight": 0, + "_alignMode": 2, + "_lockFlags": 0, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "79mURIYGtLP6O8CvZNsGga" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "c46/YsCPVOJYA4mWEpNYRx", + "instance": null, + "targetOverrides": null + } +] \ No newline at end of file diff --git a/client/assets/res/UI/Game/PlayerInfo.prefab.meta b/client/assets/res/UI/Game/PlayerInfo.prefab.meta new file mode 100644 index 0000000..9275449 --- /dev/null +++ b/client/assets/res/UI/Game/PlayerInfo.prefab.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.50", + "importer": "prefab", + "imported": true, + "uuid": "c8472648-43f2-4b7c-8881-e5461d212d58", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": { + "syncNodeName": "PlayerInfo" + } +} diff --git a/client/assets/res/UI/Game/UIGame.prefab b/client/assets/res/UI/Game/UIGame.prefab index d4dde83..22dd1c7 100644 --- a/client/assets/res/UI/Game/UIGame.prefab +++ b/client/assets/res/UI/Game/UIGame.prefab @@ -51,7 +51,7 @@ "z": 1 }, "_mobility": 0, - "_layer": 1073741824, + "_layer": 8388608, "_euler": { "__type__": "cc.Vec3", "x": 0, @@ -105,7 +105,7 @@ "z": 1 }, "_mobility": 0, - "_layer": 1073741824, + "_layer": 8388608, "_euler": { "__type__": "cc.Vec3", "x": 0, @@ -137,8 +137,8 @@ }, "_lpos": { "__type__": "cc.Vec3", - "x": 31.078, - "y": -21.978, + "x": 0, + "y": 0, "z": 0 }, "_lrot": { @@ -155,7 +155,7 @@ "z": 1 }, "_mobility": 0, - "_layer": 1073741824, + "_layer": 8388608, "_euler": { "__type__": "cc.Vec3", "x": 0, @@ -178,7 +178,7 @@ }, "_contentSize": { "__type__": "cc.Size", - "width": 42.255859375, + "width": 54.462890625, "height": 50.4 }, "_anchorPoint": { @@ -214,7 +214,7 @@ "b": 255, "a": 255 }, - "_string": "label", + "_string": "Game", "_horizontalAlign": 1, "_verticalAlign": 1, "_actualFontSize": 20, @@ -313,7 +313,7 @@ "__prefab": { "__id__": 12 }, - "_alignFlags": 9, + "_alignFlags": 18, "_target": null, "_left": 0, "_right": 0, diff --git a/client/assets/scenes/main.scene b/client/assets/scenes/main.scene index 2923cab..fe165a7 100644 --- a/client/assets/scenes/main.scene +++ b/client/assets/scenes/main.scene @@ -326,8 +326,8 @@ "_prefab": null, "_lpos": { "__type__": "cc.Vec3", - "x": 640, - "y": 360, + "x": 360, + "y": 719.9999999999999, "z": 0 }, "_lrot": { @@ -412,7 +412,7 @@ "_priority": 1073741824, "_fov": 45, "_fovAxis": 0, - "_orthoHeight": 412.3341946597761, + "_orthoHeight": 719.9999999999999, "_near": 1, "_far": 2000, "_color": { @@ -456,8 +456,8 @@ "__prefab": null, "_contentSize": { "__type__": "cc.Size", - "width": 1280, - "height": 720 + "width": 720, + "height": 1439.9999999999998 }, "_anchorPoint": { "__type__": "cc.Vec2", diff --git a/client/assets/scripts/App/AppStatus/AppStatusGame.ts b/client/assets/scripts/App/AppStatus/AppStatusGame.ts index 5f5a898..3897306 100644 --- a/client/assets/scripts/App/AppStatus/AppStatusGame.ts +++ b/client/assets/scripts/App/AppStatus/AppStatusGame.ts @@ -1,7 +1,7 @@ import { find } from "cc"; import { BaseState } from "../../Framework/FSM/BaseState"; import { UIMgr } from "../../Framework/UI/UIMgr"; -import { PlayerInfo } from "../../Shared/protocols/MsgResLogin"; +import { MsgResLogin, PlayerInfo } from "../../Shared/protocols/MsgResLogin"; import { UIGame } from "../Game/UIGame"; import { World } from "../Game/World"; @@ -14,6 +14,7 @@ import { World } from "../Game/World"; * - 游戏主循环 */ export class AppStatusGame extends BaseState { + private _cmd: MsgResLogin private _player: PlayerInfo = null; private _isNewPlayer: boolean = false; private _uiGame: UIGame = null; @@ -25,10 +26,11 @@ export class AppStatusGame extends BaseState { /** * 进入游戏状态 */ - async onEnter(params?: any): Promise { + async onEnter(params?: MsgResLogin): Promise { super.onEnter(params); - console.log("[AppStatusGame] 进入游戏世界"); + console.log("[AppStatusGame] 进入游戏世界", params); + this._cmd = params // 保存玩家信息 if (params) { @@ -87,7 +89,7 @@ export class AppStatusGame extends BaseState { } // 初始化世界,传入本地玩家信息 - await World.getInstance().init(worldRoot, this._player); + await World.getInstance().init(worldRoot, this._player, this._cmd.otherPlayers); console.log("[AppStatusGame] 游戏初始化完成"); } @@ -117,7 +119,10 @@ export class AppStatusGame extends BaseState { * 更新游戏状态(每帧调用) */ onUpdate(dt: number): void { - // TODO: 游戏主循环逻辑 + // 更新世界状态,包括玩家信息显示 + World.getInstance().update(dt); + + // TODO: 其他游戏主循环逻辑 // - 更新角色位置 // - 检测碰撞 // - 更新敌人AI diff --git a/client/assets/scripts/App/Game/CameraController.ts b/client/assets/scripts/App/Game/CameraController.ts index 38449da..10df8f4 100644 --- a/client/assets/scripts/App/Game/CameraController.ts +++ b/client/assets/scripts/App/Game/CameraController.ts @@ -1,21 +1,28 @@ -import { Camera, Component, Node, Vec3, _decorator } from 'cc'; +import { _decorator, Camera, Component, find, Node, Vec3 } from 'cc'; const { ccclass, property } = _decorator; /** * CameraController 摄像机控制器 - * 负责让摄像机跟随指定的目标物体 + * 负责让摄像机跟随指定的目标物体,并提供摄像机坐标转换功能 + * 支持世界摄像机和UI摄像机的概念 */ @ccclass('CameraController') export class CameraController extends Component { /** 跟随目标 */ private target: Node = null; - /** 摄像机节点 */ - private cameraNode: Node = null; + /** 世界摄像机节点 (Main Camera) */ + private worldCameraNode: Node = null; - /** 摄像机组件 */ - private camera: Camera = null; + /** 世界摄像机组件 */ + private worldCamera: Camera = null; + + /** UI摄像机节点 (Canvas/Camera) */ + private uiCameraNode: Node = null; + + /** UI摄像机组件 */ + private uiCamera: Camera = null; /** 相对偏移量 */ private offset: Vec3 = new Vec3(0, 10, 8); @@ -29,21 +36,49 @@ export class CameraController extends Component { public smoothFollow: boolean = true; onLoad() { - this.findMainCamera(); + this.findCameras(); } /** - * 查找主摄像机 + * 查找所有摄像机 */ - private findMainCamera(): void { - // 查找名为 "Main Camera" 的摄像机 - const mainCameraNode = this.node.scene.getChildByName('Main Camera'); + private findCameras(): void { + this.findWorldCamera(); + this.findUICamera(); + } + + /** + * 查找世界摄像机 (Main Camera) + */ + private findWorldCamera(): void { + // 查找名为 "Main Camera" 的世界摄像机 + const mainCameraNode = find('Main Camera', this.node.scene); if (mainCameraNode) { - this.cameraNode = mainCameraNode; - this.camera = mainCameraNode.getComponent(Camera); - console.log('[CameraController] 找到主摄像机:', mainCameraNode.name); + this.worldCameraNode = mainCameraNode; + this.worldCamera = mainCameraNode.getComponent(Camera); + console.log('[CameraController] 找到世界摄像机:', mainCameraNode.name); } else { - console.error('[CameraController] 未找到主摄像机(Main Camera)'); + console.error('[CameraController] 未找到世界摄像机(Main Camera)'); + } + } + + /** + * 查找UI摄像机 (Canvas/Camera) + */ + private findUICamera(): void { + // 查找Canvas下的Camera作为UI摄像机 + const canvasNode = find('Canvas', this.node.scene); + if (canvasNode) { + const uiCameraNode = canvasNode.getChildByName('Camera'); + if (uiCameraNode) { + this.uiCameraNode = uiCameraNode; + this.uiCamera = uiCameraNode.getComponent(Camera); + console.log('[CameraController] 找到UI摄像机:', uiCameraNode.name); + } else { + console.error('[CameraController] 未找到Canvas下的Camera节点'); + } + } else { + console.error('[CameraController] 未找到Canvas节点'); } } @@ -54,7 +89,7 @@ export class CameraController extends Component { public setTarget(target: Node): void { this.target = target; - if (this.target && this.cameraNode) { + if (this.target && this.worldCameraNode) { // 立即设置初始位置 this.updateCameraPosition(false); console.log('[CameraController] 设置跟随目标:', target.name); @@ -77,14 +112,35 @@ export class CameraController extends Component { } /** - * 获取摄像机节点 + * 获取世界摄像机节点 */ - public getCameraNode(): Node { - return this.cameraNode; + public getWorldCameraNode(): Node { + return this.worldCameraNode; + } + + /** + * 获取世界摄像机组件 + */ + public getWorldCamera(): Camera { + return this.worldCamera; + } + + /** + * 获取UI摄像机节点 + */ + public getUICameraNode(): Node { + return this.uiCameraNode; + } + + /** + * 获取UI摄像机组件 + */ + public getUICamera(): Camera { + return this.uiCamera; } update(deltaTime: number) { - if (this.target && this.cameraNode) { + if (this.target && this.worldCameraNode) { this.updateCameraPosition(this.smoothFollow, deltaTime); } } @@ -95,7 +151,7 @@ export class CameraController extends Component { * @param deltaTime 帧时间(smooth为true时需要) */ private updateCameraPosition(smooth: boolean = true, deltaTime: number = 0): void { - if (!this.target || !this.cameraNode) { + if (!this.target || !this.worldCameraNode) { return; } @@ -105,17 +161,17 @@ export class CameraController extends Component { if (smooth && deltaTime > 0) { // 平滑跟随 - const currentPosition = this.cameraNode.worldPosition; + const currentPosition = this.worldCameraNode.worldPosition; const newPosition = new Vec3(); Vec3.lerp(newPosition, currentPosition, targetPosition, this.followSpeed * deltaTime); - this.cameraNode.setWorldPosition(newPosition); + this.worldCameraNode.setWorldPosition(newPosition); } else { // 立即跟随 - this.cameraNode.setWorldPosition(targetPosition); + this.worldCameraNode.setWorldPosition(targetPosition); } // 让摄像机看向目标 - this.cameraNode.lookAt(this.target.worldPosition); + this.worldCameraNode.lookAt(this.target.worldPosition); } /** @@ -126,13 +182,83 @@ export class CameraController extends Component { console.log('[CameraController] 停止跟随'); } + /** + * 将世界坐标转换为指定摄像机的屏幕坐标 + * @param worldPosition 世界坐标(Vec3) + * @param camera 目标摄像机 + * @returns 摄像机坐标系下的屏幕坐标(Vec3),z为深度 + */ + public worldToCameraPosition(worldPosition: Vec3, camera: Camera): Vec3 { + if (!camera) { + console.warn('[CameraController] 摄像机参数为空,无法进行坐标转换'); + return new Vec3(0, 0, 0); + } + + // 使用摄像机的worldToScreen方法将世界坐标转换为屏幕坐标 + const screenPos = new Vec3(); + camera.worldToScreen(worldPosition, screenPos); + return screenPos; + } + + /** + * 将摄像机屏幕坐标转换为指定Node的本地坐标 + * 计算流程:摄像机坐标 -> 世界坐标 -> Node本地坐标 + * @param screenPosition 摄像机屏幕坐标 + * @param sourceCamera 源摄像机 + * @param targetNode 目标Node节点 + * @returns 目标Node的本地坐标 + */ + public CameraToNode(screenPosition: Vec3, sourceCamera: Camera, targetNode: Node): Vec3 { + if (!sourceCamera || !targetNode) { + console.warn('[CameraController] 摄像机或目标节点参数为空,无法进行坐标转换'); + return new Vec3(0, 0, 0); + } + + // 第一步:摄像机屏幕坐标转世界坐标 + const worldPosition = new Vec3(); + sourceCamera.screenToWorld(screenPosition, worldPosition); + + // 第二步:世界坐标转Node本地坐标 + const localPosition = new Vec3(); + targetNode.inverseTransformPoint(localPosition, worldPosition); + + return localPosition; + } + + /** + * 将世界坐标转换为UI摄像机节点的本地坐标 + * @param worldPosition 世界坐标 + * @returns UI摄像机节点的本地坐标 + */ + public worldToUICamera(worldPosition: Vec3, node: Node): Vec3 { + if (!this.worldCamera || !this.uiCameraNode) { + console.warn('[CameraController] 世界摄像机或UI摄像机节点未初始化'); + return new Vec3(0, 0, 0); + } + + // 第一步:世界坐标转世界摄像机屏幕坐标 + const worldCameraPos = this.worldToCameraPosition(worldPosition, this.worldCamera); + + // 第二步:世界摄像机屏幕坐标转UI摄像机节点本地坐标 + const uiCameraLocalPos = this.CameraToNode(worldCameraPos, this.uiCamera, node); + return uiCameraLocalPos; + + // // 世界坐标转Node本地坐标 + // const localPosition = new Vec3(); + // node.inverseTransformPoint(localPosition, worldPosition); + + return worldPosition; + } + /** * 销毁控制器 */ onDestroy(): void { this.target = null; - this.cameraNode = null; - this.camera = null; + this.worldCameraNode = null; + this.worldCamera = null; + this.uiCameraNode = null; + this.uiCamera = null; console.log('[CameraController] 摄像机控制器已销毁'); } } \ No newline at end of file diff --git a/client/assets/scripts/App/Game/PlayerController.ts b/client/assets/scripts/App/Game/PlayerController.ts index fa83cbe..b2729e3 100644 --- a/client/assets/scripts/App/Game/PlayerController.ts +++ b/client/assets/scripts/App/Game/PlayerController.ts @@ -43,7 +43,7 @@ export class PlayerController extends Component { this.playerInfo = playerInfo; const x = playerInfo.position.x / 1000 const y = playerInfo.position.y / 1000 - this.lastSentPosition.set(x, 0, y); + this.lastSentPosition.set(x, y, 0); // 获取 RoleController 组件 this.roleController = this.node.getComponentInChildren(RoleController); @@ -109,11 +109,11 @@ export class PlayerController extends Component { // W - 向前 if (this.keyStates.get(KeyCode.KEY_W)) { - this.moveDirection.z -= 1; + this.moveDirection.y += 1; } // S - 向后 if (this.keyStates.get(KeyCode.KEY_S)) { - this.moveDirection.z += 1; + this.moveDirection.y -= 1; } // A - 向左 if (this.keyStates.get(KeyCode.KEY_A)) { @@ -144,7 +144,7 @@ export class PlayerController extends Component { // 更新朝向(让角色面向移动方向) if (this.moveDirection.lengthSqr() > 0) { - const targetAngle = Math.atan2(this.moveDirection.x, -this.moveDirection.z) * (180 / Math.PI); + const targetAngle = Math.atan2(this.moveDirection.x, -this.moveDirection.y) * (180 / Math.PI); this.node.setRotationFromEuler(0, targetAngle, 0); } @@ -168,7 +168,7 @@ export class PlayerController extends Component { // 如果移动距离超过阈值,发送移动请求 if (distance >= this.moveSendThreshold) { - this.sendMoveRequest(currentPos.x, currentPos.z); + this.sendMoveRequest(currentPos.x, currentPos.y); this.lastSentPosition.set(currentPos); } } @@ -176,12 +176,12 @@ export class PlayerController extends Component { /** * 发送移动请求到服务器 */ - private async sendMoveRequest(x: number, z: number): Promise { + private async sendMoveRequest(x: number, y: number): Promise { try { const netManager = NetManager.getInstance(); const result = await netManager.callMsg(new MoveMessagePair(), { x: Math.round(x * 1000), - y: Math.round(z * 1000) + y: Math.round(y * 1000) }); if (!result) { @@ -194,8 +194,8 @@ export class PlayerController extends Component { const serverPos = result.position; const x = serverPos.x / 1000 const y = serverPos.y / 1000 - this.node.setPosition(x, 0, y); - this.lastSentPosition.set(x, 0, y); + this.node.setPosition(x, y, 0); + this.lastSentPosition.set(x, y, 0); } } catch (error) { console.error('[PlayerController] 发送移动请求异常:', error); diff --git a/client/assets/scripts/App/Game/PlayerInfo.ts b/client/assets/scripts/App/Game/PlayerInfo.ts new file mode 100644 index 0000000..8fc3804 --- /dev/null +++ b/client/assets/scripts/App/Game/PlayerInfo.ts @@ -0,0 +1,88 @@ +import { _decorator, Component, Label, Vec3 } from 'cc'; + +const { ccclass, property } = _decorator; + +export interface PlayerInfoData { + /** 玩家ID */ + playerId: string; + /** 玩家名称 */ + playerName: string; + /** 屏幕坐标位置(用于设置UI位置) */ + screenPosition: Vec3; + /** 真实位置(用于显示到文本中) */ + realPosition: Vec3; +} + +/** + * PlayerInfo 玩家信息显示组件 + * 显示玩家名称和坐标信息 + */ +@ccclass('PlayerInfo') +export class PlayerInfo extends Component { + /** 文本显示组件 */ + private textLabel: Label = null; + + /** 当前玩家数据 */ + private playerData: PlayerInfoData = null; + + onLoad() { + // 查找text子节点的Label组件 + const textNode = this.node.getChildByName('text'); + if (textNode) { + this.textLabel = textNode.getComponent(Label); + if (!this.textLabel) { + console.error('[PlayerInfo] text子节点没有Label组件'); + } + } else { + console.error('[PlayerInfo] 找不到text子节点'); + } + } + + /** + * 更新玩家信息 + * @param playerData 玩家数据 + */ + public updatePlayerInfo(playerData: PlayerInfoData): void { + this.playerData = playerData; + + if (!this.textLabel) { + console.error('[PlayerInfo] 文本组件未初始化'); + return; + } + + // 格式化显示文本:玩家名和真实坐标信息 + const displayText = `${playerData.playerName}\n坐标: (${Math.round(playerData.realPosition.x)}, ${Math.round(playerData.realPosition.y)})`; + this.textLabel.string = displayText; + + // 设置UI位置为屏幕坐标 + this.node.setPosition(playerData.screenPosition); + + //console.log(`[PlayerInfo] 更新玩家信息: ${playerData.playerName}, 屏幕坐标: (${playerData.screenPosition.x}, ${playerData.screenPosition.y}), 真实坐标: (${playerData.realPosition.x}, ${playerData.realPosition.y})`); + } + + /** + * 获取当前玩家数据 + */ + public getPlayerData(): PlayerInfoData { + return this.playerData; + } + + /** + * 隐藏玩家信息 + */ + public hide(): void { + this.node.active = false; + } + + /** + * 显示玩家信息 + */ + public show(): void { + this.node.active = true; + } + + onDestroy() { + this.textLabel = null; + this.playerData = null; + } +} \ No newline at end of file diff --git a/client/assets/scripts/App/Game/PlayerInfo.ts.meta b/client/assets/scripts/App/Game/PlayerInfo.ts.meta new file mode 100644 index 0000000..71f00aa --- /dev/null +++ b/client/assets/scripts/App/Game/PlayerInfo.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "files": [], + "subMetas": {}, + "userData": {} +} \ No newline at end of file diff --git a/client/assets/scripts/App/Game/RemotePlayer.ts b/client/assets/scripts/App/Game/RemotePlayer.ts index e1791a7..1f7fc10 100644 --- a/client/assets/scripts/App/Game/RemotePlayer.ts +++ b/client/assets/scripts/App/Game/RemotePlayer.ts @@ -40,8 +40,8 @@ export class RemotePlayer { this.playerName = playerName; const x = position.x / 1000 const y = position.y / 1000 - this.currentPosition.set(x, 0, y); - this.targetPosition.set(x, 0, y); + this.currentPosition.set(x, y, 0); + this.targetPosition.set(x, y, 0); // 获取 RoleController 组件 this.roleController = this.playerNode.getComponentInChildren(RoleController); @@ -79,7 +79,7 @@ export class RemotePlayer { // 计算朝向 if (distance > 0.01) { - const targetAngle = Math.atan2(direction.x, -direction.z) * (180 / Math.PI); + const targetAngle = Math.atan2(direction.x, -direction.y) * (180 / Math.PI); this.playerNode.setRotationFromEuler(0, targetAngle, 0); } diff --git a/client/assets/scripts/App/Game/UIGame.ts b/client/assets/scripts/App/Game/UIGame.ts index 07a7dd8..8af9916 100644 --- a/client/assets/scripts/App/Game/UIGame.ts +++ b/client/assets/scripts/App/Game/UIGame.ts @@ -1,5 +1,7 @@ -import { _decorator } from 'cc'; +import { _decorator, instantiate, Node, Prefab } from 'cc'; +import { ResMgr } from '../../Framework/ResMgr/ResMgr'; import { UIBase } from '../../Framework/UI/UIBase'; +import { PlayerInfo, PlayerInfoData } from './PlayerInfo'; const { ccclass, property } = _decorator; @@ -9,21 +11,22 @@ const { ccclass, property } = _decorator; */ @ccclass('UIGame') export class UIGame extends UIBase { + /** 玩家信息显示容器 */ + private playerInfoContainer: Node = null; - protected onLoad(): void { - console.log('[UIGame] onLoad'); + /** 玩家信息预制体 */ + private playerInfoPrefab: Prefab = null; + + /** 当前显示的玩家信息组件Map */ + private playerInfoComponents: Map = new Map(); + + async onStart() { + this.initPlayerInfoContainer(); + await this.loadPlayerInfoPrefab(); } - protected onEnable(): void { - console.log('[UIGame] onEnable'); - } - - protected onDisable(): void { - console.log('[UIGame] onDisable'); - } - - protected onDestroy(): void { - console.log('[UIGame] onDestroy'); + onEnd(): void { + this.clearPlayerInfoComponents(); } /** @@ -33,4 +36,95 @@ export class UIGame extends UIBase { return 'res://UI/Game/UIGame'; } + /** + * 初始化玩家信息容器 + */ + private initPlayerInfoContainer(): void { + // 查找或创建玩家信息容器 + this.playerInfoContainer = this._node.getChildByName('PlayerInfoContainer'); + if (!this.playerInfoContainer) { + this.playerInfoContainer = new Node('PlayerInfoContainer'); + this._node.addChild(this.playerInfoContainer); + } + console.log('[UIGame] 玩家信息容器已初始化'); + } + + /** + * 加载玩家信息预制体 + */ + private async loadPlayerInfoPrefab(): Promise { + try { + this.playerInfoPrefab = await ResMgr.getInstance().load('res', 'UI/Game/PlayerInfo', Prefab); + console.log('[UIGame] 玩家信息预制体加载成功'); + } catch (error) { + console.error('[UIGame] 加载玩家信息预制体失败:', error); + } + } + + /** + * 更新玩家信息显示 + * @param playerDataList 玩家数据列表 + */ + public updatePlayerInfo(playerDataList: PlayerInfoData[]): void { + if (!this.playerInfoPrefab || !this.playerInfoContainer) { + console.error('[UIGame] 玩家信息预制体或容器未准备好'); + return; + } + + // 记录当前更新的玩家ID + const currentPlayerIds = new Set(); + + for (const playerData of playerDataList) { + currentPlayerIds.add(playerData.playerId); + + let playerInfoComponent = this.playerInfoComponents.get(playerData.playerId); + + if (!playerInfoComponent) { + // 创建新的玩家信息组件 + const playerInfoNode = instantiate(this.playerInfoPrefab); + this.playerInfoContainer.addChild(playerInfoNode); + + playerInfoComponent = playerInfoNode.getComponent(PlayerInfo); + if (!playerInfoComponent) { + playerInfoComponent = playerInfoNode.addComponent(PlayerInfo); + } + + this.playerInfoComponents.set(playerData.playerId, playerInfoComponent); + console.log('[UIGame] 创建玩家信息组件:', playerData.playerName); + } + + // 更新玩家信息 + playerInfoComponent.updatePlayerInfo(playerData); + } + + // 移除不再存在的玩家信息 + for (const [playerId, playerInfoComponent] of this.playerInfoComponents.entries()) { + if (!currentPlayerIds.has(playerId)) { + playerInfoComponent.node.destroy(); + this.playerInfoComponents.delete(playerId); + console.log('[UIGame] 移除玩家信息组件:', playerId); + } + } + } + + /** + * 清理所有玩家信息组件 + */ + private clearPlayerInfoComponents(): void { + for (const [playerId, playerInfoComponent] of this.playerInfoComponents.entries()) { + if (playerInfoComponent && playerInfoComponent.node) { + playerInfoComponent.node.destroy(); + } + } + this.playerInfoComponents.clear(); + + // 释放预制体资源 + if (this.playerInfoPrefab) { + ResMgr.getInstance().release('resources', 'res://UI/Game/PlayerInfo'); + this.playerInfoPrefab = null; + } + + console.log('[UIGame] 已清理所有玩家信息组件'); + } + } diff --git a/client/assets/scripts/App/Game/World.ts b/client/assets/scripts/App/Game/World.ts index 77ae4c2..76768a9 100644 --- a/client/assets/scripts/App/Game/World.ts +++ b/client/assets/scripts/App/Game/World.ts @@ -1,12 +1,15 @@ import { instantiate, Node, Prefab } from 'cc'; import { NetManager } from '../../Framework/Net/NetManager'; import { ResMgr } from '../../Framework/ResMgr/ResMgr'; +import { UIMgr } from '../../Framework/UI/UIMgr'; import { MsgPlayerJoin } from '../../Shared/protocols/MsgPlayerJoin'; import { MsgPlayerMove } from '../../Shared/protocols/MsgPlayerMove'; import { PlayerInfo } from '../../Shared/protocols/MsgResLogin'; import { CameraController } from './CameraController'; import { PlayerController } from './PlayerController'; +import { PlayerInfoData } from './PlayerInfo'; import { RemotePlayer } from './RemotePlayer'; +import { UIGame } from './UIGame'; /** * World 世界管理器 @@ -37,6 +40,9 @@ export class World { /** 摄像机控制器 */ private cameraController: CameraController = null; + /** UIGame实例 */ + private uiGameInstance: UIGame = null; + private constructor() { } public static getInstance(): World { @@ -50,11 +56,15 @@ export class World { * 初始化世界 * @param worldRoot 世界根节点 * @param localPlayer 本地玩家信息 + * @param otherPlayers 其他在线玩家信息 */ - public async init(worldRoot: Node, localPlayer: PlayerInfo): Promise { + public async init(worldRoot: Node, localPlayer: PlayerInfo, otherPlayers?: PlayerInfo[]): Promise { this.worldRoot = worldRoot; this.localPlayer = localPlayer; + // 获取UIGame实例 + this.uiGameInstance = UIMgr.getInstance().get(UIGame) as UIGame; + // 加载玩家模型预制体 await this.loadPlayerPrefab(); @@ -64,6 +74,9 @@ export class World { // 创建本地玩家 await this.createLocalPlayer(); + // 创建其他已在线的玩家 + await this.createOtherPlayers(otherPlayers); + console.log('[World] 世界初始化完成'); } @@ -113,7 +126,7 @@ export class World { // 实例化玩家节点 this.localPlayerNode = instantiate(this.playerPrefab); this.localPlayerNode.name = `Player_${this.localPlayer.id}_Local`; - this.localPlayerNode.setPosition(x, 0, y); + this.localPlayerNode.setPosition(x, y, 0); this.worldRoot.addChild(this.localPlayerNode); // 创建本地玩家控制器 @@ -127,6 +140,56 @@ export class World { console.log('[World] 本地玩家创建完成:', this.localPlayer.name); } + /** + * 创建其他已在线的玩家 + */ + private async createOtherPlayers(otherPlayers?: PlayerInfo[]): Promise { + if (!otherPlayers || otherPlayers.length === 0) { + console.log('[World] 无其他在线玩家'); + return; + } + + if (!this.playerPrefab) { + console.error('[World] 玩家模型预制体未加载,无法创建其他玩家'); + return; + } + + console.log('[World] 开始创建其他在线玩家,数量:', otherPlayers.length); + + for (const playerInfo of otherPlayers) { + // 跳过本地玩家(双重保险) + if (playerInfo.id === this.localPlayer.id) { + continue; + } + + // 检查是否已存在 + if (this.remotePlayers.has(playerInfo.id)) { + console.warn('[World] 远程玩家已存在:', playerInfo.id); + continue; + } + + try { + // 实例化玩家节点 + const playerNode = instantiate(this.playerPrefab); + playerNode.name = `Player_${playerInfo.id}_Remote`; + const x = playerInfo.position.x / 1000; + const y = playerInfo.position.y / 1000; + playerNode.setPosition(x, y, 0); + this.worldRoot.addChild(playerNode); + + // 创建远程玩家控制器 + const remotePlayer = new RemotePlayer(); + remotePlayer.init(playerNode, playerInfo.id, playerInfo.name, playerInfo.position); + + this.remotePlayers.set(playerInfo.id, remotePlayer); + + console.log('[World] 其他玩家创建完成:', playerInfo.name); + } catch (error) { + console.error('[World] 创建其他玩家失败:', playerInfo.name, error); + } + } + } + /** * 处理玩家加入消息 */ @@ -160,7 +223,9 @@ export class World { // 实例化玩家节点 const playerNode = instantiate(this.playerPrefab); playerNode.name = `Player_${msg.playerId}_Remote`; - playerNode.setPosition(msg.position.x, 0, msg.position.y); + const x = msg.position.x / 1000 + const y = msg.position.y / 1000 + playerNode.setPosition(x, y, 0); this.worldRoot.addChild(playerNode); // 创建远程玩家控制器 @@ -195,6 +260,61 @@ export class World { return this.localPlayerController; } + /** + * 更新所有玩家信息显示 + */ + public updateAllPlayersInfo(): void { + if (!this.uiGameInstance || !this.cameraController) { + return; + } + + const uinode = this.uiGameInstance.getNode() + const playerDataList: PlayerInfoData[] = []; + + // 添加本地玩家信息 + if (this.localPlayerNode && this.localPlayer) { + // 直接使用节点的世界坐标 + const worldPos = this.localPlayerNode.worldPosition; + // 正确的坐标转换流程:世界坐标 -> 世界摄像机坐标 -> UI摄像机坐标 + const uiScreenPos = this.cameraController.worldToUICamera(worldPos, uinode); + + playerDataList.push({ + playerId: this.localPlayer.id, + playerName: this.localPlayer.name, + screenPosition: uiScreenPos, + realPosition: worldPos // 使用世界坐标作为真实位置 + }); + } + + // 添加远程玩家信息 + for (const [playerId, remotePlayer] of this.remotePlayers.entries()) { + const remotePlayerNode = remotePlayer.getPlayerNode(); + if (remotePlayerNode) { + const worldPos = remotePlayerNode.worldPosition; + // 正确的坐标转换流程:世界坐标 -> 世界摄像机坐标 -> UI摄像机坐标 + const uiScreenPos = this.cameraController.worldToUICamera(worldPos, uinode); + + playerDataList.push({ + playerId: playerId, + playerName: remotePlayer.getPlayerName(), + screenPosition: uiScreenPos, + realPosition: worldPos // 使用世界坐标作为真实位置 + }); + } + } + + // 更新UI显示 + this.uiGameInstance.updatePlayerInfo(playerDataList); + } + + /** + * 在update中定期更新玩家信息显示 + */ + public update(deltaTime: number): void { + // 每帧更新玩家信息显示 + this.updateAllPlayersInfo(); + } + /** * 销毁世界 */ diff --git a/client/assets/scripts/App/Login/UILogin.ts b/client/assets/scripts/App/Login/UILogin.ts index e876419..789a1c4 100644 --- a/client/assets/scripts/App/Login/UILogin.ts +++ b/client/assets/scripts/App/Login/UILogin.ts @@ -97,10 +97,7 @@ export class UILogin extends UIBase { // 登录成功,切换到游戏状态 const appStatusManager = AppStatusManager.getInstance(); - appStatusManager.changeState('Game', { - player: result.player, - isNewPlayer: result.isNewPlayer || false - }); + appStatusManager.changeState('Game', result); // 隐藏登录界面 this.hide(); diff --git a/client/assets/scripts/Shared/protocols/MsgPlayerJoin.ts b/client/assets/scripts/Shared/protocols/MsgPlayerJoin.ts index 28c3d40..69a2a44 100644 --- a/client/assets/scripts/Shared/protocols/MsgPlayerJoin.ts +++ b/client/assets/scripts/Shared/protocols/MsgPlayerJoin.ts @@ -11,7 +11,7 @@ export interface MsgPlayerJoin { /** 玩家昵称 */ playerName: string; - /** 玩家位置 */ + /** 玩家位置(客户端坐标,放大1000倍后的整数) */ position: Position; /** 是否新玩家 */ diff --git a/client/assets/scripts/Shared/protocols/MsgPlayerMove.ts b/client/assets/scripts/Shared/protocols/MsgPlayerMove.ts index 0ea3694..15c3183 100644 --- a/client/assets/scripts/Shared/protocols/MsgPlayerMove.ts +++ b/client/assets/scripts/Shared/protocols/MsgPlayerMove.ts @@ -11,7 +11,7 @@ export interface MsgPlayerMove { /** 玩家昵称 */ playerName: string; - /** 移动后的位置 */ + /** 移动后的位置(客户端坐标,放大1000倍后的整数) */ position: Position; /** 移动时间戳 */ diff --git a/client/assets/scripts/Shared/protocols/MsgReqMove.ts b/client/assets/scripts/Shared/protocols/MsgReqMove.ts index fbe70b7..4f505a5 100644 --- a/client/assets/scripts/Shared/protocols/MsgReqMove.ts +++ b/client/assets/scripts/Shared/protocols/MsgReqMove.ts @@ -2,9 +2,9 @@ * 移动请求消息 */ export interface MsgReqMove { - /** X坐标 */ + /** X坐标(放大1000倍后取整,服务器内部除以1000作为实际坐标) */ x: number; - /** Y坐标 */ + /** Y坐标(放大1000倍后取整,服务器内部除以1000作为实际坐标) */ y: number; } \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/MsgResLogin.ts b/client/assets/scripts/Shared/protocols/MsgResLogin.ts index 12d6819..1264720 100644 --- a/client/assets/scripts/Shared/protocols/MsgResLogin.ts +++ b/client/assets/scripts/Shared/protocols/MsgResLogin.ts @@ -10,10 +10,10 @@ export interface PlayerInfo { /** 玩家昵称 */ name: string; - /** 当前位置 */ + /** 当前位置(客户端坐标,放大1000倍后的整数) */ position: Position; - /** 出生点 */ + /** 出生点(客户端坐标,放大1000倍后的整数) */ spawnPoint: Position; /** 当前生命值 */ @@ -47,4 +47,7 @@ export interface MsgResLogin { /** 是否新玩家 */ isNewPlayer?: boolean; + + /** 房间内其他在线玩家信息 */ + otherPlayers?: PlayerInfo[]; } \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/MsgResMove.ts b/client/assets/scripts/Shared/protocols/MsgResMove.ts index 3b24251..a5d936e 100644 --- a/client/assets/scripts/Shared/protocols/MsgResMove.ts +++ b/client/assets/scripts/Shared/protocols/MsgResMove.ts @@ -10,6 +10,6 @@ export interface MsgResMove { /** 消息 */ message: string; - /** 新位置(成功时返回) */ + /** 新位置(成功时返回,客户端坐标,放大1000倍后的整数) */ position?: Position; } \ No newline at end of file diff --git a/client/assets/scripts/Shared/protocols/base.ts b/client/assets/scripts/Shared/protocols/base.ts index cdf5c5d..f1be2d4 100644 --- a/client/assets/scripts/Shared/protocols/base.ts +++ b/client/assets/scripts/Shared/protocols/base.ts @@ -15,7 +15,7 @@ export interface BaseMessage { } /** - * 位置坐标 + * 位置坐标(客户端使用放大1000倍后的整数坐标) */ export interface Position { x: number; diff --git a/client/assets/scripts/Shared/protocols/serviceProto.ts b/client/assets/scripts/Shared/protocols/serviceProto.ts index 2ad665d..4be83d1 100644 --- a/client/assets/scripts/Shared/protocols/serviceProto.ts +++ b/client/assets/scripts/Shared/protocols/serviceProto.ts @@ -27,7 +27,7 @@ export interface ServiceType { } export const serviceProto: ServiceProto = { - "version": 5, + "version": 6, "services": [ { "id": 0, @@ -273,6 +273,18 @@ export const serviceProto: ServiceProto = { "type": "Boolean" }, "optional": true + }, + { + "id": 4, + "name": "otherPlayers", + "type": { + "type": "Array", + "elementType": { + "type": "Reference", + "target": "MsgResLogin/PlayerInfo" + } + }, + "optional": true } ] }, diff --git a/client/settings/v2/packages/project.json b/client/settings/v2/packages/project.json index 4129dde..74eeba0 100644 --- a/client/settings/v2/packages/project.json +++ b/client/settings/v2/packages/project.json @@ -1,3 +1,9 @@ { - "__version__": "1.0.6" + "__version__": "1.0.6", + "general": { + "designResolution": { + "width": 720, + "height": 1440 + } + } }