198 lines
5.0 KiB
TypeScript
198 lines
5.0 KiB
TypeScript
import { _decorator, Component, Camera, Vec3, Quat, tween, Node, EventTarget } from 'cc';
|
|
const { ccclass, property } = _decorator;
|
|
|
|
// 自定义事件
|
|
const cameraEvent = new EventTarget();
|
|
|
|
// 运镜基础类型
|
|
enum CameraMoveType {
|
|
LINEAR,
|
|
BEZIER,
|
|
ORBIT
|
|
}
|
|
|
|
// 运镜配置接口
|
|
@ccclass('CameraShotConfig')
|
|
class CameraShotConfig {
|
|
@property({ type: Camera })
|
|
targetCamera: Camera = null;
|
|
|
|
@property(Node)
|
|
followTarget: Node = null;
|
|
|
|
@property(Vec3)
|
|
offset = new Vec3(0, 2, -5);
|
|
|
|
@property(Node)
|
|
lookAtTarget: Node = null;
|
|
|
|
@property
|
|
blendTime: number = 1.0;
|
|
|
|
@property
|
|
holdTime: number = 2.0;
|
|
|
|
@property
|
|
enableShake: boolean = false;
|
|
|
|
@property
|
|
shakeIntensity: number = 0.1;
|
|
}
|
|
|
|
// 运镜配置
|
|
class CameraTransitionManager {
|
|
@property({ type: Camera })
|
|
targetCamera: Camera = null;
|
|
|
|
@property
|
|
blendTime: number = 1.0;
|
|
|
|
@property
|
|
holdTime: number = 2.0;
|
|
|
|
@property(Node)
|
|
followTarget: Node = null;
|
|
|
|
@property(Vec3)
|
|
offset = new Vec3(0, 2, -5);
|
|
|
|
@property
|
|
fov: number = 60;
|
|
|
|
@property
|
|
lookAtTarget: Node = null;
|
|
|
|
@property
|
|
enableShake: boolean = false;
|
|
|
|
@property
|
|
shakeIntensity: number = 0.1;
|
|
}
|
|
|
|
@ccclass('CameraRigSystem')
|
|
export class CameraRigSystem extends Component {
|
|
|
|
@property({ type: Camera })
|
|
defaultCamera: Camera = null;
|
|
|
|
@property([CameraShotConfig])
|
|
shotSequence: CameraShotConfig[] = [];
|
|
|
|
private currentCamera: Camera = null;
|
|
private isTransitioning = false;
|
|
|
|
start() {
|
|
this.initializeCameras();
|
|
this.playSequence();
|
|
}
|
|
|
|
// 初始化所有相机
|
|
private initializeCameras() {
|
|
this.shotSequence.forEach(config => {
|
|
if (config.targetCamera) {
|
|
config.targetCamera.node.active = false;
|
|
}
|
|
});
|
|
this.defaultCamera.node.active = true;
|
|
this.currentCamera = this.defaultCamera;
|
|
}
|
|
|
|
// 播放运镜序列
|
|
public async playSequence() {
|
|
for (const shot of this.shotSequence) {
|
|
await this.executeShot(shot);
|
|
}
|
|
this.returnToDefault();
|
|
}
|
|
|
|
// 执行单个运镜镜头
|
|
private async executeShot(config: CameraShotConfig): Promise<void> {
|
|
return new Promise((resolve) => {
|
|
if (!config.targetCamera) {
|
|
console.warn("Missing target camera in shot config");
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
// 准备新相机
|
|
const newCamera = config.targetCamera;
|
|
newCamera.node.active = true;
|
|
|
|
// 相机跟随逻辑
|
|
if (config.followTarget) {
|
|
this.setupFollowCamera(newCamera.node, config);
|
|
}
|
|
|
|
// 运镜过渡
|
|
this.blendCameras(this.currentCamera, newCamera, config.blendTime);
|
|
|
|
// 相机震动
|
|
if (config.enableShake) {
|
|
this.addCameraShake(newCamera.node, config.shakeIntensity);
|
|
}
|
|
|
|
// 保持时间
|
|
setTimeout(() => {
|
|
this.currentCamera = newCamera;
|
|
resolve();
|
|
}, config.holdTime * 1000);
|
|
});
|
|
}
|
|
|
|
// 相机混合过渡
|
|
private blendCameras(from: Camera, to: Camera, duration: number) {
|
|
// 使用权重混合(需要自定义渲染逻辑或使用后期处理)
|
|
// 此处使用简单激活切换 + 动画过渡
|
|
tween(from.node)
|
|
.to(duration/2, { position: to.node.position })
|
|
.start();
|
|
|
|
tween(to.node)
|
|
.to(duration, { position: to.node.position })
|
|
.call(() => {
|
|
from.node.active = false;
|
|
})
|
|
.start();
|
|
}
|
|
|
|
// 设置跟随相机
|
|
private setupFollowCamera(cameraNode: Node, config: CameraShotConfig) {
|
|
const updateFollow = () => {
|
|
if (!config.followTarget) return;
|
|
|
|
const targetPos = config.followTarget.worldPosition;
|
|
const offset = config.offset;
|
|
cameraNode.worldPosition = new Vec3(
|
|
targetPos.x + offset.x,
|
|
targetPos.y + offset.y,
|
|
targetPos.z + offset.z
|
|
);
|
|
|
|
if (config.lookAtTarget) {
|
|
cameraNode.lookAt(config.lookAtTarget.worldPosition);
|
|
}
|
|
};
|
|
|
|
cameraNode.on(Node.EventType.TRANSFORM_CHANGED, updateFollow);
|
|
}
|
|
|
|
// 添加相机震动
|
|
private addCameraShake(cameraNode: Node, intensity: number) {
|
|
const originalPos = cameraNode.position.clone();
|
|
let shakeTimer = 0;
|
|
|
|
const updateShake = (dt: number) => {
|
|
shakeTimer += dt;
|
|
const x = originalPos.x + Math.sin(shakeTimer * 30) * intensity;
|
|
const y = originalPos.y + Math.cos(shakeTimer * 25) * intensity;
|
|
cameraNode.setPosition(x, y, originalPos.z);
|
|
};
|
|
|
|
this.schedule(updateShake);
|
|
}
|
|
|
|
// 返回默认相机
|
|
private returnToDefault() {
|
|
this.blendCameras(this.currentCamera, this.defaultCamera, 1.0);
|
|
}
|
|
} |