121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
// CameraOrbit.ts
|
||
import { _decorator, Component, Node, Vec3, EventTouch, input, Input, v3 } from 'cc';
|
||
const { ccclass, property } = _decorator;
|
||
|
||
@ccclass('CameraOrbit')
|
||
export class CameraOrbit extends Component {
|
||
@property(Node)
|
||
public targetNode: Node | null = null; // 需通过脚本动态设置目标节点
|
||
|
||
private lastTouchX: number = 0;
|
||
private readonly rotationSpeed: number = 0.6;
|
||
private isTouching: boolean = false;
|
||
private currentAngle: number = 0;
|
||
|
||
// 动态计算参数
|
||
private radius: number = 5; // 初始默认值,会自动修正
|
||
private heightOffset: number = 2; // 初始默认值,会自动修正
|
||
private centerOffset = v3(0, 1, 0);// 中心点Y偏移(角色腰部高度)
|
||
|
||
// 初始化时自动校准参数
|
||
private isInitialized: boolean = false;
|
||
|
||
start() {
|
||
this.tryInitialize();
|
||
}
|
||
|
||
// 每帧更新摄像机位置
|
||
lateUpdate() {
|
||
if (!this.isInitialized) return;
|
||
this.updateCameraPosition(true); // true表示保持当前角度
|
||
}
|
||
|
||
// 动态设置目标节点
|
||
public setTarget(node: Node) {
|
||
this.targetNode = node;
|
||
this.tryInitialize();
|
||
}
|
||
|
||
private tryInitialize() {
|
||
if (!this.targetNode || this.isInitialized) return;
|
||
|
||
// 计算机型参数与目标的相对关系
|
||
const targetWorldPos = this.targetNode.worldPosition;
|
||
const cameraWorldPos = this.node.worldPosition;
|
||
|
||
// 计算水平距离(半径)
|
||
const dx = cameraWorldPos.x - targetWorldPos.x;
|
||
const dz = cameraWorldPos.z - targetWorldPos.z;
|
||
this.radius = Math.sqrt(dx * dx + dz * dz);
|
||
|
||
// 计算起始角度(世界坐标系)
|
||
this.currentAngle = Math.atan2(dx, dz) * 180 / Math.PI;
|
||
|
||
// 计算高度差
|
||
this.heightOffset = cameraWorldPos.y - targetWorldPos.y;
|
||
|
||
this.isInitialized = true;
|
||
}
|
||
|
||
private updateCameraPosition(keepAngle: boolean = false) {
|
||
if (!this.isInitialized) return;
|
||
|
||
const targetWorldPos = this.targetNode!.worldPosition.clone()
|
||
.add(this.centerOffset);
|
||
|
||
// 计算水平坐标
|
||
const angleRad = this.currentAngle * Math.PI / 180;
|
||
const x = targetWorldPos.x + Math.sin(angleRad) * this.radius;
|
||
const z = targetWorldPos.z + Math.cos(angleRad) * this.radius;
|
||
|
||
// 设置摄像机世界坐标
|
||
this.node.worldPosition = new Vec3(
|
||
x,
|
||
targetWorldPos.y + this.heightOffset,
|
||
z
|
||
);
|
||
|
||
// 确保准确朝向目标中心点
|
||
this.node.lookAt(targetWorldPos);
|
||
}
|
||
|
||
// 触摸事件处理(保持不变)
|
||
onEnable() {
|
||
input.on(Input.EventType.TOUCH_START, this.onTouchStart, this);
|
||
input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
|
||
input.on(Input.EventType.TOUCH_END, this.onTouchEnd, this);
|
||
input.on(Input.EventType.TOUCH_CANCEL, this.onTouchEnd, this);
|
||
}
|
||
|
||
onDisable() {
|
||
input.off(Input.EventType.TOUCH_START, this.onTouchStart, this);
|
||
input.off(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
|
||
input.off(Input.EventType.TOUCH_END, this.onTouchEnd, this);
|
||
input.off(Input.EventType.TOUCH_CANCEL, this.onTouchEnd, this);
|
||
}
|
||
|
||
private onTouchStart(event: EventTouch) {
|
||
this.isTouching = true;
|
||
this.lastTouchX = event.getLocationX();
|
||
}
|
||
|
||
private onTouchMove(event: EventTouch) {
|
||
if (!this.isTouching) return;
|
||
|
||
const currentTouchX = event.getLocationX();
|
||
const deltaX = currentTouchX - this.lastTouchX;
|
||
|
||
// 更新角度
|
||
this.currentAngle -= deltaX * this.rotationSpeed;
|
||
this.currentAngle = (this.currentAngle + 360) % 360;
|
||
|
||
// 立即更新摄像机位置
|
||
this.updateCameraPosition();
|
||
this.lastTouchX = currentTouchX;
|
||
}
|
||
|
||
private onTouchEnd() {
|
||
this.isTouching = false;
|
||
}
|
||
}
|