BulletTrajectory.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import { _decorator, Component, Node, Vec3, math } from 'cc';
  2. import { BulletTrajectoryConfig } from '../BulletTypes';
  3. const { ccclass } = _decorator;
  4. /**
  5. * 弹道控制(可移植版,去除重力,采用渐近转向)
  6. * - straight:匀速直线
  7. * - arc:弧线(方向按目标或期望方向渐近旋转)
  8. * - guided:强导航(延迟后按目标方向渐近旋转)
  9. * 说明:不做命中判定/自动选敌,仅负责轨迹运动。
  10. */
  11. @ccclass('BCBulletTrajectory')
  12. export class BulletTrajectory extends Component {
  13. private cfg: BulletTrajectoryConfig | null = null;
  14. private velocity: Vec3 = new Vec3(0, 0, 0);
  15. private target: Node | null = null;
  16. private homingTimer = 0;
  17. // 渐近转向用方向缓存
  18. private arcDir: Vec3 | null = null;
  19. private arcTargetDir: Vec3 | null = null;
  20. private guidedDir: Vec3 | null = null;
  21. private guidedTargetDir: Vec3 | null = null;
  22. // 外观对齐/自旋选项
  23. private facingShouldRotate: boolean | null = null; // shouldRotate === false 时贴合速度
  24. private facingOffsetDeg: number = 0; // 图片朝向修正(度)
  25. private facingChildName: string | null = 'Pellet';// 优先旋转的子节点名
  26. private autoSpinSpeedDeg: number = 720; // 自旋速度(度/秒)
  27. public init(cfg: BulletTrajectoryConfig, direction: Vec3, startPos: Vec3, originNode?: Node | null) {
  28. this.cfg = { ...cfg };
  29. const speed = Math.max(0, this.cfg.speed ?? 0);
  30. const baseDir = direction?.clone() ?? new Vec3(1, 0, 0);
  31. if (baseDir.length() === 0) baseDir.set(1, 0, 0);
  32. baseDir.normalize();
  33. // 初始速度(straight)
  34. this.velocity.set(baseDir.x * speed, baseDir.y * speed, 0);
  35. this.node.setPosition(startPos);
  36. this.homingTimer = 0;
  37. // 为 arc/guided 建立初始偏移与目标方向(便于产生弧线)
  38. const sign = Math.random() < 0.5 ? 1 : -1;
  39. const radArc = (45 * Math.PI / 180) * sign; // 初始偏转角
  40. const c = Math.cos(radArc);
  41. const s = Math.sin(radArc);
  42. const offsetDir = new Vec3(
  43. baseDir.x * c - baseDir.y * s,
  44. baseDir.x * s + baseDir.y * c,
  45. 0
  46. ).normalize();
  47. this.arcDir = offsetDir.clone();
  48. this.arcTargetDir = baseDir.clone();
  49. const radGuided = (35 * Math.PI / 180) * sign;
  50. const cg = Math.cos(radGuided);
  51. const sg = Math.sin(radGuided);
  52. const guidedOffset = new Vec3(
  53. baseDir.x * cg - baseDir.y * sg,
  54. baseDir.x * sg + baseDir.y * cg,
  55. 0
  56. ).normalize();
  57. this.guidedDir = guidedOffset.clone();
  58. this.guidedTargetDir = baseDir.clone();
  59. }
  60. public setTarget(node: Node | null) {
  61. this.target = node;
  62. }
  63. /**
  64. * 配置外观对齐/自旋
  65. */
  66. public setFacingOptions(opts: { shouldRotate?: boolean; offsetDeg?: number; targetChildName?: string; spinSpeedDeg?: number }) {
  67. this.facingShouldRotate = (opts && 'shouldRotate' in opts) ? (opts.shouldRotate ?? null) : this.facingShouldRotate;
  68. if (opts && typeof opts.offsetDeg === 'number') this.facingOffsetDeg = opts.offsetDeg;
  69. if (opts && typeof opts.targetChildName === 'string') this.facingChildName = opts.targetChildName || null;
  70. if (opts && typeof opts.spinSpeedDeg === 'number') this.autoSpinSpeedDeg = opts.spinSpeedDeg;
  71. }
  72. public getCurrentVelocity(): Vec3 {
  73. return this.velocity.clone();
  74. }
  75. update(dt: number) {
  76. if (!this.cfg) return;
  77. const type = this.cfg.type;
  78. if (type === 'straight') {
  79. this.moveLinear(dt);
  80. } else if (type === 'arc') {
  81. this.updateArc(dt);
  82. } else if (type === 'guided') {
  83. this.updateGuided(dt);
  84. } else {
  85. this.moveLinear(dt);
  86. }
  87. // 更新外观对齐/自旋
  88. this.updateFacing(dt);
  89. }
  90. private moveLinear(dt: number) {
  91. const dx = this.velocity.x * dt;
  92. const dy = this.velocity.y * dt;
  93. const p = this.node.position;
  94. this.node.setPosition(p.x + dx, p.y + dy);
  95. }
  96. /**
  97. * 弧线:按目标或期望方向渐近旋转,保持速率恒定
  98. */
  99. private updateArc(dt: number) {
  100. if (!this.arcDir || !this.arcTargetDir || !this.cfg) {
  101. this.moveLinear(dt);
  102. return;
  103. }
  104. // 若有目标则按目标方向渐近;否则保持初始目标方向
  105. if (this.target && this.target.isValid) {
  106. const pos = this.node.worldPosition;
  107. const tpos = this.target.worldPosition;
  108. const toTarget = new Vec3(tpos.x - pos.x, tpos.y - pos.y, 0).normalize();
  109. this.arcTargetDir.set(toTarget);
  110. }
  111. const rotateSpeed = Math.max(0, Math.min(1, this.cfg.rotateSpeed ?? 0.2));
  112. const t = Math.min(1, rotateSpeed * dt);
  113. const newDir = new Vec3();
  114. Vec3.slerp(newDir, this.arcDir, this.arcTargetDir, t);
  115. this.arcDir.set(newDir);
  116. const speed = Math.max(0, this.cfg.speed ?? 0);
  117. this.velocity.set(newDir.x * speed, newDir.y * speed, 0);
  118. this.moveLinear(dt);
  119. }
  120. /**
  121. * Guided:延迟后按目标方向渐近旋转,保持速率恒定
  122. */
  123. private updateGuided(dt: number) {
  124. if (!this.guidedDir || !this.guidedTargetDir || !this.cfg) {
  125. this.moveLinear(dt);
  126. return;
  127. }
  128. const delay = Math.max(0, this.cfg.homingDelay ?? 0);
  129. this.homingTimer += dt;
  130. if (this.homingTimer < delay) {
  131. // 延迟期:沿初始引导方向匀速飞行
  132. const speed = Math.max(0, this.cfg.speed ?? 0);
  133. this.velocity.set(this.guidedDir.x * speed, this.guidedDir.y * speed, 0);
  134. this.moveLinear(dt);
  135. return;
  136. }
  137. // 计算期望方向(若无目标则维持原方向)
  138. if (this.target && this.target.isValid) {
  139. const pos = this.node.worldPosition;
  140. const tpos = this.target.worldPosition;
  141. const toTarget = new Vec3(tpos.x - pos.x, tpos.y - pos.y, 0).normalize();
  142. this.guidedTargetDir.set(toTarget);
  143. }
  144. const rotateSpeed = Math.max(0, Math.min(1, this.cfg.rotateSpeed ?? 0.25));
  145. const strength = Math.max(0, Math.min(1, this.cfg.homingStrength ?? 0.15));
  146. const t = Math.min(1, rotateSpeed * (0.8 + strength * 0.4) * dt);
  147. const newDir = new Vec3();
  148. Vec3.slerp(newDir, this.guidedDir, this.guidedTargetDir, t);
  149. this.guidedDir.set(newDir);
  150. const speed = Math.max(0, this.cfg.speed ?? 0);
  151. this.velocity.set(newDir.x * speed, newDir.y * speed, 0);
  152. this.moveLinear(dt);
  153. }
  154. /**
  155. * 根据当前速度更新外观角度(shouldRotate === false 贴合;否则自旋)
  156. */
  157. private updateFacing(dt: number) {
  158. // 未配置则不处理,保持移植版轻耦合
  159. if (this.facingShouldRotate === null) return;
  160. const vel = this.getCurrentVelocity();
  161. if (!vel || (Math.abs(vel.x) < 1e-4 && Math.abs(vel.y) < 1e-4)) return;
  162. // 目标节点:优先子节点
  163. let targetNode: Node | null = null;
  164. if (this.facingChildName) {
  165. const child = this.node.getChildByName(this.facingChildName);
  166. if (child) targetNode = child;
  167. }
  168. if (!targetNode) targetNode = this.node;
  169. if (this.facingShouldRotate === false) {
  170. const deg = math.toDegree(Math.atan2(vel.y, vel.x)) + this.facingOffsetDeg;
  171. targetNode.angle = deg;
  172. } else {
  173. // 自旋:不强制贴合轨迹切线
  174. targetNode.angle += this.autoSpinSpeedDeg * dt;
  175. }
  176. }
  177. }