SkillNodeGenerator.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. import { _decorator, Component, Node, Prefab, resources, instantiate, Label, Sprite, SpriteFrame, Button, ProgressBar, ScrollView, Vec2, Vec3, UITransform, tween, Color } from 'cc';
  2. import { PersistentSkillManager } from './PersistentSkillManager';
  3. import { SaveDataManager } from '../../LevelSystem/SaveDataManager';
  4. import { SkillConfigManager } from './SkillConfigManager';
  5. import EventBus, { GameEvents } from '../../Core/EventBus';
  6. const { ccclass, property } = _decorator;
  7. /**
  8. * 技能节点数据接口
  9. */
  10. interface SkillNodeData {
  11. node: Node;
  12. group: number;
  13. skillIndex: number;
  14. cost: number;
  15. isUnlocked: boolean; // 本地记录的解锁状态
  16. backgroundSprite: Sprite;
  17. iconSprite: Sprite;
  18. }
  19. /**
  20. * 技能节点生成器
  21. * 自动生成12组技能节点,每组包含3个技能类型
  22. * 支持技能点亮状态管理和图片切换
  23. */
  24. @ccclass('SkillNodeGenerator')
  25. export class SkillNodeGenerator extends Component {
  26. @property(Prefab)
  27. skillNode1Prefab: Prefab = null!; // Damage
  28. @property(Prefab)
  29. skillNode2Prefab: Prefab = null!; // Ball Speed
  30. @property(Prefab)
  31. skillNode3Prefab: Prefab = null!; // Crit Damage
  32. // 图片资源
  33. @property(SpriteFrame)
  34. backgroundNormal: SpriteFrame = null!; // an001.png - 未点亮状态
  35. @property(SpriteFrame)
  36. backgroundUnlocked: SpriteFrame = null!; // an000.png - 点亮状态
  37. @property(SpriteFrame)
  38. iconNormal: SpriteFrame = null!; // tb002.png - 未点亮状态
  39. @property(SpriteFrame)
  40. iconUnlocked: SpriteFrame = null!; // tb001.png - 点亮状态
  41. @property(ProgressBar)
  42. progressBar: ProgressBar = null!; // 进度条
  43. @property(ScrollView)
  44. scrollView: ScrollView = null!; // 滚动视图
  45. @property(Node)
  46. diamondNumberNode: Node = null!; // 钻石数量显示节点
  47. // 技能节点数据存储(一维数组)
  48. private skillNodes: SkillNodeData[] = [];
  49. // 当前解锁到的节点索引
  50. private currentUnlockIndex: number = -1;
  51. // 持久化技能管理器
  52. private skillManager: PersistentSkillManager | null = null;
  53. // 保存数据管理器引用
  54. private saveDataManager: SaveDataManager | null = null;
  55. // 技能配置管理器
  56. private skillConfigManager: SkillConfigManager | null = null;
  57. // 闪烁动画相关
  58. private blinkingNodes: Set<Node> = new Set(); // 正在闪烁的节点集合
  59. async start() {
  60. console.log('SkillNodeGenerator 初始化开始');
  61. this.skillManager = PersistentSkillManager.getInstance();
  62. this.saveDataManager = SaveDataManager.getInstance();
  63. this.skillConfigManager = SkillConfigManager.getInstance();
  64. // 监听货币变化事件
  65. EventBus.getInstance().on(GameEvents.CURRENCY_CHANGED, this.onCurrencyChanged, this);
  66. // 等待技能配置加载完成
  67. if (this.skillConfigManager) {
  68. await this.skillConfigManager.waitForLoad();
  69. console.log('技能配置加载完成');
  70. }
  71. // 生成所有技能节点
  72. this.generateSkillNodes();
  73. console.log(`技能节点生成完成,共 ${this.skillNodes.length} 个节点`);
  74. // 加载已解锁的技能状态
  75. this.loadUnlockedSkills();
  76. // 更新进度条
  77. this.updateProgressBar();
  78. // 延迟一帧后滚动到最新解锁的技能节点,确保所有节点都已正确布局
  79. this.scheduleOnce(() => {
  80. this.scrollToLatestUnlockedSkill();
  81. }, 0.1);
  82. // 更新钻石UI显示
  83. this.updateDiamondUI();
  84. // 初始化闪烁状态
  85. this.updateBlinkingNodes();
  86. console.log('SkillNodeGenerator 初始化完成');
  87. }
  88. /**
  89. * 生成技能节点
  90. */
  91. private generateSkillNodes() {
  92. if (!this.skillConfigManager) {
  93. console.error('SkillConfigManager not initialized');
  94. return;
  95. }
  96. // 技能节点预制体数组
  97. const prefabs = [
  98. this.skillNode1Prefab, // Damage
  99. this.skillNode2Prefab, // Ball Speed
  100. this.skillNode3Prefab // Crit Damage
  101. ];
  102. const totalGroups = this.skillConfigManager.getTotalGroups();
  103. const skillsPerGroup = this.skillConfigManager.getSkillsPerGroup();
  104. // 生成技能节点,按顺序存储到一维数组中
  105. for (let group = 1; group <= totalGroups; group++) {
  106. const skillGroupConfig = this.skillConfigManager.getSkillGroupConfig(group);
  107. if (!skillGroupConfig) {
  108. console.error(`Failed to get skill group config for group ${group}`);
  109. continue;
  110. }
  111. // 每组生成指定数量的技能节点
  112. for (let skillIndex = 0; skillIndex < skillsPerGroup; skillIndex++) {
  113. // 计算节点在一维数组中的索引
  114. const arrayIndex = (group - 1) * skillsPerGroup + skillIndex;
  115. const skillName = this.skillConfigManager.getSkillName(skillIndex, group);
  116. const skillCost = this.skillConfigManager.getSkillCost(group);
  117. this.createSkillNode(
  118. prefabs[skillIndex],
  119. skillName,
  120. skillCost.toString(),
  121. group,
  122. skillIndex,
  123. arrayIndex
  124. );
  125. }
  126. }
  127. }
  128. /**
  129. * 创建技能节点
  130. */
  131. private createSkillNode(
  132. prefab: Prefab,
  133. skillName: string,
  134. cost: string,
  135. group: number,
  136. skillIndex: number,
  137. totalIndex: number
  138. ) {
  139. if (!prefab) {
  140. console.error(`Prefab is null for skill index: ${skillIndex}`);
  141. return;
  142. }
  143. // 实例化预制体
  144. const skillNode = instantiate(prefab);
  145. // 设置节点名称
  146. skillNode.name = `SkillNode${skillIndex + 1}_Group${group}`;
  147. // 查找并设置NameLabel
  148. const nameLabel = skillNode.getChildByName('NameLabel');
  149. if (nameLabel) {
  150. const labelComponent = nameLabel.getComponent(Label);
  151. if (labelComponent) {
  152. labelComponent.string = skillName;
  153. }
  154. }
  155. // 查找并设置CostLabel
  156. const costLabel = skillNode.getChildByName('CostLabel');
  157. if (costLabel) {
  158. const labelComponent = costLabel.getComponent(Label);
  159. if (labelComponent) {
  160. labelComponent.string = cost;
  161. }
  162. }
  163. // 添加到当前节点(content节点)
  164. this.node.addChild(skillNode);
  165. // 获取Background和Icon的Sprite组件
  166. const backgroundNode = skillNode.getChildByName('Background');
  167. const iconNode = skillNode.getChildByName('Icon');
  168. const backgroundSprite = backgroundNode?.getComponent(Sprite);
  169. const iconSprite = iconNode?.getComponent(Sprite);
  170. if (!backgroundSprite || !iconSprite) {
  171. console.error(`Failed to get sprite components for skill node: ${skillName}`);
  172. return;
  173. }
  174. // 创建技能节点数据
  175. const skillNodeData: SkillNodeData = {
  176. node: skillNode,
  177. group: group,
  178. skillIndex: skillIndex,
  179. cost: parseInt(cost),
  180. isUnlocked: false, // 初始化为未解锁状态
  181. backgroundSprite: backgroundSprite,
  182. iconSprite: iconSprite
  183. };
  184. // 存储技能节点数据
  185. this.skillNodes.push(skillNodeData);
  186. // 添加点击事件
  187. const buttonComponent = skillNode.getChildByName('Background').getComponent(Button);
  188. if (buttonComponent) {
  189. buttonComponent.node.on(Button.EventType.CLICK, () => {
  190. this.onSkillNodeClick(skillNodeData);
  191. }, this);
  192. }
  193. // 初始化为未点亮状态
  194. this.updateSkillNodeVisual(skillNodeData);
  195. }
  196. /**
  197. * 技能节点点击事件处理
  198. */
  199. private onSkillNodeClick(skillNodeData: SkillNodeData) {
  200. // 计算当前节点的索引
  201. const nodeIndex = (skillNodeData.group - 1) * 3 + skillNodeData.skillIndex;
  202. // 只允许按顺序解锁,必须是下一个待解锁的节点
  203. if (nodeIndex !== this.currentUnlockIndex + 1) {
  204. console.log(`必须按顺序解锁技能,当前可解锁索引: ${this.currentUnlockIndex + 1}`);
  205. return;
  206. }
  207. // 如果已经点亮,则不处理
  208. if (skillNodeData.isUnlocked) {
  209. console.log(`技能已点亮: ${this.getSkillTypeString(skillNodeData.skillIndex)} Group ${skillNodeData.group}`);
  210. return;
  211. }
  212. // 获取技能类型和效果百分比
  213. const skillInfo = this.getSkillInfo(skillNodeData);
  214. if (!skillInfo) {
  215. console.error('无法获取技能信息');
  216. return;
  217. }
  218. // 检查玩家是否有足够的钻石
  219. const currentDiamonds = this.saveDataManager ? this.saveDataManager.getDiamonds() : 0;
  220. if (currentDiamonds < skillNodeData.cost) {
  221. console.log(`钻石不足Need: ${skillNodeData.cost}, Have: ${currentDiamonds}`);
  222. // 发送Toast提示事件
  223. EventBus.getInstance().emit(GameEvents.SHOW_TOAST, {
  224. message: `钻石不足,需要${skillNodeData.cost}钻石`,
  225. duration: 2.0
  226. });
  227. return;
  228. }
  229. // 验证是否可以解锁(由 SkillNodeGenerator 负责验证)
  230. // 这里已经通过 nodeIndex === this.currentUnlockIndex + 1 验证了顺序
  231. if (!this.skillManager) {
  232. console.error('SkillManager not initialized');
  233. return;
  234. }
  235. // 将解锁数据传递给 PersistentSkillManager
  236. const unlockResult = this.skillManager.unlockSkill(skillInfo.skillType, skillNodeData.group, skillInfo.effectPercent);
  237. if (!unlockResult.success) {
  238. // 这里主要处理技能类型无效或已解锁的情况
  239. console.log(unlockResult.message);
  240. return;
  241. }
  242. // 扣除钻石
  243. if (this.saveDataManager && !this.saveDataManager.spendDiamonds(skillNodeData.cost)) {
  244. return;
  245. }
  246. // 更新解锁索引
  247. this.currentUnlockIndex = nodeIndex;
  248. // 更新UI状态
  249. skillNodeData.isUnlocked = true;
  250. this.updateSkillNodeVisual(skillNodeData);
  251. this.updateProgressBar();
  252. this.updateDiamondUI();
  253. // 延迟更新闪烁状态,确保所有状态更新完成
  254. this.scheduleOnce(() => {
  255. this.updateBlinkingNodes();
  256. }, 0.1);
  257. // 输出技能解锁信息,包含数组索引
  258. console.log(`技能已解锁: ${this.getSkillTypeString(skillNodeData.skillIndex)} Group ${skillNodeData.group}, 索引[${nodeIndex}/${this.skillNodes.length-1}]`);
  259. // 输出已解锁技能的数组信息
  260. this.logUnlockedSkills();
  261. // 滚动到最新点亮的技能节点
  262. this.scheduleOnce(() => {
  263. this.scrollToLatestUnlockedSkill();
  264. }, 0.1);
  265. const remainingDiamonds = this.saveDataManager ? this.saveDataManager.getDiamonds() : 0;
  266. console.log(`${unlockResult.message}, Remaining diamonds: ${remainingDiamonds}`);
  267. }
  268. /**
  269. * 更新技能节点的视觉效果
  270. * 使用本地的 isUnlocked 状态来更新节点视觉效果
  271. */
  272. private updateSkillNodeVisual(skillNodeData: SkillNodeData) {
  273. if (skillNodeData.isUnlocked) {
  274. // 点亮状态:使用an000.png和tb001.png
  275. if (this.backgroundUnlocked) {
  276. skillNodeData.backgroundSprite.spriteFrame = this.backgroundUnlocked;
  277. }
  278. if (this.iconUnlocked) {
  279. skillNodeData.iconSprite.spriteFrame = this.iconUnlocked;
  280. }
  281. } else {
  282. // 未点亮状态:使用an001.png和tb002.png
  283. if (this.backgroundNormal) {
  284. skillNodeData.backgroundSprite.spriteFrame = this.backgroundNormal;
  285. }
  286. if (this.iconNormal) {
  287. skillNodeData.iconSprite.spriteFrame = this.iconNormal;
  288. }
  289. }
  290. }
  291. /**
  292. * 获取当前钻石数量
  293. */
  294. public getDiamonds(): number {
  295. return this.saveDataManager ? this.saveDataManager.getDiamonds() : 0;
  296. }
  297. /**
  298. * 设置钻石数量(用于测试或外部系统集成)
  299. */
  300. public setDiamonds(amount: number) {
  301. if (this.saveDataManager) {
  302. const playerData = this.saveDataManager.getPlayerData();
  303. if (playerData) {
  304. playerData.diamonds = amount;
  305. this.saveDataManager.savePlayerData();
  306. this.updateDiamondUI();
  307. // 更新闪烁状态
  308. this.updateBlinkingNodes();
  309. }
  310. }
  311. }
  312. /**
  313. * 更新UI显示的钻石数量
  314. */
  315. private updateDiamondUI() {
  316. // 使用装饰器属性更新主界面的钻石显示
  317. if (this.diamondNumberNode) {
  318. const label = this.diamondNumberNode.getComponent(Label);
  319. if (label && this.saveDataManager) {
  320. const diamonds = this.saveDataManager.getDiamonds();
  321. label.string = diamonds >= 1000000 ? (diamonds / 1e6).toFixed(1) + 'M' :
  322. diamonds >= 1000 ? (diamonds / 1e3).toFixed(1) + 'K' :
  323. diamonds.toString();
  324. }
  325. } else {
  326. console.warn('Diamond number node not assigned in inspector');
  327. }
  328. // 更新闪烁状态
  329. this.updateBlinkingNodes();
  330. }
  331. /**
  332. * 加载已解锁的技能状态
  333. */
  334. private loadUnlockedSkills() {
  335. if (!this.skillManager) {
  336. console.warn('SkillManager not initialized');
  337. return;
  338. }
  339. // 遍历所有技能节点,按顺序检查解锁状态
  340. let lastUnlockedIndex = -1;
  341. for (let i = 0; i < this.skillNodes.length; i++) {
  342. const skillNodeData = this.skillNodes[i];
  343. const skillInfo = this.getSkillInfo(skillNodeData);
  344. if (!skillInfo) continue;
  345. // 检查该技能是否已解锁
  346. const isUnlocked = this.skillManager.isSkillUnlocked(skillInfo.skillType, skillNodeData.group);
  347. if (isUnlocked) {
  348. // 更新本地解锁状态
  349. skillNodeData.isUnlocked = true;
  350. this.updateSkillNodeVisual(skillNodeData);
  351. lastUnlockedIndex = i;
  352. } else {
  353. // 一旦遇到未解锁的技能,就停止检查
  354. skillNodeData.isUnlocked = false;
  355. break;
  356. }
  357. }
  358. // 更新当前解锁索引
  359. this.currentUnlockIndex = lastUnlockedIndex;
  360. // 更新进度条
  361. this.updateProgressBar();
  362. // 输出已解锁技能的数组信息
  363. this.logUnlockedSkills();
  364. }
  365. /**
  366. * 重置所有技能节点状态
  367. */
  368. public resetAllSkills() {
  369. // 通过技能管理器重置技能数据
  370. if (this.skillManager) {
  371. this.skillManager.resetAllSkills();
  372. }
  373. // 重置UI状态和本地解锁状态
  374. this.skillNodes.forEach(skillNodeData => {
  375. // 重置本地解锁状态
  376. skillNodeData.isUnlocked = false;
  377. // 通过 skillManager 重置技能状态
  378. if (this.skillManager) {
  379. const skillInfo = this.getSkillInfo(skillNodeData);
  380. if (skillInfo) {
  381. this.skillManager.unlockSkill(skillInfo.skillType, skillNodeData.group, 0); // 将效果值设为0来重置技能
  382. }
  383. }
  384. this.updateSkillNodeVisual(skillNodeData);
  385. });
  386. // 重置当前解锁索引
  387. this.currentUnlockIndex = -1;
  388. // 更新进度条
  389. this.updateProgressBar();
  390. console.log('All skills have been reset');
  391. }
  392. /**
  393. * 清除技能点亮状态存档(包括持久化数据和UI状态)
  394. */
  395. public clearSkillSaveData() {
  396. // 清除持久化技能数据
  397. if (this.skillManager) {
  398. this.skillManager.resetAllSkills();
  399. }
  400. // 重置UI状态和本地解锁状态
  401. for (const skillNodeData of this.skillNodes) {
  402. // 重置本地解锁状态
  403. skillNodeData.isUnlocked = false;
  404. // 通过 skillManager 重置技能状态
  405. const skillInfo = this.getSkillInfo(skillNodeData);
  406. if (skillInfo && this.skillManager) {
  407. this.skillManager.unlockSkill(skillInfo.skillType, skillNodeData.group, 0);
  408. }
  409. this.updateSkillNodeVisual(skillNodeData);
  410. }
  411. // 重置当前解锁索引
  412. this.currentUnlockIndex = -1;
  413. // 更新进度条
  414. this.updateProgressBar();
  415. // 更新钻石UI显示
  416. this.updateDiamondUI();
  417. }
  418. /**
  419. * 更新进度条
  420. */
  421. private updateProgressBar() {
  422. if (!this.progressBar) {
  423. console.warn('ProgressBar not found');
  424. return;
  425. }
  426. // 使用当前解锁索引计算进度
  427. const totalNodes = this.skillNodes.length;
  428. const currentProgress = totalNodes > 0 ? (this.currentUnlockIndex + 1) / totalNodes : 0;
  429. // 更新进度条
  430. this.progressBar.progress = currentProgress;
  431. // 通知连接线组件更新进度
  432. this.updateConnectionLineProgress(currentProgress);
  433. // 输出当前解锁进度信息
  434. if (this.currentUnlockIndex >= 0) {
  435. console.log(`技能解锁进度: ${this.currentUnlockIndex + 1}/${totalNodes} (${Math.round(currentProgress * 100)}%)`);
  436. } else {
  437. console.log(`技能解锁进度: 0/${totalNodes} (0%)`);
  438. }
  439. }
  440. /**
  441. * 更新连接线进度
  442. */
  443. private updateConnectionLineProgress(progress: number) {
  444. // 查找连接线组件
  445. const connectionLine = this.node.parent?.parent?.getChildByName('ConnectionLine');
  446. if (connectionLine) {
  447. const connectionLineComp = connectionLine.getComponent('SkillConnectionLine') as any;
  448. if (connectionLineComp && typeof connectionLineComp.updateProgress === 'function') {
  449. connectionLineComp.updateProgress(progress);
  450. }
  451. }
  452. }
  453. /**
  454. * 滚动到最新解锁的技能节点
  455. * 使用本地的 isUnlocked 状态来查找最新解锁的技能节点
  456. */
  457. private scrollToLatestUnlockedSkill() {
  458. // 找到最新解锁技能的索引
  459. let latestUnlockedIndex = -1;
  460. for (let i = 0; i < this.skillNodes.length; i++) {
  461. const skillNode = this.skillNodes[i];
  462. // 使用本地的 isUnlocked 状态来判断节点是否解锁
  463. if (skillNode.isUnlocked) {
  464. latestUnlockedIndex = i;
  465. }
  466. }
  467. console.log('滚动到最新的未解锁技能节点或最底部');
  468. // 延迟一帧执行,确保节点布局完成
  469. this.scheduleOnce(() => {
  470. // 使用ScrollView的百分比滚动方法
  471. if (this.scrollView) {
  472. if (latestUnlockedIndex >= 0) {
  473. // 根据最新解锁技能的索引计算滚动比例
  474. const totalNodes = this.skillNodes.length;
  475. const scrollProgress = latestUnlockedIndex / (totalNodes - 1);
  476. // 使用ScrollView的scrollToPercentVertical方法设置滚动位置
  477. // 参数:百分比(0-1), 滚动时间(秒), 是否衰减
  478. this.scrollView.scrollToPercentVertical(scrollProgress, 0.3, true);
  479. console.log(`滚动到索引 ${latestUnlockedIndex}/${totalNodes - 1}, 滚动进度: ${scrollProgress.toFixed(2)}`);
  480. } else {
  481. // 如果没有解锁技能,滚动到顶部
  482. this.scrollView.scrollToPercentVertical(0, 0.3, true);
  483. console.log('滚动到顶部');
  484. }
  485. } else {
  486. console.warn('ScrollView not assigned in inspector');
  487. }
  488. }, 0);
  489. }
  490. /**
  491. * 获取最新解锁的节点位置
  492. * @returns 最新解锁节点的Y坐标位置,如果没有解锁节点则返回0
  493. */
  494. public getLatestUnlockedNodePosition(): number {
  495. // 找到最新点亮的技能节点(如果有)
  496. let latestUnlockedNode: SkillNodeData | null = null;
  497. let maxIndex = -1;
  498. for (const skillNode of this.skillNodes) {
  499. const skillInfo = this.getSkillInfo(skillNode);
  500. if (skillInfo && this.skillManager?.isSkillUnlocked(skillInfo.skillType, skillNode.group)) {
  501. const totalIndex = (skillNode.group - 1) * 3 + skillNode.skillIndex;
  502. if (totalIndex > maxIndex) {
  503. maxIndex = totalIndex;
  504. latestUnlockedNode = skillNode;
  505. }
  506. }
  507. }
  508. if (latestUnlockedNode) {
  509. // 返回节点的Y坐标位置(负值)
  510. return -latestUnlockedNode.node.position.y;
  511. }
  512. return 0;
  513. }
  514. /**
  515. * 获取当前解锁进度
  516. * @returns 解锁进度(0-1之间)
  517. */
  518. public getUnlockProgress(): number {
  519. const unlockedCount = this.skillNodes.filter(node => {
  520. // 使用本地的 isUnlocked 状态来判断节点是否解锁
  521. return node.isUnlocked;
  522. }).length;
  523. const totalNodes = this.skillNodes.length;
  524. return totalNodes > 0 ? unlockedCount / totalNodes : 0;
  525. }
  526. /**
  527. * 输出已解锁技能的数组信息
  528. * 使用本地的 isUnlocked 状态来记录解锁的技能
  529. */
  530. private logUnlockedSkills(): void {
  531. // 创建一个表示所有技能节点状态的数组
  532. const skillStatusArray = this.skillNodes.map((node, index) => {
  533. return {
  534. index,
  535. group: node.group,
  536. skillType: this.getSkillTypeString(node.skillIndex),
  537. isUnlocked: node.isUnlocked // 使用本地的 isUnlocked 状态
  538. };
  539. });
  540. // 输出已解锁的技能节点
  541. console.log('已解锁技能节点:');
  542. const unlockedNodes = skillStatusArray.filter(node => node.isUnlocked);
  543. unlockedNodes.forEach(node => {
  544. console.log(`[${node.index}] ${node.skillType} Group ${node.group}`);
  545. });
  546. // 输出下一个待解锁的节点
  547. const nextNode = skillStatusArray.find(node => !node.isUnlocked);
  548. if (nextNode) {
  549. console.log(`下一个待解锁节点: [${nextNode.index}] ${nextNode.skillType} Group ${nextNode.group}`);
  550. } else {
  551. console.log('所有技能节点已解锁!');
  552. }
  553. // 输出解锁进度
  554. console.log(`解锁进度: ${unlockedNodes.length}/${skillStatusArray.length} (${Math.round(this.getUnlockProgress() * 100)}%)`);
  555. }
  556. /**
  557. * 获取技能信息
  558. * @param skillNodeData 技能节点数据
  559. * @returns 技能信息对象,包含技能类型和效果百分比
  560. */
  561. private getSkillInfo(skillNodeData: SkillNodeData): {skillType: string, effectPercent: number} | null {
  562. if (!this.skillConfigManager) {
  563. console.error('SkillConfigManager not initialized');
  564. return null;
  565. }
  566. // 从配置管理器获取技能类型
  567. const skillType = this.skillConfigManager.getSkillType(skillNodeData.skillIndex);
  568. if (skillType === 'unknown') {
  569. console.error(`未知的技能索引: ${skillNodeData.skillIndex}`);
  570. return null;
  571. }
  572. // 从配置管理器获取效果百分比
  573. const effectPercent = this.skillConfigManager.getSkillEffectPercent(skillNodeData.group);
  574. return {
  575. skillType: skillType,
  576. effectPercent: effectPercent
  577. };
  578. }
  579. /**
  580. * 根据技能索引获取技能类型字符串
  581. * @param skillIndex 技能索引 (0: Damage, 1: Ball Speed, 2: Crit Damage)
  582. * @returns 技能类型字符串
  583. */
  584. private getSkillTypeString(skillIndex: number): string {
  585. if (!this.skillConfigManager) {
  586. return '未知技能';
  587. }
  588. return this.skillConfigManager.getSkillDisplayName(skillIndex);
  589. }
  590. /**
  591. * 更新闪烁节点状态
  592. * 检查哪些节点可以升级并开始闪烁动画
  593. */
  594. private updateBlinkingNodes() {
  595. // 停止所有当前的闪烁动画
  596. this.stopAllBlinking();
  597. // 获取当前钻石数量
  598. const currentDiamonds = this.saveDataManager ? this.saveDataManager.getDiamonds() : 0;
  599. // 找到下一个可解锁的节点
  600. const nextUnlockIndex = this.currentUnlockIndex + 1;
  601. if (nextUnlockIndex < this.skillNodes.length) {
  602. const nextNode = this.skillNodes[nextUnlockIndex];
  603. console.log(`[闪烁检查] 下一个节点 - 组别: ${nextNode.group}, 技能: ${this.getSkillTypeString(nextNode.skillIndex)}, 费用: ${nextNode.cost}, 已解锁: ${nextNode.isUnlocked}, 当前钻石: ${currentDiamonds}`);
  604. // 检查是否有足够钻石且节点未解锁
  605. if (!nextNode.isUnlocked && currentDiamonds >= nextNode.cost) {
  606. this.startBlinking(nextNode.node);
  607. } else {
  608. console.log(`[闪烁检查] 节点不满足闪烁条件`);
  609. }
  610. } else {
  611. console.log(`[闪烁检查] 已达到最后一个节点,无需闪烁`);
  612. }
  613. }
  614. /**
  615. * 开始节点闪烁动画
  616. * @param node 要闪烁的节点
  617. */
  618. private startBlinking(node: Node) {
  619. if (this.blinkingNodes.has(node)) {
  620. return; // 已经在闪烁中
  621. }
  622. this.blinkingNodes.add(node);
  623. // 创建闪烁动画:透明度在0.5和1.0之间循环
  624. const blinkTween = tween(node)
  625. .to(0.5, { scale: new Vec3(1.1, 1.1, 1.1) }, { easing: 'sineInOut' })
  626. .to(0.5, { scale: new Vec3(1.0, 1.0, 1.0) }, { easing: 'sineInOut' })
  627. .union()
  628. .repeatForever();
  629. blinkTween.start();
  630. // 将tween存储到节点上,方便后续停止
  631. node['_blinkTween'] = blinkTween;
  632. }
  633. /**
  634. * 停止节点闪烁动画
  635. * @param node 要停止闪烁的节点
  636. */
  637. private stopBlinking(node: Node) {
  638. if (!this.blinkingNodes.has(node)) {
  639. return; // 没有在闪烁
  640. }
  641. this.blinkingNodes.delete(node);
  642. // 停止tween动画
  643. if (node['_blinkTween']) {
  644. node['_blinkTween'].stop();
  645. node['_blinkTween'] = null;
  646. }
  647. // 重置节点状态
  648. node.setScale(1, 1, 1);
  649. }
  650. /**
  651. * 停止所有闪烁动画
  652. */
  653. private stopAllBlinking() {
  654. for (const node of this.blinkingNodes) {
  655. if (node['_blinkTween']) {
  656. node['_blinkTween'].stop();
  657. node['_blinkTween'] = null;
  658. }
  659. node.setScale(1, 1, 1);
  660. }
  661. this.blinkingNodes.clear();
  662. }
  663. /**
  664. * 当钻石数量变化时调用,更新闪烁状态
  665. */
  666. public onDiamondsChanged() {
  667. this.updateBlinkingNodes();
  668. }
  669. /**
  670. * 货币变化事件处理
  671. */
  672. private onCurrencyChanged() {
  673. console.log('[SkillNodeGenerator] 检测到货币变化,更新钻石UI和闪烁状态');
  674. this.updateDiamondUI();
  675. }
  676. /**
  677. * 组件销毁时清理资源
  678. */
  679. onDestroy() {
  680. // 取消事件监听
  681. EventBus.getInstance().off(GameEvents.CURRENCY_CHANGED, this.onCurrencyChanged, this);
  682. // 停止所有闪烁动画
  683. this.stopAllBlinking();
  684. }
  685. }