浏览代码

穿透和偏移共同处理

181404010226 4 月之前
父节点
当前提交
cb861344f1
共有 3 个文件被更改,包括 172 次插入18 次删除
  1. 4 0
      assets/Scenes/GameLevel.scene
  2. 120 9
      assets/scripts/CombatSystem/BallController.ts
  3. 48 9
      docs/AntiTrapMechanism.md

+ 4 - 0
assets/Scenes/GameLevel.scene

@@ -10275,6 +10275,10 @@
       "__uuid__": "54638943-4461-43b1-b351-a0380e1f30ce",
       "__expectedType__": "cc.Prefab"
     },
+    "antiTrapTimeWindow": 2,
+    "antiTrapHitThreshold": 5,
+    "deflectionAttemptThreshold": 3,
+    "antiTrapDeflectionMultiplier": 3,
     "bulletContainerPrefab": {
       "__uuid__": "b67bcc20-b46f-4cd9-9020-820246f608d6",
       "__expectedType__": "cc.Prefab"

+ 120 - 9
assets/scripts/CombatSystem/BallController.ts

@@ -82,9 +82,20 @@ export class BallController extends Component {
     })
     public antiTrapHitThreshold: number = 5;
 
+    @property({
+        tooltip: '偏移尝试次数阈值(达到后才使用穿透)'
+    })
+    public deflectionAttemptThreshold: number = 3;
+
+    @property({
+        tooltip: '防围困偏移强度倍数'
+    })
+    public antiTrapDeflectionMultiplier: number = 3.0;
+
     // 防围困机制状态
     private ballHitHistory: Map<string, number[]> = new Map(); // 记录每个球的撞击时间历史
     private ballPhaseThrough: Map<string, number> = new Map(); // 记录每个球的穿透结束时间
