|
|
@@ -9,6 +9,7 @@ import { WeaponInfo } from './BlockSelection/WeaponInfo';
|
|
|
import { BlockInfo } from './BlockSelection/BlockInfo';
|
|
|
import { BallAni } from '../Animations/BallAni';
|
|
|
import { JsonConfigLoader } from '../Core/JsonConfigLoader';
|
|
|
+import { BallInstance } from './BallInstance';
|
|
|
const { ccclass, property } = _decorator;
|
|
|
|
|
|
@ccclass('BallController')
|
|
|
@@ -164,7 +165,7 @@ export class BallController extends Component {
|
|
|
private draggingBlocks: Set<Node> = new Set();
|
|
|
|
|
|
// 小球位置检查相关变量
|
|
|
- private lastPositionCheckTime: number = 0; // 上次位置检查的时间
|
|
|
+ // 控制器不再维护全局位置检查计时,由 BallInstance 负责实例级调度
|
|
|
|
|
|
async start() {
|
|
|
// 如果没有指定placedBlocksContainer,尝试找到它
|
|
|
@@ -344,7 +345,8 @@ export class BallController extends Component {
|
|
|
*/
|
|
|
private onGamePauseEvent() {
|
|
|
console.log('[BallController] 接收到游戏暂停事件,暂停小球运动');
|
|
|
- this.pauseBall();
|
|
|
+ // 统一为暂停所有小球,避免仅暂停主球导致状态不一致
|
|
|
+ this.pauseAllBalls();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -360,7 +362,7 @@ export class BallController extends Component {
|
|
|
*/
|
|
|
private onGamePauseBlockSelectionEvent() {
|
|
|
console.log('[BallController] 接收到底板选择暂停事件,小球继续运动');
|
|
|
- // 底板选择时不暂停小球运动,只是标记游戏状态为暂停
|
|
|
+ // 底板选择时不暂停小球运动,只是标记游戏状态为暂停,不修改速度或休眠状态
|
|
|
this.isPaused = true;
|
|
|
}
|
|
|
|
|
|
@@ -385,7 +387,8 @@ export class BallController extends Component {
|
|
|
*/
|
|
|
private onGameResumeBlockSelectionEvent() {
|
|
|
console.log('[BallController] 接收到底板选择恢复事件,恢复小球运动');
|
|
|
- this.resumeAllBalls();
|
|
|
+ // 底板选择恢复,不需要恢复速度(未暂停),仅恢复标记
|
|
|
+ this.isPaused = false;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -423,14 +426,26 @@ export class BallController extends Component {
|
|
|
/**
|
|
|
* 处理子弹发射检查事件
|
|
|
*/
|
|
|
- private onBallFireBulletEvent(blockNode: Node, fireWorldPos: Vec3) {
|
|
|
- // 如果游戏暂停,则不允许发射子弹
|
|
|
+ private onBallFireBulletEvent(...args: any[]) {
|
|
|
+ // 兼容两种负载形态:
|
|
|
+ // 1) 检查形态 { canFire: (value:boolean)=>void }
|
|
|
+ // 2) 发射形态 (blockNode: Node, fireWorldPos: Vec3)
|
|
|
+ if (args.length === 1 && args[0] && typeof args[0].canFire === 'function') {
|
|
|
+ // 这是“是否允许发射”的检查事件,BallController 不参与决策,直接忽略即可
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const [blockNode, fireWorldPos] = args;
|
|
|
+ if (!(blockNode instanceof Node) || !(fireWorldPos instanceof Vec3)) {
|
|
|
+ // 负载不匹配,忽略
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
if (this.isPaused) {
|
|
|
console.log('[BallController] 游戏暂停中,阻止子弹发射');
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- // 如果游戏未暂停,则继续执行子弹发射逻辑
|
|
|
+
|
|
|
this.fireBulletAt(blockNode, fireWorldPos);
|
|
|
}
|
|
|
|
|
|
@@ -518,8 +533,7 @@ export class BallController extends Component {
|
|
|
this.node.addChild(this.activeBall);
|
|
|
}
|
|
|
|
|
|
- // 随机位置小球
|
|
|
- this.positionBallRandomly();
|
|
|
+ // 位置随机由 BallInstance 统一处理(在注入配置后调用)
|
|
|
|
|
|
// 设置球的半径
|
|
|
const transform = this.activeBall.getComponent(UITransform);
|
|
|
@@ -529,6 +543,31 @@ export class BallController extends Component {
|
|
|
this.radius = 25; // 默认半径
|
|
|
}
|
|
|
|
|
|
+ // 为该球挂载 per-ball 逻辑组件(振荡检测/防围困)
|
|
|
+ const ballComp = this.activeBall.getComponent(BallInstance) || this.activeBall.addComponent(BallInstance);
|
|
|
+ if (ballComp) {
|
|
|
+ ballComp.baseSpeed = this.baseSpeed;
|
|
|
+ ballComp.currentSpeed = this.currentSpeed;
|
|
|
+ ballComp.positionHistorySize = this.positionHistorySize;
|
|
|
+ ballComp.oscillationTimeWindow = this.oscillationTimeWindow;
|
|
|
+ ballComp.directionChangeThreshold = this.directionChangeThreshold;
|
|
|
+ ballComp.oscillationDistanceThreshold = this.oscillationDistanceThreshold;
|
|
|
+ ballComp.testMode = this.testMode;
|
|
|
+ ballComp.enableOscillationDetection = true;
|
|
|
+ // 定期位置检查(实例级)
|
|
|
+ ballComp.enableBallPositionCheck = this.enableBallPositionCheck;
|
|
|
+ ballComp.positionCheckInterval = this.positionCheckInterval;
|
|
|
+ // 注入安全区域与避让配置
|
|
|
+ ballComp.edgeOffset = this.edgeOffset;
|
|
|
+ ballComp.safeDistance = this.safeDistance;
|
|
|
+ ballComp.ballRadius = this.ballRadius;
|
|
|
+ ballComp.maxAttempts = this.maxAttempts;
|
|
|
+ ballComp.positionCheckBoundaryExtension = this.positionCheckBoundaryExtension;
|
|
|
+ ballComp.placedBlocksContainer = this.placedBlocksContainer;
|
|
|
+ // 统一使用 BallInstance 的安全随机定位(避让方块,边界安全)
|
|
|
+ ballComp.placeRandomlyInGameArea();
|
|
|
+ }
|
|
|
+
|
|
|
// 确保有碰撞组件
|
|
|
this.setupCollider();
|
|
|
|
|
|
@@ -546,7 +585,6 @@ export class BallController extends Component {
|
|
|
|
|
|
// 实例化新的小球
|
|
|
const newBall = instantiate(this.ballPrefab);
|
|
|
- newBall.name = 'AdditionalBall';
|
|
|
|
|
|
// 将小球添加到GameArea中
|
|
|
const gameArea = find('Canvas/GameLevelUI/GameArea');
|
|
|
@@ -556,12 +594,36 @@ export class BallController extends Component {
|
|
|
this.node.addChild(newBall);
|
|
|
}
|
|
|
|
|
|
- // 随机位置小球
|
|
|
- this.positionAdditionalBall(newBall);
|
|
|
+ // 位置随机改为由 BallInstance 统一处理(在注入配置后执行)
|
|
|
|
|
|
// 设置球的碰撞组件
|
|
|
this.setupBallCollider(newBall);
|
|
|
|
|
|
+ // 为额外球挂载 per-ball 逻辑组件(振荡检测/防围困)
|
|
|
+ const addComp = newBall.getComponent(BallInstance) || newBall.addComponent(BallInstance);
|
|
|
+ if (addComp) {
|
|
|
+ addComp.baseSpeed = this.baseSpeed;
|
|
|
+ addComp.currentSpeed = this.currentSpeed;
|
|
|
+ addComp.positionHistorySize = this.positionHistorySize;
|
|
|
+ addComp.oscillationTimeWindow = this.oscillationTimeWindow;
|
|
|
+ addComp.directionChangeThreshold = this.directionChangeThreshold;
|
|
|
+ addComp.oscillationDistanceThreshold = this.oscillationDistanceThreshold;
|
|
|
+ addComp.testMode = this.testMode;
|
|
|
+ addComp.enableOscillationDetection = true;
|
|
|
+ // 定期位置检查(实例级)
|
|
|
+ addComp.enableBallPositionCheck = this.enableBallPositionCheck;
|
|
|
+ addComp.positionCheckInterval = this.positionCheckInterval;
|
|
|
+ // 注入安全区域与避让配置
|
|
|
+ addComp.edgeOffset = this.edgeOffset;
|
|
|
+ addComp.safeDistance = this.safeDistance;
|
|
|
+ addComp.ballRadius = this.ballRadius;
|
|
|
+ addComp.maxAttempts = this.maxAttempts;
|
|
|
+ addComp.positionCheckBoundaryExtension = this.positionCheckBoundaryExtension;
|
|
|
+ addComp.placedBlocksContainer = this.placedBlocksContainer;
|
|
|
+ // 统一使用 BallInstance 的安全随机定位(避让方块,边界安全)
|
|
|
+ addComp.placeRandomlyInGameArea();
|
|
|
+ }
|
|
|
+
|
|
|
// 设置初始方向和速度
|
|
|
this.initializeBallDirection(newBall);
|
|
|
|
|
|
@@ -687,6 +749,10 @@ export class BallController extends Component {
|
|
|
if (blockCollider.sensor) {
|
|
|
blockCollider.sensor = false;
|
|
|
}
|
|
|
+
|
|
|
+ // 统一方块的摩擦与回弹,避免能量衰减
|
|
|
+ blockCollider.friction = 0;
|
|
|
+ blockCollider.restitution = 1;
|
|
|
}
|
|
|
|
|
|
// 检查B1子节点的碰撞体
|
|
|
@@ -704,13 +770,29 @@ export class BallController extends Component {
|
|
|
if (b1Collider.sensor) {
|
|
|
b1Collider.sensor = false;
|
|
|
}
|
|
|
+
|
|
|
+ // 统一子节点的摩擦与回弹
|
|
|
+ b1Collider.friction = 0;
|
|
|
+ b1Collider.restitution = 1;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 检查Weapon子节点
|
|
|
const weaponNode = this.findWeaponNode(block);
|
|
|
if (weaponNode) {
|
|
|
- // 武器节点存在
|
|
|
+ // 武器节点存在,确保其碰撞体也不引入能量损失
|
|
|
+ const weaponCollider = weaponNode.getComponent(Collider2D);
|
|
|
+ if (weaponCollider) {
|
|
|
+ if (weaponCollider.group !== 2) {
|
|
|
+ weaponCollider.group = 2;
|
|
|
+ fixedCount++;
|
|
|
+ }
|
|
|
+ if (weaponCollider.sensor) {
|
|
|
+ weaponCollider.sensor = false;
|
|
|
+ }
|
|
|
+ weaponCollider.friction = 0;
|
|
|
+ weaponCollider.restitution = 1;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -719,96 +801,11 @@ export class BallController extends Component {
|
|
|
positionBallRandomly() {
|
|
|
if (!this.activeBall) return;
|
|
|
|
|
|
- const transform = this.activeBall.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(this.activeBall, minX, maxX, minY, maxY);
|
|
|
- return;
|
|
|
+ // 优先委派到 BallInstance 的公开方法(统一避让与安全逻辑)
|
|
|
+ const inst = this.activeBall.getComponent(BallInstance);
|
|
|
+ if (inst) {
|
|
|
+ inst.placeRandomlyInGameArea();
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
- // 获取所有已放置的方块
|
|
|
- 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(this.activeBall, 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));
|
|
|
- this.activeBall.position = localPos;
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1487,18 +1484,15 @@ export class BallController extends Component {
|
|
|
this.maintainAllBallsSpeed();
|
|
|
|
|
|
// 更新小球运动历史数据(用于检测振荡模式)
|
|
|
- this.updateBallMovementHistory();
|
|
|
+ // 如果未挂载 BallInstance,则保持旧逻辑作为回退;否则由 BallInstance 自行处理
|
|
|
+ const hasInstance = this.activeBall && this.activeBall.isValid && this.activeBall.getComponent(BallInstance);
|
|
|
+ if (!hasInstance) {
|
|
|
+ this.updateBallMovementHistory();
|
|
|
+ }
|
|
|
|
|
|
// 清理过期的防围困状态
|
|
|
this.cleanupExpiredAntiTrapStates();
|
|
|
-
|
|
|
- // 定期检查小球位置,防止小球被挤出游戏区域
|
|
|
- this.lastPositionCheckTime += dt;
|
|
|
- if (this.lastPositionCheckTime >= this.positionCheckInterval) {
|
|
|
- this.checkAndRescueAllBalls();
|
|
|
- this.lastPositionCheckTime = 0;
|
|
|
- }
|
|
|
-
|
|
|
+
|
|
|
// 定期检查小球是否接近方块但没有触发碰撞(调试用)
|
|
|
if (this.activeBall && this.activeBall.isValid) {
|
|
|
this.debugCheckNearBlocks();
|
|
|
@@ -1799,43 +1793,20 @@ export class BallController extends Component {
|
|
|
}
|
|
|
|
|
|
|
|
|
- // 初始化方向
|
|
|
+ // 初始化方向(统一随机角度)
|
|
|
initializeDirection() {
|
|
|
- // 测试模式:使用完全垂直或水平方向来测试防围困机制
|
|
|
- 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) {
|
|
|
- const rigidBody = this.activeBall.getComponent(RigidBody2D);
|
|
|
- if (rigidBody) {
|
|
|
- rigidBody.linearVelocity = new Vec2(
|
|
|
- this.direction.x * this.currentSpeed,
|
|
|
- this.direction.y * this.currentSpeed
|
|
|
- );
|
|
|
+ if (!this.activeBall) return;
|
|
|
+ // 使用统一的随机角度初始化速度
|
|
|
+ this.initializeBallDirection(this.activeBall);
|
|
|
+ // 同步 controller.direction 为当前速度方向
|
|
|
+ const rigidBody = this.activeBall.getComponent(RigidBody2D);
|
|
|
+ if (rigidBody) {
|
|
|
+ const v = rigidBody.linearVelocity;
|
|
|
+ const dir = new Vec2(v.x, v.y);
|
|
|
+ if (dir.length() > 0) {
|
|
|
+ dir.normalize();
|
|
|
+ this.direction.x = dir.x;
|
|
|
+ this.direction.y = dir.y;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -2184,219 +2155,13 @@ export class BallController extends Component {
|
|
|
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} 个小球`);
|
|
|
- }
|
|
|
- }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
}
|