Files
rougelike-demo/client/assets/scripts/App/Game/CameraController.ts
2025-12-18 16:04:56 +08:00

264 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/** 世界摄像机节点 (Main Camera) */
private worldCameraNode: Node = 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);
/** 跟随速度 */
@property({ displayName: '跟随速度' })
public followSpeed: number = 5;
/** 是否平滑跟随 */
@property({ displayName: '平滑跟随' })
public smoothFollow: boolean = true;
onLoad() {
this.findCameras();
}
/**
* 查找所有摄像机
*/
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.worldCameraNode = mainCameraNode;
this.worldCamera = mainCameraNode.getComponent(Camera);
console.log('[CameraController] 找到世界摄像机:', mainCameraNode.name);
} else {
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节点');
}
}
/**
* 设置跟随目标
* @param target 跟随的目标节点
*/
public setTarget(target: Node): void {
this.target = target;
if (this.target && this.worldCameraNode) {
// 立即设置初始位置
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 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.worldCameraNode) {
this.updateCameraPosition(this.smoothFollow, deltaTime);
}
}
/**
* 更新摄像机位置
* @param smooth 是否使用平滑跟随
* @param deltaTime 帧时间(smooth为true时需要)
*/
private updateCameraPosition(smooth: boolean = true, deltaTime: number = 0): void {
if (!this.target || !this.worldCameraNode) {
return;
}
// 计算目标位置
const targetPosition = new Vec3();
Vec3.add(targetPosition, this.target.worldPosition, this.offset);
if (smooth && deltaTime > 0) {
// 平滑跟随
const currentPosition = this.worldCameraNode.worldPosition;
const newPosition = new Vec3();
Vec3.lerp(newPosition, currentPosition, targetPosition, this.followSpeed * deltaTime);
this.worldCameraNode.setWorldPosition(newPosition);
} else {
// 立即跟随
this.worldCameraNode.setWorldPosition(targetPosition);
}
// 让摄像机看向目标
this.worldCameraNode.lookAt(this.target.worldPosition);
}
/**
* 停止跟随
*/
public stopFollow(): void {
this.target = null;
console.log('[CameraController] 停止跟随');
}
/**
* 将世界坐标转换为指定摄像机的屏幕坐标
* @param worldPosition 世界坐标Vec3
* @param camera 目标摄像机
* @returns 摄像机坐标系下的屏幕坐标Vec3z为深度
*/
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.worldCameraNode = null;
this.worldCamera = null;
this.uiCameraNode = null;
this.uiCamera = null;
console.log('[CameraController] 摄像机控制器已销毁');
}
}