SkillSelectionController.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. import { _decorator, Component, Node, Button, find, Label, Sprite, Vec3, CCFloat, resources, JsonAsset, SpriteFrame } from 'cc';
  2. import { GameManager } from '../../LevelSystem/GameManager';
  3. import { InGameManager } from '../../LevelSystem/IN_game';
  4. import { SkillButtonAnimator } from './SkillButtonAnimator';
  5. // GamePause已通过事件系统解耦,不再需要直接导入
  6. import { SkillManager, SkillData } from './SkillManager';
  7. import { Wall } from '../Wall';
  8. import { EnemyController } from '../EnemyController';
  9. import EventBus, { GameEvents } from '../../Core/EventBus';
  10. const { ccclass, property } = _decorator;
  11. // 移除本地SkillData声明,直接用SkillManager中的SkillData
  12. interface SkillConfig {
  13. skills: SkillData[];
  14. }
  15. /**
  16. * SkillSelectionController
  17. * 放在 Canvas/SelectSkillUI 节点上。
  18. * 负责监听技能按钮点击,随机选择技能显示,并按顺序播放缩小动画,最后关闭整个 SelectSkillUI。
  19. */
  20. @ccclass('SkillSelectionController')
  21. export class SkillSelectionController extends Component {
  22. @property({ type: [Node], tooltip: '技能按钮节点列表,留空则自动从 SkillsContainer 获取' })
  23. public skillButtons: Node[] = [];
  24. @property({ type: CCFloat, tooltip: '收缩动画时长' })
  25. public shrinkDuration: number = 0.3;
  26. // 防止重复点击标记
  27. private _clicked = false;
  28. // 技能配置数据
  29. private _skillConfig: SkillConfig | null = null;
  30. // 当前显示的技能数据
  31. private _currentSkills: SkillData[] = [];
  32. start() {
  33. this.loadSkillConfig();
  34. }
  35. /**
  36. * 当技能选择UI激活时暂停游戏
  37. */
  38. onEnable() {
  39. // 通过事件系统暂停游戏
  40. const eventBus = EventBus.getInstance();
  41. eventBus.emit(GameEvents.GAME_PAUSE);
  42. }
  43. /**
  44. * 当技能选择UI关闭时的处理
  45. * 注意:这里不直接恢复游戏,因为可能需要显示方块选择UI
  46. */
  47. onDisable() {
  48. console.log('[SkillSelectionController] 技能选择UI关闭');
  49. // 不在这里直接恢复游戏,因为可能需要显示方块选择UI
  50. // 游戏恢复的逻辑在onSkillSelected方法中处理
  51. }
  52. /**
  53. * 加载技能配置
  54. */
  55. private loadSkillConfig() {
  56. resources.load('data/skill', JsonAsset, (err, jsonAsset: JsonAsset) => {
  57. if (err) {
  58. console.error('加载技能配置失败:', err);
  59. return;
  60. }
  61. this._skillConfig = jsonAsset.json as SkillConfig;
  62. // 初始化技能管理器
  63. const skillManager = SkillManager.getInstance();
  64. if (skillManager) {
  65. skillManager.initSkills(this._skillConfig.skills);
  66. }
  67. this.setupSkillButtons();
  68. this.randomizeSkills();
  69. });
  70. }
  71. private setupSkillButtons() {
  72. if (this.skillButtons.length === 0) {
  73. const container = this.node.getChildByName('SkillsContainer');
  74. if (container) {
  75. this.skillButtons = container.children.slice();
  76. }
  77. }
  78. this.skillButtons.forEach(btnNode => {
  79. const btn = btnNode.getComponent(Button);
  80. if (btn) {
  81. btn.node.on(Button.EventType.CLICK, () => this.onSkillSelected(btnNode), this);
  82. }
  83. });
  84. }
  85. /**
  86. * 随机选择3个技能显示
  87. */
  88. private randomizeSkills() {
  89. if (!this._skillConfig || this._skillConfig.skills.length < 3) {
  90. console.error('技能配置不足,无法随机选择');
  91. return;
  92. }
  93. // 从6个技能中随机选择3个
  94. const allSkills = [...this._skillConfig.skills];
  95. this._currentSkills = [];
  96. for (let i = 0; i < 3 && allSkills.length > 0; i++) {
  97. const randomIndex = Math.floor(Math.random() * allSkills.length);
  98. this._currentSkills.push(allSkills.splice(randomIndex, 1)[0]);
  99. }
  100. // 更新UI显示
  101. this.updateSkillUI();
  102. }
  103. /**
  104. * 更新技能UI显示
  105. */
  106. private updateSkillUI() {
  107. this.skillButtons.forEach((btnNode, index) => {
  108. if (index < this._currentSkills.length) {
  109. const skillData = this._currentSkills[index];
  110. // 更新技能图标
  111. const skillSprite = btnNode.getChildByName('SkillSprite');
  112. if (skillSprite) {
  113. const sprite = skillSprite.getComponent(Sprite);
  114. if (sprite) {
  115. // 加载技能图标
  116. resources.load(skillData.icon, SpriteFrame, (err, spriteFrame) => {
  117. if (!err && spriteFrame && sprite) {
  118. sprite.spriteFrame = spriteFrame;
  119. }
  120. });
  121. }
  122. }
  123. // 更新技能名称
  124. const skillName = btnNode.getChildByName('SkillName');
  125. if (skillName) {
  126. const label = skillName.getComponent(Label);
  127. if (label) {
  128. label.string = skillData.name;
  129. }
  130. }
  131. // 更新技能介绍
  132. const skillIntroduce = btnNode.getChildByName('SkillIntroduce');
  133. if (skillIntroduce) {
  134. const label = skillIntroduce.getComponent(Label);
  135. if (label) {
  136. label.string = skillData.description;
  137. }
  138. }
  139. // 设置星级显示
  140. const animator = btnNode.getComponent(SkillButtonAnimator);
  141. if (animator) {
  142. animator.setSkillLevel(skillData.currentLevel);
  143. }
  144. btnNode.active = true;
  145. } else {
  146. btnNode.active = false;
  147. }
  148. });
  149. }
  150. /**
  151. * 玩家选择某技能按钮
  152. */
  153. private onSkillSelected(selectedBtn: Node) {
  154. if (this._clicked) return;
  155. this._clicked = true;
  156. // 获取选中的技能
  157. const btnIndex = this.skillButtons.indexOf(selectedBtn);
  158. if (btnIndex >= 0 && btnIndex < this._currentSkills.length) {
  159. const selectedSkill = this._currentSkills[btnIndex];
  160. // 通过技能管理器升级技能
  161. const skillManager = SkillManager.getInstance();
  162. if (skillManager) {
  163. skillManager.upgradeSkill(selectedSkill.id);
  164. // 如果是治疗技能,立即应用治疗效果
  165. if (selectedSkill.id === 'heal') {
  166. this.applyHealEffect(selectedSkill.id);
  167. }
  168. }
  169. }
  170. // 禁用所有按钮交互
  171. this.skillButtons.forEach(btn => {
  172. const b = btn.getComponent(Button);
  173. if (b) b.interactable = false;
  174. });
  175. const otherBtns = this.skillButtons.filter(btn => btn !== selectedBtn);
  176. let finishedOthers = 0;
  177. // 所有其他按钮完成动画后,再收缩选中按钮
  178. const onOtherFinished = () => {
  179. finishedOthers++;
  180. if (finishedOthers >= otherBtns.length) {
  181. // 收缩选中按钮
  182. const selAnim = selectedBtn.getComponent(SkillButtonAnimator);
  183. selAnim?.playShrink(this.shrinkDuration, undefined, () => {
  184. const igm = this.getInGameManager();
  185. if (igm) {
  186. igm.resetEnergyValue();
  187. // 检查是否需要弹出方块选择UI
  188. if (igm.shouldShowBlockSelection()) {
  189. // 需要显示方块选择UI,保持游戏暂停状态
  190. console.log('[SkillSelectionController] 技能选择完成,即将显示方块选择UI,保持游戏暂停');
  191. // 关闭技能UI
  192. this.node.active = false;
  193. // 立即显示方块选择UI,不恢复游戏
  194. igm.showBlockSelectionAfterSkill();
  195. } else {
  196. // 不需要显示方块选择UI,关闭技能UI并恢复游戏
  197. console.log('[SkillSelectionController] 技能选择完成,没有方块选择UI,恢复游戏');
  198. this.node.active = false;
  199. // 通过事件系统恢复游戏
  200. const eventBus = EventBus.getInstance();
  201. eventBus.emit(GameEvents.GAME_RESUME);
  202. }
  203. } else {
  204. // 没有InGameManager,直接关闭UI
  205. this.node.active = false;
  206. }
  207. // 关闭后立刻重置UI
  208. this.resetUI();
  209. });
  210. }
  211. };
  212. // 播放其他按钮动画
  213. const targetPos = selectedBtn.position.clone();
  214. otherBtns.forEach(btn => {
  215. const anim = btn.getComponent(SkillButtonAnimator);
  216. anim?.playShrink(this.shrinkDuration, targetPos, onOtherFinished);
  217. });
  218. }
  219. /**
  220. * 重置 UI 状态,供下次开启时使用
  221. */
  222. public resetUI() {
  223. this._clicked = false;
  224. // 重新启用所有按钮交互
  225. this.skillButtons.forEach(btn => {
  226. const b = btn.getComponent(Button);
  227. if (b) b.interactable = true;
  228. // 重置按钮动画状态
  229. const anim = btn.getComponent(SkillButtonAnimator);
  230. anim?.resetState();
  231. });
  232. // 重新随机选择技能
  233. this.randomizeSkills();
  234. }
  235. /**
  236. * 应用治疗技能效果
  237. */
  238. private applyHealEffect(skillId: string) {
  239. const skillManager = SkillManager.getInstance();
  240. if (!skillManager) return;
  241. const skillLevel = skillManager.getSkillLevel(skillId);
  242. if (skillLevel <= 0) return;
  243. // 查找墙体组件
  244. const wall = this.findWallComponent();
  245. if (!wall) {
  246. console.warn('[SkillSelectionController] 未找到墙体组件,无法应用治疗效果');
  247. return;
  248. }
  249. // 计算治疗量:使用SkillManager的计算方法
  250. const maxHealth = wall.getMaxHealth();
  251. const healAmount = SkillManager.calculateInstantHeal(maxHealth, skillLevel);
  252. // 应用治疗效果
  253. wall.heal(healAmount);
  254. // 由于治疗技能会增加最大血量,需要更新血量显示
  255. wall.updateHealthDisplay();
  256. console.log(`[SkillSelectionController] 应用治疗效果: +${healAmount} 血量,技能等级: ${skillLevel}`);
  257. }
  258. /**
  259. * 查找墙体组件
  260. */
  261. private findWallComponent(): Wall | null {
  262. // 尝试从EnemyController获取墙体组件
  263. const enemyController = EnemyController.getInstance();
  264. if (enemyController) {
  265. // 通过topFenceNode或bottomFenceNode查找Wall组件
  266. if (enemyController.topFenceNode) {
  267. const wall = enemyController.topFenceNode.getComponent(Wall);
  268. if (wall) return wall;
  269. }
  270. if (enemyController.bottomFenceNode) {
  271. const wall = enemyController.bottomFenceNode.getComponent(Wall);
  272. if (wall) return wall;
  273. }
  274. }
  275. // 备用方案:直接在场景中查找
  276. const wallNode = find('Canvas/GameLevelUI/GameArea/TopFence') ||
  277. find('Canvas/GameLevelUI/GameArea/BottomFence') ||
  278. find('Canvas/GameLevelUI/Wall') ||
  279. find('Canvas/Wall');
  280. if (wallNode) {
  281. return wallNode.getComponent(Wall);
  282. }
  283. return null;
  284. }
  285. private getGameManager(): GameManager | null {
  286. const gmNode = find('Canvas/GameLevelUI/GameManager');
  287. return gmNode?.getComponent(GameManager) || null;
  288. }
  289. private getInGameManager(): InGameManager | null {
  290. const gm = this.getGameManager();
  291. return gm ? gm.getInGameManager() : null;
  292. }
  293. }