Browse Source

美术优化

181404010226 3 months ago
parent
commit
bba4115837

File diff suppressed because it is too large
+ 255 - 138
assets/Scenes/GameLevel.scene


BIN
assets/resources/Font/波数.png


+ 134 - 0
assets/resources/Font/波数.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "9c2be90e-5643-4bb5-a3c7-db679c53c7fc",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "9c2be90e-5643-4bb5-a3c7-db679c53c7fc@6c48a",
+      "displayName": "波数",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "9c2be90e-5643-4bb5-a3c7-db679c53c7fc",
+        "isUuid": true,
+        "visible": false,
+        "minfilter": "linear",
+        "magfilter": "linear",
+        "mipfilter": "none",
+        "anisotropy": 0
+      },
+      "ver": "1.0.22",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    },
+    "f9941": {
+      "importer": "sprite-frame",
+      "uuid": "9c2be90e-5643-4bb5-a3c7-db679c53c7fc@f9941",
+      "displayName": "波数",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 51,
+        "height": 22,
+        "rawWidth": 51,
+        "rawHeight": 22,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -25.5,
+            -11,
+            0,
+            25.5,
+            -11,
+            0,
+            -25.5,
+            11,
+            0,
+            25.5,
+            11,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            22,
+            51,
+            22,
+            0,
+            0,
+            51,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -25.5,
+            -11,
+            0
+          ],
+          "maxPos": [
+            25.5,
+            11,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "9c2be90e-5643-4bb5-a3c7-db679c53c7fc@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "9c2be90e-5643-4bb5-a3c7-db679c53c7fc@6c48a"
+  }
+}

+ 267 - 5
assets/scripts/CombatSystem/BallController.ts

