新增CameraController,现在项目可以正常运作了。

This commit is contained in:
janing
2025-12-18 13:27:22 +08:00
parent 90175a1665
commit e677b1e11b
11 changed files with 223 additions and 53 deletions

View File

@@ -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] 摄像机控制器已销毁');
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "b94f3dcb-3630-4ebd-87b3-81ae3d560352",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -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<void> {
try {
const netManager = NetManager.getInstance();
const result = await netManager.callApi<ReqMove, ResMove>('Move', {
x: x,
y: z
const result = await netManager.callMsg(new MoveMessagePair(), {
x: Math.round(x * 1000),
y: Math.round(z * 1000)
});
if (!result) {

View File

@@ -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 组件

View File

@@ -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;
}
}

View File

@@ -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<void> {
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<void> {
const netManager = NetManager.getInstance();
// 使用类型化的登录协议
const result = await netManager.callApi<ReqLogin, ResLogin>('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清理
*/