|
|
@@ -22,7 +22,7 @@ export class BallController extends Component {
|
|
|
// 已放置方块容器节点
|
|
|
@property({
|
|
|
type: Node,
|
|
|
- tooltip: '拖拽PlacedBlocks节点到这里(Canvas/GameLevelUI/PlacedBlocks)'
|
|
|
+ tooltip: '拖拽PlacedBlocks节点到这里(Canvas/GameLevelUI/GameArea/PlacedBlocks)'
|
|
|
})
|
|
|
public placedBlocksContainer: Node = null;
|
|
|
|
|
|
@@ -116,6 +116,22 @@ export class BallController extends Component {
|
|
|
})
|
|
|
public testMode: boolean = true;
|
|
|
|
|
|
+ // 小球位置检查配置
|
|
|
+ @property({
|
|
|
+ tooltip: '启用小球位置检查:定期检查小球是否在游戏区域内,如果不在则找回'
|
|
|
+ })
|
|
|
+ public enableBallPositionCheck: boolean = true;
|
|
|
+
|
|
|
+ @property({
|
|
|
+ tooltip: '位置检查间隔(秒):多久检查一次小球位置'
|
|
|
+ })
|
|
|
+ public positionCheckInterval: number = 2.0;
|
|
|
+
|
|
|
+ @property({
|
|
|
+ tooltip: '位置检查边界扩展(像素):检查边界比游戏区域稍大,避免误判'
|
|
|
+ })
|
|
|
+ public positionCheckBoundaryExtension: number = 50;
|
|
|
+
|
|
|
// 防围困机制状态
|
|
|
private ballHitHistory: Map<string, number[]> = new Map(); // 记录每个球的撞击时间历史
|
|
|
private ballPhaseThrough: Map<string, number> = new Map(); // 记录每个球的穿透结束时间
|
|
|
@@ -141,10 +157,13 @@ export class BallController extends Component {
|
|
|
// 正在被拖拽的方块集合
|
|
|
private draggingBlocks: Set<Node> = new Set();
|
|
|
|
|
|
+ // 小球位置检查相关变量
|
|
|
+ private lastPositionCheckTime: number = 0; // 上次位置检查的时间
|
|
|
+
|
|
|
start() {
|
|
|
// 如果没有指定placedBlocksContainer,尝试找到它
|
|
|
if (!this.placedBlocksContainer) {
|
|
|
- this.placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
|
|
|
+ this.placedBlocksContainer = find('Canvas/GameLevelUI/GameArea/PlacedBlocks');
|
|
|
if (!this.placedBlocksContainer) {
|
|
|
// 找不到PlacedBlocks节点,某些功能可能无法正常工作
|
|
|
}
|
|
|
@@ -666,14 +685,10 @@ export class BallController extends Component {
|
|
|
|
|
|
// 查找PlacedBlocks节点,它包含所有放置的方块
|
|
|
if (!this.placedBlocksContainer) {
|
|
|
- this.setRandomPositionDefault(minX, maxX, minY, maxY);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (!this.placedBlocksContainer.isValid) {
|
|
|
- this.setRandomPositionDefault(minX, maxX, minY, maxY);
|
|
|
+ this.setRandomPositionDefault(this.activeBall, minX, maxX, minY, maxY);
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
|
|
|
// 获取所有已放置的方块
|
|
|
const placedBlocks = [];
|
|
|
@@ -687,7 +702,7 @@ export class BallController extends Component {
|
|
|
|
|
|
// 如果没有方块,使用默认随机位置
|
|
|
if (placedBlocks.length === 0) {
|
|
|
- this.setRandomPositionDefault(minX, maxX, minY, maxY);
|
|
|
+ this.setRandomPositionDefault(this.activeBall, minX, maxX, minY, maxY);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -745,22 +760,6 @@ export class BallController extends Component {
|
|
|
this.activeBall.position = localPos;
|
|
|
}
|
|
|
|
|
|
- // 设置默认随机位置
|
|
|
- setRandomPositionDefault(minX, maxX, minY, maxY) {
|
|
|
- // 随机生成位置
|
|
|
- const randomX = Math.random() * (maxX - minX) + minX;
|
|
|
- const randomY = Math.random() * (maxY - minY) + minY;
|
|
|
-
|
|
|
- // 将世界坐标转换为相对于GameArea的本地坐标
|
|
|
- const gameArea = find('Canvas/GameLevelUI/GameArea');
|
|
|
- if (gameArea) {
|
|
|
- const localPos = gameArea.getComponent(UITransform).convertToNodeSpaceAR(new Vec3(randomX, randomY, 0));
|
|
|
- this.activeBall.position = localPos;
|
|
|
- } else {
|
|
|
- // 直接设置位置(不太准确,但作为后备)
|
|
|
- this.activeBall.position = new Vec3(randomX - this.gameBounds.left, randomY - this.gameBounds.bottom, 0);
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
// 设置碰撞组件
|
|
|
setupCollider() {
|
|
|
@@ -1443,6 +1442,13 @@ export class BallController extends Component {
|
|
|
// 清理过期的防围困状态
|
|
|
this.cleanupExpiredAntiTrapStates();
|
|
|
|
|
|
+ // 定期检查小球位置,防止小球被挤出游戏区域
|
|
|
+ this.lastPositionCheckTime += dt;
|
|
|
+ if (this.lastPositionCheckTime >= this.positionCheckInterval) {
|
|
|
+ this.checkAndRescueAllBalls();
|
|
|
+ this.lastPositionCheckTime = 0;
|
|
|
+ }
|
|
|
+
|
|
|
// 定期检查小球是否接近方块但没有触发碰撞(调试用)
|
|
|
if (this.activeBall && this.activeBall.isValid) {
|
|
|
this.debugCheckNearBlocks();
|
|
|
@@ -2043,4 +2049,248 @@ export class BallController extends Component {
|
|
|
this.currentSpeed = this.baseSpeed;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // ==================== 小球位置检查机制 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取所有活跃的小球
|
|
|
+ * @returns 返回所有活跃小球的数组
|
|
|
+ */
|
|
|
+ private getAllActiveBalls(): Node[] {
|
|
|
+ const balls: Node[] = [];
|
|
|
+
|
|
|
+ // 添加主小球
|
|
|
+ if (this.activeBall && this.activeBall.isValid) {
|
|
|
+ balls.push(this.activeBall);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查找所有额外小球
|
|
|
+ const gameArea = find('Canvas/GameLevelUI/GameArea');
|
|
|
+ if (gameArea) {
|
|
|
+ // 遍历GameArea的所有子节点,查找小球
|
|
|
+ gameArea.children.forEach(child => {
|
|
|
+ if (child.name === 'AdditionalBall' && child.isValid) {
|
|
|
+ balls.push(child);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return balls;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查单个小球是否在游戏区域内
|
|
|
+ * @param ball 要检查的小球节点
|
|
|
+ * @returns 如果小球在游戏区域内返回true,否则返回false
|
|
|
+ */
|
|
|
+ private checkBallPosition(ball: Node): boolean {
|
|
|
+ if (!ball || !ball.isValid) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取小球的世界坐标
|
|
|
+ const worldPos = ball.getWorldPosition();
|
|
|
+
|
|
|
+ // 计算扩展后的边界(比游戏区域稍大,避免误判)
|
|
|
+ const extension = this.positionCheckBoundaryExtension;
|
|
|
+ const extendedBounds = {
|
|
|
+ left: this.gameBounds.left - extension,
|
|
|
+ right: this.gameBounds.right + extension,
|
|
|
+ top: this.gameBounds.top + extension,
|
|
|
+ bottom: this.gameBounds.bottom - extension
|
|
|
+ };
|
|
|
+
|
|
|
+ // 检查小球是否在扩展边界内
|
|
|
+ const isInBounds = worldPos.x >= extendedBounds.left &&
|
|
|
+ worldPos.x <= extendedBounds.right &&
|
|
|
+ worldPos.y >= extendedBounds.bottom &&
|
|
|
+ worldPos.y <= extendedBounds.top;
|
|
|
+
|
|
|
+ if (!isInBounds) {
|
|
|
+ console.warn(`[BallController] 小球 ${ball.name} 超出游戏区域:`, {
|
|
|
+ position: { x: worldPos.x, y: worldPos.y },
|
|
|
+ bounds: extendedBounds
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return isInBounds;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将小球重置回游戏区域
|
|
|
+ * @param ball 要重置的小球节点
|
|
|
+ */
|
|
|
+ private rescueBallToGameArea(ball: Node): void {
|
|
|
+ if (!ball || !ball.isValid) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`[BallController] 正在找回小球 ${ball.name}`);
|
|
|
+
|
|
|
+ // 使用与小球生成相同的安全位置设置逻辑
|
|
|
+ this.positionBallSafely(ball);
|
|
|
+
|
|
|
+ // 重新设置小球的运动方向和速度
|
|
|
+ const rigidBody = ball.getComponent(RigidBody2D);
|
|
|
+ if (rigidBody) {
|
|
|
+ // 随机新的运动方向
|
|
|
+ const angle = Math.random() * Math.PI * 2;
|
|
|
+ const direction = new Vec2(Math.cos(angle), Math.sin(angle)).normalize();
|
|
|
+
|
|
|
+ // 设置新的速度
|
|
|
+ rigidBody.linearVelocity = new Vec2(
|
|
|
+ direction.x * this.currentSpeed,
|
|
|
+ direction.y * this.currentSpeed
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`[BallController] 小球 ${ball.name} 已重置到安全位置`);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 为小球设置安全位置(复用生成位置逻辑)
|
|
|
+ * @param ball 要设置位置的小球节点
|
|
|
+ */
|
|
|
+ private positionBallSafely(ball: Node): void {
|
|
|
+ if (!ball) return;
|
|
|
+
|
|
|
+ const transform = ball.getComponent(UITransform);
|
|
|
+ const ballRadius = transform ? transform.width / 2 : this.ballRadius;
|
|
|
+
|
|
|
+ // 计算可生成的范围(考虑小球半径,避免生成在边缘)
|
|
|
+ const minX = this.gameBounds.left + ballRadius + this.edgeOffset;
|
|
|
+ const maxX = this.gameBounds.right - ballRadius - this.edgeOffset;
|
|
|
+ const minY = this.gameBounds.bottom + ballRadius + this.edgeOffset;
|
|
|
+ const maxY = this.gameBounds.top - ballRadius - this.edgeOffset;
|
|
|
+
|
|
|
+ // 获取GameArea节点
|
|
|
+ const gameArea = find('Canvas/GameLevelUI/GameArea');
|
|
|
+ if (!gameArea) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查找PlacedBlocks节点,它包含所有放置的方块
|
|
|
+ if (!this.placedBlocksContainer) {
|
|
|
+ this.setRandomPositionDefault(ball, minX, maxX, minY, maxY);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.placedBlocksContainer.isValid) {
|
|
|
+ this.setRandomPositionDefault(ball, minX, maxX, minY, maxY);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取所有已放置的方块
|
|
|
+ const placedBlocks = [];
|
|
|
+ for (let i = 0; i < this.placedBlocksContainer.children.length; i++) {
|
|
|
+ const block = this.placedBlocksContainer.children[i];
|
|
|
+ // 检查是否是方块节点(通常以Block命名或有特定标识)
|
|
|
+ if (block.name.includes('Block') || block.getChildByName('B1')) {
|
|
|
+ placedBlocks.push(block);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有方块,使用默认随机位置
|
|
|
+ if (placedBlocks.length === 0) {
|
|
|
+ this.setRandomPositionDefault(ball, minX, maxX, minY, maxY);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 尝试找到一个不与任何方块重叠的位置
|
|
|
+ let validPosition = false;
|
|
|
+ let attempts = 0;
|
|
|
+ const maxAttempts = this.maxAttempts;
|
|
|
+ let randomX, randomY;
|
|
|
+
|
|
|
+ while (!validPosition && attempts < maxAttempts) {
|
|
|
+ // 随机生成位置
|
|
|
+ randomX = Math.random() * (maxX - minX) + minX;
|
|
|
+ randomY = Math.random() * (maxY - minY) + minY;
|
|
|
+
|
|
|
+ // 检查是否与任何方块重叠
|
|
|
+ let overlapping = false;
|
|
|
+ for (const block of placedBlocks) {
|
|
|
+ // 获取方块的世界坐标
|
|
|
+ const blockWorldPos = block.worldPosition;
|
|
|
+
|
|
|
+ // 计算小球与方块的距离
|
|
|
+ const distance = Math.sqrt(
|
|
|
+ Math.pow(randomX - blockWorldPos.x, 2) +
|
|
|
+ Math.pow(randomY - blockWorldPos.y, 2)
|
|
|
+ );
|
|
|
+
|
|
|
+ // 获取方块的尺寸
|
|
|
+ const blockTransform = block.getComponent(UITransform);
|
|
|
+ const blockSize = blockTransform ?
|
|
|
+ Math.max(blockTransform.width, blockTransform.height) / 2 : this.safeDistance;
|
|
|
+
|
|
|
+ // 如果距离小于小球半径+方块尺寸的一半+安全距离,认为重叠
|
|
|
+ if (distance < ballRadius + blockSize + this.safeDistance) {
|
|
|
+ overlapping = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有重叠,找到了有效位置
|
|
|
+ if (!overlapping) {
|
|
|
+ validPosition = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ attempts++;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果找不到有效位置,使用默认位置(游戏区域底部中心)
|
|
|
+ if (!validPosition) {
|
|
|
+ randomX = (this.gameBounds.left + this.gameBounds.right) / 2;
|
|
|
+ randomY = this.gameBounds.bottom + ballRadius + this.safeDistance;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将世界坐标转换为相对于GameArea的本地坐标
|
|
|
+ const localPos = gameArea.getComponent(UITransform).convertToNodeSpaceAR(new Vec3(randomX, randomY, 0));
|
|
|
+ ball.position = localPos;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置小球默认位置
|
|
|
+ * @param ball 小球节点
|
|
|
+ * @param minX 最小X坐标
|
|
|
+ * @param maxX 最大X坐标
|
|
|
+ * @param minY 最小Y坐标
|
|
|
+ * @param maxY 最大Y坐标
|
|
|
+ */
|
|
|
+ private setRandomPositionDefault(ball: Node, minX: number, maxX: number, minY: number, maxY: number): void {
|
|
|
+ const gameArea = find('Canvas/GameLevelUI/GameArea');
|
|
|
+ if (!gameArea) return;
|
|
|
+
|
|
|
+ // 随机生成位置
|
|
|
+ const randomX = Math.random() * (maxX - minX) + minX;
|
|
|
+ const randomY = Math.random() * (maxY - minY) + minY;
|
|
|
+
|
|
|
+ // 将世界坐标转换为相对于GameArea的本地坐标
|
|
|
+ const localPos = gameArea.getComponent(UITransform).convertToNodeSpaceAR(new Vec3(randomX, randomY, 0));
|
|
|
+ ball.position = localPos;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查并找回所有超出游戏区域的小球
|
|
|
+ */
|
|
|
+ private checkAndRescueAllBalls(): void {
|
|
|
+ if (!this.enableBallPositionCheck) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const balls = this.getAllActiveBalls();
|
|
|
+ let rescuedCount = 0;
|
|
|
+
|
|
|
+ balls.forEach(ball => {
|
|
|
+ if (!this.checkBallPosition(ball)) {
|
|
|
+ this.rescueBallToGameArea(ball);
|
|
|
+ rescuedCount++;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (rescuedCount > 0) {
|
|
|
+ console.log(`[BallController] 本次检查找回了 ${rescuedCount} 个小球`);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|