181404010226 1 month ago
parent
commit
39323fd3a6

+ 10 - 0
assets/scripts/BulletControl/BulletEngine.ts

@@ -83,6 +83,16 @@ export class WeaponBulletController extends Component {
       this.node.angle = deg;
     }
 
+    // 启用持续外观对齐/自旋(贴合两种模式)
+    if (this.trajectory) {
+      this.trajectory.setFacingOptions({
+        shouldRotate: shouldRotate,
+        offsetDeg: this.getOrientationOffset(),
+        targetChildName: pelletNode ? 'Pellet' : undefined,
+        spinSpeedDeg: 720
+      });
+    }
+
     // 碰撞监听(若存在 Collider2D)
     const collider = this.getComponent(Collider2D);
     if (collider) {

+ 1 - 2
assets/scripts/BulletControl/BulletTypes.ts

@@ -30,11 +30,10 @@ export type BulletTrajectoryType = 'straight' | 'arc' | 'guided';
 export interface BulletTrajectoryConfig {
   type: BulletTrajectoryType;
   speed: number;         // 初始速度(像素/秒),若与 stats.bulletSpeed 混用,以调用方为准
-  gravity?: number;      // 重力加速度(像素/秒²,向下为正)
   arcHeight?: number;    // 弧线高度(可选,具体实现可按需使用)
   homingStrength?: number; // 追踪强度(0~1 建议范围)
   homingDelay?: number;    // 追踪延迟(秒)
-  rotateSpeed?: number;    // 旋转速度(用于回旋镖等
+  rotateSpeed?: number;    // 渐近旋转速度(0~1,越大转向越快
 }
 
 export type HitEffectType = 'normal_damage' | 'pierce_damage' | 'ricochet_damage' | 'explosion' | 'ground_burn';

+ 2 - 2
assets/scripts/BulletControl/README.md

@@ -58,8 +58,8 @@ bullets.forEach(n => myShootRoot.addChild(n));
   - `burst`:连发(支持每发延迟)。
 - 弹道模式(`BulletTrajectory`):
   - `straight`:直线,按 `speed` 前进。
-  - `arc`:带重力的弧线(`gravity` 生效)。
-  - `guided`:简单追踪(`turnRate` 控制最大旋转速率)。
+  - `arc`:弧线(采用方向渐近旋转,无重力)。
+  - `guided`:追踪(延迟后按目标方向渐近旋转,`rotateSpeed` 影响转向快慢)。
   - 可通过 `setTarget(nodeOrPosition)` 设置/更新目标。
 
 ## 命中与生命周期

+ 147 - 30
assets/scripts/BulletControl/effects/BulletTrajectory.ts

@@ -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;
+    }
+  }
 }

+ 8 - 0
assets/scripts/CombatSystem/MenuSystem/SoundController.ts

@@ -117,6 +117,8 @@ export class SoundController extends Component {
             this.updateProgressBar(this.soundEffectProgressBar, slider.progress);
             // TODO: 应用音效音量到音频系统
             this.applySoundEffectVolume(slider.progress);
+            // 立即保存设置,确保退出前已落盘
+            this.saveSettings();
         }
     }
     
@@ -129,6 +131,8 @@ export class SoundController extends Component {
             this.updateProgressBar(this.musicProgressBar, slider.progress);
             // TODO: 应用音乐音量到音频系统
             this.applyMusicVolume(slider.progress);
+            // 立即保存设置,确保退出前已落盘
+            this.saveSettings();
         }
     }
     
@@ -312,6 +316,10 @@ export class SoundController extends Component {
             sdm.updateSetting('musicEnabled', this.musicEnabled);
             sdm.updateSetting('soundVolume', this.savedSoundEffectVolume);
             sdm.updateSetting('musicVolume', this.savedMusicVolume);
+            // 设置变更需要立即强制保存,避免5秒节流导致未写盘
+            if (typeof sdm.savePlayerData === 'function') {
+                sdm.savePlayerData(true);
+            }
         }
     }