import { _decorator, Component, Node, Button, Label, find, EventTouch, Vec2, Vec3, UITransform, Rect, Collider2D, Graphics, Color, Sprite } from 'cc'; import { LevelSessionManager } from '../../Core/LevelSessionManager'; import { BallController } from '../BallController'; import { BlockManager } from '../BlockManager'; import { ConfigManager } from '../../Core/ConfigManager'; import { BlockTag } from './BlockTag'; import { WeaponInfo } from './WeaponInfo'; import { SkillManager } from '../SkillSelection/SkillManager'; import { Audio } from '../../AudioManager/AudioManager'; import { AdManager } from '../../Ads/AdManager'; import { JsonConfigLoader } from '../../Core/JsonConfigLoader'; import { AnalyticsManager, BlockSelectionClickProperties } from '../../Utils/AnalyticsManager'; import EventBus, { GameEvents } from '../../Core/EventBus'; import { NewbieGuideManager } from '../../Core/NewbieGuideManager'; import { SaveDataManager } from '../../LevelSystem/SaveDataManager'; import { InGameManager } from '../../LevelSystem/IN_game'; const { ccclass, property } = _decorator; @ccclass('GameBlockSelection') export class GameBlockSelection extends Component { @property({ type: Node, tooltip: '拖拽diban/ann001按钮节点到这里' }) public addBallButton: Node = null; @property({ type: Node, tooltip: '拖拽diban/ann002按钮节点到这里' }) public addCoinButton: Node = null; @property({ type: Node, tooltip: '拖拽diban/ann003按钮节点到这里' }) public refreshButton: Node = null; @property({ type: Node, tooltip: '拖拽Canvas-001/TopArea/CoinNode/CoinLabel节点到这里' }) public coinLabelNode: Node = null; @property({ type: Node, tooltip: '拖拽Canvas/GameLevelUI/BallController节点到这里' }) public ballControllerNode: Node = null; @property({ type: Node, tooltip: '拖拽Canvas/GameLevelUI/BlockController节点到这里' }) public blockManagerNode: Node = null; @property({ type: Node, tooltip: '拖拽Canvas/GameLevelUI/GameArea/GridContainer节点到这里' }) public gridContainer: Node = null; @property({ type: Node, tooltip: '拖拽confirm按钮节点到这里' }) public confirmButton: Node = null; @property({ type: Node, tooltip: '拖拽Canvas/GameLevelUI/InGameManager节点到这里' }) public inGameManagerNode: Node = null; @property({ type: Node, tooltip: '拖拽Canvas/Camera节点到这里' }) public cameraNode: Node = null; // 武器配置数据,通过 BundleLoader 异步加载 private weaponsConfigData: any = null; // 小球价格配置数据 - 通过BundleLoader动态加载 private ballPriceConfigData: any = null; // 新增小球价格显示节点 - 对应addBallPricing @property({ type: Node, tooltip: '拖拽Canvas/GameLevelUI/BlockSelectionUI/diban/ann001/Ball/db01/Price节点到这里' }) public addBallPriceNode: Node = null; // 刷新方块价格显示节点 - 对应refreshBlockPricing @property({ type: Node, tooltip: '拖拽Canvas/GameLevelUI/BlockSelectionUI/diban/ann003/Ball/db01/Price节点到这里' }) public refreshBlockPriceNode: Node = null; // 增加金币奖励显示节点 - 对应addCoinReward @property({ type: Node, tooltip: '拖拽Canvas/GameLevelUI/BlockSelectionUI/diban/ann002/db01/addCoin节点到这里' }) public addCoinRewardNode: Node = null; // 价格配置默认值 private readonly DEFAULT_ADD_BALL_BASE_PRICE = 80; private readonly DEFAULT_ADD_BALL_INCREMENT = 10; private readonly DEFAULT_ADD_BALL_MAX_PRICE = 500; private readonly DEFAULT_REFRESH_BASE_PRICE = 5; private readonly DEFAULT_REFRESH_INCREMENT = 2; private readonly DEFAULT_REFRESH_MAX_PRICE = 50; private readonly DEFAULT_ADD_COIN_BASE_REWARD = 60; private readonly DEFAULT_ADD_COIN_INCREMENT = 10; private readonly DEFAULT_ADD_COIN_MAX_REWARD = 200; // 获取增加金币奖励数量 private getAddCoinReward(): number { // 优先从JSON配置中获取奖励数量 if (this.ballPriceConfigData && this.ballPriceConfigData.addCoinReward) { const config = this.ballPriceConfigData.addCoinReward; const baseReward = config.initialPrice ?? this.DEFAULT_ADD_COIN_BASE_REWARD; const increment = config.priceIncrement ?? this.DEFAULT_ADD_COIN_INCREMENT; const maxReward = config.maxPrice ?? this.DEFAULT_ADD_COIN_MAX_REWARD; // 获取当前使用次数 const session = this.session || LevelSessionManager.inst; const usageCount = session ? session.getAddCoinUsageCount() : 0; // 计算当前奖励数量 const currentReward = Math.min(baseReward + (usageCount * increment), maxReward); return currentReward; } // 其次从装饰器绑定的节点获取奖励数量 try { if (this.addCoinRewardNode) { const label = this.addCoinRewardNode.getComponent(Label); if (label) { const reward = parseInt(label.string); return isNaN(reward) ? this.DEFAULT_ADD_COIN_BASE_REWARD : reward; } } else { // 回退到find方法(兼容性) const rewardNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/ann002/db01/addCoin'); if (rewardNode) { const label = rewardNode.getComponent(Label); if (label) { const reward = parseInt(label.string); return isNaN(reward) ? this.DEFAULT_ADD_COIN_BASE_REWARD : reward; } } } } catch (error) { console.warn('[GameBlockSelection] 获取增加金币奖励失败:', error); } return this.DEFAULT_ADD_COIN_BASE_REWARD; // 默认奖励 } // 价格获取方法 private getAddBallCost(): number { // 优先从JSON配置中获取价格 if (this.ballPriceConfigData && this.ballPriceConfigData.addBallPricing) { const config = this.ballPriceConfigData.addBallPricing; const basePrice = config.initialPrice ?? this.DEFAULT_ADD_BALL_BASE_PRICE; const increment = config.priceIncrement ?? this.DEFAULT_ADD_BALL_INCREMENT; const maxPrice = config.maxPrice ?? this.DEFAULT_ADD_BALL_MAX_PRICE; // 获取当前使用次数 const session = this.session || LevelSessionManager.inst; const usageCount = session ? session.getAddBallUsageCount() : 0; // 计算当前价格 const currentPrice = Math.min(basePrice + (usageCount * increment), maxPrice); return currentPrice; } // 其次从装饰器绑定的节点获取价格 try { if (this.addBallPriceNode) { const label = this.addBallPriceNode.getComponent(Label); if (label) { const price = parseInt(label.string); return isNaN(price) ? this.DEFAULT_ADD_BALL_BASE_PRICE : price; } } else { // 回退到find方法(兼容性) const priceNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/ann001/Ball/db01/Price'); if (priceNode) { const label = priceNode.getComponent(Label); if (label) { const price = parseInt(label.string); return isNaN(price) ? this.DEFAULT_ADD_BALL_BASE_PRICE : price; } } } } catch (error) { console.warn('[GameBlockSelection] 获取新增小球价格失败:', error); } return this.DEFAULT_ADD_BALL_BASE_PRICE; // 默认价格 } public getRefreshCost(): number { // 优先从JSON配置中获取价格 console.log('[GameBlockSelection] 刷新方块价格配置:', this.ballPriceConfigData?.refreshBlockPricing); if (this.ballPriceConfigData && this.ballPriceConfigData.refreshBlockPricing) { const config = this.ballPriceConfigData.refreshBlockPricing; const basePrice = config.initialPrice ?? this.DEFAULT_REFRESH_BASE_PRICE; const increment = config.priceIncrement ?? this.DEFAULT_REFRESH_INCREMENT; const maxPrice = config.maxPrice ?? this.DEFAULT_REFRESH_MAX_PRICE; // 获取当前使用次数 const session = this.session || LevelSessionManager.inst; const usageCount = session ? session.getRefreshUsageCount() : 0; // 计算当前价格 const currentPrice = Math.min(basePrice + (usageCount * increment), maxPrice); return currentPrice; } // 其次从装饰器绑定的节点获取价格 try { if (this.refreshBlockPriceNode) { console.log('[GameBlockSelection] 刷新方块价格节点:', this.refreshBlockPriceNode); const label = this.refreshBlockPriceNode.getComponent(Label); if (label) { const price = parseInt(label.string); return isNaN(price) ? this.DEFAULT_REFRESH_BASE_PRICE : price; } } else { // 回退到find方法(兼容性) const priceNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/ann003/Ball/db01/Price'); if (priceNode) { const label = priceNode.getComponent(Label); if (label) { const price = parseInt(label.string); return isNaN(price) ? this.DEFAULT_REFRESH_BASE_PRICE : price; } } } } catch (error) { console.warn('[GameBlockSelection] 获取刷新方块价格失败:', error); } return this.DEFAULT_REFRESH_BASE_PRICE; // 默认价格 } private session: LevelSessionManager = null; private ballController: BallController = null; private blockManager: BlockManager = null; // 回调函数,用于通知GameManager public onConfirmCallback: () => void = null; // 标记是否已初始化 private isInitialized: boolean = false; // 标记是否应该生成方块(只有在onBattle触发后才为true) private shouldGenerateBlocks: boolean = false; // 用户操作方块相关属性 private currentDragBlock: Node | null = null; private startPos = new Vec2(); private blockStartPos: Vec3 = new Vec3(); private activeTouchId: number | null = null; // 调试绘制相关属性 @property({ tooltip: '是否启用吸附检测范围调试绘制' }) public debugDrawSnapRange: boolean = false; private debugDrawNode: Node = null; private debugGraphics: Graphics = null; // 引导期间按钮原始颜色缓存 private originalButtonColors: Map = new Map(); // 引导期禁用颜色置灰(HEX: 4D4545) private readonly GUIDE_DISABLED_COLOR: Color = new Color(0x4D, 0x45, 0x45, 255); // 引导阶段确认按钮交互覆盖:true 启用;false 禁用;null 不覆盖 private guideConfirmEnabledOverride: boolean | null = null; onEnable() { // 如果还未初始化,则进行初始化 if (!this.isInitialized) { this.initializeComponent(); } else { // 如果已经初始化,重新设置事件监听器(因为onDisable时会移除) this.setupEventListeners(); } // this.initDebugDraw(); } start() { // 如果还未初始化,则进行初始化 if (!this.isInitialized) { this.initializeComponent(); } } private async initializeComponent() { // 异步加载小球价格配置 try { this.ballPriceConfigData = await JsonConfigLoader.getInstance().loadConfig('ballPrice'); console.log('[GameBlockSelection] 小球价格配置加载成功:', this.ballPriceConfigData); } catch (error) { console.warn('[GameBlockSelection] 小球价格配置加载失败:', error); this.ballPriceConfigData = null; } // 异步加载武器配置 try { this.weaponsConfigData = await JsonConfigLoader.getInstance().loadConfig('weapons'); console.log('[GameBlockSelection] 武器配置加载成功:', this.weaponsConfigData); } catch (error) { console.warn('[GameBlockSelection] 武器配置加载失败:', error); this.weaponsConfigData = null; } // 获取管理器实例 this.session = LevelSessionManager.inst; // 获取BallController if (this.ballControllerNode) { this.ballController = this.ballControllerNode.getComponent(BallController); } else { console.warn('BallController节点未绑定,请在Inspector中拖拽Canvas/GameLevelUI/BallController节点'); } // 获取BlockManager if (this.blockManagerNode) { this.blockManager = this.blockManagerNode.getComponent(BlockManager); // 如果武器配置已通过 BundleLoader 加载,传递给BlockManager if (this.weaponsConfigData) { // 通过公共方法将加载的配置传递给BlockManager if (this.blockManager && typeof this.blockManager.setPreloadedWeaponsConfig === 'function') { this.blockManager.setPreloadedWeaponsConfig(this.weaponsConfigData); console.log('[GameBlockSelection] 武器配置已传递给BlockManager'); } else { console.warn('[GameBlockSelection] BlockManager不支持setPreloadedWeaponsConfig方法'); } } else { console.warn('[GameBlockSelection] 武器配置加载失败,BlockManager将使用默认配置'); } } else { console.warn('BlockManager节点未绑定,请在Inspector中拖拽Canvas/GameLevelUI/BlockController节点'); } // 如果没有指定coinLabelNode,尝试找到它 if (!this.coinLabelNode) { this.coinLabelNode = find('Canvas-001/TopArea/CoinNode/CoinLabel'); } // 初始化金币显示 this.updateCoinDisplay(); // 绑定按钮事件 this.bindButtonEvents(); // 设置事件监听器 this.setupEventListeners(); // 更新价格显示 this.updatePriceDisplay(); // 更新奖励显示 this.updateRewardDisplay(); // 标记为已初始化 this.isInitialized = true; // 初始化完成 } // 设置事件监听器 private setupEventListeners() { const eventBus = EventBus.getInstance(); // 监听重置方块选择事件 eventBus.on(GameEvents.RESET_BLOCK_SELECTION, this.onResetBlockSelectionEvent, this); // 监听全局UI重置事件,确保按钮视觉状态恢复 eventBus.on(GameEvents.RESET_UI_STATES, this.onResetUIStatesEvent, this); // 监听游戏开始事件,用于标记可以开始生成方块 eventBus.on(GameEvents.GAME_START, this.onGameStartEvent, this); // 监听方块拖拽事件设置请求 eventBus.on(GameEvents.SETUP_BLOCK_DRAG_EVENTS, this.onSetupBlockDragEventsEvent, this); // 监听进入游玩状态与敌人开始生成以更新引导按钮状态(波次变化时) eventBus.on(GameEvents.ENTER_PLAYING_STATE, this.updateGuideButtonStates, this); eventBus.on(GameEvents.ENEMY_START_GAME, this.updateGuideButtonStates, this); eventBus.on(GameEvents.GAME_START, this.updateGuideButtonStates, this); // 监听敌人数量更新与新波次开始,精确在第二波结束时解禁按钮 eventBus.on(GameEvents.ENEMY_UPDATE_COUNT, this.updateGuideButtonStates, this); eventBus.on(GameEvents.ENEMY_START_WAVE, this.updateGuideButtonStates, this); // 监听:全局强制结束所有拖拽(用于教程自动放置期间收敛拖拽状态) eventBus.on(GameEvents.FORCE_END_ALL_DRAGS, this.onForceEndAllDrags, this); // 监听:新手引导确认按钮覆盖事件 eventBus.on(GameEvents.GUIDE_SET_CONFIRM_ENABLED, this.onGuideSetConfirmEnabledEvent, this); eventBus.on(GameEvents.GUIDE_CLEAR_CONFIRM_OVERRIDE, this.onGuideClearConfirmOverrideEvent, this); } // 处理重置方块选择事件 private onResetBlockSelectionEvent() { this.resetSelection(); // 恢复按钮颜色与交互状态,并清理颜色缓存,避免跨关卡残留 this.applyGuideDisabledVisual(this.addCoinButton, false); this.applyGuideDisabledVisual(this.refreshButton, false); this.applyGuideDisabledVisual(this.confirmButton, false); this.originalButtonColors.clear(); } // 处理全局UI重置事件(返回主界面或关卡结束时) private onResetUIStatesEvent() { // 恢复按钮颜色与交互状态,并清理颜色缓存,避免跨关卡残留 this.applyGuideDisabledVisual(this.addCoinButton, false); this.applyGuideDisabledVisual(this.refreshButton, false); this.applyGuideDisabledVisual(this.confirmButton, false); this.originalButtonColors.clear(); } // 处理游戏开始事件 private onGameStartEvent() { // 接收到游戏开始事件,允许生成方块 this.shouldGenerateBlocks = true; } // 处理方块拖拽事件设置请求 private onSetupBlockDragEventsEvent(blocks: Node[]) { // 确保组件仍然有效 if (!this.node || !this.node.isValid) { console.warn('[GameBlockSelection] 组件节点无效,跳过拖拽事件设置'); return; } for (const block of blocks) { if (block && block.isValid) { // 先移除可能存在的旧事件监听器 block.off(Node.EventType.TOUCH_START); block.off(Node.EventType.TOUCH_MOVE); block.off(Node.EventType.TOUCH_END); block.off(Node.EventType.TOUCH_CANCEL); // 重新设置拖拽事件 this.setupBlockDragEvents(block); } else { // 跳过无效方块的拖拽事件设置 } } } // 绑定按钮事件 private bindButtonEvents() { // 绑定新增小球按钮 if (this.addBallButton) { const btn = this.addBallButton.getComponent(Button); if (btn) { this.addBallButton.on(Button.EventType.CLICK, this.onAddBallClicked, this); } else { this.addBallButton.on(Node.EventType.TOUCH_END, this.onAddBallClicked, this); } } // 绑定增加金币按钮 if (this.addCoinButton) { const btn = this.addCoinButton.getComponent(Button); if (btn) { this.addCoinButton.on(Button.EventType.CLICK, this.onAddCoinClicked, this); } else { this.addCoinButton.on(Node.EventType.TOUCH_END, this.onAddCoinClicked, this); } } // 绑定刷新方块按钮 if (this.refreshButton) { const btn = this.refreshButton.getComponent(Button); if (btn) { this.refreshButton.on(Button.EventType.CLICK, this.onRefreshClicked, this); } else { this.refreshButton.on(Node.EventType.TOUCH_END, this.onRefreshClicked, this); } } // 绑定确认按钮 if (this.confirmButton) { const btn = this.confirmButton.getComponent(Button); if (btn) { this.confirmButton.on(Button.EventType.CLICK, this.onConfirmButtonClicked, this); } else { this.confirmButton.on(Node.EventType.TOUCH_END, this.onConfirmButtonClicked, this); } } // 初始更新引导期间按钮状态 this.updateGuideButtonStates(); } // 新增小球按钮点击 private onAddBallClicked() { // 播放UI点击音效 Audio.playUISound('data/弹球音效/ui play'); // 从UI节点获取价格并应用便宜技能效果计算实际费用 const baseCost = this.getAddBallCost(); const actualCost = this.getActualCost(baseCost); // 检查是否有足够金币 const canAfford = this.canSpendCoins(actualCost); let success = false; let failureReason: string | undefined; if (!canAfford) { failureReason = 'insufficient_coins'; this.showInsufficientCoinsUI(); } else { // 扣除金币 if (this.session.spendCoins(actualCost)) { success = true; // 增加使用次数 this.session.incrementAddBallUsageCount(); this.updateCoinDisplay(); // 更新价格显示 this.updatePriceDisplay(); // 通过事件系统创建新的小球 const eventBus = EventBus.getInstance(); eventBus.emit(GameEvents.BALL_CREATE_ADDITIONAL); // 新增小球成功 } else { failureReason = 'spend_coins_failed'; } } // 数据分析追踪 const properties: BlockSelectionClickProperties = { button_type: 'add_ball', button_cost: actualCost, user_money: this.session.getCoins(), usage_count: this.session.getAddBallUsageCount(), success: success, failure_reason: failureReason }; AnalyticsManager.getInstance().trackBlockSelectionClick(properties); } // 增加金币按钮点击 private onAddCoinClicked() { // 引导第2波结束前禁用此按钮 if (this.isGuideRestrictActive()) { this.applyGuideDisabledVisual(this.addCoinButton, true); return; } // 播放UI点击音效 Audio.playUISound('data/弹球音效/ui play'); let success = false; let failureReason: string | undefined; // 显示激励视频广告 AdManager.getInstance().showRewardedVideoAd( () => { // 广告观看完成,增加金币 const coinsToAdd = this.getAddCoinReward(); // 从配置中获取广告奖励的金币数量 this.session.addCoins(coinsToAdd); // 增加使用次数 this.session.incrementAddCoinUsageCount(); // 更新显示 this.updateCoinDisplay(); this.updateRewardDisplay(); success = true; // 数据分析追踪 - 成功情况 const properties: BlockSelectionClickProperties = { button_type: 'add_coin', user_money: this.session.getCoins(), usage_count: this.session.getAddCoinUsageCount(), success: success }; AnalyticsManager.getInstance().trackBlockSelectionClick(properties); }, (error) => { console.error('[GameBlockSelection] 广告显示失败:', error); // 广告失败时不给予奖励 failureReason = 'ad_failed'; // 数据分析追踪 - 失败情况 const properties: BlockSelectionClickProperties = { button_type: 'add_coin', user_money: this.session.getCoins(), usage_count: this.session.getAddCoinUsageCount(), success: false, failure_reason: failureReason }; AnalyticsManager.getInstance().trackBlockSelectionClick(properties); } ); } // 刷新方块按钮点击 private onRefreshClicked() { // 引导第2波结束前禁用此按钮 if (this.isGuideRestrictActive()) { this.applyGuideDisabledVisual(this.refreshButton, true); return; } // 播放UI点击音效 Audio.playUISound('data/弹球音效/ui play'); // 从UI节点获取价格并应用便宜技能效果计算实际费用 const baseCost = this.getRefreshCost(); const actualCost = this.getActualCost(baseCost); // 检查是否有足够金币 const canAfford = this.canSpendCoins(actualCost); let success = false; let failureReason: string | undefined; if (!canAfford) { failureReason = 'insufficient_coins'; this.showInsufficientCoinsUI(); } else { // 扣除金币 if (this.session.spendCoins(actualCost)) { success = true; // 增加使用次数 this.session.incrementRefreshUsageCount(); // 成功扣除金币 this.updateCoinDisplay(); // 更新价格显示 this.updatePriceDisplay(); // 刷新方块 if (this.blockManager) { console.log('[GameBlockSelection] 开始刷新方块流程'); // 找到PlacedBlocks容器 const placedBlocksContainer = find('Canvas/GameLevelUI/GameArea/PlacedBlocks'); if (placedBlocksContainer) { // 移除已放置方块的标签 // 移除已放置方块的标签 BlockTag.removeTagsInContainer(placedBlocksContainer); } // 刷新方块 // 调用 blockManager.refreshBlocks() this.blockManager.refreshBlocks(); // 等待一帧确保方块生成完成 this.scheduleOnce(() => { }, 0.1); } else { console.error('[GameBlockSelection] 找不到BlockManager,无法刷新方块'); success = false; failureReason = 'block_manager_not_found'; } } else { console.error('[GameBlockSelection] 扣除金币失败'); failureReason = 'spend_coins_failed'; } } // 数据分析追踪 const properties: BlockSelectionClickProperties = { button_type: 'refresh_blocks', button_cost: actualCost, user_money: this.session.getCoins(), usage_count: this.session.getRefreshUsageCount(), success: success, failure_reason: failureReason }; AnalyticsManager.getInstance().trackBlockSelectionClick(properties); } // 确认按钮点击 public onConfirmButtonClicked() { // 引导阶段需要禁用时直接拦截点击 const restrictGuide = this.isGuideRestrictActive(); if (this.guideConfirmEnabledOverride === false || (restrictGuide && this.guideConfirmEnabledOverride !== true)) { this.applyGuideDisabledVisual(this.confirmButton, true); return; } // 检查是否有上阵方块 const hasBlocks = this.hasPlacedBlocks(); if (!hasBlocks) { this.showNoPlacedBlocksToast(); return; } // 播放UI音效 Audio.playUISound('data/弹球音效/ui play'); // 保存已放置的方块 this.preservePlacedBlocks(); // 清理kuang区域的方块(用户期望的行为) if (this.blockManager) { this.blockManager.clearBlocks(); console.log('[GameBlockSelection] 已清理kuang区域的方块'); } // 先回调通知GameManager,让它处理波次逻辑 if (this.onConfirmCallback) { this.onConfirmCallback(); } // 发出方块选择确认事件(用于新手引导第三步结束) EventBus.getInstance().emit(GameEvents.BLOCK_SELECTION_CONFIRMED); // 播放下滑diban动画,结束备战进入playing状态 this.playDibanSlideDownAnimation(); // 确认后更新一次(可能进入下一波) this.updateGuideButtonStates(); } // 播放diban下滑动画 private playDibanSlideDownAnimation() { // 开始播放diban下滑动画 // 使用装饰器属性获取Camera节点上的GameStartMove组件 if (!this.cameraNode) { console.warn('[GameBlockSelection] Camera节点未设置,请在Inspector中拖拽Canvas/Camera节点'); return; } const gameStartMove = this.cameraNode.getComponent('GameStartMove'); if (!gameStartMove) { console.warn('[GameBlockSelection] GameStartMove组件未找到'); return; } // 调用GameStartMove的下滑动画方法 (gameStartMove as any).slideDibanDownAndHide(0.3); // 已调用GameStartMove的diban下滑动画 } // 保存已放置的方块(从GameManager迁移) private preservePlacedBlocks() { if (this.blockManager) { this.blockManager.onGameStart(); } } // 检查是否有足够金币 private canSpendCoins(amount: number): boolean { return this.session.getCoins() >= amount; } // 计算应用便宜技能效果后的实际费用 private getActualCost(baseCost: number): number { const skillManager = SkillManager.getInstance(); if (skillManager) { const cheaperSkillLevel = skillManager.getSkillLevel('cheaper_units'); return Math.ceil(SkillManager.calculateCheaperUnitsPrice(baseCost, cheaperSkillLevel)); } return baseCost; } // 更新金币显示 private updateCoinDisplay() { if (this.coinLabelNode) { const label = this.coinLabelNode.getComponent(Label); if (label) { const coins = this.session.getCoins(); label.string = coins.toString(); // 更新金币显示 } else { console.warn('[GameBlockSelection] coinLabelNode缺少Label组件'); } } else { console.warn('[GameBlockSelection] coinLabelNode未找到'); } } // ——— 引导期间按钮禁用与颜色处理 ——— private isGuideRestrictActive(): boolean { try { // 仅在第1关启用引导期限制;进入其他关卡一律不限制 const sdm = SaveDataManager.getInstance(); const currentLevel = sdm ? sdm.getCurrentLevel() : 1; if (currentLevel !== 1) return false; const guideMgr = NewbieGuideManager.getInstance(); const isGuide = guideMgr && guideMgr.isNewbieGuideInProgress(); if (!isGuide) return false; const inGameMgr = this.inGameManagerNode ? this.inGameManagerNode.getComponent(InGameManager) : null; const currentWave = inGameMgr ? inGameMgr.getCurrentWave() : 1; // 第1波:禁用 if (currentWave < 2) return true; // 第3波及以后:解禁 if (currentWave > 2) return false; // 第2波:直到所有敌人被击败前禁用 const total = inGameMgr ? inGameMgr.getCurrentWaveTotalEnemies() : 0; const killed = inGameMgr ? inGameMgr.getCurrentWaveEnemyCount() : 0; if (total <= 0) { // 无法获取总数时保守禁用 return true; } return killed < total; } catch (e) { return false; } } private applyGuideDisabledVisual(targetButtonNode: Node | null, disabled: boolean) { if (!targetButtonNode) return; const btn = targetButtonNode.getComponent(Button); if (btn) btn.interactable = !disabled; // 设置按钮节点及其子节点的Sprite颜色 const applyColorRecursively = (node: Node, color: Color) => { const sp = node.getComponent(Sprite); if (sp) { // 缓存原始颜色 if (!this.originalButtonColors.has(node)) { this.originalButtonColors.set(node, sp.color?.clone?.() || new Color(255, 255, 255, 255)); } sp.color = color; } node.children?.forEach(child => applyColorRecursively(child, color)); }; if (disabled) { applyColorRecursively(targetButtonNode, this.GUIDE_DISABLED_COLOR); } else { // 恢复至原始颜色(若无缓存则使用白色) const restoreColorRecursively = (node: Node) => { const sp = node.getComponent(Sprite); if (sp) { const orig = this.originalButtonColors.get(node) || new Color(255, 255, 255, 255); sp.color = orig; } node.children?.forEach(child => restoreColorRecursively(child)); }; restoreColorRecursively(targetButtonNode); } } private updateGuideButtonStates = () => { // 根据引导期与波次实时计算禁用状态(仅第1关的第1/2波禁用) const restrict = this.isGuideRestrictActive(); this.applyGuideDisabledVisual(this.addCoinButton, restrict); this.applyGuideDisabledVisual(this.refreshButton, restrict); // 同步确认按钮禁用(允许由引导覆盖) let confirmDisabled = restrict; if (this.guideConfirmEnabledOverride !== null) { confirmDisabled = !this.guideConfirmEnabledOverride; } this.applyGuideDisabledVisual(this.confirmButton, confirmDisabled); }; // 引导:设置确认按钮启用/禁用覆盖 private onGuideSetConfirmEnabledEvent(enabled: boolean) { if (typeof enabled === 'boolean') { this.guideConfirmEnabledOverride = enabled; } else { this.guideConfirmEnabledOverride = null; } this.updateGuideButtonStates(); } // 引导:清除确认按钮覆盖 private onGuideClearConfirmOverrideEvent() { this.guideConfirmEnabledOverride = null; this.updateGuideButtonStates(); } // 显示金币不足UI private showInsufficientCoinsUI() { // 使用事件机制显示资源不足Toast EventBus.getInstance().emit(GameEvents.SHOW_RESOURCE_TOAST, { message: '金币不足!', duration: 3.0 }); // 金币不足 } // === 公共方法:供GameManager调用 === // 生成方块选择(不再控制UI显示,只负责生成方块) public generateBlockSelection() { // 尝试确保拿到 BlockManager(兼容场景切换后生命周期先后顺序) if (!this.blockManager) { // 1) 通过已绑定的节点拿组件 if (this.blockManagerNode && this.blockManagerNode.isValid) { this.blockManager = this.blockManagerNode.getComponent(BlockManager); } // 2) 回退到路径查找(确保场景加载后也能获取到) if (!this.blockManager) { const bmNode = find('Canvas/GameLevelUI/BlockController'); if (bmNode) { this.blockManager = bmNode.getComponent(BlockManager) as any; if (!this.blockManager) { // 兼容字符串组件名(某些环境下主动通过字符串拿组件) this.blockManager = bmNode.getComponent('BlockManager') as any; } } } // 3) 如果武器配置已预加载,补传一次给 BlockManager if (this.blockManager && this.weaponsConfigData && typeof (this.blockManager as any).setPreloadedWeaponsConfig === 'function') { (this.blockManager as any).setPreloadedWeaponsConfig(this.weaponsConfigData); console.log('[GameBlockSelection] 懒加载后已向BlockManager传递武器配置'); } } // 直接生成方块,不再依赖shouldGenerateBlocks标志 if (this.blockManager) { this.blockManager.refreshBlocks(); } else { console.warn('[GameBlockSelection] BlockManager未找到,无法生成随机方块'); } // 触发进入备战状态事件 EventBus.getInstance().emit(GameEvents.ENTER_BATTLE_PREPARATION); } // 设置确认回调 public setConfirmCallback(callback: () => void) { this.onConfirmCallback = callback; } // 统一的方块占用情况刷新方法 public refreshGridOccupation() { if (this.blockManager) { this.blockManager.resetGridOccupation(); // 重新计算所有已放置方块的占用情况 const placedBlocksContainer = find('Canvas/GameLevelUI/GameArea/PlacedBlocks'); if (placedBlocksContainer) { for (let i = 0; i < placedBlocksContainer.children.length; i++) { const block = placedBlocksContainer.children[i]; // 按照BlockManager.tryPlaceBlockToGrid的正确方法获取位置 let b1Node = block; if (block.name !== 'B1') { b1Node = block.getChildByName('B1'); if (!b1Node) { console.warn(`[GameBlockSelection] 方块 ${block.name} 没有B1子节点`); continue; } } // 获取B1节点的世界坐标,然后转换为网格本地坐标 const b1WorldPos = b1Node.parent.getComponent(UITransform).convertToWorldSpaceAR(b1Node.position); const gridPos = this.blockManager.gridContainer.getComponent(UITransform).convertToNodeSpaceAR(b1WorldPos); // 重新标记方块占用的网格位置 const gridNode = this.blockManager.findNearestGridNode(gridPos); if (gridNode) { this.blockManager.markOccupiedPositions(block, gridNode); } else { console.warn(`[GameBlockSelection] 方块 ${block.name} 未找到对应的网格节点`); } } } } else { console.warn('[GameBlockSelection] BlockManager未找到,无法刷新占用情况'); } this.blockManager.printGridOccupationMatrix(); } // 重置方块选择状态 public resetSelection() { // 重置方块生成标志,等待下次onBattle触发 this.shouldGenerateBlocks = false; // 注意:不再直接调用blockManager.onGameReset(),因为StartGame.clearGameStates() // 已经通过RESET_BLOCK_MANAGER事件触发了BlockManager的重置,避免重复生成方块 // 清理所有方块标签 BlockTag.clearAllTags(); // 更新价格显示(使用次数已在session中重置) this.updatePriceDisplay(); // 更新金币显示 this.updateCoinDisplay(); } // 检查是否有上阵方块 private hasPlacedBlocks(): boolean { // 优先使用BlockManager的方法检查 if (this.blockManager) { return this.blockManager.hasPlacedBlocks(); } // 备用方案:直接检查PlacedBlocks容器 const placedBlocksContainer = find('Canvas/GameLevelUI/GameArea/PlacedBlocks'); if (!placedBlocksContainer) { console.warn('找不到PlacedBlocks容器'); return false; } // 检查容器中是否有子节点(方块) return placedBlocksContainer.children.length > 0; } // 显示没有上阵方块的Toast提示 private showNoPlacedBlocksToast() { // 使用事件机制显示Toast EventBus.getInstance().emit(GameEvents.SHOW_RESOURCE_TOAST, { message: '请至少上阵一个植物!', duration: 3.0 }); } // 设置方块拖拽事件 public setupBlockDragEvents(block: Node) { block.on(Node.EventType.TOUCH_START, (event: EventTouch) => { // 仅处理首个触点,过滤其他触点事件 const touchId = this.getTouchId(event); if (this.activeTouchId !== null && this.activeTouchId !== touchId) { return; } // 检查游戏是否已开始 if (!this.blockManager.gameStarted) { return; } // 只对grid区域的方块检查移动限制,kuang区域的方块可以自由拖拽 const blockLocation = this.blockManager.blockLocations.get(block); // 金币不足时禁止从kuang区域拖拽,并在点击时提示 if (blockLocation !== 'grid') { const price = this.blockManager.getBlockPrice(block); if (!this.canSpendCoins(price)) { this.blockManager.showInsufficientCoinsEffect(block); return; // 不进入拖拽流程 } } // grid区域的方块目前允许自由移动 // 绑定当前拖拽手指ID this.activeTouchId = touchId; this.currentDragBlock = block; this.startPos = event.getUILocation(); this.blockStartPos.set(block.position); this.currentDragBlock['startLocation'] = blockLocation; // 如果方块在grid区域,拿起时清除其占用状态 if (blockLocation === 'grid') { this.blockManager.clearOccupiedPositions(block); // 输出更新后的占用情况 this.blockManager.printGridOccupationMatrix(); } // 设置拖动状态,隐藏价格标签 block['isDragging'] = true; block.setSiblingIndex(block.parent.children.length - 1); // 拖拽开始时禁用碰撞体 const collider = block.getComponent(Collider2D); if (collider) collider.enabled = false; // 通知BallController有方块开始拖拽 EventBus.getInstance().emit(GameEvents.BLOCK_DRAG_START, { block: block }); }, this); block.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => { // 只处理与当前绑定触点一致的移动事件 const touchId = this.getTouchId(event); if (this.activeTouchId !== touchId) { return; } // 检查游戏是否已开始 if (!this.blockManager.gameStarted) { return; } // 只对grid区域的方块检查移动限制,kuang区域的方块可以自由拖拽 const blockLocation = this.blockManager.blockLocations.get(block); // 教程阶段:第6步结束前禁止移动已上阵方块(第4步除外) if (blockLocation === 'grid' && !this.blockManager.canMovePlacedBlocks()) { return; } if (!this.currentDragBlock) return; const location = event.getUILocation(); const deltaX = location.x - this.startPos.x; const deltaY = location.y - this.startPos.y; // 目标位置(本地坐标) const targetLocal = new Vec3( this.blockStartPos.x + deltaX, this.blockStartPos.y + deltaY, this.blockStartPos.z ); const parentTransform = this.currentDragBlock.parent ? this.currentDragBlock.parent.getComponent(UITransform) : null; const targetWorld = parentTransform ? parentTransform.convertToWorldSpaceAR(targetLocal) : targetLocal.clone(); // GameArea 边界夹紧(仅在目标点位于 GameArea 内部时进行夹紧) const gameAreaNode = find('Canvas/GameLevelUI/GameArea'); const gameAreaTransform = gameAreaNode ? gameAreaNode.getComponent(UITransform) : null; if (gameAreaTransform) { const bounds = gameAreaTransform.getBoundingBoxToWorld(); const inside = bounds.contains(new Vec2(targetWorld.x, targetWorld.y)); if (inside && parentTransform) { const minX = bounds.x; const maxX = bounds.x + bounds.width; const minY = bounds.y; const maxY = bounds.y + bounds.height; const clampedWorld = new Vec3( Math.min(Math.max(targetWorld.x, minX), maxX), Math.min(Math.max(targetWorld.y, minY), maxY), targetWorld.z ); const clampedLocal = parentTransform.convertToNodeSpaceAR(clampedWorld); this.currentDragBlock.position = clampedLocal; } else { this.currentDragBlock.position = targetLocal; } } else { this.currentDragBlock.position = targetLocal; } }, this); block.on(Node.EventType.TOUCH_END, async (event: EventTouch) => { // 只处理与当前绑定触点一致的结束事件 const touchId = this.getTouchId(event); if (this.activeTouchId !== null && this.activeTouchId !== touchId) { return; } // 检查游戏是否已开始 if (!this.blockManager.gameStarted) { return; } // 只对grid区域的方块检查移动限制,kuang区域的方块可以自由拖拽 const blockLocation = this.blockManager.blockLocations.get(block); // 教程阶段:第6步结束前禁止移动已上阵方块(第4步除外) if (blockLocation === 'grid' && !this.blockManager.canMovePlacedBlocks()) { return; } if (this.currentDragBlock) { try { await this.handleBlockDrop(event); } catch (error) { console.error('[GameBlockSelection] 处理方块拖拽放置时发生错误:', error); // 发生错误时,将方块返回原位置 this.returnBlockToOriginalPosition(); } // 清除拖动状态,恢复价格标签显示 block['isDragging'] = false; this.currentDragBlock = null; // 拖拽结束时恢复碰撞体 const collider = block.getComponent(Collider2D); if (collider) collider.enabled = true; // 通知BallController方块拖拽结束 EventBus.getInstance().emit(GameEvents.BLOCK_DRAG_END, { block: block }); // 释放触点绑定 this.activeTouchId = null; } }, this); block.on(Node.EventType.TOUCH_CANCEL, (event: EventTouch) => { const touchId = this.getTouchId(event); if (this.activeTouchId !== null && this.activeTouchId !== touchId) { return; } if (this.currentDragBlock) { this.returnBlockToOriginalPosition(); // 清除拖动状态,恢复价格标签显示 block['isDragging'] = false; this.currentDragBlock = null; // 拖拽取消时恢复碰撞体 const collider = block.getComponent(Collider2D); if (collider) collider.enabled = true; // 通知BallController方块拖拽结束 EventBus.getInstance().emit(GameEvents.BLOCK_DRAG_END, { block: block }); // 释放触点绑定 this.activeTouchId = null; } }, this); } // 处理方块放下 private async handleBlockDrop(event: EventTouch) { try { const touchPos = event.getLocation(); const startLocation = this.currentDragBlock['startLocation']; if (this.isInKuangArea(touchPos)) { // 检查是否有标签,只有有标签的方块才能放回kuang if (BlockTag.hasTag(this.currentDragBlock)) { this.returnBlockToKuang(startLocation); } else { // 没有标签的方块不能放回kuang,返回原位置 this.returnBlockToOriginalPosition(); } } else if (this.blockManager.tryPlaceBlockToGrid(this.currentDragBlock)) { await this.handleSuccessfulPlacement(startLocation); } else { // 放置失败,尝试直接与重叠方块合成 // 先检查金币是否充足(如果来自kuang区域) if (startLocation !== 'grid') { const price = this.blockManager.getBlockPrice(this.currentDragBlock); if (!this.canSpendCoins(price)) { this.returnBlockToOriginalPosition(); return; } } if (this.blockManager.tryMergeOnOverlap(this.currentDragBlock)) { // 合成成功时,若来自 kuang 则扣费 if (startLocation !== 'grid') { const price = this.blockManager.getBlockPrice(this.currentDragBlock); this.blockManager.deductPlayerCoins(price); } // 当前拖拽块已被销毁(合并时),无需复位 } else { this.returnBlockToOriginalPosition(); } } } catch (error) { console.error('[GameBlockSelection] handleBlockDrop 发生错误:', error); // 发生错误时,将方块返回原位置 this.returnBlockToOriginalPosition(); throw error; // 重新抛出错误,让上层处理 } finally { // 无论成功还是失败,都要清理拖拽状态 if (this.currentDragBlock && this.currentDragBlock.isValid) { // 重新启用碰撞器 const collider = this.currentDragBlock.getComponent(Collider2D); if (collider) { collider.enabled = true; console.log('[GameBlockSelection] 拖拽结束,重新启用碰撞器:', this.currentDragBlock.name); } // 发射拖拽结束事件 console.log('[GameBlockSelection] 发射 BLOCK_DRAG_END 事件:', this.currentDragBlock.name); EventBus.getInstance().emit(GameEvents.BLOCK_DRAG_END, { block: this.currentDragBlock }); } // 清理拖拽状态 this.currentDragBlock = null; // 释放触点绑定 this.activeTouchId = null; } // 刷新方块占用情况 this.refreshGridOccupation(); } // 检查是否在kuang区域内 private isInKuangArea(touchPos: Vec2): boolean { // 检查是否在任何一个Block容器区域内 const blockContainers = [ this.blockManager.block1Container, this.blockManager.block2Container, this.blockManager.block3Container ]; for (const container of blockContainers) { if (container) { const transform = container.getComponent(UITransform); if (transform) { const boundingBox = new Rect( container.worldPosition.x - transform.width * transform.anchorX, container.worldPosition.y - transform.height * transform.anchorY, transform.width, transform.height ); if (boundingBox.contains(new Vec2(touchPos.x, touchPos.y))) { return true; } } } } // 如果Block容器都不可用,回退到检查kuang容器 if (this.blockManager.kuangContainer) { const kuangTransform = this.blockManager.kuangContainer.getComponent(UITransform); if (kuangTransform) { const kuangBoundingBox = new Rect( this.blockManager.kuangContainer.worldPosition.x - kuangTransform.width * kuangTransform.anchorX, this.blockManager.kuangContainer.worldPosition.y - kuangTransform.height * kuangTransform.anchorY, kuangTransform.width, kuangTransform.height ); return kuangBoundingBox.contains(new Vec2(touchPos.x, touchPos.y)); } } return false; } // 返回方块到对应的Block容器区域 private returnBlockToKuang(startLocation: string) { const originalPos = this.blockManager.originalPositions.get(this.currentDragBlock); const blockLocation = this.blockManager.blockLocations.get(this.currentDragBlock); if (originalPos) { // 根据方块的原始位置确定应该返回到哪个Block容器 let targetContainer: Node = null; let targetLocation = 'kuang'; // 默认位置 if (blockLocation && blockLocation.startsWith('block')) { // 如果方块原本在Block容器中,返回到对应的容器 if (blockLocation === 'block1') { targetContainer = this.blockManager.block1Container; targetLocation = 'block1'; } else if (blockLocation === 'block2') { targetContainer = this.blockManager.block2Container; targetLocation = 'block2'; } else if (blockLocation === 'block3') { targetContainer = this.blockManager.block3Container; targetLocation = 'block3'; } } // 如果没有找到对应的Block容器,回退到kuang容器 if (!targetContainer) { targetContainer = this.blockManager.kuangContainer; targetLocation = 'kuang'; } if (targetContainer && this.currentDragBlock.parent !== targetContainer) { this.currentDragBlock.removeFromParent(); targetContainer.addChild(this.currentDragBlock); } this.currentDragBlock.position = originalPos.clone(); this.blockManager.blockLocations.set(this.currentDragBlock, targetLocation); } // 当方块返回kuang区域时,重新设置植物图标缩放至0.4倍 const weaponNode = this.findWeaponNodeInBlock(this.currentDragBlock); if (weaponNode) { weaponNode.setScale(0.4, 0.4, 1); // 方块返回kuang区域,设置植物图标缩放: 0.4倍 } if (startLocation === 'grid') { const price = this.blockManager.getBlockPrice(this.currentDragBlock); this.blockManager.refundPlayerCoins(price); this.currentDragBlock['placedBefore'] = false; } // 显示对应的db标签节点 this.showDbNodeByBlockLocation(this.currentDragBlock); } /** * 为方块的武器节点添加武器信息组件 * @param block 方块节点 */ private attachWeaponInfoToBlock(block: Node): void { try { // 查找方块中的Weapon节点 const weaponNode = this.findWeaponNodeInBlock(block); if (!weaponNode) { console.warn(`[GameBlockSelection] 方块 ${block.name} 中未找到Weapon节点,无法添加武器信息组件`); return; } // 检查是否已经有WeaponInfo组件 let weaponInfo = weaponNode.getComponent(WeaponInfo); if (weaponInfo) { return; } // 添加WeaponInfo组件 weaponInfo = weaponNode.addComponent(WeaponInfo); // 获取方块的武器配置 const weaponConfig = this.blockManager.getBlockWeaponConfig(block); if (weaponConfig) { // 设置武器配置到WeaponInfo组件 weaponInfo.setWeaponConfig(weaponConfig); } else { console.warn(`[GameBlockSelection] 方块 ${block.name} 没有武器配置,无法设置武器信息`); // 移除刚添加的组件 weaponNode.removeComponent(WeaponInfo); } } catch (error) { console.error(`[GameBlockSelection] 为方块 ${block.name} 添加武器信息组件时发生错误:`, error); } } /** * 在方块中查找Weapon节点 * @param block 方块节点 * @returns Weapon节点,如果未找到返回null */ private findWeaponNodeInBlock(block: Node): Node | null { if (!block || !block.isValid) { return null; } // 根据新的预制体结构,武器节点直接位于方块根节点下 const weaponNode = block.getChildByName('Weapon'); if (weaponNode) { return weaponNode; } // 兼容旧结构:如果直接查找失败,尝试B1/Weapon路径 const b1Node = block.getChildByName('B1'); if (b1Node) { const b1WeaponNode = b1Node.getChildByName('Weapon'); if (b1WeaponNode) { return b1WeaponNode; } } // 最后使用递归方式查找(兼容其他可能的结构) const findWeaponRecursive = (node: Node): Node | null => { if (node.name === 'Weapon') { return node; } for (let i = 0; i < node.children.length; i++) { const result = findWeaponRecursive(node.children[i]); if (result) { return result; } } return null; }; return findWeaponRecursive(block); } // 根据方块位置隐藏对应的db标签节点 private hideDbNodeByBlockLocation(block: Node) { const originalLocation = this.getBlockOriginalLocation(block); if (originalLocation === 'block1') { console.log(`[GameBlockSelection] 隐藏block1的db标签节点`); const blockInContainer = this.getBlockInContainer(this.blockManager.block1Container); if (blockInContainer) { this.blockManager.hideDbLabel(blockInContainer); } } else if (originalLocation === 'block2') { console.log(`[GameBlockSelection] 隐藏block2的db标签节点`); const blockInContainer = this.getBlockInContainer(this.blockManager.block2Container); if (blockInContainer) { this.blockManager.hideDbLabel(blockInContainer); } } else if (originalLocation === 'block3') { console.log(`[GameBlockSelection] 隐藏block3的db标签节点`); const blockInContainer = this.getBlockInContainer(this.blockManager.block3Container); if (blockInContainer) { this.blockManager.hideDbLabel(blockInContainer); } } } // 根据方块位置显示对应的db标签节点 private showDbNodeByBlockLocation(block: Node) { const originalLocation = this.getBlockOriginalLocation(block); if (originalLocation === 'block1') { const blockInContainer = this.getBlockInContainer(this.blockManager.block1Container); if (blockInContainer) { this.blockManager.showDbLabel(blockInContainer); } } else if (originalLocation === 'block2') { const blockInContainer = this.getBlockInContainer(this.blockManager.block2Container); if (blockInContainer) { this.blockManager.showDbLabel(blockInContainer); } } else if (originalLocation === 'block3') { const blockInContainer = this.getBlockInContainer(this.blockManager.block3Container); if (blockInContainer) { this.blockManager.showDbLabel(blockInContainer); } } } // 获取容器中的方块节点 private getBlockInContainer(container: Node): Node | null { if (!container) { return null; } // 查找容器中的方块节点(通常以Block命名或有B1子节点) for (let i = 0; i < container.children.length; i++) { const child = container.children[i]; if (child.name.includes('Block') || child.getChildByName('B1')) { return child; } } return null; } // 处理成功放置 private async handleSuccessfulPlacement(startLocation: string) { try { const price = this.blockManager.getBlockPrice(this.currentDragBlock); if (startLocation === 'grid') { this.blockManager.clearTempStoredOccupiedGrids(this.currentDragBlock); this.blockManager.blockLocations.set(this.currentDragBlock, 'grid'); // 隐藏对应的db标签节点 this.hideDbNodeByBlockLocation(this.currentDragBlock); // 立即将方块移动到PlacedBlocks节点下,不等游戏开始 this.blockManager.moveBlockToPlacedBlocks(this.currentDragBlock); // 为武器节点添加武器信息组件 this.attachWeaponInfoToBlock(this.currentDragBlock); // 如果游戏已开始,添加锁定视觉提示 if (this.blockManager.gameStarted) { this.blockManager.addLockedVisualHint(this.currentDragBlock); BlockTag.removeTag(this.currentDragBlock); } // 检查并执行合成 try { await this.blockManager.tryMergeBlock(this.currentDragBlock); } catch (mergeError) { console.error('[GameBlockSelection] 方块合成时发生错误:', mergeError); } } else { if (this.blockManager.deductPlayerCoins(price)) { this.blockManager.clearTempStoredOccupiedGrids(this.currentDragBlock); this.blockManager.blockLocations.set(this.currentDragBlock, 'grid'); // 隐藏对应的db标签节点 this.hideDbNodeByBlockLocation(this.currentDragBlock); this.currentDragBlock['placedBefore'] = true; // 立即将方块移动到PlacedBlocks节点下,不等游戏开始 this.blockManager.moveBlockToPlacedBlocks(this.currentDragBlock); // 为武器节点添加武器信息组件 this.attachWeaponInfoToBlock(this.currentDragBlock); // 如果游戏已开始,添加锁定视觉提示 if (this.blockManager.gameStarted) { this.blockManager.addLockedVisualHint(this.currentDragBlock); // 游戏开始后放置的方块移除标签,不能再放回kuang BlockTag.removeTag(this.currentDragBlock); } // 检查并执行合成 try { await this.blockManager.tryMergeBlock(this.currentDragBlock); } catch (mergeError) { console.error('[GameBlockSelection] 方块合成时发生错误:', mergeError); // 合成失败不影响方块放置,只记录错误 } } else { // 金币不足时显示价格标签闪烁效果和Toast提示 this.blockManager.showInsufficientCoinsEffect(this.currentDragBlock); this.returnBlockToOriginalPosition(); } } } catch (error) { console.error('[GameBlockSelection] handleSuccessfulPlacement 发生错误:', error); // 发生错误时,将方块返回原位置 this.returnBlockToOriginalPosition(); throw error; // 重新抛出错误,让上层处理 } } // 返回方块到原位置 private returnBlockToOriginalPosition() { const currentLocation = this.blockManager.blockLocations.get(this.currentDragBlock); const startLocation = this.currentDragBlock['startLocation']; if (currentLocation === 'kuang') { const originalPos = this.blockManager.originalPositions.get(this.currentDragBlock); if (originalPos) { this.currentDragBlock.position = originalPos.clone(); } } else { this.currentDragBlock.position = this.blockStartPos.clone(); // 如果方块原本在grid区域,返回原位置后需要重新标记占用状态 if (startLocation === 'grid') { // 获取方块当前位置对应的网格节点 let b1Node = this.currentDragBlock; if (this.currentDragBlock.name !== 'B1') { b1Node = this.currentDragBlock.getChildByName('B1'); } if (b1Node) { const b1WorldPos = b1Node.parent.getComponent(UITransform).convertToWorldSpaceAR(b1Node.position); const gridPos = this.blockManager.gridContainer.getComponent(UITransform).convertToNodeSpaceAR(b1WorldPos); const gridNode = this.blockManager.findNearestGridNode(gridPos); if (gridNode) { this.blockManager.markOccupiedPositions(this.currentDragBlock, gridNode); // 输出更新后的占用情况 this.blockManager.printGridOccupationMatrix(); } } } } // 清除拖动状态,恢复db节点显示 this.currentDragBlock['isDragging'] = false; this.showDbNodeByBlockLocation(this.currentDragBlock); this.currentDragBlock.emit(Node.EventType.TRANSFORM_CHANGED); } // === 调试绘制相关方法 === private initDebugDraw() { // 调试绘制功能已禁用 } private drawGridSnapRanges() { // 调试绘制功能已禁用 } private drawBlockSnapRange(block: Node) { // 调试绘制功能已禁用 } private updateDebugDraw() { // 调试绘制功能已禁用 } // 清理调试绘制 private cleanupDebugDraw() { if (this.debugDrawNode && this.debugDrawNode.isValid) { this.debugDrawNode.destroy(); this.debugDrawNode = null; this.debugGraphics = null; } } public setDebugDrawSnapRange(enabled: boolean) { this.debugDrawSnapRange = enabled; // 调试绘制功能已禁用 } onDisable() { this.removeEventListeners(); this.cleanupDebugDraw(); } onDestroy() { this.removeEventListeners(); this.cleanupDebugDraw(); } // 移除事件监听器 private removeEventListeners() { const eventBus = EventBus.getInstance(); eventBus.off(GameEvents.RESET_BLOCK_SELECTION, this.onResetBlockSelectionEvent, this); eventBus.off(GameEvents.RESET_UI_STATES, this.onResetUIStatesEvent, this); eventBus.off(GameEvents.GAME_START, this.onGameStartEvent, this); eventBus.off(GameEvents.GAME_START, this.updateGuideButtonStates, this); eventBus.off(GameEvents.ENTER_PLAYING_STATE, this.updateGuideButtonStates, this); eventBus.off(GameEvents.ENEMY_START_GAME, this.updateGuideButtonStates, this); eventBus.off(GameEvents.ENEMY_UPDATE_COUNT, this.updateGuideButtonStates, this); eventBus.off(GameEvents.ENEMY_START_WAVE, this.updateGuideButtonStates, this); eventBus.off(GameEvents.SETUP_BLOCK_DRAG_EVENTS, this.onSetupBlockDragEventsEvent, this); eventBus.off(GameEvents.FORCE_END_ALL_DRAGS, this.onForceEndAllDrags, this); } // 强制结束当前拖拽:用于教程自动放置发生时,立即清空拖拽上下文 private onForceEndAllDrags() { if (!this.currentDragBlock || !this.currentDragBlock.isValid) { return; } try { // 清除拖拽标记并恢复碰撞 (this.currentDragBlock as any)['isDragging'] = false; const rootCol = this.currentDragBlock.getComponent(Collider2D); if (rootCol && !rootCol.enabled) rootCol.enabled = true; const b1 = this.currentDragBlock.getChildByName('B1'); if (b1) { const b1Col = b1.getComponent(Collider2D); if (b1Col && !b1Col.enabled) b1Col.enabled = true; } // 根据方块原始位置决定是否需要回到kuang容器 const startLocation = this.blockManager.blockLocations.get(this.currentDragBlock); if (startLocation && startLocation !== 'grid') { this.returnBlockToKuang(startLocation); } // 统一抛出拖拽结束事件,维持各模块状态一致 EventBus.getInstance().emit(GameEvents.BLOCK_DRAG_END, { block: this.currentDragBlock }); } catch (e) { console.warn('[GameBlockSelection] 强制结束拖拽失败:', e); } finally { this.currentDragBlock = null; this.activeTouchId = null; } } // 统一获取触点ID,兼容不同版本API private getTouchId(event: EventTouch): number { try { const anyEvent = event as any; const touch = anyEvent.touch; if (touch && typeof touch.getID === 'function') { return touch.getID(); } if (touch && typeof touch._id === 'number') { return touch._id; } if (typeof anyEvent.id === 'number') { return anyEvent.id; } } catch {} return 0; } /** * 获取方块的原始位置 * @param block 方块节点 * @returns 原始位置标识符 */ private getBlockOriginalLocation(block: Node): string | null { // 检查方块是否在block1Container中 if (this.blockManager.block1Container && this.isBlockInContainer(block, this.blockManager.block1Container)) { return 'block1'; } // 检查方块是否在block2Container中 if (this.blockManager.block2Container && this.isBlockInContainer(block, this.blockManager.block2Container)) { return 'block2'; } // 检查方块是否在block3Container中 if (this.blockManager.block3Container && this.isBlockInContainer(block, this.blockManager.block3Container)) { return 'block3'; } return null; } /** * 检查方块是否在指定容器中 * @param block 方块节点 * @param container 容器节点 * @returns 是否在容器中 */ private isBlockInContainer(block: Node, container: Node): boolean { let parent = block.parent; while (parent) { if (parent === container) { return true; } parent = parent.parent; } return false; } // === 价格配置管理方法 === // 更新价格显示 private updatePriceDisplay() { // 更新新增小球价格显示 - 使用装饰器绑定的节点 if (this.addBallPriceNode) { console.log(`[GameBlockSelection] 更新新增小球价格显示: ${this.getAddBallCost()}`); const label = this.addBallPriceNode.getComponent(Label); if (label) { label.string = this.getAddBallCost().toString(); } } else { // 回退到find方法(兼容性) const addBallPriceNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/ann001/Ball/db01/Price'); if (addBallPriceNode) { const label = addBallPriceNode.getComponent(Label); if (label) { label.string = this.getAddBallCost().toString(); } } } // 更新刷新方块价格显示 - 使用装饰器绑定的节点 if (this.refreshBlockPriceNode) { console.log(`[GameBlockSelection] 更新刷新方块价格显示: ${this.getRefreshCost()}`); const label = this.refreshBlockPriceNode.getComponent(Label); if (label) { label.string = this.getRefreshCost().toString(); } } else { // 回退到find方法(兼容性) const refreshPriceNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/ann003/Ball/db01/Price'); if (refreshPriceNode) { const label = refreshPriceNode.getComponent(Label); if (label) { label.string = this.getRefreshCost().toString(); } } } } // 更新奖励显示 private updateRewardDisplay() { // 更新增加金币奖励显示 - 使用装饰器绑定的节点 if (this.addCoinRewardNode) { const label = this.addCoinRewardNode.getComponent(Label); if (label) { label.string = this.getAddCoinReward().toString(); } } else { // 回退到find方法(兼容性) try { const rewardNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/ann002/db01/addCoin'); if (rewardNode) { const label = rewardNode.getComponent(Label); if (label) { label.string = this.getAddCoinReward().toString(); } } } catch (error) { console.warn('[GameBlockSelection] 更新增加金币奖励显示失败:', error); } } } }