134 lines
3.7 KiB
TypeScript
134 lines
3.7 KiB
TypeScript
// assets/scripts/BallPhysics.ts
|
||
import {
|
||
_decorator,
|
||
Component,
|
||
Node,
|
||
RigidBody,
|
||
Vec3,
|
||
math,
|
||
SphereCollider,
|
||
Contact2DType,
|
||
MeshCollider,
|
||
ICollisionEvent,
|
||
ITriggerEvent,
|
||
} from "cc";
|
||
const { ccclass, property } = _decorator;
|
||
|
||
@ccclass("BallPhysics")
|
||
export class BallPhysics extends Component {
|
||
@property({ tooltip: "马格努斯系数(越大弧线越明显)" }) magnusK = 0.1;
|
||
@property({ tooltip: "空气阻力" }) dragK = 0.001;
|
||
@property({ tooltip: "风力(m/s^2)" }) windStrength = 0;
|
||
@property({ tooltip: "风向" }) windDir: Vec3 = new Vec3(1, 0, 0);
|
||
|
||
@property({ tooltip: "自动重置高度阈值(低于视为无效球)" }) resetY = -2;
|
||
@property(Node) spawnPoint!: Node;
|
||
|
||
@property(Node) ball!: Node;
|
||
|
||
private rb!: RigidBody;
|
||
private tmpF = new Vec3();
|
||
private v = new Vec3();
|
||
private w = new Vec3();
|
||
private wind = new Vec3();
|
||
private pos = new Vec3();
|
||
|
||
// ⚽ 保存 spin(KickInput 设置进来)
|
||
private spin = new Vec3();
|
||
|
||
onLoad() {
|
||
this.rb = this.ball.getComponent(RigidBody)!;
|
||
const col = this.ball.getComponent(MeshCollider);
|
||
if (col) {
|
||
console.log(22223333, col);
|
||
col.on("onTriggerEnter", this.onTriggerEnter, this);
|
||
col.on("onTriggerStay", this.onTriggerStay, this);
|
||
col.on("onCollisionEnter", this.onCollisionEnter, this);
|
||
}
|
||
this.wind.set(this.windDir).normalize();
|
||
}
|
||
|
||
private onTriggerStay(event: ITriggerEvent) {
|
||
console.log(event.type, event);
|
||
}
|
||
|
||
onTriggerEnter(event: ICollisionEvent) {
|
||
console.log("⚽ 触发器进入:", event.otherCollider.node.name);
|
||
}
|
||
|
||
onCollisionEnter(event: ICollisionEvent) {
|
||
console.log("⚽ 碰撞进入:", event.otherCollider.node.name);
|
||
}
|
||
|
||
init() {
|
||
this.rb = this.node.getComponent(RigidBody)!;
|
||
this.wind.set(this.windDir).normalize();
|
||
this.pos = this.node.worldPosition.clone();
|
||
}
|
||
|
||
setWind(direction: Vec3, strength: number) {
|
||
this.wind.set(direction).normalize();
|
||
this.windStrength = strength;
|
||
}
|
||
|
||
resetBall() {
|
||
if (!this.spawnPoint) return;
|
||
this.rb.clearState();
|
||
this.node.worldPosition = this.pos; //this.spawnPoint.worldPosition.clone();
|
||
this.rb.setLinearVelocity(new Vec3());
|
||
this.rb.setAngularVelocity(new Vec3());
|
||
}
|
||
|
||
/** 设置自旋(由 KickInput 调用) */
|
||
public setSpin(spin: Vec3) {
|
||
this.spin.set(spin);
|
||
}
|
||
|
||
/** 发射足球 */
|
||
shootBall(dir: Vec3, speed: number) {
|
||
this.rb.clearState();
|
||
this.rb.setLinearVelocity(dir.clone().multiplyScalar(speed));
|
||
this.rb.setAngularVelocity(this.spin); // 应用横向旋转
|
||
}
|
||
|
||
update(dt: number) {
|
||
if (!this.rb) return;
|
||
|
||
this.rb.getLinearVelocity(this.v);
|
||
|
||
// 1. 空气阻力(与预测保持一致: -k * v * |v|)
|
||
const speed = this.v.length();
|
||
if (speed > 0.01) {
|
||
const drag = this.v.clone().multiplyScalar(-this.dragK * speed);
|
||
this.rb.applyForce(drag);
|
||
}
|
||
|
||
// 2. 马格努斯力: F = k * (spin × v)
|
||
if (!this.spin.equals(Vec3.ZERO) && speed > 0.01) {
|
||
const magnus = new Vec3();
|
||
Vec3.cross(magnus, this.spin, this.v);
|
||
magnus.multiplyScalar(this.magnusK);
|
||
this.rb.applyForce(magnus);
|
||
}
|
||
|
||
// if (!this.spin.equals(Vec3.ZERO) && speed > 0.01) {
|
||
// const magnus = new Vec3();
|
||
// const vNorm = this.v.clone().normalize();
|
||
// Vec3.cross(magnus, this.spin, vNorm);
|
||
// magnus.multiplyScalar(this.magnusK * Math.sqrt(speed));
|
||
// this.rb.applyForce(magnus);
|
||
// }
|
||
|
||
// 3. 风
|
||
if (this.windStrength > 0.0001) {
|
||
this.tmpF.set(this.wind).multiplyScalar(this.windStrength);
|
||
this.rb.applyForce(this.tmpF);
|
||
}
|
||
|
||
// 4. 掉出场地自动重置
|
||
if (this.node.worldPosition.y < this.resetY && this.spawnPoint) {
|
||
this.resetBall();
|
||
}
|
||
}
|
||
}
|