| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- 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;
- }
- }
- }
|