import { _decorator, Component, Node, Vec2, Vec3, UITransform, Collider2D, Contact2DType, IPhysics2DContact, RigidBody2D, Prefab, instantiate, find, CircleCollider2D } from 'cc'; import { PhysicsManager } from '../Core/PhysicsManager'; import { WeaponBullet, BulletInitData, WeaponConfig } from './WeaponBullet'; import EventBus, { GameEvents } from '../Core/EventBus'; import { PersistentSkillManager } from '../FourUI/SkillSystem/PersistentSkillManager'; import { BallAni } from '../Animations/BallAni'; const { ccclass, property } = _decorator; @ccclass('BallController') export class BallController extends Component { // 球的预制体 @property({ type: Prefab, tooltip: '拖拽Ball预制体到这里' }) public ballPrefab: Prefab = null; // 已放置方块容器节点 @property({ type: Node, tooltip: '拖拽PlacedBlocks节点到这里(Canvas/GameLevelUI/PlacedBlocks)' }) public placedBlocksContainer: Node = null; // 球的移动速度 @property public baseSpeed: number = 60; // 原speed改为baseSpeed public currentSpeed: number = 60; // 反弹随机偏移最大角度(弧度) @property public maxReflectionRandomness: number = 0.2; // 当前活动的球 private activeBall: Node = null; // 球的方向向量 private direction: Vec2 = new Vec2(); // GameArea区域边界 private gameBounds = { left: 0, right: 0, top: 0, bottom: 0 }; // 球的半径 private radius: number = 0; // 是否已初始化 private initialized: boolean = false; // 子弹预制体 @property({ type: Prefab, tooltip: '拖拽子弹预制体到这里' }) public bulletPrefab: Prefab = null; // 小球是否已开始运动 private ballStarted: boolean = false; // 小球暂停时记录的速度 private pausedVelocity: Vec2 = new Vec2(); // 标记是否处于暂停状态 private isPaused: boolean = false; // 在类字段区添加 private blockFireCooldown: Map = new Map(); private FIRE_COOLDOWN = 0.05; // 带尾部特效的子弹容器预制体 @property({ type: Prefab, tooltip: '拖拽带尾部特效的子弹容器预制体到这里(例如 PelletContainer)' }) public bulletContainerPrefab: Prefab = null; start() { // 如果没有指定placedBlocksContainer,尝试找到它 if (!this.placedBlocksContainer) { this.placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks'); if (!this.placedBlocksContainer) { // 找不到PlacedBlocks节点,某些功能可能无法正常工作 } } // 只进行初始设置,不创建小球 this.calculateGameBounds(); // 监听游戏事件 this.setupEventListeners(); // 监听球速技能变化并更新球速 this.updateBallSpeed(); } /** * 设置事件监听器 */ private setupEventListeners() { const eventBus = EventBus.getInstance(); // 监听暂停事件 eventBus.on(GameEvents.GAME_PAUSE, this.onGamePauseEvent, this); // 监听恢复事件 eventBus.on(GameEvents.GAME_RESUME, this.onGameResumeEvent, this); // 监听重置球控制器事件 eventBus.on(GameEvents.RESET_BALL_CONTROLLER, this.onResetBallControllerEvent, this); // 监听球创建事件 eventBus.on(GameEvents.BALL_CREATE, this.onBallCreateEvent, this); // 监听球启动事件 eventBus.on(GameEvents.BALL_START, this.onBallStartEvent, this); // 监听创建额外球事件 eventBus.on(GameEvents.BALL_CREATE_ADDITIONAL, this.onBallCreateAdditionalEvent, this); } /** * 处理游戏暂停事件 */ private onGamePauseEvent() { console.log('[BallController] 接收到游戏暂停事件'); // 根据需求,暂停时小球继续运动但不发射子弹 // 子弹发射的控制已经通过GamePause.isBulletFireEnabled()实现 // 这里可以添加其他暂停相关的逻辑 } /** * 处理游戏恢复事件 */ private onGameResumeEvent() { console.log('[BallController] 接收到游戏恢复事件'); // 恢复时重新启用子弹发射 // 子弹发射的控制已经通过GamePause.isBulletFireEnabled()实现 // 这里可以添加其他恢复相关的逻辑 } /** * 处理重置球控制器事件 */ private onResetBallControllerEvent() { console.log('[BallController] 接收到重置球控制器事件'); this.resetBallController(); } /** * 处理球创建事件 */ private onBallCreateEvent() { console.log('[BallController] 接收到球创建事件'); this.createBall(); } /** * 处理球启动事件 */ private onBallStartEvent() { console.log('[BallController] 接收到球启动事件'); this.startBall(); } /** * 处理创建额外球事件 */ private onBallCreateAdditionalEvent() { console.log('[BallController] 接收到创建额外球事件'); this.createAdditionalBall(); } // 计算游戏边界(使用GameArea节点) calculateGameBounds() { // 获取GameArea节点 const gameArea = find('Canvas/GameLevelUI/GameArea'); if (!gameArea) { return; } const gameAreaUI = gameArea.getComponent(UITransform); if (!gameAreaUI) { return; } // 获取GameArea的尺寸 const areaWidth = gameAreaUI.width; const areaHeight = gameAreaUI.height; // 获取GameArea的世界坐标位置 const worldPos = gameArea.worldPosition; // 计算GameArea的世界坐标边界 this.gameBounds.left = worldPos.x - areaWidth / 2; this.gameBounds.right = worldPos.x + areaWidth / 2; this.gameBounds.bottom = worldPos.y - areaHeight / 2; this.gameBounds.top = worldPos.y + areaHeight / 2; } // 创建小球 createBall() { if (!this.ballPrefab) { console.error('[BallController] ballPrefab 未设置,无法创建小球'); return; } // 实例化小球 this.activeBall = instantiate(this.ballPrefab); if (!this.activeBall) { console.error('[BallController] 小球实例化失败'); return; } // 将小球添加到GameArea中 const gameArea = find('Canvas/GameLevelUI/GameArea'); if (gameArea) { gameArea.addChild(this.activeBall); } else { console.warn('[BallController] 未找到GameArea,将小球添加到当前节点'); this.node.addChild(this.activeBall); } // 随机位置小球 this.positionBallRandomly(); // 设置球的半径 const transform = this.activeBall.getComponent(UITransform); if (transform) { this.radius = transform.width / 2; } else { this.radius = 25; // 默认半径 } // 确保有碰撞组件 this.setupCollider(); // 注意:不在这里初始化方向,等待 startBall() 调用 this.initialized = true; } // 创建额外的小球(不替换现有的小球) public createAdditionalBall() { if (!this.ballPrefab) { console.error('无法创建额外小球:ballPrefab 未设置'); return; } // 实例化新的小球 const newBall = instantiate(this.ballPrefab); newBall.name = 'AdditionalBall'; // 将小球添加到GameArea中 const gameArea = find('Canvas/GameLevelUI/GameArea'); if (gameArea) { gameArea.addChild(newBall); } else { this.node.addChild(newBall); } // 随机位置小球 this.positionAdditionalBall(newBall); // 设置球的碰撞组件 this.setupBallCollider(newBall); // 设置初始方向和速度 this.initializeBallDirection(newBall); console.log('创建了额外的小球'); return newBall; } // 为额外小球设置随机位置 private positionAdditionalBall(ball: Node) { if (!ball) return; const transform = ball.getComponent(UITransform); const ballRadius = transform ? transform.width / 2 : 25; // 计算可生成的范围(考虑小球半径,避免生成在边缘) const minX = this.gameBounds.left + ballRadius + 20; const maxX = this.gameBounds.right - ballRadius - 20; const minY = this.gameBounds.bottom + ballRadius + 20; const maxY = this.gameBounds.top - ballRadius - 20; // 获取GameArea节点 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 setupBallCollider(ball: Node) { // 确保有碰撞组件 let collider = ball.getComponent(CircleCollider2D); if (!collider) { collider = ball.addComponent(CircleCollider2D); } // 设置碰撞属性 collider.radius = ball.getComponent(UITransform)?.width / 2 || 25; collider.group = 1; // 设置为球的碰撞组 collider.tag = 1; // 小球标签 collider.sensor = false; collider.friction = 0; // 无摩擦 collider.restitution = 1; // 完全弹性碰撞 // 添加刚体组件 let rigidBody = ball.getComponent(RigidBody2D); if (!rigidBody) { rigidBody = ball.addComponent(RigidBody2D); } // 设置刚体属性 rigidBody.type = 2; // 2 = 动态刚体 rigidBody.allowSleep = false; rigidBody.gravityScale = 0; rigidBody.linearDamping = 0; // 无线性阻尼,保持速度不衰减 rigidBody.angularDamping = 0; // 无角阻尼 rigidBody.fixedRotation = true; rigidBody.enabledContactListener = true; // 启用碰撞监听 // 注意:不需要为每个小球单独添加碰撞回调, // 因为我们使用的是全局物理系统回调 } // 为额外小球初始化方向和速度 private initializeBallDirection(ball: Node) { // 随机初始方向 const angle = Math.random() * Math.PI * 2; // 0-2π之间的随机角度 const direction = new Vec2(Math.cos(angle), Math.sin(angle)).normalize(); // 设置初始速度 const rigidBody = ball.getComponent(RigidBody2D); if (rigidBody) { rigidBody.linearVelocity = new Vec2( direction.x * this.currentSpeed, direction.y * this.currentSpeed ); } } // 检查所有已放置方块的碰撞体组件 private checkBlockColliders() { if (!this.placedBlocksContainer) { return; } if (!this.placedBlocksContainer.isValid) { return; } const blocks = []; for (let i = 0; i < this.placedBlocksContainer.children.length; i++) { const block = this.placedBlocksContainer.children[i]; if (block.name.includes('Block') || block.getChildByName('B1')) { blocks.push(block); } } let fixedCount = 0; for (let i = 0; i < blocks.length; i++) { const block = blocks[i]; // 检查方块本身的碰撞体 const blockCollider = block.getComponent(Collider2D); if (blockCollider) { // 🔧 自动修复碰撞组设置 if (blockCollider.group !== 2) { blockCollider.group = 2; // 设置为Block组 fixedCount++; } // 确保不是传感器 if (blockCollider.sensor) { blockCollider.sensor = false; } } // 检查B1子节点的碰撞体 const b1Node = block.getChildByName('B1'); if (b1Node) { const b1Collider = b1Node.getComponent(Collider2D); if (b1Collider) { // 🔧 修复B1子节点的碰撞设置 if (b1Collider.group !== 2) { b1Collider.group = 2; // 设置为Block组 fixedCount++; } // 确保B1不是传感器(需要实际碰撞) if (b1Collider.sensor) { b1Collider.sensor = false; } } } // 检查Weapon子节点 const weaponNode = this.findWeaponNode(block); if (weaponNode) { // 武器节点存在 } } } // 随机位置小球 positionBallRandomly() { if (!this.activeBall) return; const transform = this.activeBall.getComponent(UITransform); const ballRadius = transform ? transform.width / 2 : 25; // 计算可生成的范围(考虑小球半径,避免生成在边缘) const minX = this.gameBounds.left + ballRadius + 20; // 额外偏移,避免生成在边缘 const maxX = this.gameBounds.right - ballRadius - 20; const minY = this.gameBounds.bottom + ballRadius + 20; const maxY = this.gameBounds.top - ballRadius - 20; // 获取GameArea节点 const gameArea = find('Canvas/GameLevelUI/GameArea'); if (!gameArea) { return; } // 查找PlacedBlocks节点,它包含所有放置的方块 if (!this.placedBlocksContainer) { this.setRandomPositionDefault(minX, maxX, minY, maxY); return; } if (!this.placedBlocksContainer.isValid) { this.setRandomPositionDefault(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(minX, maxX, minY, maxY); return; } // 尝试找到一个不与任何方块重叠的位置 let validPosition = false; let attempts = 0; const maxAttempts = 50; // 最大尝试次数 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 : 50; // 如果距离小于小球半径+方块尺寸的一半+安全距离,认为重叠 const safeDistance = 20; // 额外安全距离 if (distance < ballRadius + blockSize + safeDistance) { overlapping = true; break; } } // 如果没有重叠,找到了有效位置 if (!overlapping) { validPosition = true; } attempts++; } // 如果找不到有效位置,使用默认位置(游戏区域底部中心) if (!validPosition) { randomX = (this.gameBounds.left + this.gameBounds.right) / 2; randomY = this.gameBounds.bottom + ballRadius + 50; // 底部上方50单位 } // 将世界坐标转换为相对于GameArea的本地坐标 const localPos = gameArea.getComponent(UITransform).convertToNodeSpaceAR(new Vec3(randomX, randomY, 0)); 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() { if (!this.activeBall) return; // 确保小球有刚体组件 let rigidBody = this.activeBall.getComponent(RigidBody2D); if (!rigidBody) { rigidBody = this.activeBall.addComponent(RigidBody2D); rigidBody.type = 2; // Dynamic rigidBody.gravityScale = 0; // 不受重力影响 rigidBody.enabledContactListener = true; // 启用碰撞监听 rigidBody.fixedRotation = true; // 固定旋转 rigidBody.allowSleep = false; // 不允许休眠 rigidBody.linearDamping = 0; // 无线性阻尼,保持速度不衰减 rigidBody.angularDamping = 0; // 无角阻尼 } else { // 确保已有的刚体组件设置正确 rigidBody.enabledContactListener = true; rigidBody.gravityScale = 0; rigidBody.linearDamping = 0; // 确保无线性阻尼,保持速度不衰减 rigidBody.angularDamping = 0; // 确保无角阻尼 rigidBody.allowSleep = false; // 不允许休眠 } // 确保小球有碰撞组件 let collider = this.activeBall.getComponent(CircleCollider2D); if (!collider) { collider = this.activeBall.addComponent(CircleCollider2D); collider.radius = this.radius || 25; // 使用已计算的半径或默认值 collider.tag = 1; // 小球标签 collider.group = 1; // 碰撞组1 - Ball组 collider.sensor = false; // 非传感器(实际碰撞) collider.friction = 0; // 无摩擦 collider.restitution = 1; // 完全弹性碰撞 } else { // 确保已有的碰撞组件设置正确 collider.sensor = false; collider.restitution = 1; collider.group = 1; // 确保是Ball组 collider.tag = 1; // 确保标签正确 } // === 使用全局回调监听器 === const physics = PhysicsManager.getInstance()?.getSystem(); if (physics) { // 先移除旧监听,避免重复注册 physics.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); physics.off(Contact2DType.END_CONTACT, this.onEndContact, this); physics.off(Contact2DType.PRE_SOLVE, this.onPreSolve, this); physics.off(Contact2DType.POST_SOLVE, this.onPostSolve, this); physics.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); physics.on(Contact2DType.END_CONTACT, this.onEndContact, this); physics.on(Contact2DType.PRE_SOLVE, this.onPreSolve, this); physics.on(Contact2DType.POST_SOLVE, this.onPostSolve, this); } } // 碰撞回调 - 处理小球与方块以及小球之间的碰撞 onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { // 检查是否为小球之间的碰撞 if (selfCollider.group === 1 && otherCollider.group === 1) { // 小球之间的碰撞 - 防止速度改变 this.handleBallToBallCollision(selfCollider, otherCollider, contact); return; } // 判断哪个是小球,哪个是碰撞对象 let ballNode: Node = null; let otherNode: Node = null; // 检查self是否为小球(组1) if (selfCollider.group === 1) { ballNode = selfCollider.node; otherNode = otherCollider.node; } // 检查other是否为小球(组1) else if (otherCollider.group === 1) { ballNode = otherCollider.node; otherNode = selfCollider.node; } // 如果没有找到小球,跳过处理 if (!ballNode || !otherNode) { return; } // 计算碰撞世界坐标 let contactPos: Vec3 = null; if (contact && (contact as any).getWorldManifold) { const wm = (contact as any).getWorldManifold(); if (wm && wm.points && wm.points.length > 0) { contactPos = new Vec3(wm.points[0].x, wm.points[0].y, 0); } } if (!contactPos) { contactPos = otherNode.worldPosition.clone(); } // 播放撞击特效 - 对所有碰撞都播放特效 const ballAni = BallAni.getInstance(); if (ballAni) { ballAni.playImpactEffect(contactPos); } // 检查碰撞对象是否为方块,只有方块碰撞才发射子弹 const nodeName = otherNode.name; const nodePath = this.getNodePath(otherNode); // 检查是否有Weapon子节点(判断是否为方块) const hasWeaponChild = otherNode.getChildByName('Weapon') !== null; const isBlock = nodeName.includes('Block') || nodePath.includes('Block') || hasWeaponChild; if (isBlock) { // 通过事件检查是否可以发射子弹 const eventBus = EventBus.getInstance(); let canFire = true; // 发送检查事件,如果有监听器返回false则不发射 eventBus.emit(GameEvents.BALL_FIRE_BULLET, { canFire: (value: boolean) => { canFire = value; } }); if (canFire) { const now = performance.now(); const lastTime = this.blockFireCooldown.get(otherNode.uuid) || 0; if (now - lastTime > this.FIRE_COOLDOWN * 1000) { this.blockFireCooldown.set(otherNode.uuid, now); this.fireBulletAt(otherNode, contactPos); } } } } /** * 处理小球之间的碰撞 - 保持恒定速度 */ private handleBallToBallCollision(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { const ball1 = selfCollider.node; const ball2 = otherCollider.node; const rigidBody1 = ball1.getComponent(RigidBody2D); const rigidBody2 = ball2.getComponent(RigidBody2D); if (!rigidBody1 || !rigidBody2) { return; } // 计算碰撞位置并播放撞击特效 let contactPos: Vec3 = null; if (contact && (contact as any).getWorldManifold) { const wm = (contact as any).getWorldManifold(); if (wm && wm.points && wm.points.length > 0) { contactPos = new Vec3(wm.points[0].x, wm.points[0].y, 0); } } if (!contactPos) { // 使用两个小球位置的中点作为碰撞位置 const pos1 = ball1.worldPosition; const pos2 = ball2.worldPosition; contactPos = new Vec3( (pos1.x + pos2.x) / 2, (pos1.y + pos2.y) / 2, 0 ); } // 播放撞击特效 const ballAni = BallAni.getInstance(); if (ballAni) { ballAni.playImpactEffect(contactPos); } // 获取碰撞前的速度 const velocity1 = rigidBody1.linearVelocity.clone(); const velocity2 = rigidBody2.linearVelocity.clone(); // 计算速度大小 const speed1 = Math.sqrt(velocity1.x * velocity1.x + velocity1.y * velocity1.y); const speed2 = Math.sqrt(velocity2.x * velocity2.x + velocity2.y * velocity2.y); // 延迟一帧后恢复正确的速度,避免物理引擎的速度改变 this.scheduleOnce(() => { if (ball1.isValid && rigidBody1.isValid) { const currentVel1 = rigidBody1.linearVelocity; const currentSpeed1 = Math.sqrt(currentVel1.x * currentVel1.x + currentVel1.y * currentVel1.y); // 如果速度发生了显著变化,恢复到目标速度 if (Math.abs(currentSpeed1 - this.currentSpeed) > 5) { const normalizedVel1 = currentVel1.clone().normalize(); rigidBody1.linearVelocity = new Vec2( normalizedVel1.x * this.currentSpeed, normalizedVel1.y * this.currentSpeed ); } } if (ball2.isValid && rigidBody2.isValid) { const currentVel2 = rigidBody2.linearVelocity; const currentSpeed2 = Math.sqrt(currentVel2.x * currentVel2.x + currentVel2.y * currentVel2.y); // 如果速度发生了显著变化,恢复到目标速度 if (Math.abs(currentSpeed2 - this.currentSpeed) > 5) { const normalizedVel2 = currentVel2.clone().normalize(); rigidBody2.linearVelocity = new Vec2( normalizedVel2.x * this.currentSpeed, normalizedVel2.y * this.currentSpeed ); } } }, 0.016); // 约一帧的时间 } // 碰撞结束事件 - 简化版本 onEndContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { // Debug log removed } // 碰撞预处理事件 - 简化版本 onPreSolve(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { // console.log('⚙️ 碰撞预处理:', otherCollider.node.name); } // 碰撞后处理事件 - 简化版本 onPostSolve(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { // console.log('✅ 碰撞后处理:', otherCollider.node.name); } // 计算反射向量 calculateReflection(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 randomAngle = (Math.random() - 0.5) * this.maxReflectionRandomness; // 随机角度 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.1; // 约5.7度 // 检查是否接近水平方向 if (Math.abs(randomizedReflection.y) < minAngleFromAxis) { // 调整y分量,使其远离水平方向 const sign = randomizedReflection.y >= 0 ? 1 : -1; randomizedReflection.y = sign * (minAngleFromAxis + Math.random() * 0.1); // 重新归一化 randomizedReflection.normalize(); } // 检查是否接近垂直方向 if (Math.abs(randomizedReflection.x) < minAngleFromAxis) { // 调整x分量,使其远离垂直方向 const sign = randomizedReflection.x >= 0 ? 1 : -1; randomizedReflection.x = sign * (minAngleFromAxis + Math.random() * 0.1); // 重新归一化 randomizedReflection.normalize(); } randomizedReflection.normalize(); // Debug log removed return randomizedReflection; } /** * 从方块武器发射子弹攻击敌人 - 重构版本 * 现在直接创建子弹实例并使用BulletController的实例方法 * @param blockNode 激活的方块节点 */ fireBullet(blockNode: Node) { // Debug logs removed // 检查子弹预制体是否存在 if (!this.bulletPrefab) { return; } // 查找方块中的Weapon节点 const weaponNode = this.findWeaponNode(blockNode); if (!weaponNode) { const blockWorldPos = blockNode.worldPosition; const weaponConfig2: WeaponConfig | null = (blockNode as any)['weaponConfig'] || null; this.createAndFireBullet(blockWorldPos, weaponConfig2); return; } // 获取武器的世界坐标作为发射位置 let firePosition: Vec3; try { firePosition = weaponNode.worldPosition; } catch (error) { // 备用方案:使用方块坐标 firePosition = blockNode.worldPosition; } // 创建并发射子弹 const weaponConfig: WeaponConfig | null = (blockNode as any)['weaponConfig'] || null; this.createAndFireBullet(firePosition, weaponConfig); } /** * 创建并发射子弹 - 使用新的WeaponBullet系统 * @param firePosition 发射位置(世界坐标) * @param weaponConfig 武器配置 */ private createAndFireBullet(firePosition: Vec3, weaponConfig: WeaponConfig | null) { // 确保武器配置加载 WeaponBullet.loadWeaponsData().then(() => { // 默认使用毛豆射手配置,后续可以根据方块类型动态选择 const defaultWeaponId = 'pea_shooter'; const finalConfig = weaponConfig || WeaponBullet.getWeaponConfig(defaultWeaponId); if (!finalConfig) { return; } // 创建子弹初始化数据 const initData: BulletInitData = { weaponId: finalConfig.id, firePosition: firePosition, autoTarget: true, weaponConfig: finalConfig }; // 验证初始化数据 if (!WeaponBullet.validateInitData(initData)) { return; } // 判断是否为多发子弹(散射/连发等) const countCfg = finalConfig.bulletConfig.count; const isMultiShot = countCfg && countCfg.type !== 'single' && countCfg.amount > 1; // 查找GameArea(子弹统一添加到此节点) const gameArea = find('Canvas/GameLevelUI/GameArea'); if (!gameArea) { return; } // === 根据武器配置选择合适的预制体 === const needsTrail = !!(finalConfig.bulletConfig?.visual?.trailEffect); const prefabToUse: Prefab = (needsTrail && this.bulletContainerPrefab) ? this.bulletContainerPrefab : this.bulletPrefab; if (!prefabToUse) { return; // 如果没有可用的预制体则直接退出 } if (isMultiShot) { // 使用批量创建逻辑 const bullets = WeaponBullet.createBullets(initData, prefabToUse as any); bullets.forEach(b => { gameArea.addChild(b); }); } else { // 单发逻辑(沿用原流程) const bullet = instantiate(prefabToUse); if (!bullet) { return; } gameArea.addChild(bullet); let weaponBullet = bullet.getComponent(WeaponBullet); if (!weaponBullet) { weaponBullet = bullet.addComponent(WeaponBullet); } weaponBullet.init(initData); } }).catch(error => { // 武器配置加载失败 }); } // 递归查找Weapon节点 private findWeaponNode(node: Node): Node | null { // logs removed // 先检查当前节点是否有Weapon子节点 const weaponNode = node.getChildByName('Weapon'); if (weaponNode) { return weaponNode; } // 如果没有,递归检查所有子节点 for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; const foundWeapon = this.findWeaponNode(child); if (foundWeapon) { return foundWeapon; } } // 如果都没找到,返回null return null; } // 获取节点的完整路径 private getNodePath(node: Node): string { let path = node.name; let current = node; while (current.parent) { current = current.parent; path = current.name + '/' + path; } return path; } update(dt: number) { // 只有当小球已启动时才执行运动逻辑 if (!this.ballStarted || !this.initialized) { return; } // 维持所有小球的恒定速度 this.maintainAllBallsSpeed(); // 定期检查小球是否接近方块但没有触发碰撞(调试用) if (this.activeBall && this.activeBall.isValid) { this.debugCheckNearBlocks(); } } /** * 维持所有小球的恒定速度 */ private maintainAllBallsSpeed() { // 维持主小球速度 if (this.activeBall && this.activeBall.isValid) { this.maintainBallSpeed(this.activeBall); } // 维持额外小球的速度 const gameArea = find('Canvas/GameLevelUI/GameArea'); if (gameArea) { const additionalBalls = gameArea.children.filter(child => child.name === 'AdditionalBall' && child.isValid ); for (const ball of additionalBalls) { this.maintainBallSpeed(ball); } } } /** * 维持单个小球的恒定速度 */ private maintainBallSpeed(ball: Node) { const rigidBody = ball.getComponent(RigidBody2D); if (!rigidBody) return; // 获取当前速度 const currentVelocity = rigidBody.linearVelocity; const speed = Math.sqrt(currentVelocity.x * currentVelocity.x + currentVelocity.y * currentVelocity.y); // 如果速度过低或过高,重新设置速度以维持恒定运动 if (speed < this.currentSpeed * 0.85 || speed > this.currentSpeed * 1.15) { // 保持当前方向,但调整速度大小 if (speed > 0.1) { const normalizedVelocity = currentVelocity.clone().normalize(); rigidBody.linearVelocity = new Vec2( normalizedVelocity.x * this.currentSpeed, normalizedVelocity.y * this.currentSpeed ); } else { // 如果速度几乎为0,给一个随机方向 const angle = Math.random() * Math.PI * 2; rigidBody.linearVelocity = new Vec2( Math.cos(angle) * this.currentSpeed, Math.sin(angle) * this.currentSpeed ); } } // 更新主小球的方向向量(用于其他逻辑) if (ball === this.activeBall && speed > 1.0) { this.direction.x = currentVelocity.x / speed; this.direction.y = currentVelocity.y / speed; } } // 调试方法:检查小球是否接近方块但没有触发物理碰撞 private debugCheckCounter = 0; private debugCheckNearBlocks() { this.debugCheckCounter++; // 每60帧(约1秒)检查一次 if (this.debugCheckCounter % 60 !== 0) return; const ballPos = this.activeBall.worldPosition; if (!this.placedBlocksContainer || !this.placedBlocksContainer.isValid) return; let nearestDistance = Infinity; let nearestBlock = null; for (let i = 0; i < this.placedBlocksContainer.children.length; i++) { const block = this.placedBlocksContainer.children[i]; if (block.name.includes('Block') || block.getChildByName('B1')) { const blockPos = block.worldPosition; const distance = Math.sqrt( Math.pow(ballPos.x - blockPos.x, 2) + Math.pow(ballPos.y - blockPos.y, 2) ); if (distance < nearestDistance) { nearestDistance = distance; nearestBlock = block; } } } if (nearestBlock && nearestDistance < 100) { // 检查小球的碰撞体状态 const ballCollider = this.activeBall.getComponent(Collider2D); if (ballCollider) { if (ballCollider instanceof CircleCollider2D) { // 小球碰撞半径检查 } } // 检查小球的刚体状态 const ballRigidBody = this.activeBall.getComponent(RigidBody2D); if (ballRigidBody) { // 刚体状态检查 } // 检查最近的方块碰撞体 const blockCollider = nearestBlock.getComponent(Collider2D); if (blockCollider) { // 方块碰撞体检查 } } } // 初始化方向 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.activeBall) { const rigidBody = this.activeBall.getComponent(RigidBody2D); if (rigidBody) { rigidBody.linearVelocity = new Vec2( this.direction.x * this.currentSpeed, this.direction.y * this.currentSpeed ); } } } // 初始化球的参数 - 公开方法,供GameManager调用 public initialize() { this.calculateGameBounds(); this.createBall(); // 检查方块碰撞体(延迟执行,确保方块已放置) this.scheduleOnce(() => { this.checkBlockColliders(); }, 0.5); } // 启动小球 - 公开方法,在确定按钮点击后调用 public startBall() { // 检查ballPrefab是否设置 if (!this.ballPrefab) { console.error('[BallController] ballPrefab 未设置,无法创建小球'); return; } // 如果还没有初始化,先初始化 if (!this.initialized) { this.initialize(); } // 确保小球存在且有效 if (!this.activeBall || !this.activeBall.isValid) { this.createBall(); } // 检查小球是否成功创建 if (!this.activeBall) { console.error('[BallController] 小球创建失败'); return; } // 重新定位小球,避免与方块重叠 this.positionBallRandomly(); // 确保物理组件设置正确 this.setupCollider(); // 初始化运动方向并开始运动 this.initializeDirection(); // 设置运动状态 this.ballStarted = true; console.log('[BallController] 小球启动完成'); } /** * 暂停小球运动:记录当前速度并停止刚体 */ public pauseBall() { if (this.isPaused) return; this.isPaused = true; this.ballStarted = false; if (this.activeBall && this.activeBall.isValid) { const rb = this.activeBall.getComponent(RigidBody2D); if (rb) { this.pausedVelocity = rb.linearVelocity.clone(); rb.linearVelocity = new Vec2(0, 0); rb.sleep(); } } } /** * 恢复小球运动:恢复暂停前的速度 */ public resumeBall() { if (!this.isPaused) return; this.isPaused = false; this.ballStarted = true; console.log('恢复小球运动'); if (this.activeBall && this.activeBall.isValid) { const rb = this.activeBall.getComponent(RigidBody2D); if (rb) { rb.wakeUp(); const hasPrevVelocity = this.pausedVelocity && (this.pausedVelocity.x !== 0 || this.pausedVelocity.y !== 0); if (hasPrevVelocity) { rb.linearVelocity = this.pausedVelocity.clone(); } else { // 若没有记录速度,则重新初始化方向 this.initializeDirection(); } } } } /** * 从给定世界坐标发射子弹 */ private fireBulletAt(blockNode: Node, fireWorldPos: Vec3) { // 检查子弹预制体是否存在 if (!this.bulletPrefab) { return; } // 直接使用碰撞世界坐标作为发射点 const weaponConfig: WeaponConfig | null = (blockNode as any)['weaponConfig'] || null; this.createAndFireBullet(fireWorldPos, weaponConfig); } /** * 重置球控制器状态 - 游戏重置时调用 */ public resetBallController() { console.log('[BallController] 重置球控制器状态'); // 停止球的运动 this.ballStarted = false; this.isPaused = false; // 清理暂停状态 this.pausedVelocity = new Vec2(); // 销毁当前活动的球 if (this.activeBall && this.activeBall.isValid) { console.log('[BallController] 销毁当前活动的球'); this.activeBall.destroy(); } this.activeBall = null; // 重置初始化状态 this.initialized = false; // 清理冷却时间 this.blockFireCooldown.clear(); // 重置球速 this.updateBallSpeed(); console.log('[BallController] 球控制器重置完成'); } onDestroy() { // 清理事件监听 const eventBus = EventBus.getInstance(); eventBus.off(GameEvents.GAME_PAUSE, this.onGamePauseEvent, this); eventBus.off(GameEvents.GAME_RESUME, this.onGameResumeEvent, this); eventBus.off(GameEvents.RESET_BALL_CONTROLLER, this.onResetBallControllerEvent, this); } private updateBallSpeed() { const skillManager = PersistentSkillManager.getInstance(); if (skillManager) { this.currentSpeed = skillManager.applyBallSpeedBonus(this.baseSpeed); } else { this.currentSpeed = this.baseSpeed; } } }