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();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|