From e677b1e11bbc9d41fb74af41a2b73cd3bd7da591 Mon Sep 17 00:00:00 2001 From: janing <1175861874@qq.com> Date: Thu, 18 Dec 2025 13:27:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9ECameraController=EF=BC=8C?= =?UTF-8?q?=E7=8E=B0=E5=9C=A8=E9=A1=B9=E7=9B=AE=E5=8F=AF=E4=BB=A5=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E8=BF=90=E4=BD=9C=E4=BA=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assets/res/UI/{World.meta => Game.meta} | 0 .../UIWorld.prefab => Game/UIGame.prefab} | 4 +- .../UIGame.prefab.meta} | 2 +- client/assets/scenes/main.scene | 4 +- .../scripts/App/Game/CameraController.ts | 138 ++++++++++++++++++ .../scripts/App/Game/CameraController.ts.meta | 9 ++ .../scripts/App/Game/PlayerController.ts | 18 +-- client/assets/scripts/App/Game/README.md | 33 ++++- client/assets/scripts/App/Game/UIGame.ts | 13 +- client/assets/scripts/App/Login/UILogin.ts | 48 +++--- client/tsconfig.json | 7 +- 11 files changed, 223 insertions(+), 53 deletions(-) rename client/assets/res/UI/{World.meta => Game.meta} (100%) rename client/assets/res/UI/{World/UIWorld.prefab => Game/UIGame.prefab} (99%) rename client/assets/res/UI/{World/UIWorld.prefab.meta => Game/UIGame.prefab.meta} (86%) create mode 100644 client/assets/scripts/App/Game/CameraController.ts create mode 100644 client/assets/scripts/App/Game/CameraController.ts.meta diff --git a/client/assets/res/UI/World.meta b/client/assets/res/UI/Game.meta similarity index 100% rename from client/assets/res/UI/World.meta rename to client/assets/res/UI/Game.meta diff --git a/client/assets/res/UI/World/UIWorld.prefab b/client/assets/res/UI/Game/UIGame.prefab similarity index 99% rename from client/assets/res/UI/World/UIWorld.prefab rename to client/assets/res/UI/Game/UIGame.prefab index 5ed5a93..d4dde83 100644 --- a/client/assets/res/UI/World/UIWorld.prefab +++ b/client/assets/res/UI/Game/UIGame.prefab @@ -1,7 +1,7 @@ [ { "__type__": "cc.Prefab", - "_name": "UIWorld", + "_name": "UIGame", "_objFlags": 0, "__editorExtras__": {}, "_native": "", @@ -13,7 +13,7 @@ }, { "__type__": "cc.Node", - "_name": "UIWorld", + "_name": "UIGame", "_objFlags": 0, "__editorExtras__": {}, "_parent": null, diff --git a/client/assets/res/UI/World/UIWorld.prefab.meta b/client/assets/res/UI/Game/UIGame.prefab.meta similarity index 86% rename from client/assets/res/UI/World/UIWorld.prefab.meta rename to client/assets/res/UI/Game/UIGame.prefab.meta index 3769b44..bb43816 100644 --- a/client/assets/res/UI/World/UIWorld.prefab.meta +++ b/client/assets/res/UI/Game/UIGame.prefab.meta @@ -8,6 +8,6 @@ ], "subMetas": {}, "userData": { - "syncNodeName": "UIWorld" + "syncNodeName": "UIGame" } } diff --git a/client/assets/scenes/main.scene b/client/assets/scenes/main.scene index ca2c22a..2923cab 100644 --- a/client/assets/scenes/main.scene +++ b/client/assets/scenes/main.scene @@ -259,7 +259,7 @@ }, { "__type__": "cc.Node", - "_name": "Plane", + "_name": "Game", "_objFlags": 0, "__editorExtras__": {}, "_parent": { @@ -412,7 +412,7 @@ "_priority": 1073741824, "_fov": 45, "_fovAxis": 0, - "_orthoHeight": 360, + "_orthoHeight": 412.3341946597761, "_near": 1, "_far": 2000, "_color": { diff --git a/client/assets/scripts/App/Game/CameraController.ts b/client/assets/scripts/App/Game/CameraController.ts new file mode 100644 index 0000000..38449da --- /dev/null +++ b/client/assets/scripts/App/Game/CameraController.ts @@ -0,0 +1,138 @@ +import { Camera, Component, Node, Vec3, _decorator } from 'cc'; + +const { ccclass, property } = _decorator; + +/** + * CameraController 摄像机控制器 + * 负责让摄像机跟随指定的目标物体 + */ +@ccclass('CameraController') +export class CameraController extends Component { + /** 跟随目标 */ + private target: Node = null; + + /** 摄像机节点 */ + private cameraNode: Node = null; + + /** 摄像机组件 */ + private camera: Camera = null; + + /** 相对偏移量 */ + private offset: Vec3 = new Vec3(0, 10, 8); + + /** 跟随速度 */ + @property({ displayName: '跟随速度' }) + public followSpeed: number = 5; + + /** 是否平滑跟随 */ + @property({ displayName: '平滑跟随' }) + public smoothFollow: boolean = true; + + onLoad() { + this.findMainCamera(); + } + + /** + * 查找主摄像机 + */ + private findMainCamera(): void { + // 查找名为 "Main Camera" 的摄像机 + const mainCameraNode = this.node.scene.getChildByName('Main Camera'); + if (mainCameraNode) { + this.cameraNode = mainCameraNode; + this.camera = mainCameraNode.getComponent(Camera); + console.log('[CameraController] 找到主摄像机:', mainCameraNode.name); + } else { + console.error('[CameraController] 未找到主摄像机(Main Camera)'); + } + } + + /** + * 设置跟随目标 + * @param target 跟随的目标节点 + */ + public setTarget(target: Node): void { + this.target = target; + + if (this.target && this.cameraNode) { + // 立即设置初始位置 + this.updateCameraPosition(false); + console.log('[CameraController] 设置跟随目标:', target.name); + } + } + + /** + * 设置摄像机偏移量 + * @param offset 相对于目标的偏移量 + */ + public setOffset(offset: Vec3): void { + this.offset.set(offset); + } + + /** + * 获取当前跟随目标 + */ + public getTarget(): Node { + return this.target; + } + + /** + * 获取摄像机节点 + */ + public getCameraNode(): Node { + return this.cameraNode; + } + + update(deltaTime: number) { + if (this.target && this.cameraNode) { + this.updateCameraPosition(this.smoothFollow, deltaTime); + } + } + + /** + * 更新摄像机位置 + * @param smooth 是否使用平滑跟随 + * @param deltaTime 帧时间(smooth为true时需要) + */ + private updateCameraPosition(smooth: boolean = true, deltaTime: number = 0): void { + if (!this.target || !this.cameraNode) { + return; + } + + // 计算目标位置 + const targetPosition = new Vec3(); + Vec3.add(targetPosition, this.target.worldPosition, this.offset); + + if (smooth && deltaTime > 0) { + // 平滑跟随 + const currentPosition = this.cameraNode.worldPosition; + const newPosition = new Vec3(); + Vec3.lerp(newPosition, currentPosition, targetPosition, this.followSpeed * deltaTime); + this.cameraNode.setWorldPosition(newPosition); + } else { + // 立即跟随 + this.cameraNode.setWorldPosition(targetPosition); + } + + // 让摄像机看向目标 + this.cameraNode.lookAt(this.target.worldPosition); + } + + /** + * 停止跟随 + */ + public stopFollow(): void { + this.target = null; + console.log('[CameraController] 停止跟随'); + } + + /** + * 销毁控制器 + */ + onDestroy(): void { + this.target = null; + this.cameraNode = null; + this.camera = null; + console.log('[CameraController] 摄像机控制器已销毁'); + } +} \ No newline at end of file diff --git a/client/assets/scripts/App/Game/CameraController.ts.meta b/client/assets/scripts/App/Game/CameraController.ts.meta new file mode 100644 index 0000000..f6718d7 --- /dev/null +++ b/client/assets/scripts/App/Game/CameraController.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "b94f3dcb-3630-4ebd-87b3-81ae3d560352", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/App/Game/PlayerController.ts b/client/assets/scripts/App/Game/PlayerController.ts index fa200ee..f36c70b 100644 --- a/client/assets/scripts/App/Game/PlayerController.ts +++ b/client/assets/scripts/App/Game/PlayerController.ts @@ -1,8 +1,8 @@ -import { _decorator, Component, Node, EventKeyboard, KeyCode, Input, input, Vec3 } from 'cc'; -import { NetManager } from '../../Framework/Net/NetManager'; -import { ReqMove, ResMove } from '../../Shared/protocols/PtlMove'; -import { PlayerInfo } from '../../Shared/protocols/PtlLogin'; +import { _decorator, Component, EventKeyboard, Input, input, KeyCode, Vec3 } from 'cc'; import { RoleController } from '../../CC/RoleController'; +import { NetManager } from '../../Framework/Net/NetManager'; +import { PlayerInfo } from '../../Shared/protocols/MsgResLogin'; +import { MoveMessagePair } from '../Msg/Pair/MoveMessagePair'; const { ccclass } = _decorator; @@ -134,7 +134,7 @@ export class PlayerController extends Component { private move(dt: number): void { // 计算移动增量 const moveOffset = this.moveDirection.clone().multiplyScalar(this.moveSpeed * dt); - + // 更新节点位置 const currentPos = this.node.position.clone(); currentPos.add(moveOffset); @@ -163,7 +163,7 @@ export class PlayerController extends Component { */ private checkSendMoveRequest(currentPos: Vec3): void { const distance = Vec3.distance(currentPos, this.lastSentPosition); - + // 如果移动距离超过阈值,发送移动请求 if (distance >= this.moveSendThreshold) { this.sendMoveRequest(currentPos.x, currentPos.z); @@ -177,9 +177,9 @@ export class PlayerController extends Component { private async sendMoveRequest(x: number, z: number): Promise { try { const netManager = NetManager.getInstance(); - const result = await netManager.callApi('Move', { - x: x, - y: z + const result = await netManager.callMsg(new MoveMessagePair(), { + x: Math.round(x * 1000), + y: Math.round(z * 1000) }); if (!result) { diff --git a/client/assets/scripts/App/Game/README.md b/client/assets/scripts/App/Game/README.md index b99fa30..14d43a9 100644 --- a/client/assets/scripts/App/Game/README.md +++ b/client/assets/scripts/App/Game/README.md @@ -11,6 +11,7 @@ App/Game/ ├── World.ts # 世界管理器,管理所有玩家 ├── PlayerController.ts # 本地玩家控制器,处理输入和移动 ├── RemotePlayer.ts # 远程玩家类,处理其他玩家的显示和同步 +├── CameraController.ts # 摄像机控制器,跟随本地玩家 ├── UIGame.ts # 游戏主界面UI └── README.md # 本文档 ``` @@ -82,7 +83,37 @@ await World.getInstance().init(worldRoot, playerInfo); - `updatePosition(position)`: 更新远程玩家位置(收到 MsgPlayerMove 时调用) - `destroy()`: 销毁远程玩家 -### 4. UIGame (游戏主界面) +### 4. CameraController (摄像机控制器) + +**职责:** +- 自动查找场景中的主摄像机(Main Camera) +- 让摄像机跟随指定的目标物体(通常是本地玩家) +- 提供平滑跟随和即时跟随两种模式 +- 自动让摄像机朝向目标 + +**主要特性:** +- **自动查找**: 自动在场景中查找名为"Main Camera"的摄像机节点 +- **平滑跟随**: 使用线性插值实现平滑的摄像机跟随效果 +- **偏移设置**: 可配置摄像机相对于目标的偏移量(默认: Y+10, Z+8) +- **朝向目标**: 摄像机会自动朝向跟随目标 + +**主要方法:** +- `setTarget(target)`: 设置跟随目标节点(本地玩家) +- `setOffset(offset)`: 设置摄像机相对偏移量 +- `stopFollow()`: 停止跟随 +- `getTarget()`: 获取当前跟随目标 +- `getCameraNode()`: 获取摄像机节点 + +**属性配置:** +- `followSpeed`: 跟随速度(默认: 5) +- `smoothFollow`: 是否启用平滑跟随(默认: true) + +**使用说明:** +- CameraController 由 World 管理器自动创建 +- 只有本地玩家会绑定摄像机跟随,远程玩家不会 +- 摄像机控制器会挂载到世界根节点上 + +### 5. UIGame (游戏主界面) **职责:** - 游戏主界面 UI 组件 diff --git a/client/assets/scripts/App/Game/UIGame.ts b/client/assets/scripts/App/Game/UIGame.ts index 6966a38..07a7dd8 100644 --- a/client/assets/scripts/App/Game/UIGame.ts +++ b/client/assets/scripts/App/Game/UIGame.ts @@ -1,6 +1,5 @@ -import { _decorator, Component, Node } from 'cc'; +import { _decorator } from 'cc'; import { UIBase } from '../../Framework/UI/UIBase'; -import { World } from './World'; const { ccclass, property } = _decorator; @@ -10,8 +9,6 @@ const { ccclass, property } = _decorator; */ @ccclass('UIGame') export class UIGame extends UIBase { - @property(Node) - worldRoot: Node = null; protected onLoad(): void { console.log('[UIGame] onLoad'); @@ -33,13 +30,7 @@ export class UIGame extends UIBase { * 获取 UI 预制体路径 */ public onGetUrl(): string { - return 'res://UI/UIGame'; + return 'res://UI/Game/UIGame'; } - /** - * 获取世界根节点 - */ - public getWorldRoot(): Node { - return this.worldRoot; - } } diff --git a/client/assets/scripts/App/Login/UILogin.ts b/client/assets/scripts/App/Login/UILogin.ts index dc25089..e876419 100644 --- a/client/assets/scripts/App/Login/UILogin.ts +++ b/client/assets/scripts/App/Login/UILogin.ts @@ -1,8 +1,8 @@ -import { _decorator, EditBox, Button } from 'cc'; -import { UIBase } from '../../Framework/UI/UIBase'; +import { _decorator, Button, EditBox } from 'cc'; import { NetManager } from '../../Framework/Net/NetManager'; -import { ReqLogin, ResLogin } from '../../Framework/Net/LoginProtocol'; +import { UIBase } from '../../Framework/UI/UIBase'; import { AppStatusManager } from '../AppStatus/AppStatusManager'; +import { LoginMessagePair } from '../Msg/Pair/LoginMessagePair'; const { ccclass } = _decorator; @@ -19,34 +19,34 @@ const { ccclass } = _decorator; export class UILogin extends UIBase { /** 账号输入框 */ private _inputAccount: EditBox | null = null; - + /** 登录按钮 */ private _btnLogin: Button | null = null; - + /** 是否正在登录中 */ private _isLogging: boolean = false; - + /** * 获取UI资源路径 */ onGetUrl(): string { return 'res://UI/Login/UILogin'; } - + /** * UI初始化 */ async onStart(params?: any): Promise { console.log('[UILogin] 登录界面初始化', params); - + // 使用GetChild查找子节点 this._inputAccount = this.GetChild('mid/input_account', EditBox); this._btnLogin = this.GetChild('mid/btn_login', Button); - + // 使用SetClick绑定点击事件 this.SetClick(this._btnLogin, this.onLoginClick, this); } - + /** * 登录按钮点击 */ @@ -55,21 +55,21 @@ export class UILogin extends UIBase { console.log('[UILogin] 正在登录中,请稍候...'); return; } - + // 获取账号 const account = this._inputAccount?.string?.trim() || ''; - + if (!account) { console.warn('[UILogin] 请输入账号'); // TODO: 显示提示UI return; } - + console.log(`[UILogin] 开始登录,账号: ${account}`); - + // 开始登录 this._isLogging = true; - + try { await this.login(account); } catch (error) { @@ -79,29 +79,29 @@ export class UILogin extends UIBase { this._isLogging = false; } } - + /** * 执行登录 * @param account 账号 */ private async login(account: string): Promise { const netManager = NetManager.getInstance(); - + // 使用类型化的登录协议 - const result = await netManager.callApi('Login', { - account: account - }); - + const result = await netManager.callMsg(new LoginMessagePair(), { + playerId: account + },); + if (result && result.success) { console.log('[UILogin] 登录成功:', result); - + // 登录成功,切换到游戏状态 const appStatusManager = AppStatusManager.getInstance(); appStatusManager.changeState('Game', { player: result.player, isNewPlayer: result.isNewPlayer || false }); - + // 隐藏登录界面 this.hide(); } else { @@ -109,7 +109,7 @@ export class UILogin extends UIBase { // TODO: 显示错误提示UI } } - + /** * UI清理 */ diff --git a/client/tsconfig.json b/client/tsconfig.json index c94fedb..b1e50d7 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -1,11 +1,12 @@ { /* Base configuration. Do not edit this field. */ "extends": "./temp/tsconfig.cocos.json", - /* Add your custom configuration here. */ "compilerOptions": { "strict": false, "target": "es2020", - "downlevelIteration": true + "downlevelIteration": true, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true } -} +} \ No newline at end of file