+    private ballDeflectionAttempts: Map<string, number> = new Map(); // 记录每个球的偏移尝试次数
 
     // 带尾部特效的子弹容器预制体
     @property({
@@ -697,16 +708,53 @@ export class BallController extends Component {
 
             // 检查是否达到防围困条件
             if (hitHistory.length >= this.antiTrapHitThreshold) {
-                // 激活穿透状态,持续0.5秒
-                this.ballPhaseThrough.set(ballId, currentTime + 0.5);
-                // 清空撞击历史
-                hitHistory.length = 0;
-                // 禁用当前碰撞
-                if (contact) {
-                    contact.disabled = true;
+                // 获取当前偏移尝试次数
+                const deflectionAttempts = this.ballDeflectionAttempts.get(ballId) || 0;
+                
+                if (deflectionAttempts < this.deflectionAttemptThreshold) {
+                    // 优先使用偏移方式
+                    this.ballDeflectionAttempts.set(ballId, deflectionAttempts + 1);
+                    
+                    // 应用增强偏移反弹
+                    const rigidBody = ballNode.getComponent(RigidBody2D);
+                    if (rigidBody && contact) {
+                        // 获取碰撞法线
+                        let normal = new Vec2(0, 1);
+                        if ((contact as any).getWorldManifold) {
+                            const wm = (contact as any).getWorldManifold();
+                            if (wm && wm.normal) {
+                                normal = new Vec2(wm.normal.x, wm.normal.y);
+                            }
+                        }
+                        
+                        // 计算增强偏移反弹方向
+                        const currentVelocity = rigidBody.linearVelocity;
+                        const currentDirection = new Vec2(currentVelocity.x, currentVelocity.y).normalize();
+                        const newDirection = this.calculateAntiTrapReflection(currentDirection, normal);
+                        
+                        // 应用新的速度方向
+                        const speed = currentVelocity.length();
+                        rigidBody.linearVelocity = new Vec2(newDirection.x * speed, newDirection.y * speed);
+                        
+                        console.log(`Ball ${ballId} applied anti-trap deflection (attempt ${deflectionAttempts + 1}/${this.deflectionAttemptThreshold})`);
+                    }
+                    
+                    // 清空撞击历史,给偏移一个机会
+                    hitHistory.length = 0;
+                } else {
+                    // 偏移尝试次数已达上限,使用穿透
+                    this.ballPhaseThrough.set(ballId, currentTime + 0.5);
+                    // 重置偏移尝试次数
+                    this.ballDeflectionAttempts.set(ballId, 0);
+                    // 清空撞击历史
+                    hitHistory.length = 0;
+                    // 禁用当前碰撞
+                    if (contact) {
+                        contact.disabled = true;
+                    }
+                    console.log(`Ball ${ballId} entered phase-through mode after ${this.deflectionAttemptThreshold} deflection attempts failed`);
+                    return;
                 }
-                console.log(`Ball ${ballId} entered phase-through mode due to frequent collisions`);
-                return;
             }
         }
         
@@ -894,6 +942,56 @@ export class BallController extends Component {
         return randomizedReflection;
     }
 
+    /**
+     * 计算防围困增强偏移反弹方向
+     * @param direction 当前方向
+     * @param normal 碰撞法线
+     * @returns 增强偏移后的反弹方向
+     */
+    calculateAntiTrapReflection(direction: Vec2, normal: Vec2): Vec2 {
+        // 使用反射公式: R = V - 2(V·N)N
+        const dot = direction.x * normal.x + direction.y * normal.y;
+        const reflection = new Vec2(
+            direction.x - 2 * dot * normal.x,
+            direction.y - 2 * dot * normal.y
+        );
+        reflection.normalize();
+        
+        // 应用更强的随机偏移来帮助脱困
+        const enhancedRandomness = this.maxReflectionRandomness * this.antiTrapDeflectionMultiplier;
+        const randomAngle = (Math.random() - 0.5) * enhancedRandomness;
+        const cos = Math.cos(randomAngle);
+        const sin = Math.sin(randomAngle);
+        
+        // 应用随机旋转
+        const randomizedReflection = new Vec2(
+            reflection.x * cos - reflection.y * sin,
+            reflection.x * sin + reflection.y * cos
+        );
+        
+        // 更强的轴向偏移避免
+        const minAngleFromAxis = 0.3; // 约17度,比普通反弹更大的偏移
+        
+        // 检查是否接近水平方向
+        if (Math.abs(randomizedReflection.y) < minAngleFromAxis) {
+            const sign = randomizedReflection.y >= 0 ? 1 : -1;
+            randomizedReflection.y = sign * (minAngleFromAxis + Math.random() * 0.2);
+            randomizedReflection.normalize();
+        }
+        
+        // 检查是否接近垂直方向
+        if (Math.abs(randomizedReflection.x) < minAngleFromAxis) {
+            const sign = randomizedReflection.x >= 0 ? 1 : -1;
+            randomizedReflection.x = sign * (minAngleFromAxis + Math.random() * 0.2);
+            randomizedReflection.normalize();
+        }
+        
+        randomizedReflection.normalize();
+        console.log(`Applied anti-trap enhanced deflection with angle: ${randomAngle}`);
+        
+        return randomizedReflection;
+    }
+
     /**
      * 从方块武器发射子弹攻击敌人 - 重构版本
      * 现在直接创建子弹实例并使用BulletController的实例方法
@@ -1325,6 +1423,7 @@ export class BallController extends Component {
         // 清理防围困状态数据
         this.ballHitHistory.clear();
         this.ballPhaseThrough.clear();
+        this.ballDeflectionAttempts.clear();
         
         // 重置球速
         this.updateBallSpeed();
@@ -1344,6 +1443,17 @@ export class BallController extends Component {
                 this.ballPhaseThrough.delete(ballId);
             }
         }
+        
+        // 检查并重置长时间未频繁撞击的球的偏移尝试次数
+        for (const [ballId, hitHistory] of this.ballHitHistory.entries()) {
+            // 如果撞击历史为空或最近一次撞击超过时间窗口的一半,重置偏移尝试次数
+            if (hitHistory.length === 0 || 
+                (hitHistory.length > 0 && currentTime - hitHistory[hitHistory.length - 1] > this.antiTrapTimeWindow / 2)) {
+                if (this.ballDeflectionAttempts.has(ballId)) {
+                    this.ballDeflectionAttempts.set(ballId, 0);
+                }
+            }
+        }
     }
     
     /**
@@ -1353,6 +1463,7 @@ export class BallController extends Component {
     public cleanupBallAntiTrapState(ballId: string) {
         this.ballHitHistory.delete(ballId);
         this.ballPhaseThrough.delete(ballId);
+        this.ballDeflectionAttempts.delete(ballId);
         console.log(`Cleaned up anti-trap state for ball ${ballId}`);
     }
 

+ 48 - 9
docs/AntiTrapMechanism.md

@@ -2,7 +2,9 @@
 
 ## 功能描述
 
-防围困机制是为了防止小球被方块围住而无法继续游戏的情况。当小球在规定时间内撞击方块的次数达到阈值时,小球将进入短暂的穿透状态,可以无视碰撞体积穿过方块。
+防围困机制是为了防止小球被方块围住而无法继续游戏的情况。当小球在规定时间内撞击方块的次数达到阈值时,系统会采用两阶段防围困策略:
+1. **优先偏移**:首先尝试通过增强的角度偏移来帮助小球脱困
+2. **备用穿透**:当偏移尝试失败后,才激活穿透状态使小球无视碰撞体积穿过方块
 
 ## 配置参数
 
@@ -18,17 +20,39 @@
 - **默认值**: 5 次
 - **说明**: 在时间窗口内达到这个撞击次数时,将触发防围困机制。
 
+### deflectionAttemptThreshold (偏移尝试次数阈值)
+- **类型**: number
+- **默认值**: 3 次
+- **说明**: 偏移尝试次数阈值。系统会优先尝试偏移方式,只有在偏移尝试次数达到此阈值后才会使用穿透方式。
+
+### antiTrapDeflectionMultiplier (防围困偏移强度倍数)
+- **类型**: number
+- **默认值**: 3.0 倍
+- **说明**: 防围困偏移强度倍数。用于增强偏移效果,帮助小球更容易脱困。
+
 ## 工作原理
 
+### 两阶段防围困策略
+
+#### 第一阶段:偏移尝试
 1. **撞击记录**: 每当小球撞击方块时,系统会记录撞击时间。
 
 2. **时间窗口清理**: 系统会自动清理超出时间窗口的旧撞击记录。
 
 3. **阈值检测**: 当时间窗口内的撞击次数达到阈值时,触发防围困机制。
 
-4. **穿透状态**: 小球进入穿透状态,持续 0.5 秒,期间可以穿过方块但仍会播放撞击特效。
+4. **偏移应用**: 达到阈值时,首先尝试应用增强的角度偏移
+   - 计算当前速度方向和碰撞法线
+   - 应用防围困增强偏移算法
+   - 重新设置小球的运动方向
+   - 清空撞击历史,给偏移一个机会
 
-5. **状态清理**: 穿透状态结束后,小球恢复正常碰撞,撞击历史被清空。
+#### 第二阶段:穿透备用
+5. **偏移失效检测**: 如果偏移尝试次数达到 `deflectionAttemptThreshold`
+
+6. **穿透状态**: 激活0.5秒的穿透状态,小球可无视碰撞体积穿过方块但仍会播放撞击特效。
+
+7. **状态清理**: 穿透状态结束后,小球恢复正常碰撞,撞击历史被清空,重置偏移尝试计数。
 
 ## 技术实现
 
@@ -40,11 +64,15 @@ private ballHitHistory: Map<string, number[]> = new Map();
 
 // 记录每个球的穿透结束时间
 private ballPhaseThrough: Map<string, number> = new Map();
+
+// 记录每个球的偏移尝试次数
+private ballDeflectionAttempts: Map<string, number> = new Map();
 ```
 
 ### 关键方法
 
-- `onBeginContact()`: 碰撞检测的核心方法,包含防围困逻辑
+- `onBeginContact()`: 碰撞检测的核心方法,包含两阶段防围困逻辑
+- `calculateAntiTrapReflection()`: 计算防围困增强偏移反弹方向
 - `cleanupExpiredAntiTrapStates()`: 清理过期的穿透状态
 - `cleanupBallAntiTrapState()`: 清理单个小球的防围困状态
 
@@ -60,13 +88,24 @@ private ballPhaseThrough: Map<string, number> = new Map();
 
 ## 调试信息
 
-当小球进入穿透状态时,控制台会输出调试信息:
+系统会在控制台输出以下调试信息:
 ```
-Ball {ballId} entered phase-through mode due to frequent collisions
+Ball {ballId} applied anti-trap deflection (attempt {current}/{max})
+Ball {ballId} entered phase-through mode after {threshold} deflection attempts failed
+Cleaned up anti-trap state for ball {ballId}
 ```
 
 ## 配置建议
 
-- **休闲模式**: 时间窗口 1.5 秒,阈值 3-4 次
-- **普通模式**: 时间窗口 2.0 秒,阈值 5 次(默认)
-- **困难模式**: 时间窗口 2.5 秒,阈值 6-7 次
+- **时间窗口**: 建议设置为1.5-2.5秒,太短可能误触发,太长可能反应迟钝
+- **撞击阈值**: 建议设置为3-7次,根据游戏难度调整
+- **偏移尝试阈值**: 建议设置为2-5次,平衡偏移和穿透的使用频率
+- **偏移强度倍数**: 建议设置为2.0-5.0,过低可能无效,过高可能过于激进
+- **测试方法**: 可以通过创建密集的方块布局来测试两阶段防围困效果
+
+## 优势
+
+1. **减少穿透频率**: 优先使用偏移方式,只在必要时才使用穿透
+2. **保持游戏体验**: 偏移方式保持了物理碰撞的真实感
+3. **渐进式解决**: 从轻微干预到强力干预的渐进式防围困策略
+4. **可配置性**: 多个参数可根据游戏需求灵活调整