|
|
@@ -1,12 +1,13 @@
|
|
|
-import { _decorator, Component, Node, Vec3 } from 'cc';
|
|
|
+import { _decorator, Component, Node, Vec3, math } from 'cc';
|
|
|
import { BulletTrajectoryConfig } from '../BulletTypes';
|
|
|
const { ccclass } = _decorator;
|
|
|
|
|
|
/**
|
|
|
- * 弹道控制(可移植版)
|
|
|
+ * 弹道控制(可移植版,去除重力,采用渐近转向)
|
|
|
* - straight:匀速直线
|
|
|
- * - arc:带重力或弧线(简化为重力)
|
|
|
- * - guided:简单追踪(需要调用 setTarget 或外部提供方向更新)
|
|
|
+ * - arc:弧线(方向按目标或期望方向渐近旋转)
|
|
|
+ * - guided:强导航(延迟后按目标方向渐近旋转)
|
|
|
+ * 说明:不做命中判定/自动选敌,仅负责轨迹运动。
|
|
|
*/
|
|
|
@ccclass('BCBulletTrajectory')
|
|
|
export class BulletTrajectory extends Component {
|
|
|
@@ -14,24 +15,71 @@ export class BulletTrajectory extends Component {
|
|
|
private velocity: Vec3 = new Vec3(0, 0, 0);
|
|
|
private target: Node | null = null;
|
|
|
private homingTimer = 0;
|
|
|
- private origin: Vec3 = new Vec3();
|
|
|
+
|
|
|
+ // 渐近转向用方向缓存
|
|
|
+ private arcDir: Vec3 | null = null;
|
|
|
+ private arcTargetDir: Vec3 | null = null;
|
|
|
+ private guidedDir: Vec3 | null = null;
|
|
|
+ private guidedTargetDir: Vec3 | null = null;
|
|
|
+
|
|
|
+ // 外观对齐/自旋选项
|
|
|
+ private facingShouldRotate: boolean | null = null; // shouldRotate === false 时贴合速度
|
|
|
+ private facingOffsetDeg: number = 0; // 图片朝向修正(度)
|
|
|
+ private facingChildName: string | null = 'Pellet';// 优先旋转的子节点名
|
|
|
+ private autoSpinSpeedDeg: number = 720; // 自旋速度(度/秒)
|
|
|
|
|
|
public init(cfg: BulletTrajectoryConfig, direction: Vec3, startPos: Vec3, originNode?: Node | null) {
|
|
|
this.cfg = { ...cfg };
|
|
|
const speed = Math.max(0, this.cfg.speed ?? 0);
|
|
|
- const dir = direction?.clone() ?? new Vec3(1, 0, 0);
|
|
|
- if (dir.length() === 0) dir.set(1, 0, 0);
|
|
|
- dir.normalize();
|
|
|
- this.velocity.set(dir.x * speed, dir.y * speed, 0);
|
|
|
+ const baseDir = direction?.clone() ?? new Vec3(1, 0, 0);
|
|
|
+ if (baseDir.length() === 0) baseDir.set(1, 0, 0);
|
|
|
+ baseDir.normalize();
|
|
|
+
|
|
|
+ // 初始速度(straight)
|
|
|
+ this.velocity.set(baseDir.x * speed, baseDir.y * speed, 0);
|
|
|
this.node.setPosition(startPos);
|
|
|
- this.origin.set(startPos);
|
|
|
this.homingTimer = 0;
|
|
|
+
|
|
|
+ // 为 arc/guided 建立初始偏移与目标方向(便于产生弧线)
|
|
|
+ const sign = Math.random() < 0.5 ? 1 : -1;
|
|
|
+ const radArc = (45 * Math.PI / 180) * sign; // 初始偏转角
|
|
|
+ const c = Math.cos(radArc);
|
|
|
+ const s = Math.sin(radArc);
|
|
|
+ const offsetDir = new Vec3(
|
|
|
+ baseDir.x * c - baseDir.y * s,
|
|
|
+ baseDir.x * s + baseDir.y * c,
|
|
|
+ 0
|
|
|
+ ).normalize();
|
|
|
+
|
|
|
+ this.arcDir = offsetDir.clone();
|
|
|
+ this.arcTargetDir = baseDir.clone();
|
|
|
+
|
|
|
+ const radGuided = (35 * Math.PI / 180) * sign;
|
|
|
+ const cg = Math.cos(radGuided);
|
|
|
+ const sg = Math.sin(radGuided);
|
|
|
+ const guidedOffset = new Vec3(
|
|
|
+ baseDir.x * cg - baseDir.y * sg,
|
|
|
+ baseDir.x * sg + baseDir.y * cg,
|
|
|
+ 0
|
|
|
+ ).normalize();
|
|
|
+ this.guidedDir = guidedOffset.clone();
|
|
|
+ this.guidedTargetDir = baseDir.clone();
|
|
|
}
|
|
|
|
|
|
public setTarget(node: Node | null) {
|
|
|
this.target = node;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 配置外观对齐/自旋
|
|
|
+ */
|
|
|
+ public setFacingOptions(opts: { shouldRotate?: boolean; offsetDeg?: number; targetChildName?: string; spinSpeedDeg?: number }) {
|
|
|
+ this.facingShouldRotate = (opts && 'shouldRotate' in opts) ? (opts.shouldRotate ?? null) : this.facingShouldRotate;
|
|
|
+ if (opts && typeof opts.offsetDeg === 'number') this.facingOffsetDeg = opts.offsetDeg;
|
|
|
+ if (opts && typeof opts.targetChildName === 'string') this.facingChildName = opts.targetChildName || null;
|
|
|
+ if (opts && typeof opts.spinSpeedDeg === 'number') this.autoSpinSpeedDeg = opts.spinSpeedDeg;
|
|
|
+ }
|
|
|
+
|
|
|
public getCurrentVelocity(): Vec3 {
|
|
|
return this.velocity.clone();
|
|
|
}
|
|
|
@@ -42,12 +90,15 @@ export class BulletTrajectory extends Component {
|
|
|
if (type === 'straight') {
|
|
|
this.moveLinear(dt);
|
|
|
} else if (type === 'arc') {
|
|
|
- this.moveArc(dt);
|
|
|
+ this.updateArc(dt);
|
|
|
} else if (type === 'guided') {
|
|
|
- this.moveGuided(dt);
|
|
|
+ this.updateGuided(dt);
|
|
|
} else {
|
|
|
this.moveLinear(dt);
|
|
|
}
|
|
|
+
|
|
|
+ // 更新外观对齐/自旋
|
|
|
+ this.updateFacing(dt);
|
|
|
}
|
|
|
|
|
|
private moveLinear(dt: number) {
|
|
|
@@ -57,31 +108,97 @@ export class BulletTrajectory extends Component {
|
|
|
this.node.setPosition(p.x + dx, p.y + dy);
|
|
|
}
|
|
|
|
|
|
- private moveArc(dt: number) {
|
|
|
- const g = this.cfg?.gravity ?? 0;
|
|
|
- // 简化:重力沿 y 轴正方向(向下)
|
|
|
- this.velocity.y -= g * dt;
|
|
|
+ /**
|
|
|
+ * 弧线:按目标或期望方向渐近旋转,保持速率恒定
|
|
|
+ */
|
|
|
+ private updateArc(dt: number) {
|
|
|
+ if (!this.arcDir || !this.arcTargetDir || !this.cfg) {
|
|
|
+ this.moveLinear(dt);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 若有目标则按目标方向渐近;否则保持初始目标方向
|
|
|
+ if (this.target && this.target.isValid) {
|
|
|
+ const pos = this.node.worldPosition;
|
|
|
+ const tpos = this.target.worldPosition;
|
|
|
+ const toTarget = new Vec3(tpos.x - pos.x, tpos.y - pos.y, 0).normalize();
|
|
|
+ this.arcTargetDir.set(toTarget);
|
|
|
+ }
|
|
|
+
|
|
|
+ const rotateSpeed = Math.max(0, Math.min(1, this.cfg.rotateSpeed ?? 0.2));
|
|
|
+ const t = Math.min(1, rotateSpeed * dt);
|
|
|
+ const newDir = new Vec3();
|
|
|
+ Vec3.slerp(newDir, this.arcDir, this.arcTargetDir, t);
|
|
|
+ this.arcDir.set(newDir);
|
|
|
+
|
|
|
+ const speed = Math.max(0, this.cfg.speed ?? 0);
|
|
|
+ this.velocity.set(newDir.x * speed, newDir.y * speed, 0);
|
|
|
this.moveLinear(dt);
|
|
|
}
|
|
|
|
|
|
- private moveGuided(dt: number) {
|
|
|
- const delay = Math.max(0, this.cfg?.homingDelay ?? 0);
|
|
|
+ /**
|
|
|
+ * Guided:延迟后按目标方向渐近旋转,保持速率恒定
|
|
|
+ */
|
|
|
+ private updateGuided(dt: number) {
|
|
|
+ if (!this.guidedDir || !this.guidedTargetDir || !this.cfg) {
|
|
|
+ this.moveLinear(dt);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const delay = Math.max(0, this.cfg.homingDelay ?? 0);
|
|
|
this.homingTimer += dt;
|
|
|
- if (this.homingTimer >= delay && this.target && this.target.isValid) {
|
|
|
+ if (this.homingTimer < delay) {
|
|
|
+ // 延迟期:沿初始引导方向匀速飞行
|
|
|
+ const speed = Math.max(0, this.cfg.speed ?? 0);
|
|
|
+ this.velocity.set(this.guidedDir.x * speed, this.guidedDir.y * speed, 0);
|
|
|
+ this.moveLinear(dt);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算期望方向(若无目标则维持原方向)
|
|
|
+ if (this.target && this.target.isValid) {
|
|
|
const pos = this.node.worldPosition;
|
|
|
const tpos = this.target.worldPosition;
|
|
|
- const toTarget = new Vec3(tpos.x - pos.x, tpos.y - pos.y, 0);
|
|
|
- const dist = toTarget.length();
|
|
|
- if (dist > 1e-3) {
|
|
|
- toTarget.normalize();
|
|
|
- const strength = Math.max(0, Math.min(1, this.cfg?.homingStrength ?? 0));
|
|
|
- // 插值改变速度方向,但保持速率(magnitude)接近 cfg.speed
|
|
|
- const speed = Math.max(0, this.cfg?.speed ?? 0);
|
|
|
- const desired = new Vec3(toTarget.x * speed, toTarget.y * speed, 0);
|
|
|
- this.velocity.x = this.velocity.x + (desired.x - this.velocity.x) * strength;
|
|
|
- this.velocity.y = this.velocity.y + (desired.y - this.velocity.y) * strength;
|
|
|
- }
|
|
|
+ const toTarget = new Vec3(tpos.x - pos.x, tpos.y - pos.y, 0).normalize();
|
|
|
+ this.guidedTargetDir.set(toTarget);
|
|
|
}
|
|
|
+
|
|
|
+ const rotateSpeed = Math.max(0, Math.min(1, this.cfg.rotateSpeed ?? 0.25));
|
|
|
+ const strength = Math.max(0, Math.min(1, this.cfg.homingStrength ?? 0.15));
|
|
|
+ const t = Math.min(1, rotateSpeed * (0.8 + strength * 0.4) * dt);
|
|
|
+ const newDir = new Vec3();
|
|
|
+ Vec3.slerp(newDir, this.guidedDir, this.guidedTargetDir, t);
|
|
|
+ this.guidedDir.set(newDir);
|
|
|
+
|
|
|
+ const speed = Math.max(0, this.cfg.speed ?? 0);
|
|
|
+ this.velocity.set(newDir.x * speed, newDir.y * speed, 0);
|
|
|
this.moveLinear(dt);
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据当前速度更新外观角度(shouldRotate === false 贴合;否则自旋)
|
|
|
+ */
|
|
|
+ private updateFacing(dt: number) {
|
|
|
+ // 未配置则不处理,保持移植版轻耦合
|
|
|
+ if (this.facingShouldRotate === null) return;
|
|
|
+
|
|
|
+ const vel = this.getCurrentVelocity();
|
|
|
+ if (!vel || (Math.abs(vel.x) < 1e-4 && Math.abs(vel.y) < 1e-4)) return;
|
|
|
+
|
|
|
+ // 目标节点:优先子节点
|
|
|
+ let targetNode: Node | null = null;
|
|
|
+ if (this.facingChildName) {
|
|
|
+ const child = this.node.getChildByName(this.facingChildName);
|
|
|
+ if (child) targetNode = child;
|
|
|
+ }
|
|
|
+ if (!targetNode) targetNode = this.node;
|
|
|
+
|
|
|
+ if (this.facingShouldRotate === false) {
|
|
|
+ const deg = math.toDegree(Math.atan2(vel.y, vel.x)) + this.facingOffsetDeg;
|
|
|
+ targetNode.angle = deg;
|
|
|
+ } else {
|
|
|
+ // 自旋:不强制贴合轨迹切线
|
|
|
+ targetNode.angle += this.autoSpinSpeedDeg * dt;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|