173 lines
5.1 KiB
TypeScript
173 lines
5.1 KiB
TypeScript
import {
|
||
_decorator,
|
||
Component,
|
||
Node,
|
||
EventTouch,
|
||
Input,
|
||
input,
|
||
Vec2,
|
||
Vec3,
|
||
RigidBody,
|
||
math,
|
||
Graphics,
|
||
MeshRenderer,
|
||
geometry,
|
||
} from "cc";
|
||
import { TrajectoryPreview } from "./TrajectoryPreview";
|
||
import { Preview } from "./Preview";
|
||
import { BallPhysics } from "./BallPhysics";
|
||
const { ccclass, property } = _decorator;
|
||
|
||
@ccclass("KickInput")
|
||
export class KickInput extends Component {
|
||
@property(Node) ball!: Node;
|
||
// @property(Node) goal!: Node;
|
||
|
||
@property(Node) cameraRig!: Node;
|
||
|
||
// 力度与角度控制
|
||
@property({ tooltip: "最大初速度(m/s)" }) maxSpeed = 20;
|
||
@property({ tooltip: "最小仰角(度)" }) minPitchDeg = 8;
|
||
@property({ tooltip: "最大仰角(度)" }) maxPitchDeg = 18;
|
||
|
||
// 自旋与弧线
|
||
@property({ tooltip: "自旋强度系数" }) spinFactor = 20;
|
||
|
||
@property(Preview) preview: Preview = null!;
|
||
|
||
private startPos = new Vec2();
|
||
private dragging = false;
|
||
private lastPos = new Vec2();
|
||
private trail: Vec2[] = [];
|
||
|
||
private ballPhys: BallPhysics = null;
|
||
|
||
protected onLoad(): void {
|
||
this.ballPhys = this.ball.getComponent(BallPhysics)!;
|
||
}
|
||
|
||
onEnable() {
|
||
input.on(Input.EventType.TOUCH_START, this.onStart, this);
|
||
input.on(Input.EventType.TOUCH_MOVE, this.onMove, this);
|
||
input.on(Input.EventType.TOUCH_END, this.onEnd, this);
|
||
input.on(Input.EventType.TOUCH_CANCEL, this.onEnd, this);
|
||
}
|
||
onDisable() {
|
||
input.off(Input.EventType.TOUCH_START, this.onStart, this);
|
||
input.off(Input.EventType.TOUCH_MOVE, this.onMove, this);
|
||
input.off(Input.EventType.TOUCH_END, this.onEnd, this);
|
||
input.off(Input.EventType.TOUCH_CANCEL, this.onEnd, this);
|
||
}
|
||
|
||
private onStart(e: EventTouch) {
|
||
this.dragging = true;
|
||
e.getLocation(this.startPos);
|
||
e.getLocation(this.lastPos);
|
||
this.trail.length = 0;
|
||
this.trail.push(this.startPos.clone());
|
||
}
|
||
|
||
private onMove(e: EventTouch) {
|
||
if (!this.dragging) return;
|
||
const p = e.getLocation();
|
||
this.trail.push(p.clone());
|
||
this.lastPos.set(p);
|
||
|
||
// 1) 拖拽向量
|
||
const drag = this.lastPos.clone().subtract(this.startPos);
|
||
const dragLen = drag.length();
|
||
|
||
// 2) 速度(映射拖拽长度)
|
||
const speed = math.clamp(dragLen / 8, 5, this.maxSpeed);
|
||
// const speed = 12;
|
||
|
||
// 3) 仰角 pitch(映射拖拽的纵向)
|
||
const t = math.clamp(drag.y / 300, 0, 1);
|
||
const pitchDeg = math.lerp(this.minPitchDeg, this.maxPitchDeg, t);
|
||
|
||
// 4) 偏航角 yaw(映射拖拽的横向)
|
||
const yawDeg = math.clamp(-drag.x / 8, -45, 45);
|
||
|
||
// 5) 得到方向向量
|
||
const dir = this.dirFromAngles(yawDeg, pitchDeg);
|
||
|
||
// 6) 自旋(用轨迹估计)
|
||
const spin = this.estimateSpin(this.trail, drag).multiplyScalar(
|
||
this.spinFactor
|
||
);
|
||
|
||
this.preview.showTrajectory(this.ball.worldPosition, dir, speed, spin);
|
||
}
|
||
|
||
private onEnd() {
|
||
if (!this.dragging) return;
|
||
this.dragging = false;
|
||
|
||
this.preview.clear();
|
||
|
||
// 拖拽向量
|
||
const drag = this.lastPos.clone().subtract(this.startPos);
|
||
const dragLen = drag.length();
|
||
|
||
if (!this.ball || !this.ballPhys) return;
|
||
|
||
// 1) 计算初速度
|
||
const speed = math.clamp(dragLen / 8, 5, this.maxSpeed);
|
||
// const speed = 12;
|
||
// 2) 仰角 pitch
|
||
const t = math.clamp(drag.y / 300, 0, 1);
|
||
const pitchDeg = math.lerp(this.minPitchDeg, this.maxPitchDeg, t);
|
||
|
||
// 3) 偏航 yaw
|
||
const yawDeg = math.clamp(-drag.x / 8, -45, 45);
|
||
|
||
// 4) 方向向量
|
||
const dir = this.dirFromAngles(yawDeg, pitchDeg);
|
||
|
||
// 5) 自旋
|
||
const spin = this.estimateSpin(this.trail, drag).multiplyScalar(
|
||
this.spinFactor
|
||
);
|
||
this.ballPhys.setSpin(spin);
|
||
console.log(`t: ${t}, pitchDeg:${pitchDeg}`);
|
||
// 6) 发射
|
||
this.ballPhys.shootBall(dir, speed);
|
||
}
|
||
|
||
private dirFromAngles(yawDeg: number, pitchDeg: number) {
|
||
const yaw = math.toRadian(yawDeg);
|
||
const pitch = math.toRadian(pitchDeg);
|
||
// 水平前方Z轴,右手坐标:根据你场景轴向可能需调整
|
||
const x = Math.sin(yaw) * Math.cos(pitch);
|
||
const y = Math.sin(pitch);
|
||
const z = Math.cos(yaw) * Math.cos(pitch);
|
||
return new Vec3(x, y, z).normalize();
|
||
}
|
||
|
||
// 简单估计自旋方向:用拖拽路径的“弯曲方向”
|
||
private estimateSpin(path: Vec2[], drag: Vec2): Vec3 {
|
||
if (path.length < 4) return new Vec3(0, 0, 0);
|
||
// 取开始、中点、终点,计算平面曲率符号,映射为Y轴为主或Z轴自旋
|
||
const a = path[0],
|
||
b = path[Math.floor(path.length / 2)],
|
||
c = path[path.length - 1];
|
||
// 2D 叉积 z 分量(曲率方向)
|
||
const ab = new Vec2(b.x - a.x, b.y - a.y);
|
||
const bc = new Vec2(c.x - b.x, c.y - b.y);
|
||
const cross = ab.x * bc.y - ab.y * bc.x; // >0 逆时针弯曲
|
||
|
||
// 横向弯 -> 绕Y轴的自旋(产生左右弧线)
|
||
|
||
const spinY = math.clamp(drag.x / 200, -3.5, 3.5);
|
||
return new Vec3(0, spinY, 0); // 只绕Y轴,产生左右弧度
|
||
|
||
// const spinY = math.clamp(cross / 20000, -1.5, 1.5) * 2.0; // 横向弧度提前
|
||
|
||
// // 竖向弯 -> 绕X轴/或Z轴的自旋(下坠/挑球),这里简单给点Z
|
||
// const dy = c.y - a.y;
|
||
// const spinZ = math.clamp(dy / 1500, -1.0, 1.0);
|
||
|
||
// return new Vec3(0, spinY, spinZ);
|
||
}
|
||
}
|