|
|
@@ -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();
|
|
|
|