Files
oops-plugin-framework/assets/libs/camera/FreeFlightCamera.ts
2025-12-13 23:32:56 +08:00

211 lines
6.4 KiB
TypeScript
Raw Permalink 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.
/**
* 自由飞行摄像机
* 使用方式:
* 1、组件绑定到任意一设想机上
* 2、通过W(上、S(下)、A(左、D(右、Q(Y轴向下、E(Y轴向上来操作摄像机移动
* 3、按住SHIFT键会加速飞行
* 4、鼠标左或右键按下滑动控制摄像机视角原地旋转
* 5、鼠标滚轮键滑动摄像机拉近或拉远
* 6、只支持PC上使用
*/
import type { EventKeyboard, EventMouse, EventTouch } from 'cc';
import { CCFloat, Component, game, Input, input, KeyCode, math, _decorator } from 'cc';
const { ccclass, property, menu } = _decorator;
const { Vec2, Vec3, Quat } = math;
const v2_1 = new Vec2();
const v2_2 = new Vec2();
const v3_1 = new Vec3();
const qt_1 = new Quat();
const KEYCODE = {
W: 'W'.charCodeAt(0),
S: 'S'.charCodeAt(0),
A: 'A'.charCodeAt(0),
D: 'D'.charCodeAt(0),
Q: 'Q'.charCodeAt(0),
E: 'E'.charCodeAt(0),
SHIFT: KeyCode.SHIFT_LEFT
};
@ccclass('FreeFlightCamera')
@menu('OopsFramework/Camera/FreeFlightCamera (自由飞行摄像机)')
export class FreeFlightCamera extends Component {
@property({
type: CCFloat,
tooltip: '移动速度'
})
moveSpeed = 1;
@property({
type: CCFloat,
tooltip: '按Shift键后的速度'
})
moveSpeedShiftScale = 5;
@property({
type: CCFloat,
slide: true,
range: [0.05, 0.5, 0.01],
tooltip: '移动后惯性效果'
})
damp = 0.2;
@property({
type: CCFloat,
tooltip: '旋转速度'
})
rotateSpeed = 1;
_euler = new Vec3();
_velocity = new Vec3();
_position = new Vec3();
_speedScale = 1;
onLoad() {
input.on(Input.EventType.MOUSE_WHEEL, this.onMouseWheel, this);
input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
input.on(Input.EventType.KEY_UP, this.onKeyUp, this);
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);
Vec3.copy(this._euler, this.node.eulerAngles);
Vec3.copy(this._position, this.node.position);
}
onDestroy() {
input.off(Input.EventType.MOUSE_WHEEL, this.onMouseWheel, this);
input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
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);
}
update(dt: number) {
// position
Vec3.transformQuat(v3_1, this._velocity, this.node.rotation);
Vec3.scaleAndAdd(this._position, this._position, v3_1, this.moveSpeed * this._speedScale);
Vec3.lerp(v3_1, this.node.position, this._position, dt / this.damp); // 向量线性插值产生位移惯性效果
this.node.setPosition(v3_1);
// rotation
Quat.fromEuler(qt_1, this._euler.x, this._euler.y, this._euler.z);
Quat.slerp(qt_1, this.node.rotation, qt_1, dt / this.damp); // 四元素线性插值产生旋转惯性效果
this.node.setRotation(qt_1);
}
onMouseWheel(event: EventMouse) {
const delta = -event.getScrollY() * this.moveSpeed * 0.1; // 向下滚动时增量为正
Vec3.transformQuat(v3_1, Vec3.UNIT_Z, this.node.rotation);
Vec3.scaleAndAdd(this._position, this.node.position, v3_1, delta);
}
onKeyDown(event: EventKeyboard) {
const v = this._velocity;
if (event.keyCode === KEYCODE.SHIFT) {
this._speedScale = this.moveSpeedShiftScale;
}
else if (event.keyCode === KEYCODE.W) {
if (v.z === 0) {
v.z = -1;
}
}
else if (event.keyCode === KEYCODE.S) {
if (v.z === 0) {
v.z = 1;
}
}
else if (event.keyCode === KEYCODE.A) {
if (v.x === 0) {
v.x = -1;
}
}
else if (event.keyCode === KEYCODE.D) {
if (v.x === 0) {
v.x = 1;
}
}
else if (event.keyCode === KEYCODE.Q) {
if (v.y === 0) {
v.y = -1;
}
}
else if (event.keyCode === KEYCODE.E) {
if (v.y === 0) {
v.y = 1;
}
}
}
onKeyUp(event: EventKeyboard) {
const v = this._velocity;
if (event.keyCode === KEYCODE.SHIFT) {
this._speedScale = 1;
}
else if (event.keyCode === KEYCODE.W) {
if (v.z < 0) {
v.z = 0;
}
}
else if (event.keyCode === KEYCODE.S) {
if (v.z > 0) {
v.z = 0;
}
}
else if (event.keyCode === KEYCODE.A) {
if (v.x < 0) {
v.x = 0;
}
}
else if (event.keyCode === KEYCODE.D) {
if (v.x > 0) {
v.x = 0;
}
}
else if (event.keyCode === KEYCODE.Q) {
if (v.y < 0) {
v.y = 0;
}
}
else if (event.keyCode === KEYCODE.E) {
if (v.y > 0) {
v.y = 0;
}
}
}
private onTouchStart(e: EventTouch) {
game.canvas!.requestPointerLock();
}
private onTouchMove(e: EventTouch) {
e.getStartLocation(v2_1);
if (v2_1.x > game.canvas!.width * 0.4) { // rotation
e.getDelta(v2_2);
this._euler.y -= v2_2.x * this.rotateSpeed * 0.1; // 上下旋转
this._euler.x += v2_2.y * this.rotateSpeed * 0.1; // 左右旋转
}
else { // position
e.getLocation(v2_2);
Vec2.subtract(v2_2, v2_2, v2_1);
this._velocity.x = v2_2.x * 0.01;
this._velocity.z = -v2_2.y * 0.01;
}
}
private onTouchEnd(e: EventTouch) {
if (document.exitPointerLock) {
document.exitPointerLock();
}
e.getStartLocation(v2_1);
if (v2_1.x < game.canvas!.width * 0.4) { // position
this._velocity.x = 0;
this._velocity.z = 0;
}
}
}