import { _decorator, Component, Node, Prefab, resources, instantiate, Label, Sprite, SpriteFrame, Button, ProgressBar, ScrollView, Vec2, Vec3, UITransform, tween, Color } from 'cc'; import { PersistentSkillManager } from './PersistentSkillManager'; import { SaveDataManager } from '../../LevelSystem/SaveDataManager'; import EventBus, { GameEvents } from '../../Core/EventBus'; const { ccclass, property } = _decorator; /** * 技能节点数据接口 */ interface SkillNodeData { node: Node; group: number; skillIndex: number; cost: number; isUnlocked: boolean; // 本地记录的解锁状态 backgroundSprite: Sprite; iconSprite: Sprite; } /** * 技能节点生成器 * 自动生成12组技能节点,每组包含3个技能类型 * 支持技能点亮状态管理和图片切换 */ @ccclass('SkillNodeGenerator') export class SkillNodeGenerator extends Component { @property(Prefab) skillNode1Prefab: Prefab = null!; // Damage @property(Prefab) skillNode2Prefab: Prefab = null!; // Ball Speed @property(Prefab) skillNode3Prefab: Prefab = null!; // Crit Damage // 图片资源 @property(SpriteFrame) backgroundNormal: SpriteFrame = null!; // an001.png - 未点亮状态 @property(SpriteFrame) backgroundUnlocked: SpriteFrame = null!; // an000.png - 点亮状态 @property(SpriteFrame) iconNormal: SpriteFrame = null!; // tb002.png - 未点亮状态 @property(SpriteFrame) iconUnlocked: SpriteFrame = null!; // tb001.png - 点亮状态 @property(ProgressBar) progressBar: ProgressBar = null!; // 进度条 @property(ScrollView) scrollView: ScrollView = null!; // 滚动视图 @property(Node) diamondNumberNode: Node = null!; // 钻石数量显示节点 // 技能节点数据存储(一维数组) private skillNodes: SkillNodeData[] = []; // 当前解锁到的节点索引 private currentUnlockIndex: number = -1; // 持久化技能管理器 private skillManager: PersistentSkillManager | null = null; // 保存数据管理器引用 private saveDataManager: SaveDataManager | null = null; // 闪烁动画相关 private blinkingNodes: Set = new Set(); // 正在闪烁的节点集合 start() { console.log('SkillNodeGenerator 初始化开始'); this.skillManager = PersistentSkillManager.getInstance(); this.saveDataManager = SaveDataManager.getInstance(); // 监听货币变化事件 EventBus.getInstance().on(GameEvents.CURRENCY_CHANGED, this.onCurrencyChanged, this); // 生成所有技能节点 this.generateSkillNodes(); console.log(`技能节点生成完成,共 ${this.skillNodes.length} 个节点`); // 加载已解锁的技能状态 this.loadUnlockedSkills(); // 更新进度条 this.updateProgressBar(); // 延迟一帧后滚动到最新解锁的技能节点,确保所有节点都已正确布局 this.scheduleOnce(() => { this.scrollToLatestUnlockedSkill(); }, 0.1); // 更新钻石UI显示 this.updateDiamondUI(); // 初始化闪烁状态 this.updateBlinkingNodes(); console.log('SkillNodeGenerator 初始化完成'); } /** * 生成技能节点 */ private generateSkillNodes() { // 技能节点预制体数组 const prefabs = [ this.skillNode1Prefab, // Damage this.skillNode2Prefab, // Ball Speed this.skillNode3Prefab // Crit Damage ]; // 技能名称模板 const skillNames = [ 'Damage +{}%', 'Ball Speed +{}%', 'Crit Damage +{}%' ]; // 生成12组技能节点,按顺序存储到一维数组中 for (let group = 1; group <= 12; group++) { const effectPercent = group * 5; // 5%, 10%, 15%, ..., 60% // 计算技能消耗,每组递增10点 const skillCost = 10 + (group - 1) * 10; // 每组生成3个技能节点:Damage、Ball Speed、Crit Damage for (let skillIndex = 0; skillIndex < 3; skillIndex++) { // 计算节点在一维数组中的索引 const arrayIndex = (group - 1) * 3 + skillIndex; this.createSkillNode( prefabs[skillIndex], skillNames[skillIndex].replace('{}', effectPercent.toString()), skillCost.toString(), group, skillIndex, arrayIndex ); } } } /** * 创建技能节点 */ private createSkillNode( prefab: Prefab, skillName: string, cost: string, group: number, skillIndex: number, totalIndex: number ) { if (!prefab) { console.error(`Prefab is null for skill index: ${skillIndex}`); return; } // 实例化预制体 const skillNode = instantiate(prefab); // 设置节点名称 skillNode.name = `SkillNode${skillIndex + 1}_Group${group}`; // 查找并设置NameLabel const nameLabel = skillNode.getChildByName('NameLabel'); if (nameLabel) { const labelComponent = nameLabel.getComponent(Label); if (labelComponent) { labelComponent.string = skillName; } } // 查找并设置CostLabel const costLabel = skillNode.getChildByName('CostLabel'); if (costLabel) { const labelComponent = costLabel.getComponent(Label); if (labelComponent) { labelComponent.string = cost; } } // 添加到当前节点(content节点) this.node.addChild(skillNode); // 获取Background和Icon的Sprite组件 const backgroundNode = skillNode.getChildByName('Background'); const iconNode = skillNode.getChildByName('Icon'); const backgroundSprite = backgroundNode?.getComponent(Sprite); const iconSprite = iconNode?.getComponent(Sprite); if (!backgroundSprite || !iconSprite) { console.error(`Failed to get sprite components for skill node: ${skillName}`); return; } // 创建技能节点数据 const skillNodeData: SkillNodeData = { node: skillNode, group: group, skillIndex: skillIndex, cost: parseInt(cost), isUnlocked: false, // 初始化为未解锁状态 backgroundSprite: backgroundSprite, iconSprite: iconSprite }; // 存储技能节点数据 this.skillNodes.push(skillNodeData); // 添加点击事件 const buttonComponent = skillNode.getChildByName('Background').getComponent(Button); if (buttonComponent) { buttonComponent.node.on(Button.EventType.CLICK, () => { this.onSkillNodeClick(skillNodeData); }, this); } // 初始化为未点亮状态 this.updateSkillNodeVisual(skillNodeData); } /** * 技能节点点击事件处理 */ private onSkillNodeClick(skillNodeData: SkillNodeData) { // 计算当前节点的索引 const nodeIndex = (skillNodeData.group - 1) * 3 + skillNodeData.skillIndex; // 只允许按顺序解锁,必须是下一个待解锁的节点 if (nodeIndex !== this.currentUnlockIndex + 1) { console.log(`必须按顺序解锁技能,当前可解锁索引: ${this.currentUnlockIndex + 1}`); return; } // 如果已经点亮,则不处理 if (skillNodeData.isUnlocked) { console.log(`技能已点亮: ${this.getSkillTypeString(skillNodeData.skillIndex)} Group ${skillNodeData.group}`); return; } // 获取技能类型和效果百分比 const skillInfo = this.getSkillInfo(skillNodeData); if (!skillInfo) { console.error('无法获取技能信息'); return; } // 检查玩家是否有足够的钻石 const currentDiamonds = this.saveDataManager ? this.saveDataManager.getDiamonds() : 0; if (currentDiamonds < skillNodeData.cost) { console.log(`钻石不足Need: ${skillNodeData.cost}, Have: ${currentDiamonds}`); // 发送Toast提示事件 EventBus.getInstance().emit(GameEvents.SHOW_TOAST, { message: `钻石不足,需要${skillNodeData.cost}钻石`, duration: 2.0 }); return; } // 验证是否可以解锁(由 SkillNodeGenerator 负责验证) // 这里已经通过 nodeIndex === this.currentUnlockIndex + 1 验证了顺序 if (!this.skillManager) { console.error('SkillManager not initialized'); return; } // 将解锁数据传递给 PersistentSkillManager const unlockResult = this.skillManager.unlockSkill(skillInfo.skillType, skillNodeData.group, skillInfo.effectPercent); if (!unlockResult.success) { // 这里主要处理技能类型无效或已解锁的情况 console.log(unlockResult.message); return; } // 扣除钻石 if (this.saveDataManager && !this.saveDataManager.spendDiamonds(skillNodeData.cost)) { return; } // 更新解锁索引 this.currentUnlockIndex = nodeIndex; // 更新UI状态 skillNodeData.isUnlocked = true; this.updateSkillNodeVisual(skillNodeData); this.updateProgressBar(); this.updateDiamondUI(); // 延迟更新闪烁状态,确保所有状态更新完成 this.scheduleOnce(() => { this.updateBlinkingNodes(); }, 0.1); // 输出技能解锁信息,包含数组索引 console.log(`技能已解锁: ${this.getSkillTypeString(skillNodeData.skillIndex)} Group ${skillNodeData.group}, 索引[${nodeIndex}/${this.skillNodes.length-1}]`); // 输出已解锁技能的数组信息 this.logUnlockedSkills(); // 滚动到最新点亮的技能节点 this.scheduleOnce(() => { this.scrollToLatestUnlockedSkill(); }, 0.1); const remainingDiamonds = this.saveDataManager ? this.saveDataManager.getDiamonds() : 0; console.log(`${unlockResult.message}, Remaining diamonds: ${remainingDiamonds}`); } /** * 更新技能节点的视觉效果 * 使用本地的 isUnlocked 状态来更新节点视觉效果 */ private updateSkillNodeVisual(skillNodeData: SkillNodeData) { if (skillNodeData.isUnlocked) { // 点亮状态:使用an000.png和tb001.png if (this.backgroundUnlocked) { skillNodeData.backgroundSprite.spriteFrame = this.backgroundUnlocked; } if (this.iconUnlocked) { skillNodeData.iconSprite.spriteFrame = this.iconUnlocked; } } else { // 未点亮状态:使用an001.png和tb002.png if (this.backgroundNormal) { skillNodeData.backgroundSprite.spriteFrame = this.backgroundNormal; } if (this.iconNormal) { skillNodeData.iconSprite.spriteFrame = this.iconNormal; } } } /** * 获取当前钻石数量 */ public getDiamonds(): number { return this.saveDataManager ? this.saveDataManager.getDiamonds() : 0; } /** * 设置钻石数量(用于测试或外部系统集成) */ public setDiamonds(amount: number) { if (this.saveDataManager) { const playerData = this.saveDataManager.getPlayerData(); if (playerData) { playerData.diamonds = amount; this.saveDataManager.savePlayerData(); this.updateDiamondUI(); // 更新闪烁状态 this.updateBlinkingNodes(); } } } /** * 更新UI显示的钻石数量 */ private updateDiamondUI() { // 使用装饰器属性更新主界面的钻石显示 if (this.diamondNumberNode) { const label = this.diamondNumberNode.getComponent(Label); if (label && this.saveDataManager) { const diamonds = this.saveDataManager.getDiamonds(); label.string = diamonds >= 1000000 ? (diamonds / 1e6).toFixed(1) + 'M' : diamonds >= 1000 ? (diamonds / 1e3).toFixed(1) + 'K' : diamonds.toString(); } } else { console.warn('Diamond number node not assigned in inspector'); } // 更新闪烁状态 this.updateBlinkingNodes(); } /** * 加载已解锁的技能状态 */ private loadUnlockedSkills() { if (!this.skillManager) { console.warn('SkillManager not initialized'); return; } // 遍历所有技能节点,按顺序检查解锁状态 let lastUnlockedIndex = -1; for (let i = 0; i < this.skillNodes.length; i++) { const skillNodeData = this.skillNodes[i]; const skillInfo = this.getSkillInfo(skillNodeData); if (!skillInfo) continue; // 检查该技能是否已解锁 const isUnlocked = this.skillManager.isSkillUnlocked(skillInfo.skillType, skillNodeData.group); if (isUnlocked) { // 更新本地解锁状态 skillNodeData.isUnlocked = true; this.updateSkillNodeVisual(skillNodeData); lastUnlockedIndex = i; } else { // 一旦遇到未解锁的技能,就停止检查 skillNodeData.isUnlocked = false; break; } } // 更新当前解锁索引 this.currentUnlockIndex = lastUnlockedIndex; // 更新进度条 this.updateProgressBar(); // 输出已解锁技能的数组信息 this.logUnlockedSkills(); } /** * 重置所有技能节点状态 */ public resetAllSkills() { // 通过技能管理器重置技能数据 if (this.skillManager) { this.skillManager.resetAllSkills(); } // 重置UI状态和本地解锁状态 this.skillNodes.forEach(skillNodeData => { // 重置本地解锁状态 skillNodeData.isUnlocked = false; // 通过 skillManager 重置技能状态 if (this.skillManager) { const skillInfo = this.getSkillInfo(skillNodeData); if (skillInfo) { this.skillManager.unlockSkill(skillInfo.skillType, skillNodeData.group, 0); // 将效果值设为0来重置技能 } } this.updateSkillNodeVisual(skillNodeData); }); // 重置当前解锁索引 this.currentUnlockIndex = -1; // 更新进度条 this.updateProgressBar(); console.log('All skills have been reset'); } /** * 清除技能点亮状态存档(包括持久化数据和UI状态) */ public clearSkillSaveData() { // 清除持久化技能数据 if (this.skillManager) { this.skillManager.resetAllSkills(); } // 重置UI状态和本地解锁状态 for (const skillNodeData of this.skillNodes) { // 重置本地解锁状态 skillNodeData.isUnlocked = false; // 通过 skillManager 重置技能状态 const skillInfo = this.getSkillInfo(skillNodeData); if (skillInfo && this.skillManager) { this.skillManager.unlockSkill(skillInfo.skillType, skillNodeData.group, 0); } this.updateSkillNodeVisual(skillNodeData); } // 重置当前解锁索引 this.currentUnlockIndex = -1; // 更新进度条 this.updateProgressBar(); // 更新钻石UI显示 this.updateDiamondUI(); } /** * 更新进度条 */ private updateProgressBar() { if (!this.progressBar) { console.warn('ProgressBar not found'); return; } // 使用当前解锁索引计算进度 const totalNodes = this.skillNodes.length; const currentProgress = totalNodes > 0 ? (this.currentUnlockIndex + 1) / totalNodes : 0; // 更新进度条 this.progressBar.progress = currentProgress; // 通知连接线组件更新进度 this.updateConnectionLineProgress(currentProgress); // 输出当前解锁进度信息 if (this.currentUnlockIndex >= 0) { console.log(`技能解锁进度: ${this.currentUnlockIndex + 1}/${totalNodes} (${Math.round(currentProgress * 100)}%)`); } else { console.log(`技能解锁进度: 0/${totalNodes} (0%)`); } } /** * 更新连接线进度 */ private updateConnectionLineProgress(progress: number) { // 查找连接线组件 const connectionLine = this.node.parent?.parent?.getChildByName('ConnectionLine'); if (connectionLine) { const connectionLineComp = connectionLine.getComponent('SkillConnectionLine') as any; if (connectionLineComp && typeof connectionLineComp.updateProgress === 'function') { connectionLineComp.updateProgress(progress); } } } /** * 滚动到最新解锁的技能节点 * 使用本地的 isUnlocked 状态来查找最新解锁的技能节点 */ private scrollToLatestUnlockedSkill() { // 找到最新解锁技能的索引 let latestUnlockedIndex = -1; for (let i = 0; i < this.skillNodes.length; i++) { const skillNode = this.skillNodes[i]; // 使用本地的 isUnlocked 状态来判断节点是否解锁 if (skillNode.isUnlocked) { latestUnlockedIndex = i; } } console.log('滚动到最新的未解锁技能节点或最底部'); // 延迟一帧执行,确保节点布局完成 this.scheduleOnce(() => { // 使用ScrollView的百分比滚动方法 if (this.scrollView) { if (latestUnlockedIndex >= 0) { // 根据最新解锁技能的索引计算滚动比例 const totalNodes = this.skillNodes.length; const scrollProgress = latestUnlockedIndex / (totalNodes - 1); // 使用ScrollView的scrollToPercentVertical方法设置滚动位置 // 参数:百分比(0-1), 滚动时间(秒), 是否衰减 this.scrollView.scrollToPercentVertical(scrollProgress, 0.3, true); console.log(`滚动到索引 ${latestUnlockedIndex}/${totalNodes - 1}, 滚动进度: ${scrollProgress.toFixed(2)}`); } else { // 如果没有解锁技能,滚动到顶部 this.scrollView.scrollToPercentVertical(0, 0.3, true); console.log('滚动到顶部'); } } else { console.warn('ScrollView not assigned in inspector'); } }, 0); } /** * 获取最新解锁的节点位置 * @returns 最新解锁节点的Y坐标位置,如果没有解锁节点则返回0 */ public getLatestUnlockedNodePosition(): number { // 找到最新点亮的技能节点(如果有) let latestUnlockedNode: SkillNodeData | null = null; let maxIndex = -1; for (const skillNode of this.skillNodes) { const skillInfo = this.getSkillInfo(skillNode); if (skillInfo && this.skillManager?.isSkillUnlocked(skillInfo.skillType, skillNode.group)) { const totalIndex = (skillNode.group - 1) * 3 + skillNode.skillIndex; if (totalIndex > maxIndex) { maxIndex = totalIndex; latestUnlockedNode = skillNode; } } } if (latestUnlockedNode) { // 返回节点的Y坐标位置(负值) return -latestUnlockedNode.node.position.y; } return 0; } /** * 获取当前解锁进度 * @returns 解锁进度(0-1之间) */ public getUnlockProgress(): number { const unlockedCount = this.skillNodes.filter(node => { // 使用本地的 isUnlocked 状态来判断节点是否解锁 return node.isUnlocked; }).length; const totalNodes = this.skillNodes.length; return totalNodes > 0 ? unlockedCount / totalNodes : 0; } /** * 输出已解锁技能的数组信息 * 使用本地的 isUnlocked 状态来记录解锁的技能 */ private logUnlockedSkills(): void { // 创建一个表示所有技能节点状态的数组 const skillStatusArray = this.skillNodes.map((node, index) => { return { index, group: node.group, skillType: this.getSkillTypeString(node.skillIndex), isUnlocked: node.isUnlocked // 使用本地的 isUnlocked 状态 }; }); // 输出已解锁的技能节点 console.log('已解锁技能节点:'); const unlockedNodes = skillStatusArray.filter(node => node.isUnlocked); unlockedNodes.forEach(node => { console.log(`[${node.index}] ${node.skillType} Group ${node.group}`); }); // 输出下一个待解锁的节点 const nextNode = skillStatusArray.find(node => !node.isUnlocked); if (nextNode) { console.log(`下一个待解锁节点: [${nextNode.index}] ${nextNode.skillType} Group ${nextNode.group}`); } else { console.log('所有技能节点已解锁!'); } // 输出解锁进度 console.log(`解锁进度: ${unlockedNodes.length}/${skillStatusArray.length} (${Math.round(this.getUnlockProgress() * 100)}%)`); } /** * 从技能节点数据中提取技能信息 * @param skillNodeData 技能节点数据 * @returns 技能信息 {skillType: string, effectPercent: number} */ private getSkillInfo(skillNodeData: SkillNodeData): {skillType: string, effectPercent: number} | null { const nameLabel = skillNodeData.node.getChildByName('NameLabel'); if (!nameLabel) { console.error('NameLabel not found in skill node'); return null; } const labelComponent = nameLabel.getComponent(Label); if (!labelComponent) { console.error('Label component not found in NameLabel'); return null; } const skillName = labelComponent.string; // 确定技能类型 let skillType: string; if (skillName.includes('Crit Damage')) { skillType = 'crit_damage'; } else if (skillName.includes('Damage')) { skillType = 'damage'; } else if (skillName.includes('Ball Speed')) { skillType = 'ball_speed'; } else { console.error('Unknown skill type in skill name:', skillName); return null; } // 从技能名称中提取效果百分比 const match = skillName.match(/\+(\d+)%/); if (!match) { console.error('Could not extract effect percent from skill name:', skillName); return null; } const effectPercent = parseInt(match[1]); return {skillType, effectPercent}; } /** * 根据技能索引获取技能类型字符串 * @param skillIndex 技能索引 (0: Damage, 1: Ball Speed, 2: Crit Damage) * @returns 技能类型字符串 */ private getSkillTypeString(skillIndex: number): string { switch (skillIndex) { case 0: return 'Damage'; case 1: return 'Ball Speed'; case 2: return 'Crit Damage'; default: return '未知技能'; } } /** * 更新闪烁节点状态 * 检查哪些节点可以升级并开始闪烁动画 */ private updateBlinkingNodes() { // 停止所有当前的闪烁动画 this.stopAllBlinking(); // 获取当前钻石数量 const currentDiamonds = this.saveDataManager ? this.saveDataManager.getDiamonds() : 0; // 找到下一个可解锁的节点 const nextUnlockIndex = this.currentUnlockIndex + 1; if (nextUnlockIndex < this.skillNodes.length) { const nextNode = this.skillNodes[nextUnlockIndex]; console.log(`[闪烁检查] 下一个节点 - 组别: ${nextNode.group}, 技能: ${this.getSkillTypeString(nextNode.skillIndex)}, 费用: ${nextNode.cost}, 已解锁: ${nextNode.isUnlocked}, 当前钻石: ${currentDiamonds}`); // 检查是否有足够钻石且节点未解锁 if (!nextNode.isUnlocked && currentDiamonds >= nextNode.cost) { this.startBlinking(nextNode.node); } else { console.log(`[闪烁检查] 节点不满足闪烁条件`); } } else { console.log(`[闪烁检查] 已达到最后一个节点,无需闪烁`); } } /** * 开始节点闪烁动画 * @param node 要闪烁的节点 */ private startBlinking(node: Node) { if (this.blinkingNodes.has(node)) { return; // 已经在闪烁中 } this.blinkingNodes.add(node); // 创建闪烁动画:透明度在0.5和1.0之间循环 const blinkTween = tween(node) .to(0.5, { scale: new Vec3(1.1, 1.1, 1.1) }, { easing: 'sineInOut' }) .to(0.5, { scale: new Vec3(1.0, 1.0, 1.0) }, { easing: 'sineInOut' }) .union() .repeatForever(); blinkTween.start(); // 将tween存储到节点上,方便后续停止 node['_blinkTween'] = blinkTween; } /** * 停止节点闪烁动画 * @param node 要停止闪烁的节点 */ private stopBlinking(node: Node) { if (!this.blinkingNodes.has(node)) { return; // 没有在闪烁 } this.blinkingNodes.delete(node); // 停止tween动画 if (node['_blinkTween']) { node['_blinkTween'].stop(); node['_blinkTween'] = null; } // 重置节点状态 node.setScale(1, 1, 1); } /** * 停止所有闪烁动画 */ private stopAllBlinking() { for (const node of this.blinkingNodes) { if (node['_blinkTween']) { node['_blinkTween'].stop(); node['_blinkTween'] = null; } node.setScale(1, 1, 1); } this.blinkingNodes.clear(); } /** * 当钻石数量变化时调用,更新闪烁状态 */ public onDiamondsChanged() { this.updateBlinkingNodes(); } /** * 货币变化事件处理 */ private onCurrencyChanged() { console.log('[SkillNodeGenerator] 检测到货币变化,更新钻石UI和闪烁状态'); this.updateDiamondUI(); } /** * 组件销毁时清理资源 */ onDestroy() { // 取消事件监听 EventBus.getInstance().off(GameEvents.CURRENCY_CHANGED, this.onCurrencyChanged, this); // 停止所有闪烁动画 this.stopAllBlinking(); } }