264 lines
8.2 KiB
TypeScript
264 lines
8.2 KiB
TypeScript
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 摄像机坐标系下的屏幕坐标(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.worldCameraNode = null;
|
||
this.worldCamera = null;
|
||
this.uiCameraNode = null;
|
||
this.uiCamera = null;
|
||
console.log('[CameraController] 摄像机控制器已销毁');
|
||
}
|
||
} |