import { _decorator, Component, Node, Vec3, math } from 'cc'; import { BulletTrajectoryConfig } from '../BulletTypes'; const { ccclass } = _decorator; /** * 弹道控制(可移植版,去除重力,采用渐近转向) * - straight:匀速直线 * - arc:弧线(方向按目标或期望方向渐近旋转) * - guided:强导航(延迟后按目标方向渐近旋转) * 说明:不做命中判定/自动选敌,仅负责轨迹运动。 */ @ccclass('BCBulletTrajectory') export class BulletTrajectory extends Component { private cfg: BulletTrajectoryConfig | null = null; private velocity: Vec3 = new Vec3(0, 0, 0); private target: Node | null = null; private homingTimer = 0; // 渐近转向用方向缓存 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 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.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(); } update(dt: number) { if (!this.cfg) return; const type = this.cfg.type; if (type === 'straight') { this.moveLinear(dt); } else if (type === 'arc') { this.updateArc(dt); } else if (type === 'guided') { this.updateGuided(dt); } else { this.moveLinear(dt); } // 更新外观对齐/自旋 this.updateFacing(dt); } private moveLinear(dt: number) { const dx = this.velocity.x * dt; const dy = this.velocity.y * dt; const p = this.node.position; this.node.setPosition(p.x + dx, p.y + dy); } /** * 弧线:按目标或期望方向渐近旋转,保持速率恒定 */ 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); } /** * 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) { // 延迟期:沿初始引导方向匀速飞行 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).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; } } }