@@ -88,11 +88,48 @@ export class BallController extends Component {
     public antiTrapHitThreshold: number = 5;
     public deflectionAttemptThreshold: number = 3;
     public antiTrapDeflectionMultiplier: number = 3.0;
+    
+    // 增强防围困机制配置
+    @property({
+        tooltip: '振荡检测时间窗口(秒):检测小球往复运动的时间范围'
+    })
+    public oscillationTimeWindow: number = 3.0;
+    
+    @property({
+        tooltip: '方向改变阈值:触发振荡检测的方向改变次数'
+    })
+    public directionChangeThreshold: number = 4;
+    
+    @property({
+        tooltip: '位置历史记录数量:用于检测往复运动的位置样本数'
+    })
+    public positionHistorySize: number = 20;
+    
+    @property({
+        tooltip: '振荡距离阈值:判定为往复运动的最小距离范围'
+    })
+    public oscillationDistanceThreshold: number = 100;
+    
+    // 测试模式开关
+    @property({
+        tooltip: '启用测试模式:小球只会向上/下/左/右四个基本方向移动,便于测试防围困机制'
+    })
+    public testMode: boolean = true;
 
     // 防围困机制状态
     private ballHitHistory: Map<string, number[]> = new Map(); // 记录每个球的撞击时间历史
     private ballPhaseThrough: Map<string, number> = new Map(); // 记录每个球的穿透结束时间
     private ballDeflectionAttempts: Map<string, number> = new Map(); // 记录每个球的偏移尝试次数
+    
+    // 增强防围困机制:运动模式检测
+    private ballPositionHistory: Map<string, Vec2[]> = new Map(); // 记录小球位置历史
+    private ballDirectionHistory: Map<string, Vec2[]> = new Map(); // 记录小球方向历史
+    private ballOscillationDetection: Map<string, {
+        lastDirectionChange: number,
+        directionChangeCount: number,
+        oscillationAxis: 'horizontal' | 'vertical' | 'none',
+        oscillationStartTime: number
+    }> = new Map(); // 振荡检测数据
 
     // 带尾部特效的子弹容器预制体
     @property({
@@ -884,14 +921,19 @@ export class BallController extends Component {
                 hitHistory.shift();
             }
 
+            // 测试模式:显示详细的防围困信息
+            console.log(`[BallController] 测试模式 - 小球撞击统计: 撞击次数=${hitHistory.length}/${this.antiTrapHitThreshold}, 时间窗口=${this.antiTrapTimeWindow}秒, 方块=${otherNode.name}`);
+
             // 检查是否达到防围困条件
             if (hitHistory.length >= this.antiTrapHitThreshold) {
+                console.log(`[BallController] 测试模式 - 触发防围困机制! 撞击次数已达到阈值 ${this.antiTrapHitThreshold}`);
                 // 获取当前偏移尝试次数
                 const deflectionAttempts = this.ballDeflectionAttempts.get(ballId) || 0;
                 
                 if (deflectionAttempts < this.deflectionAttemptThreshold) {
                     // 优先使用偏移方式
                     this.ballDeflectionAttempts.set(ballId, deflectionAttempts + 1);
+                    console.log(`[BallController] 测试模式 - 应用防围困偏移 (第${deflectionAttempts + 1}/${this.deflectionAttemptThreshold}次尝试)`);
                     
                     // 应用增强偏移反弹
                     const rigidBody = ballNode.getComponent(RigidBody2D);
@@ -908,7 +950,11 @@ export class BallController extends Component {
                         // 计算增强偏移反弹方向
                         const currentVelocity = rigidBody.linearVelocity;
                         const currentDirection = new Vec2(currentVelocity.x, currentVelocity.y).normalize();
+                        const oldDirection = `(${currentDirection.x.toFixed(2)}, ${currentDirection.y.toFixed(2)})`;
                         const newDirection = this.calculateAntiTrapReflection(currentDirection, normal);
+                        const newDirectionStr = `(${newDirection.x.toFixed(2)}, ${newDirection.y.toFixed(2)})`;
+                        
+                        console.log(`[BallController] 测试模式 - 方向改变: ${oldDirection} -> ${newDirectionStr}`);
                         
                         // 应用新的速度方向
                         const speed = currentVelocity.length();
@@ -920,6 +966,7 @@ export class BallController extends Component {
                     hitHistory.length = 0;
                 } else {
                     // 偏移尝试次数已达上限,使用穿透
+                    console.log(`[BallController] 测试模式 - 偏移尝试已达上限,启用穿透模式 (0.5秒)`);
                     this.ballPhaseThrough.set(ballId, currentTime + 0.5);
                     // 重置偏移尝试次数
                     this.ballDeflectionAttempts.set(ballId, 0);
@@ -1390,6 +1437,9 @@ export class BallController extends Component {
         // 维持所有小球的恒定速度
         this.maintainAllBallsSpeed();
         
+        // 更新小球运动历史数据(用于检测振荡模式)
+        this.updateBallMovementHistory();
+        
         // 清理过期的防围困状态
         this.cleanupExpiredAntiTrapStates();
         
@@ -1407,6 +1457,192 @@ export class BallController extends Component {
         }
     }
     
+    /**
+     * 更新小球运动历史数据(用于检测振荡模式)
+     */
+    private updateBallMovementHistory() {
+        if (!this.activeBall || !this.activeBall.isValid) {
+            return;
+        }
+
+        const ballId = this.activeBall.uuid;
+        const currentTime = Date.now() / 1000; // 转换为秒
+        const currentPos = this.activeBall.getWorldPosition();
+        const rigidBody = this.activeBall.getComponent(RigidBody2D);
+        
+        if (!rigidBody) {
+            return;
+        }
+
+        const currentVelocity = rigidBody.linearVelocity;
+        const currentDirection = new Vec2(currentVelocity.x, currentVelocity.y).normalize();
+
+        // 初始化历史记录
+        if (!this.ballPositionHistory.has(ballId)) {
+            this.ballPositionHistory.set(ballId, []);
+            this.ballDirectionHistory.set(ballId, []);
+            this.ballOscillationDetection.set(ballId, {
+                lastDirectionChange: currentTime,
+                directionChangeCount: 0,
+                oscillationAxis: 'none',
+                oscillationStartTime: 0
+            });
+        }
+
+        const posHistory = this.ballPositionHistory.get(ballId);
+        const dirHistory = this.ballDirectionHistory.get(ballId);
+        const oscillationData = this.ballOscillationDetection.get(ballId);
+
+        // 更新位置历史
+        posHistory.push(new Vec2(currentPos.x, currentPos.y));
+        if (posHistory.length > this.positionHistorySize) {
+            posHistory.shift();
+        }
+
+        // 更新方向历史
+        dirHistory.push(currentDirection.clone());
+        if (dirHistory.length > this.positionHistorySize) {
+            dirHistory.shift();
+        }
+
+        // 检测方向改变
+        if (dirHistory.length >= 2) {
+            const prevDirection = dirHistory[dirHistory.length - 2];
+            const dotProduct = Vec2.dot(prevDirection, currentDirection);
+            
+            // 如果方向改变超过90度(点积小于0),认为是显著的方向改变
+            if (dotProduct < 0) {
+                oscillationData.directionChangeCount++;
+                oscillationData.lastDirectionChange = currentTime;
+                
+                if (this.testMode) {
+                    console.log(`[防围困] 小球 ${ballId.substring(0, 8)} 方向改变,累计次数: ${oscillationData.directionChangeCount}`);
+                }
+            }
+        }
+
+        // 检测振荡模式
+        this.detectOscillationPattern(ballId, currentTime);
+    }
+
+    /**
+     * 检测振荡模式
+     */
+    private detectOscillationPattern(ballId: string, currentTime: number) {
+        const oscillationData = this.ballOscillationDetection.get(ballId);
+        const posHistory = this.ballPositionHistory.get(ballId);
+        
+        if (!oscillationData || !posHistory || posHistory.length < 10) {
+            return;
+        }
+
+        // 检查是否在时间窗口内有足够的方向改变
+        const timeSinceLastChange = currentTime - oscillationData.lastDirectionChange;
+        if (timeSinceLastChange > this.oscillationTimeWindow) {
+            // 重置计数器
+            oscillationData.directionChangeCount = 0;
+            oscillationData.oscillationAxis = 'none';
+            return;
+        }
+
+        // 如果方向改变次数超过阈值,分析振荡轴向
+        if (oscillationData.directionChangeCount >= this.directionChangeThreshold) {
+            const axis = this.analyzeOscillationAxis(posHistory);
+            
+            if (axis !== 'none' && oscillationData.oscillationAxis === 'none') {
+                // 开始振荡检测
+                oscillationData.oscillationAxis = axis;
+                oscillationData.oscillationStartTime = currentTime;
+                
+                if (this.testMode) {
+                    console.log(`[防围困] 检测到小球 ${ballId.substring(0, 8)} 在${axis === 'horizontal' ? '水平' : '垂直'}方向振荡`);
+                }
+                
+                // 触发增强防围困机制
+                this.triggerEnhancedAntiTrap(ballId, axis);
+            }
+        }
+    }
+
+    /**
+     * 分析振荡轴向
+     */
+    private analyzeOscillationAxis(posHistory: Vec2[]): 'horizontal' | 'vertical' | 'none' {
+        if (posHistory.length < 10) {
+            return 'none';
+        }
+
+        const recentPositions = posHistory.slice(-10);
+        let minX = recentPositions[0].x, maxX = recentPositions[0].x;
+        let minY = recentPositions[0].y, maxY = recentPositions[0].y;
+
+        for (const pos of recentPositions) {
+            minX = Math.min(minX, pos.x);
+            maxX = Math.max(maxX, pos.x);
+            minY = Math.min(minY, pos.y);
+            maxY = Math.max(maxY, pos.y);
+        }
+
+        const xRange = maxX - minX;
+        const yRange = maxY - minY;
+
+        // 判断主要运动方向
+        if (xRange > this.oscillationDistanceThreshold && yRange < this.oscillationDistanceThreshold / 2) {
+            return 'horizontal';
+        } else if (yRange > this.oscillationDistanceThreshold && xRange < this.oscillationDistanceThreshold / 2) {
+            return 'vertical';
+        }
+
+        return 'none';
+    }
+
+    /**
+     * 触发增强防围困机制
+     */
+    private triggerEnhancedAntiTrap(ballId: string, oscillationAxis: 'horizontal' | 'vertical') {
+        const ball = this.activeBall;
+        if (!ball || !ball.isValid || ball.uuid !== ballId) {
+            return;
+        }
+
+        const rigidBody = ball.getComponent(RigidBody2D);
+        if (!rigidBody) {
+            return;
+        }
+
+        if (this.testMode) {
+            console.log(`[防围困] 触发增强防围困机制 - 小球 ${ballId.substring(0, 8)},轴向: ${oscillationAxis}`);
+        }
+
+        // 根据振荡轴向应用不同的策略
+        if (oscillationAxis === 'horizontal') {
+            // 水平振荡:给予垂直方向的随机冲量
+            const verticalImpulse = (Math.random() - 0.5) * this.baseSpeed * 0.8;
+            const newVelocity = new Vec2(rigidBody.linearVelocity.x * 0.3, verticalImpulse);
+            rigidBody.linearVelocity = newVelocity;
+            
+            if (this.testMode) {
+                console.log(`[防围困] 水平振荡处理 - 应用垂直冲量: ${verticalImpulse.toFixed(2)}`);
+            }
+        } else if (oscillationAxis === 'vertical') {
+            // 垂直振荡:给予水平方向的随机冲量
+            const horizontalImpulse = (Math.random() - 0.5) * this.baseSpeed * 0.8;
+            const newVelocity = new Vec2(horizontalImpulse, rigidBody.linearVelocity.y * 0.3);
+            rigidBody.linearVelocity = newVelocity;
+            
+            if (this.testMode) {
+                console.log(`[防围困] 垂直振荡处理 - 应用水平冲量: ${horizontalImpulse.toFixed(2)}`);
+            }
+        }
+
+        // 重置振荡检测数据
+        const oscillationData = this.ballOscillationDetection.get(ballId);
+        if (oscillationData) {
+            oscillationData.directionChangeCount = 0;
+            oscillationData.oscillationAxis = 'none';
+        }
+    }
+
     /**
      * 维持所有小球的恒定速度
      */
@@ -1521,11 +1757,32 @@ export class BallController extends Component {
 
     // 初始化方向
     initializeDirection() {
-        // 随机初始方向
-        const angle = Math.random() * Math.PI * 2; // 0-2π之间的随机角度
-        this.direction.x = Math.cos(angle);
-        this.direction.y = Math.sin(angle);
-        this.direction.normalize();
+        // 测试模式:使用完全垂直或水平方向来测试防围困机制
+        if (this.testMode) {
+            // 四个基本方向:上、下、左、右
+            const directions = [
+                { x: 0, y: 1 },   // 向上
+                { x: 0, y: -1 },  // 向下
+                { x: 1, y: 0 },   // 向右
+                { x: -1, y: 0 }   // 向左
+            ];
+            
+            // 随机选择一个基本方向
+            const randomIndex = Math.floor(Math.random() * directions.length);
+            const selectedDirection = directions[randomIndex];
+            
+            this.direction.x = selectedDirection.x;
+            this.direction.y = selectedDirection.y;
+            this.direction.normalize();
+            
+            console.log(`[BallController] 测试模式 - 小球初始方向: ${randomIndex === 0 ? '向上' : randomIndex === 1 ? '向下' : randomIndex === 2 ? '向右' : '向左'}`);
+        } else {
+            // 原始随机方向模式
+            const angle = Math.random() * Math.PI * 2; // 0-2π之间的随机角度
+            this.direction.x = Math.cos(angle);
+            this.direction.y = Math.sin(angle);
+            this.direction.normalize();
+        }
         
         // 设置初始速度
         if (this.activeBall) {
@@ -1703,6 +1960,11 @@ export class BallController extends Component {
         this.ballPhaseThrough.clear();
         this.ballDeflectionAttempts.clear();
         
+        // 清理增强防围困机制的历史数据
+        this.ballPositionHistory.clear();
+        this.ballDirectionHistory.clear();
+        this.ballOscillationDetection.clear();
+        
         // 清理拖拽方块集合
         this.draggingBlocks.clear();
         

+ 85 - 0
test_ball_anti_trap.js

@@ -0,0 +1,85 @@
+/**
+ * 小球防围困机制测试脚本
+ * 用于测试小球在完全垂直或水平方向下是否能有效避免来回弹跳
+ */
+
+console.log('=== 小球防围困机制测试 ===');
+console.log('测试目标:验证小球在完全垂直或水平方向下的防围困效果');
+console.log('');
+
+// 测试配置
+const testConfig = {
+    // 防围困参数
+    antiTrapTimeWindow: 5.0,        // 时间窗口(秒)
+    antiTrapHitThreshold: 5,        // 撞击次数阈值
+    deflectionAttemptThreshold: 3,  // 偏移尝试次数阈值
+    antiTrapDeflectionMultiplier: 3.0, // 偏移倍数
+    
+    // 测试参数
+    testDuration: 30,               // 测试持续时间(秒)
+    expectedDirections: ['向上', '向下', '向左', '向右']
+};
+
+console.log('测试配置:');
+console.log(`- 防围困时间窗口: ${testConfig.antiTrapTimeWindow}秒`);
+console.log(`- 撞击次数阈值: ${testConfig.antiTrapHitThreshold}次`);
+console.log(`- 偏移尝试阈值: ${testConfig.deflectionAttemptThreshold}次`);
+console.log(`- 偏移增强倍数: ${testConfig.antiTrapDeflectionMultiplier}倍`);
+console.log(`- 测试持续时间: ${testConfig.testDuration}秒`);
+console.log('');
+
+// 测试步骤说明
+console.log('测试步骤:');
+console.log('1. 确保BallController.ts中的testMode设置为true');
+console.log('2. 在游戏中放置一些方块,形成可能导致小球来回弹跳的布局');
+console.log('3. 启动小球,观察控制台输出');
+console.log('4. 记录防围困机制的触发情况');
+console.log('');
+
+// 预期结果
+console.log('预期结果:');
+console.log('✅ 小球初始方向应该是完全垂直或水平的(向上/向下/向左/向右)');
+console.log('✅ 当小球在短时间内多次撞击同一区域时,应该触发防围困机制');
+console.log('✅ 防围困机制应该先尝试偏移反弹,如果偏移无效则启用穿透模式');
+console.log('✅ 控制台应该显示详细的防围困状态信息');
+console.log('');
+
+// 观察要点
+console.log('观察要点:');
+console.log('🔍 小球初始方向是否为四个基本方向之一');
+console.log('🔍 撞击统计是否正确累计(在时间窗口内)');
+console.log('🔍 达到阈值时是否正确触发防围困机制');
+console.log('🔍 偏移反弹是否改变了小球方向');
+console.log('🔍 穿透模式是否让小球暂时忽略碰撞');
+console.log('🔍 防围困机制是否有效解决了来回弹跳问题');
+console.log('');
+
+// 常见问题排查
+console.log('常见问题排查:');
+console.log('❌ 如果小球方向不是基本方向 -> 检查initializeDirection()中的testMode是否为true');
+console.log('❌ 如果没有防围困日志 -> 检查方块碰撞体设置,确保group=2');
+console.log('❌ 如果防围困无效 -> 调整antiTrapHitThreshold和antiTrapTimeWindow参数');
+console.log('❌ 如果偏移效果不明显 -> 增加antiTrapDeflectionMultiplier值');
+console.log('');
+
+// 性能监控建议
+console.log('性能监控建议:');
+console.log('📊 监控ballHitHistory的大小,避免内存泄漏');
+console.log('📊 观察防围困机制的触发频率,避免过于频繁');
+console.log('📊 检查穿透模式的持续时间是否合适(当前0.5秒)');
+console.log('');
+
+// 参数调优建议
+console.log('参数调优建议:');
+console.log('⚙️ 如果防围困触发太频繁 -> 增加antiTrapHitThreshold或减少antiTrapTimeWindow');
+console.log('⚙️ 如果防围困触发太少 -> 减少antiTrapHitThreshold或增加antiTrapTimeWindow');
+console.log('⚙️ 如果偏移效果不够 -> 增加antiTrapDeflectionMultiplier');
+console.log('⚙️ 如果穿透时间太短 -> 在代码中增加穿透持续时间(当前0.5秒)');
+console.log('');
+
+console.log('=== 开始测试,请在游戏中启动小球并观察控制台输出 ===');
+
+// 导出测试配置供其他脚本使用
+if (typeof module !== 'undefined' && module.exports) {
+    module.exports = testConfig;
+}

+ 334 - 0
test_enhanced_anti_trap.js

@@ -0,0 +1,334 @@
+/**
+ * 增强防围困机制测试脚本
+ * 专门测试长距离振荡检测和干预功能
+ * 
+ * 使用方法:
+ * 1. 确保BallController.ts中testMode = true
+ * 2. 在浏览器控制台运行此脚本
+ * 3. 观察控制台输出和小球行为
+ */
+
+// 测试配置
+const TEST_CONFIG = {
+    // 测试持续时间(秒)
+    testDuration: 60,
+    
+    // 监控间隔(毫秒)
+    monitorInterval: 1000,
+    
+    // 预期的振荡检测触发次数
+    expectedOscillationTriggers: 2,
+    
+    // 测试场景
+    scenarios: [
+        {
+            name: "水平振荡测试",
+            description: "小球在左右墙体间来回弹跳",
+            expectedAxis: "horizontal"
+        },
+        {
+            name: "垂直振荡测试", 
+            description: "小球在上下墙体间来回弹跳",
+            expectedAxis: "vertical"
+        },
+        {
+            name: "长距离方块振荡测试",
+            description: "小球在距离较远的方块间来回弹跳",
+            expectedAxis: "horizontal" // 或 "vertical"
+        }
+    ]
+};
+
+// 测试状态
+let testState = {
+    startTime: 0,
+    oscillationTriggers: 0,
+    directionChanges: 0,
+    lastPosition: null,
+    positionHistory: [],
+    testResults: []
+};
+
+/**
+ * 开始增强防围困机制测试
+ */
+function startEnhancedAntiTrapTest() {
+    console.log("\n=== 增强防围困机制测试开始 ===");
+    console.log("测试目标:验证长距离振荡检测和干预功能");
+    console.log("测试时长:" + TEST_CONFIG.testDuration + "秒");
+    console.log("\n请确保:");
+    console.log("1. BallController中testMode = true");
+    console.log("2. 小球已启动并开始运动");
+    console.log("3. 场景中有适当的墙体或方块布局\n");
+    
+    // 重置测试状态
+    testState.startTime = Date.now();
+    testState.oscillationTriggers = 0;
+    testState.directionChanges = 0;
+    testState.lastPosition = null;
+    testState.positionHistory = [];
+    testState.testResults = [];
+    
+    // 开始监控
+    startMonitoring();
+    
+    // 设置测试结束定时器
+    setTimeout(() => {
+        endTest();
+    }, TEST_CONFIG.testDuration * 1000);
+}
+
+/**
+ * 开始监控小球状态
+ */
+function startMonitoring() {
+    const monitorInterval = setInterval(() => {
+        if (Date.now() - testState.startTime >= TEST_CONFIG.testDuration * 1000) {
+            clearInterval(monitorInterval);
+            return;
+        }
+        
+        monitorBallState();
+    }, TEST_CONFIG.monitorInterval);
+    
+    // 监听控制台输出(模拟)
+    interceptConsoleLog();
+}
+
+/**
+ * 监控小球状态
+ */
+function monitorBallState() {
+    // 这里需要根据实际的Cocos Creator API来获取小球状态
+    // 以下是示例代码,需要根据实际情况调整
+    
+    try {
+        // 获取小球节点(需要根据实际场景结构调整)
+        const gameArea = cc.find('Canvas/GameLevelUI/GameArea');
+        if (!gameArea) {
+            console.log("[测试] 未找到GameArea节点");
+            return;
+        }
+        
+        const balls = gameArea.children.filter(child => 
+            child.name === 'Ball' || child.name === 'AdditionalBall'
+        );
+        
+        if (balls.length === 0) {
+            console.log("[测试] 未找到小球节点");
+            return;
+        }
+        
+        const ball = balls[0];
+        const currentPos = ball.getWorldPosition();
+        
+        // 记录位置历史
+        testState.positionHistory.push({
+            x: currentPos.x,
+            y: currentPos.y,
+            time: Date.now()
+        });
+        
+        // 保持历史记录在合理范围内
+        if (testState.positionHistory.length > 50) {
+            testState.positionHistory.shift();
+        }
+        
+        // 检测方向改变
+        if (testState.lastPosition) {
+            const deltaX = currentPos.x - testState.lastPosition.x;
+            const deltaY = currentPos.y - testState.lastPosition.y;
+            
+            // 简单的方向改变检测
+            if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10) {
+                testState.directionChanges++;
+            }
+        }
+        
+        testState.lastPosition = { x: currentPos.x, y: currentPos.y };
+        
+        // 分析运动模式
+        analyzeMovementPattern();
+        
+    } catch (error) {
+        console.log("[测试] 监控过程中出现错误:", error.message);
+    }
+}
+
+/**
+ * 分析运动模式
+ */
+function analyzeMovementPattern() {
+    if (testState.positionHistory.length < 10) {
+        return;
+    }
+    
+    const recent = testState.positionHistory.slice(-10);
+    let minX = recent[0].x, maxX = recent[0].x;
+    let minY = recent[0].y, maxY = recent[0].y;
+    
+    for (const pos of recent) {
+        minX = Math.min(minX, pos.x);
+        maxX = Math.max(maxX, pos.x);
+        minY = Math.min(minY, pos.y);
+        maxY = Math.max(maxY, pos.y);
+    }
+    
+    const xRange = maxX - minX;
+    const yRange = maxY - minY;
+    
+    // 检测振荡模式
+    let detectedPattern = 'none';
+    if (xRange > 100 && yRange < 50) {
+        detectedPattern = 'horizontal';
+    } else if (yRange > 100 && xRange < 50) {
+        detectedPattern = 'vertical';
+    }
+    
+    if (detectedPattern !== 'none') {
+        console.log(`[测试] 检测到${detectedPattern === 'horizontal' ? '水平' : '垂直'}振荡模式`);
+        console.log(`[测试] X范围: ${xRange.toFixed(1)}, Y范围: ${yRange.toFixed(1)}`);
+    }
+}
+
+/**
+ * 拦截控制台输出以统计防围困触发次数
+ */
+function interceptConsoleLog() {
+    const originalLog = console.log;
+    console.log = function(...args) {
+        const message = args.join(' ');
+        
+        // 检测振荡检测触发
+        if (message.includes('[防围困] 检测到小球') && message.includes('振荡')) {
+            testState.oscillationTriggers++;
+            testState.testResults.push({
+                time: Date.now() - testState.startTime,
+                type: 'oscillation_detected',
+                message: message
+            });
+        }
+        
+        // 检测增强防围困触发
+        if (message.includes('[防围困] 触发增强防围困机制')) {
+            testState.testResults.push({
+                time: Date.now() - testState.startTime,
+                type: 'enhanced_anti_trap_triggered',
+                message: message
+            });
+        }
+        
+        // 检测冲量应用
+        if (message.includes('[防围困]') && (message.includes('冲量') || message.includes('处理'))) {
+            testState.testResults.push({
+                time: Date.now() - testState.startTime,
+                type: 'impulse_applied',
+                message: message
+            });
+        }
+        
+        // 调用原始console.log
+        originalLog.apply(console, args);
+    };
+}
+
+/**
+ * 结束测试并生成报告
+ */
+function endTest() {
+    console.log("\n=== 增强防围困机制测试结束 ===");
+    
+    const testDuration = (Date.now() - testState.startTime) / 1000;
+    
+    console.log("\n📊 测试统计:");
+    console.log(`⏱️  测试时长: ${testDuration.toFixed(1)}秒`);
+    console.log(`🔄 方向改变次数: ${testState.directionChanges}`);
+    console.log(`🎯 振荡检测触发次数: ${testState.oscillationTriggers}`);
+    console.log(`📝 总事件记录: ${testState.testResults.length}`);
+    
+    console.log("\n📋 详细事件记录:");
+    testState.testResults.forEach((result, index) => {
+        const timeStr = (result.time / 1000).toFixed(1) + 's';
+        console.log(`${index + 1}. [${timeStr}] ${result.type}: ${result.message}`);
+    });
+    
+    // 生成测试结论
+    generateTestConclusion();
+}
+
+/**
+ * 生成测试结论
+ */
+function generateTestConclusion() {
+    console.log("\n🎯 测试结论:");
+    
+    const oscillationDetected = testState.oscillationTriggers > 0;
+    const enhancedAntiTrapTriggered = testState.testResults.some(r => r.type === 'enhanced_anti_trap_triggered');
+    const impulseApplied = testState.testResults.some(r => r.type === 'impulse_applied');
+    
+    if (oscillationDetected) {
+        console.log("✅ 振荡检测功能正常工作");
+    } else {
+        console.log("❌ 未检测到振荡模式,可能需要调整参数或测试场景");
+    }
+    
+    if (enhancedAntiTrapTriggered) {
+        console.log("✅ 增强防围困机制成功触发");
+    } else {
+        console.log("❌ 增强防围困机制未触发");
+    }
+    
+    if (impulseApplied) {
+        console.log("✅ 冲量干预成功应用");
+    } else {
+        console.log("❌ 未检测到冲量干预");
+    }
+    
+    // 总体评估
+    const successCount = [oscillationDetected, enhancedAntiTrapTriggered, impulseApplied].filter(Boolean).length;
+    const totalTests = 3;
+    const successRate = (successCount / totalTests * 100).toFixed(1);
+    
+    console.log(`\n📈 总体成功率: ${successRate}% (${successCount}/${totalTests})`);
+    
+    if (successRate >= 66.7) {
+        console.log("🎉 增强防围困机制测试通过!");
+    } else {
+        console.log("⚠️  增强防围困机制需要进一步调优");
+        
+        console.log("\n🔧 建议调整:");
+        if (!oscillationDetected) {
+            console.log("- 降低directionChangeThreshold(当前4次,建议2-3次)");
+            console.log("- 减小oscillationDistanceThreshold(当前100,建议50-80)");
+        }
+        if (!enhancedAntiTrapTriggered) {
+            console.log("- 检查小球是否真的在振荡");
+            console.log("- 确认testMode已启用");
+        }
+        if (!impulseApplied) {
+            console.log("- 检查冲量计算逻辑");
+            console.log("- 确认RigidBody2D组件正常工作");
+        }
+    }
+}
+
+/**
+ * 快速测试函数(简化版)
+ */
+function quickEnhancedAntiTrapTest() {
+    console.log("🚀 快速增强防围困测试(30秒)");
+    TEST_CONFIG.testDuration = 30;
+    startEnhancedAntiTrapTest();
+}
+
+// 导出测试函数
+if (typeof window !== 'undefined') {
+    window.startEnhancedAntiTrapTest = startEnhancedAntiTrapTest;
+    window.quickEnhancedAntiTrapTest = quickEnhancedAntiTrapTest;
+}
+
+console.log("\n🔧 增强防围困机制测试脚本已加载");
+console.log("使用方法:");
+console.log("- startEnhancedAntiTrapTest() // 完整测试(60秒)");
+console.log("- quickEnhancedAntiTrapTest() // 快速测试(30秒)");
+console.log("\n⚠️  注意:确保BallController中testMode = true");

+ 225 - 0
小球防围困机制测试指南.md

@@ -0,0 +1,225 @@
+# 小球防围困机制测试指南
+
+## 概述
+
+本指南介绍如何测试小球的防围困机制,特别是在完全垂直或水平方向下的表现。防围困机制旨在防止小球在狭窄空间内来回弹跳,提升游戏体验。
+
+## 测试功能
+
+### 1. 测试模式开关
+
+在 `BallController` 组件中,新增了 `testMode` 属性:
+- **位置**: Inspector面板中的BallController组件
+- **功能**: 启用后,小球只会向上/下/左/右四个基本方向移动
+- **默认值**: true(测试模式开启)
+
+### 2. 方向限制
+
+测试模式下,小球初始方向被限制为:
+- 向上 (0, 1)
+- 向下 (0, -1) 
+- 向右 (1, 0)
+- 向左 (-1, 0)
+
+### 3. 详细日志输出
+
+测试模式会在控制台输出详细信息:
+- 小球初始方向
+- 撞击统计(次数/阈值)
+- 防围困机制触发
+- 偏移尝试和方向改变
+- 穿透模式启用
+
+## 测试步骤
+
+### 准备工作
+
+1. **确保测试模式开启**
+   - 在Inspector中找到BallController组件
+   - 确认 `Test Mode` 复选框已勾选
+
+2. **设置测试场景**
+   - 在游戏区域放置一些方块
+   - 创建可能导致小球来回弹跳的布局
+   - 建议创建狭窄通道或角落区域
+
+### 执行测试
+
+1. **启动游戏**
+   - 运行游戏场景
+   - 点击开始按钮创建小球
+
+2. **观察初始方向**
+   - 查看控制台输出的初始方向信息
+   - 确认小球确实按基本方向移动
+
+3. **监控防围困机制**
+   - 观察小球与方块的碰撞
+   - 注意控制台的撞击统计信息
+   - 等待防围困机制触发
+
+### 测试用例
+
+#### 用例1:垂直通道测试
+```
+布局:创建垂直狭窄通道
+期望:小球垂直移动时遇到阻碍能够偏移脱困
+观察:偏移反弹是否改变方向
+```
+
+#### 用例2:水平通道测试
+```
+布局:创建水平狭窄通道
+期望:小球水平移动时遇到阻碍能够偏移脱困
+观察:偏移反弹是否改变方向
+```
+
+#### 用例3:角落陷阱测试
+```
+布局:创建L形或U形陷阱
+期望:小球被困时能够通过穿透模式脱困
+观察:穿透模式是否正常工作
+```
+
+## 防围困机制参数
+
+### 核心参数
+
+- **antiTrapTimeWindow**: 5.0秒 - 撞击历史时间窗口
+- **antiTrapHitThreshold**: 5次 - 触发防围困的撞击次数
+- **deflectionAttemptThreshold**: 3次 - 偏移尝试次数上限
+- **antiTrapDeflectionMultiplier**: 3.0倍 - 偏移增强倍数
+
+### 增强防围困参数
+
+- **oscillationTimeWindow**: 3.0秒 - 振荡检测时间窗口
+- **directionChangeThreshold**: 4次 - 触发振荡检测的方向改变次数
+- **positionHistorySize**: 20个 - 位置历史记录数量
+- **oscillationDistanceThreshold**: 100像素 - 振荡距离阈值
+
+### 工作流程
+
+#### 基础防围困机制
+1. **撞击记录**: 记录小球在时间窗口内的撞击次数
+2. **阈值检查**: 撞击次数达到阈值时触发防围困
+3. **偏移尝试**: 优先使用增强偏移反弹改变方向
+4. **穿透模式**: 偏移无效时启用0.5秒穿透模式
+
+#### 增强防围困机制(解决长距离振荡)
+1. **运动追踪**: 实时记录小球位置和方向历史
+2. **方向检测**: 监测小球方向的显著改变(>90度)
+3. **振荡分析**: 分析位置历史判断是否为水平或垂直振荡
+4. **智能干预**: 根据振荡轴向施加垂直方向的随机冲量
+5. **状态重置**: 干预后重置振荡检测数据
+
+## 预期结果
+
+### 正常表现
+
+✅ **初始方向正确**: 小球按四个基本方向之一移动  
+✅ **撞击统计准确**: 控制台显示正确的撞击计数  
+✅ **防围困触发**: 达到阈值时正确触发机制  
+✅ **偏移有效**: 偏移反弹改变小球方向  
+✅ **穿透工作**: 穿透模式让小球暂时忽略碰撞  
+✅ **脱困成功**: 小球能够从陷阱中脱困  
+✅ **振荡检测**: 小球在长距离墙体或方块间来回弹跳时能被检测并干预  
+✅ **智能冲量**: 水平振荡时给予垂直冲量,垂直振荡时给予水平冲量  
+✅ **增强日志**: 控制台输出详细的振荡检测信息和干预记录  
+
+### 异常情况
+
+❌ **方向异常**: 小球方向不是基本方向 → 检查testMode设置  
+❌ **无防围困日志**: 没有撞击统计输出 → 检查方块碰撞体group设置  
+❌ **防围困无效**: 小球仍然来回弹跳 → 调整参数阈值  
+❌ **偏移无效**: 方向改变不明显 → 增加偏移倍数  
+❌ **振荡检测失效**: 检查`oscillationDistanceThreshold`是否过大或过小  
+❌ **冲量过强**: 调整冲量系数(当前为`baseSpeed * 0.8`)  
+❌ **检测过敏**: 增加`directionChangeThreshold`或`oscillationTimeWindow`  
+
+## 参数调优
+
+### 基础参数调整
+- **增加敏感度**: 降低`antiTrapHitThreshold`(如改为3-4次)
+- **减少敏感度**: 增加`antiTrapHitThreshold`(如改为6-8次)
+- **延长检测窗口**: 增加`antiTrapTimeWindow`(如改为6-8秒)
+- **增强偏移效果**: 增加`antiTrapDeflectionMultiplier`(如改为4-5倍)
+
+### 增强防围困参数调整
+- **振荡检测敏感度**: 调整`directionChangeThreshold`(2-6次)
+- **检测时间窗口**: 调整`oscillationTimeWindow`(2-5秒)
+- **位置历史精度**: 调整`positionHistorySize`(10-30个)
+- **距离阈值**: 调整`oscillationDistanceThreshold`(50-200像素)
+- **冲量强度**: 修改代码中的冲量系数(0.5-1.2倍baseSpeed)
+
+### 防围困过于敏感
+```
+问题:防围困触发太频繁
+解决:增加 antiTrapHitThreshold 或减少 antiTrapTimeWindow
+```
+
+### 防围困不够敏感
+```
+问题:小球长时间被困才触发
+解决:减少 antiTrapHitThreshold 或增加 antiTrapTimeWindow
+```
+
+### 偏移效果不足
+```
+问题:偏移后仍然容易被困
+解决:增加 antiTrapDeflectionMultiplier
+```
+
+### 穿透时间不当
+```
+问题:穿透时间过短或过长
+解决:修改代码中的穿透持续时间(当前0.5秒)
+```
+
+## 测试脚本
+
+运行 `test_ball_anti_trap.js` 获取详细的测试指导和配置信息:
+
+```bash
+node test_ball_anti_trap.js
+```
+
+## 测试脚本运行
+
+### 基础防围困测试
+```javascript
+// 在浏览器控制台中运行
+startAntiTrapTest();
+```
+
+### 增强防围困测试(新增)
+```javascript
+// 测试长距离振荡检测功能
+startEnhancedAntiTrapTest();
+```
+
+### 快速测试
+```javascript
+// 30秒快速基础测试
+quickAntiTrapTest();
+
+// 30秒快速增强测试
+quickEnhancedAntiTrapTest();
+```
+
+## 关闭测试模式
+
+测试完成后,记得关闭测试模式:
+1. 在Inspector中取消勾选 `Test Mode`
+2. 小球将恢复随机方向移动
+3. 防围困机制仍然正常工作,但日志输出会减少
+
+## 注意事项
+
+- 测试模式仅影响小球初始方向,不影响防围困机制本身
+- 防围困机制在正常游戏中也会工作,测试模式只是便于观察
+- 建议在不同的方块布局下进行多次测试
+- 注意观察控制台输出,它提供了机制工作的详细信息
+- **增强功能**:新增的振荡检测功能专门解决长距离来回弹跳问题
+- **内存管理**:位置和方向历史会占用内存,已设置合理上限
+- **实时监控**:增强防围困机制会实时分析小球运动模式
+- **智能干预**:根据振荡轴向自动选择最佳干预方向

Some files were not shown because too many files changed in this diff