EnemyController.ts 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432
  1. import { _decorator, Node, Label, Vec3, Prefab, find, UITransform, resources, RigidBody2D, instantiate } from 'cc';
  2. import { sp } from 'cc';
  3. import { ConfigManager, EnemyConfig } from '../Core/ConfigManager';
  4. import { EnemyComponent } from '../CombatSystem/EnemyComponent';
  5. import { EnemyInstance } from './EnemyInstance';
  6. import { BaseSingleton } from '../Core/BaseSingleton';
  7. import { SaveDataManager } from '../LevelSystem/SaveDataManager';
  8. import { LevelConfigManager } from '../LevelSystem/LevelConfigManager';
  9. import EventBus, { GameEvents } from '../Core/EventBus';
  10. import { Wall } from './Wall';
  11. import { BurnEffect } from './BulletEffects/BurnEffect';
  12. import { Audio } from '../AudioManager/AudioManager';
  13. import { BundleLoader } from '../Core/BundleLoader';
  14. const { ccclass, property } = _decorator;
  15. @ccclass('EnemyController')
  16. export class EnemyController extends BaseSingleton {
  17. // 仅类型声明,实例由 BaseSingleton 维护
  18. public static _instance: EnemyController;
  19. // 敌人预制体
  20. @property({
  21. type: Prefab,
  22. tooltip: '拖拽Enemy预制体到这里'
  23. })
  24. public enemyPrefab: Prefab = null;
  25. // 敌人容器节点
  26. @property({
  27. type: Node,
  28. tooltip: '拖拽enemyContainer节点到这里(Canvas/GameLevelUI/enemyContainer)'
  29. })
  30. public enemyContainer: Node = null;
  31. // 金币预制体
  32. @property({ type: Prefab, tooltip: '金币预制体 CoinDrop' })
  33. public coinPrefab: Prefab = null;
  34. // 移除Toast预制体属性,改用事件机制
  35. // @property({
  36. // type: Prefab,
  37. // tooltip: 'Toast预制体,用于显示波次提示'
  38. // })
  39. // public toastPrefab: Prefab = null;
  40. // === 生成 & 属性参数(保留需要可在内部自行设定,Inspector 不再显示) ===
  41. private spawnInterval: number = 3;
  42. // === 默认数值(当配置文件尚未加载时使用) ===
  43. private defaultEnemySpeed: number = 50;
  44. private defaultAttackPower: number = 20; // 对应combat.attackDamage
  45. private defaultHealth: number = 10; // 对应stats.health
  46. @property({ type: Node, tooltip: '拖拽 TopFence 节点到这里' })
  47. public topFenceNode: Node = null;
  48. @property({ type: Node, tooltip: '拖拽 BottomFence 节点到这里' })
  49. public bottomFenceNode: Node = null;
  50. @property({
  51. type: Node,
  52. tooltip: '拖拽 Line5 节点到这里 (Canvas/GameLevelUI/enemyContainer/Line5)'
  53. })
  54. public line5Node: Node = null;
  55. @property({
  56. type: Node,
  57. tooltip: '拖拽 Line6 节点到这里 (Canvas/GameLevelUI/enemyContainer/Line6)'
  58. })
  59. public line6Node: Node = null;
  60. // 关卡配置管理器节点
  61. @property({
  62. type: Node,
  63. tooltip: '拖拽Canvas/LevelConfigManager节点到这里,用于获取LevelConfigManager组件'
  64. })
  65. public levelConfigManagerNode: Node = null;
  66. // 主墙体组件
  67. private wallComponent: Wall = null;
  68. // 游戏区域边界 - 改为public,让敌人实例可以访问
  69. public gameBounds = {
  70. left: 0,
  71. right: 0,
  72. top: 0,
  73. bottom: 0
  74. };
  75. // 活跃的敌人列表
  76. private activeEnemies: Node[] = [];
  77. // 游戏是否已开始
  78. private gameStarted: boolean = false;
  79. // 墙体节点
  80. private wallNodes: Node[] = [];
  81. // 是否正在清理敌人(用于阻止清理时触发游戏胜利判断)
  82. private isClearing: boolean = false;
  83. // 配置管理器
  84. private configManager: ConfigManager = null;
  85. // 关卡配置管理器
  86. private levelConfigManager: LevelConfigManager = null;
  87. // 当前关卡血量倍率
  88. private currentHealthMultiplier: number = 1.0;
  89. private currentWaveHealthMultiplier: number = 1.0; // 当前波次的血量系数
  90. @property({
  91. type: Node,
  92. tooltip: '敌人数量显示节点 (EnemyNumber)'
  93. })
  94. public enemyCountLabelNode: Node = null;
  95. @property({
  96. type: Node,
  97. tooltip: '当前波数显示Label (WaveNumber-前)'
  98. })
  99. public currentWaveNumberLabelNode: Node = null;
  100. @property({
  101. type: Node,
  102. tooltip: '总波数显示Label (WaveNumber-后)'
  103. })
  104. public totalWaveNumberLabelNode: Node = null;
  105. // 当前波次敌人数据
  106. private totalWaves: number = 1;
  107. private currentWave: number = 1;
  108. private currentWaveTotalEnemies: number = 0;
  109. private currentWaveEnemiesKilled: number = 0;
  110. private currentWaveEnemiesSpawned: number = 0; // 当前波次已生成的敌人数量
  111. private currentWaveEnemyConfigs: any[] = []; // 当前波次的敌人配置列表
  112. // 暂停前保存的敌人状态
  113. private pausedEnemyStates: Map<Node, {
  114. velocity: any,
  115. angularVelocity: number,
  116. isMoving: boolean,
  117. attackTimer: number
  118. }> = new Map();
  119. /**
  120. * BaseSingleton 首次实例化回调
  121. */
  122. protected init() {
  123. // 获取配置管理器实例
  124. this.configManager = ConfigManager.getInstance();
  125. // 获取关卡配置管理器实例
  126. if (this.levelConfigManagerNode) {
  127. this.levelConfigManager = this.levelConfigManagerNode.getComponent(LevelConfigManager) || null;
  128. if (!this.levelConfigManager) {
  129. // LevelConfigManager节点上未找到LevelConfigManager组件
  130. }
  131. } else {
  132. // levelConfigManagerNode未通过装饰器绑定,请在编辑器中拖拽Canvas/LevelConfigManager节点
  133. this.levelConfigManager = null;
  134. }
  135. // 如果没有指定enemyContainer,尝试找到它
  136. if (!this.enemyContainer) {
  137. this.enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
  138. if (!this.enemyContainer) {
  139. // 找不到enemyContainer节点,将尝试创建
  140. }
  141. }
  142. // 获取游戏区域边界
  143. this.calculateGameBounds();
  144. // 查找墙体节点和组件
  145. this.findWallNodes();
  146. // 直接通过拖拽节点获取 Wall 组件
  147. if (this.topFenceNode && this.topFenceNode.getComponent(Wall)) {
  148. this.wallComponent = this.topFenceNode.getComponent(Wall);
  149. } else if (this.bottomFenceNode && this.bottomFenceNode.getComponent(Wall)) {
  150. this.wallComponent = this.bottomFenceNode.getComponent(Wall);
  151. } else {
  152. // 未找到墙体组件,将无法处理墙体伤害
  153. }
  154. // 确保enemyContainer节点存在
  155. this.ensureEnemyContainer();
  156. // UI节点已通过装饰器拖拽绑定,无需find查找
  157. if (!this.enemyCountLabelNode) {
  158. // enemyCountLabelNode 未通过装饰器绑定,请在编辑器中拖拽 Canvas-001/TopArea/EnemyNode/EnemyNumber 节点
  159. }
  160. if (!this.currentWaveNumberLabelNode) {
  161. // currentWaveNumberLabelNode 未通过装饰器绑定,请在编辑器中拖拽 Canvas-001/TopArea/WaveInfo/WaveNumber-前 节点
  162. }
  163. if (!this.totalWaveNumberLabelNode) {
  164. // totalWaveNumberLabelNode 未通过装饰器绑定,请在编辑器中拖拽 Canvas-001/TopArea/WaveInfo/WaveNumber-后 节点
  165. }
  166. // 初始化敌人数量显示
  167. this.updateEnemyCountLabel();
  168. // 监听游戏事件
  169. this.setupEventListeners();
  170. }
  171. /**
  172. * 设置事件监听器
  173. */
  174. private setupEventListeners() {
  175. const eventBus = EventBus.getInstance();
  176. // 监听暂停事件
  177. eventBus.on(GameEvents.GAME_PAUSE, this.onGamePauseEvent, this);
  178. eventBus.on(GameEvents.GAME_PAUSE_SKILL_SELECTION, this.onGamePauseEvent, this);
  179. eventBus.on(GameEvents.GAME_PAUSE_BLOCK_SELECTION, this.onGamePauseEvent, this);
  180. // 监听游戏恢复事件
  181. eventBus.on(GameEvents.GAME_RESUME, this.onGameResumeEvent, this);
  182. eventBus.on(GameEvents.GAME_RESUME_SKILL_SELECTION, this.onGameResumeSkillSelectionEvent, this);
  183. eventBus.on(GameEvents.GAME_RESUME_BLOCK_SELECTION, this.onGameResumeBlockSelectionEvent, this);
  184. // 监听游戏成功/失败事件
  185. eventBus.on(GameEvents.GAME_SUCCESS, this.onGameEndEvent, this);
  186. eventBus.on(GameEvents.GAME_DEFEAT, this.onGameDefeatEvent, this);
  187. // 监听敌人相关事件
  188. eventBus.on(GameEvents.ENEMY_UPDATE_COUNT, this.onUpdateEnemyCountEvent, this);
  189. eventBus.on(GameEvents.ENEMY_START_WAVE, this.onStartWaveEvent, this);
  190. eventBus.on(GameEvents.ENEMY_START_GAME, this.onStartGameEvent, this);
  191. eventBus.on(GameEvents.ENEMY_SHOW_START_WAVE_PROMPT, this.onShowStartWavePromptEvent, this);
  192. // 监听子弹发射检查事件
  193. eventBus.on(GameEvents.BALL_FIRE_BULLET, this.onBallFireBulletEvent, this);
  194. // 监听获取最近敌人事件
  195. eventBus.on(GameEvents.ENEMY_GET_NEAREST, this.onGetNearestEnemyEvent, this);
  196. // 监听重置敌人控制器事件
  197. eventBus.on(GameEvents.RESET_ENEMY_CONTROLLER, this.onResetEnemyControllerEvent, this);
  198. // 监听重置相关事件
  199. eventBus.on(GameEvents.CLEAR_ALL_GAME_OBJECTS, this.onClearAllGameObjectsEvent, this);
  200. eventBus.on(GameEvents.CLEAR_ALL_ENEMIES, this.onClearAllEnemiesEvent, this);
  201. eventBus.on(GameEvents.RESET_ENEMY_CONTROLLER, this.onResetEnemyControllerEvent, this);
  202. // 监听伤害事件
  203. eventBus.on(GameEvents.APPLY_DAMAGE_TO_ENEMY, this.onApplyDamageToEnemyEvent, this);
  204. // 监听子弹击中敌人事件
  205. eventBus.on(GameEvents.BULLET_HIT_ENEMY, this.onBulletHitEnemyEvent, this);
  206. }
  207. /**
  208. * 获取当前关卡的血量倍率
  209. */
  210. private async getCurrentHealthMultiplier(): Promise<number> {
  211. // 优先使用当前波次的血量系数
  212. if (this.currentWaveHealthMultiplier > 0) {
  213. return this.currentWaveHealthMultiplier;
  214. }
  215. if (!this.levelConfigManager) {
  216. return 1.0;
  217. }
  218. try {
  219. const saveDataManager = SaveDataManager.getInstance();
  220. const currentLevel = saveDataManager ? saveDataManager.getCurrentLevel() : 1;
  221. const levelConfig = await this.levelConfigManager.getLevelConfig(currentLevel);
  222. if (levelConfig && levelConfig.levelSettings && levelConfig.levelSettings.healthMultiplier) {
  223. return levelConfig.levelSettings.healthMultiplier;
  224. }
  225. } catch (error) {
  226. console.warn('[EnemyController] 获取关卡血量倍率失败:', error);
  227. }
  228. return 1.0;
  229. }
  230. /**
  231. * 处理游戏暂停事件
  232. */
  233. private onGamePauseEvent() {
  234. console.log('[EnemyController] 接收到游戏暂停事件,暂停所有敌人');
  235. this.pauseAllEnemies();
  236. this.pauseSpawning();
  237. }
  238. /**
  239. * 处理游戏恢复事件
  240. */
  241. private onGameResumeEvent() {
  242. console.log('[EnemyController] 接收到游戏恢复事件,恢复所有敌人');
  243. this.resumeAllEnemies();
  244. // 通过事件检查游戏状态,决定是否恢复敌人生成
  245. this.resumeSpawning();
  246. }
  247. /**
  248. * 处理技能选择恢复事件
  249. */
  250. private onGameResumeSkillSelectionEvent() {
  251. console.log('[EnemyController] 接收到技能选择恢复事件,恢复所有敌人');
  252. this.resumeAllEnemies();
  253. this.resumeSpawning();
  254. }
  255. /**
  256. * 处理底板选择恢复事件
  257. */
  258. private onGameResumeBlockSelectionEvent() {
  259. console.log('[EnemyController] 接收到底板选择恢复事件,恢复所有敌人');
  260. this.resumeAllEnemies();
  261. this.resumeSpawning();
  262. }
  263. /**
  264. * 处理游戏结束事件
  265. */
  266. private onGameEndEvent() {
  267. this.stopGame(false); // 停止游戏但不清除敌人
  268. }
  269. /**
  270. * 处理游戏失败事件
  271. */
  272. private onGameDefeatEvent() {
  273. this.stopGame(true); // 停止游戏并清除所有敌人
  274. //是否是应该此时调用onGameDefeat()方法?
  275. }
  276. /**
  277. * 处理清除所有游戏对象事件
  278. */
  279. private onClearAllGameObjectsEvent() {
  280. this.clearAllEnemies(false); // 清除敌人但不触发事件
  281. }
  282. /**
  283. * 处理清除所有敌人事件
  284. * 专门响应CLEAR_ALL_ENEMIES事件,用于游戏结束时的敌人清理
  285. */
  286. private onClearAllEnemiesEvent() {
  287. console.log('[EnemyController] 接收到CLEAR_ALL_ENEMIES事件,开始清理所有敌人');
  288. this.clearAllEnemies(false); // 清除敌人但不触发事件,避免循环
  289. }
  290. /**
  291. * 处理重置敌人控制器事件
  292. */
  293. private onResetEnemyControllerEvent() {
  294. this.resetToInitialState();
  295. }
  296. /**
  297. * 处理子弹发射检查事件
  298. */
  299. private onBallFireBulletEvent(data: { canFire: (value: boolean) => void }) {
  300. // 检查是否有活跃敌人
  301. const hasActiveEnemies = this.hasActiveEnemies();
  302. data.canFire(hasActiveEnemies);
  303. }
  304. /**
  305. * 处理获取最近敌人事件
  306. */
  307. private onGetNearestEnemyEvent(data: { position: Vec3, callback: (enemy: Node | null) => void }) {
  308. const { position, callback } = data;
  309. if (callback && typeof callback === 'function') {
  310. const nearestEnemy = this.getNearestEnemy(position);
  311. callback(nearestEnemy);
  312. }
  313. }
  314. /**
  315. * 处理对敌人造成伤害事件
  316. */
  317. private onApplyDamageToEnemyEvent(data: { enemyNode: Node, damage: number, isCritical: boolean, source: string }) {
  318. console.log(`[EnemyController] 接收到伤害事件 - 敌人: ${data.enemyNode.name}, 伤害: ${data.damage}, 暴击: ${data.isCritical}, 来源: ${data.source}`);
  319. if (!data.enemyNode || !data.enemyNode.isValid) {
  320. console.log(`[EnemyController] 敌人节点无效,跳过伤害处理`);
  321. return;
  322. }
  323. // 调用现有的damageEnemy方法
  324. this.damageEnemy(data.enemyNode, data.damage, data.isCritical);
  325. }
  326. /**
  327. * 处理子弹击中敌人事件
  328. */
  329. private onBulletHitEnemyEvent(data: { enemyNode: Node, damage: number, isCritical: boolean, source: string }) {
  330. console.log(`[EnemyController] 接收到子弹击中敌人事件 - 敌人: ${data.enemyNode.name}, 伤害: ${data.damage}, 暴击: ${data.isCritical}, 来源: ${data.source}`);
  331. if (!data.enemyNode || !data.enemyNode.isValid) {
  332. console.log(`[EnemyController] 敌人节点无效,跳过伤害处理`);
  333. return;
  334. }
  335. // 调用现有的damageEnemy方法
  336. this.damageEnemy(data.enemyNode, data.damage, data.isCritical);
  337. }
  338. /**
  339. * 暂停所有敌人
  340. */
  341. private pauseAllEnemies(): void {
  342. const activeEnemies = this.getActiveEnemies();
  343. console.log(`[EnemyController] 暂停 ${activeEnemies.length} 个敌人`);
  344. for (const enemy of activeEnemies) {
  345. if (!enemy || !enemy.isValid) continue;
  346. // 保存敌人状态 - 从EnemySprite子节点获取RigidBody2D
  347. const enemySprite = enemy.getChildByName('EnemySprite');
  348. if (!enemySprite) {
  349. console.error('[EnemyController] pauseAllEnemies: 未找到EnemySprite子节点');
  350. continue;
  351. }
  352. const rigidBody = enemySprite.getComponent(RigidBody2D);
  353. if (rigidBody) {
  354. this.pausedEnemyStates.set(enemy, {
  355. velocity: rigidBody.linearVelocity.clone(),
  356. angularVelocity: rigidBody.angularVelocity,
  357. isMoving: true,
  358. attackTimer: 0 // 可以扩展保存攻击计时器
  359. });
  360. // 停止敌人运动
  361. rigidBody.linearVelocity.set(0, 0);
  362. rigidBody.angularVelocity = 0;
  363. rigidBody.sleep();
  364. }
  365. // 暂停EnemyInstance组件
  366. const enemyInstance = enemy.getComponent('EnemyInstance');
  367. if (enemyInstance && typeof (enemyInstance as any).pause === 'function') {
  368. (enemyInstance as any).pause();
  369. }
  370. // 暂停敌人AI组件(如果有的话)
  371. const enemyAI = enemy.getComponent('EnemyAI');
  372. if (enemyAI && typeof (enemyAI as any).pause === 'function') {
  373. (enemyAI as any).pause();
  374. }
  375. // 暂停敌人动画(如果有的话)
  376. const animation = enemy.getComponent('Animation');
  377. if (animation && typeof (animation as any).pause === 'function') {
  378. (animation as any).pause();
  379. }
  380. }
  381. }
  382. /**
  383. * 恢复所有敌人
  384. */
  385. private resumeAllEnemies(): void {
  386. const activeEnemies = this.getActiveEnemies();
  387. for (const enemy of activeEnemies) {
  388. if (!enemy || !enemy.isValid) continue;
  389. const savedState = this.pausedEnemyStates.get(enemy);
  390. if (savedState) {
  391. // 从EnemySprite子节点获取RigidBody2D
  392. const enemySprite = enemy.getChildByName('EnemySprite');
  393. if (!enemySprite) {
  394. console.error('[EnemyController] resumeAllEnemies: 未找到EnemySprite子节点');
  395. continue;
  396. }
  397. const rigidBody = enemySprite.getComponent(RigidBody2D);
  398. if (rigidBody) {
  399. // 恢复敌人运动
  400. rigidBody.wakeUp();
  401. rigidBody.linearVelocity = savedState.velocity;
  402. rigidBody.angularVelocity = savedState.angularVelocity;
  403. }
  404. }
  405. // 恢复EnemyInstance组件
  406. const enemyInstance = enemy.getComponent('EnemyInstance');
  407. if (enemyInstance && typeof (enemyInstance as any).resume === 'function') {
  408. (enemyInstance as any).resume();
  409. }
  410. // 恢复敌人AI组件
  411. const enemyAI = enemy.getComponent('EnemyAI');
  412. if (enemyAI && typeof (enemyAI as any).resume === 'function') {
  413. (enemyAI as any).resume();
  414. }
  415. // 恢复敌人动画
  416. const animation = enemy.getComponent('Animation');
  417. if (animation && typeof (animation as any).resume === 'function') {
  418. (animation as any).resume();
  419. }
  420. }
  421. // 清空保存的状态
  422. this.pausedEnemyStates.clear();
  423. }
  424. // 计算游戏区域边界
  425. private calculateGameBounds() {
  426. const gameArea = find('Canvas/GameLevelUI/GameArea');
  427. if (!gameArea) {
  428. return;
  429. }
  430. const uiTransform = gameArea.getComponent(UITransform);
  431. if (!uiTransform) {
  432. return;
  433. }
  434. const worldPos = gameArea.worldPosition;
  435. const width = uiTransform.width;
  436. const height = uiTransform.height;
  437. this.gameBounds = {
  438. left: worldPos.x - width / 2,
  439. right: worldPos.x + width / 2,
  440. top: worldPos.y + height / 2,
  441. bottom: worldPos.y - height / 2
  442. };
  443. }
  444. // 查找墙体节点
  445. private findWallNodes() {
  446. const gameArea = find('Canvas/GameLevelUI/GameArea');
  447. if (gameArea) {
  448. this.wallNodes = [];
  449. for (let i = 0; i < gameArea.children.length; i++) {
  450. const child = gameArea.children[i];
  451. if (child.name.includes('Wall') || child.name.includes('wall') || child.name.includes('墙')) {
  452. this.wallNodes.push(child);
  453. }
  454. }
  455. }
  456. }
  457. /**
  458. * 查找墙体组件
  459. */
  460. private findWallComponent() {
  461. // 查找墙体节点上的Wall组件
  462. for (const wallNode of this.wallNodes) {
  463. this.wallComponent = wallNode.getComponent(Wall);
  464. if (this.wallComponent) {
  465. break;
  466. }
  467. }
  468. }
  469. // 移除 initWallHealthDisplay 和 updateWallHealthDisplay 方法,这些现在由Wall组件处理
  470. // 确保enemyContainer节点存在
  471. ensureEnemyContainer() {
  472. // 如果已经通过拖拽设置了节点,直接使用
  473. if (this.enemyContainer && this.enemyContainer.isValid) {
  474. return;
  475. }
  476. // 尝试查找节点
  477. this.enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
  478. if (this.enemyContainer) {
  479. return;
  480. }
  481. // 如果找不到,创建新节点
  482. const gameLevelUI = find('Canvas/GameLevelUI');
  483. if (!gameLevelUI) {
  484. console.error('找不到GameLevelUI节点,无法创建enemyContainer');
  485. return;
  486. }
  487. this.enemyContainer = new Node('enemyContainer');
  488. gameLevelUI.addChild(this.enemyContainer);
  489. if (!this.enemyContainer.getComponent(UITransform)) {
  490. this.enemyContainer.addComponent(UITransform);
  491. }
  492. }
  493. // 游戏开始
  494. startGame() {
  495. // 游戏状态检查现在通过事件系统处理
  496. this.gameStarted = true;
  497. // 确保enemyContainer节点存在
  498. this.ensureEnemyContainer();
  499. // 确保先取消之前的定时器,避免重复调度
  500. this.unschedule(this.spawnEnemy);
  501. // 播放游戏开始音效
  502. Audio.playUISound('data/弹球音效/start zombie');
  503. // 立即生成第一个敌人,与音效同步
  504. this.spawnEnemy();
  505. // 然后按间隔生成后续敌人
  506. this.schedule(this.spawnEnemy, this.spawnInterval);
  507. // 触发敌人生成开始事件
  508. const eventBus = EventBus.getInstance();
  509. eventBus.emit(GameEvents.ENEMY_SPAWNING_STARTED);
  510. }
  511. // 游戏结束
  512. stopGame(clearEnemies: boolean = true) {
  513. this.gameStarted = false;
  514. // 停止生成敌人
  515. this.unschedule(this.spawnEnemy);
  516. // 触发敌人生成停止事件
  517. const eventBus = EventBus.getInstance();
  518. eventBus.emit(GameEvents.ENEMY_SPAWNING_STOPPED);
  519. // 只有在指定时才清除所有敌人
  520. if (clearEnemies) {
  521. // 清除敌人,在重置状态时不触发事件,避免错误的游戏成功判定
  522. this.clearAllEnemies(false);
  523. }
  524. }
  525. // 生成敌人
  526. async spawnEnemy() {
  527. if (!this.gameStarted || !this.enemyPrefab) {
  528. return;
  529. }
  530. // 游戏状态检查现在通过事件系统处理
  531. // 检查是否已达到当前波次的敌人生成上限
  532. if (this.currentWaveEnemiesSpawned >= this.currentWaveTotalEnemies) {
  533. this.unschedule(this.spawnEnemy); // 停止定时生成
  534. return;
  535. }
  536. // 随机选择从Line1或Line2生成
  537. const fromTop = Math.random() > 0.5;
  538. // 实例化敌人
  539. const enemy = instantiate(this.enemyPrefab) as Node;
  540. enemy.name = 'Enemy'; // 确保敌人节点名称为Enemy
  541. // 添加到场景中
  542. const enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
  543. if (!enemyContainer) {
  544. return;
  545. }
  546. enemyContainer.addChild(enemy);
  547. // 根据随机选择生成在对应线上的随机位置
  548. const lineName = fromTop ? 'Line1' : 'Line2';
  549. const lineNode = enemyContainer.getChildByName(lineName);
  550. if (!lineNode) {
  551. console.warn(`[EnemyController] 未找到 ${lineName} 节点,取消本次敌人生成`);
  552. enemy.destroy();
  553. return;
  554. }
  555. // 在对应线上随机 X 坐标
  556. const spawnWorldX = this.gameBounds.left + Math.random() * (this.gameBounds.right - this.gameBounds.left);
  557. const spawnWorldY = lineNode.worldPosition.y;
  558. // 根据生成位置计算漂移目标位置
  559. let driftWorldX = spawnWorldX;
  560. let driftWorldY = spawnWorldY;
  561. // 获取漂移目标线节点
  562. let targetLineNode: Node = null;
  563. if (fromTop) {
  564. // 从Line1生成,漂移到Line5
  565. targetLineNode = this.line5Node || enemyContainer.getChildByName('Line5');
  566. console.log(`[EnemyController] 从Line1生成敌人,查找Line5节点: this.line5Node=${!!this.line5Node}, 通过名称查找=${!!enemyContainer.getChildByName('Line5')}`);
  567. } else {
  568. // 从Line2生成,漂移到Line6
  569. targetLineNode = this.line6Node || enemyContainer.getChildByName('Line6');
  570. console.log(`[EnemyController] 从Line2生成敌人,查找Line6节点: this.line6Node=${!!this.line6Node}, 通过名称查找=${!!enemyContainer.getChildByName('Line6')}`);
  571. }
  572. if (targetLineNode && targetLineNode.isValid) {
  573. // 使用目标线的Y坐标作为漂移目标
  574. driftWorldY = targetLineNode.worldPosition.y;
  575. console.log(`[EnemyController] 使用新逻辑:目标线节点找到`);
  576. console.log(`[EnemyController] 节点名称: ${targetLineNode.name}`);
  577. console.log(`[EnemyController] 本地坐标Y: ${targetLineNode.position.y}`);
  578. console.log(`[EnemyController] 世界坐标Y: ${targetLineNode.worldPosition.y}`);
  579. console.log(`[EnemyController] 漂移目标Y坐标: ${driftWorldY}`);
  580. } else {
  581. console.log(`[EnemyController] 目标线节点未找到或无效,使用旧逻辑(屏幕边界)`);
  582. // 如果找不到目标线节点,回退到原来的屏幕边界逻辑
  583. const canvas = find('Canvas');
  584. if (canvas) {
  585. const canvasUI = canvas.getComponent(UITransform);
  586. if (canvasUI) {
  587. const screenHeight = canvasUI.height;
  588. const canvasWorldPos = canvas.worldPosition;
  589. if (fromTop) {
  590. // 从Line1生成,漂移到屏幕上边界位置-30px
  591. const screenTop = canvasWorldPos.y + screenHeight / 2;
  592. driftWorldY = screenTop - 30;
  593. console.log(`[EnemyController] 旧逻辑:从Line1漂移到屏幕上边界,Y坐标: ${driftWorldY}`);
  594. } else {
  595. // 从Line2生成,漂移到屏幕下边界位置+30px
  596. const screenBottom = canvasWorldPos.y - screenHeight / 2;
  597. driftWorldY = screenBottom + 30;
  598. console.log(`[EnemyController] 旧逻辑:从Line2漂移到屏幕下边界,Y坐标: ${driftWorldY}`);
  599. }
  600. }
  601. }
  602. }
  603. // 设置初始位置(线上的位置)
  604. const initialWorldPos = new Vec3(spawnWorldX, spawnWorldY, 0);
  605. const initialLocalPos = enemyContainer.getComponent(UITransform).convertToNodeSpaceAR(initialWorldPos);
  606. enemy.position = initialLocalPos;
  607. // 记录漂移目标位置(墙体附近的位置)
  608. const driftWorldPos = new Vec3(driftWorldX, driftWorldY, 0);
  609. // === 根据配置设置敌人 ===
  610. const enemyComp = enemy.addComponent(EnemyInstance);
  611. // 确保敌人数据库已加载
  612. await EnemyInstance.loadEnemyDatabase();
  613. let enemyConfig: EnemyConfig = null;
  614. let enemyId: string = null;
  615. if (this.configManager && this.configManager.isConfigLoaded()) {
  616. // 优先使用当前波次配置中的敌人类型
  617. if (this.currentWaveEnemyConfigs.length > 0) {
  618. const configIndex = this.currentWaveEnemiesSpawned % this.currentWaveEnemyConfigs.length;
  619. const enemyTypeConfig = this.currentWaveEnemyConfigs[configIndex];
  620. const enemyType = enemyTypeConfig.enemyType || enemyTypeConfig;
  621. // enemyType本身就是敌人ID,直接使用
  622. enemyId = enemyType;
  623. console.log(`[EnemyController] 使用敌人类型: ${enemyType} 作为敌人ID`);
  624. enemyConfig = this.configManager.getEnemyById(enemyId) || this.configManager.getRandomEnemy();
  625. } else {
  626. enemyConfig = this.configManager.getRandomEnemy();
  627. enemyId = enemyConfig?.id || 'normal_zombie';
  628. }
  629. }
  630. // 获取当前敌人的血量倍率
  631. let healthMultiplier = 1.0;
  632. if (this.currentWaveEnemyConfigs.length > 0) {
  633. const configIndex = this.currentWaveEnemiesSpawned % this.currentWaveEnemyConfigs.length;
  634. const enemyTypeConfig = this.currentWaveEnemyConfigs[configIndex];
  635. if (enemyTypeConfig && typeof enemyTypeConfig.healthMultiplier === 'number') {
  636. healthMultiplier = enemyTypeConfig.healthMultiplier;
  637. }
  638. }
  639. // 如果没有找到敌人配置中的血量系数,则使用波次级别的血量系数作为备用
  640. if (healthMultiplier === 1.0) {
  641. healthMultiplier = await this.getCurrentHealthMultiplier();
  642. }
  643. if (enemyConfig && enemyId) {
  644. try {
  645. const cfgComp = enemy.addComponent(EnemyComponent);
  646. cfgComp.enemyConfig = enemyConfig;
  647. cfgComp.spawner = this;
  648. } catch (error) {
  649. console.error(`[EnemyController] 添加EnemyComponent失败:`, error);
  650. }
  651. // 使用EnemyInstance的新配置系统
  652. try {
  653. // 设置敌人配置
  654. enemyComp.setEnemyConfig(enemyId);
  655. // 应用血量倍率
  656. const baseHealth = enemyComp.health || this.defaultHealth;
  657. const finalHealth = Math.round(baseHealth * healthMultiplier);
  658. enemyComp.health = finalHealth;
  659. enemyComp.maxHealth = finalHealth;
  660. console.log(`[EnemyController] 敌人 ${enemyId} 配置已应用,血量: ${finalHealth} (系数: ${healthMultiplier})`);
  661. } catch (error) {
  662. console.error(`[EnemyController] 应用敌人配置时出错:`, error);
  663. // 使用默认值
  664. const finalHealth = Math.round(this.defaultHealth * healthMultiplier);
  665. enemyComp.health = finalHealth;
  666. enemyComp.maxHealth = finalHealth;
  667. enemyComp.speed = this.defaultEnemySpeed;
  668. enemyComp.attackPower = this.defaultAttackPower;
  669. }
  670. // 加载动画
  671. this.loadEnemyAnimation(enemy, enemyConfig);
  672. } else {
  673. // 使用默认配置
  674. try {
  675. enemyComp.setEnemyConfig('normal_zombie'); // 默认敌人类型
  676. const baseHealth = enemyComp.health || this.defaultHealth;
  677. const finalHealth = Math.round(baseHealth * healthMultiplier);
  678. enemyComp.health = finalHealth;
  679. enemyComp.maxHealth = finalHealth;
  680. } catch (error) {
  681. console.error(`[EnemyController] 应用默认敌人配置时出错:`, error);
  682. // 使用硬编码默认值
  683. const finalHealth = Math.round(this.defaultHealth * healthMultiplier);
  684. enemyComp.health = finalHealth;
  685. enemyComp.maxHealth = finalHealth;
  686. enemyComp.speed = this.defaultEnemySpeed;
  687. enemyComp.attackPower = this.defaultAttackPower;
  688. }
  689. }
  690. // 额外的属性设置
  691. enemyComp.spawnFromTop = fromTop;
  692. enemyComp.targetFence = fromTop ?
  693. find('Canvas/GameLevelUI/GameArea/TopFence') :
  694. find('Canvas/GameLevelUI/GameArea/BottomFence');
  695. // 设置漂移目标位置(墙体附近的位置)
  696. enemyComp.driftTargetPosition = driftWorldPos.clone();
  697. // 计算墙体上的随机目标位置
  698. if (enemyComp.targetFence && enemyComp.targetFence.isValid) {
  699. const fenceWorldPos = enemyComp.targetFence.worldPosition.clone();
  700. const fenceTransform = enemyComp.targetFence.getComponent(UITransform);
  701. if (fenceTransform) {
  702. // 在墙体宽度范围内随机选择X坐标
  703. const fenceWidth = fenceTransform.width;
  704. const randomX = fenceWorldPos.x + (Math.random() - 0.5) * fenceWidth * 0.8; // 0.8是为了避免太靠近边缘
  705. enemyComp.targetPosition = new Vec3(randomX, fenceWorldPos.y, fenceWorldPos.z);
  706. } else {
  707. // 如果无法获取墙体尺寸,使用墙体中心位置
  708. enemyComp.targetPosition = fenceWorldPos;
  709. }
  710. }
  711. enemyComp.movingDirection = Math.random() > 0.5 ? 1 : -1;
  712. enemyComp.targetY = fromTop ?
  713. this.gameBounds.top - 50 :
  714. this.gameBounds.bottom + 50;
  715. enemyComp.changeDirectionTime = 0;
  716. enemyComp.controller = this;
  717. // 更新敌人血量显示
  718. enemyComp.updateHealthDisplay();
  719. // 添加到活跃敌人列表
  720. this.activeEnemies.push(enemy);
  721. // 增加已生成敌人计数
  722. this.currentWaveEnemiesSpawned++;
  723. // 生成敌人时不更新敌人数量显示,避免重置计数
  724. }
  725. // 清除所有敌人
  726. clearAllEnemies(triggerEvents: boolean = true) {
  727. // 设置清理标志,阻止清理过程中触发游戏胜利判断
  728. if (!triggerEvents) {
  729. this.isClearing = true;
  730. }
  731. // 如果不触发事件,先暂时禁用 notifyEnemyDead 方法
  732. const originalNotifyMethod = this.notifyEnemyDead;
  733. if (!triggerEvents) {
  734. // 临时替换为空函数
  735. this.notifyEnemyDead = () => {};
  736. }
  737. for (const enemy of this.activeEnemies) {
  738. if (enemy && enemy.isValid) {
  739. enemy.destroy();
  740. }
  741. }
  742. // 恢复原来的方法
  743. if (!triggerEvents) {
  744. this.notifyEnemyDead = originalNotifyMethod;
  745. }
  746. this.activeEnemies = [];
  747. // 重置清理标志
  748. if (!triggerEvents) {
  749. this.isClearing = false;
  750. }
  751. // 清除敌人时不更新敌人数量显示,避免重置计数
  752. }
  753. // 获取所有活跃的敌人
  754. getActiveEnemies(): Node[] {
  755. // 过滤掉已经无效的敌人
  756. this.activeEnemies = this.activeEnemies.filter(enemy => enemy && enemy.isValid);
  757. return this.activeEnemies;
  758. }
  759. // 获取当前敌人数量
  760. getCurrentEnemyCount(): number {
  761. return this.getActiveEnemies().length;
  762. }
  763. // 获取最近的敌人节点(排除漂移状态的敌人)
  764. public getNearestEnemy(fromPosition: Vec3): Node | null {
  765. const enemies = this.getActiveEnemies();
  766. if (enemies.length === 0) return null;
  767. // 过滤掉漂移状态的敌人
  768. const nonDriftingEnemies = enemies.filter(enemy => {
  769. const enemyInstance = enemy.getComponent('EnemyInstance') as any;
  770. return !enemyInstance || !enemyInstance.isDrifting();
  771. });
  772. if (nonDriftingEnemies.length === 0) return null;
  773. let nearestEnemy: Node = null;
  774. let nearestDistance = Infinity;
  775. for (const enemy of nonDriftingEnemies) {
  776. const distance = Vec3.distance(fromPosition, enemy.worldPosition);
  777. if (distance < nearestDistance) {
  778. nearestDistance = distance;
  779. nearestEnemy = enemy;
  780. }
  781. }
  782. return nearestEnemy;
  783. }
  784. // 检查是否有活跃敌人(排除漂移状态的敌人)
  785. public hasActiveEnemies(): boolean {
  786. const activeEnemies = this.getActiveEnemies();
  787. // 过滤掉漂移状态的敌人
  788. const nonDriftingEnemies = activeEnemies.filter(enemy => {
  789. const enemyInstance = enemy.getComponent('EnemyInstance') as any;
  790. return !enemyInstance || !enemyInstance.isDrifting();
  791. });
  792. return nonDriftingEnemies.length > 0;
  793. }
  794. // 调试方法:检查enemyContainer中的实际敌人节点
  795. private debugEnemyContainer() {
  796. const enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
  797. if (!enemyContainer) {
  798. return;
  799. }
  800. let enemyNodeCount = 0;
  801. enemyContainer.children.forEach((child, index) => {
  802. if (child.name === 'Enemy' || child.name.includes('Enemy')) {
  803. enemyNodeCount++;
  804. }
  805. });
  806. }
  807. // 调试方法:强制清理无效敌人引用
  808. public debugCleanupEnemies() {
  809. const beforeCount = this.activeEnemies.length;
  810. this.activeEnemies = this.activeEnemies.filter(enemy => enemy && enemy.isValid);
  811. const afterCount = this.activeEnemies.length;
  812. return afterCount;
  813. }
  814. // 获取游戏是否已开始状态
  815. public isGameStarted(): boolean {
  816. return this.gameStarted;
  817. }
  818. // 暂停生成敌人
  819. public pauseSpawning(): void {
  820. if (this.gameStarted) {
  821. this.unschedule(this.spawnEnemy);
  822. // 触发敌人生成停止事件
  823. const eventBus = EventBus.getInstance();
  824. eventBus.emit(GameEvents.ENEMY_SPAWNING_STOPPED);
  825. }
  826. }
  827. // 恢复生成敌人
  828. public resumeSpawning(): void {
  829. // 游戏状态检查现在通过事件系统处理
  830. if (!this.gameStarted) {
  831. return;
  832. }
  833. // 检查是否已达到当前波次的敌人生成上限
  834. if (this.currentWaveEnemiesSpawned >= this.currentWaveTotalEnemies) {
  835. return;
  836. }
  837. // 确保先取消之前的定时器,避免重复调度
  838. this.unschedule(this.spawnEnemy);
  839. this.schedule(this.spawnEnemy, this.spawnInterval);
  840. // 触发敌人生成开始事件
  841. const eventBus = EventBus.getInstance();
  842. eventBus.emit(GameEvents.ENEMY_SPAWNING_STARTED);
  843. }
  844. // 敌人受到伤害
  845. damageEnemy(enemy: Node, damage: number, isCritical: boolean = false) {
  846. if (!enemy || !enemy.isValid) return;
  847. // 游戏状态检查现在通过事件系统处理
  848. // 获取敌人组件
  849. const enemyComp = enemy.getComponent(EnemyInstance);
  850. if (!enemyComp) return;
  851. // 减少敌人血量
  852. enemyComp.takeDamage(damage, isCritical);
  853. // 检查敌人是否死亡
  854. if (enemyComp.health <= 0) {
  855. // 从活跃敌人列表中移除
  856. const index = this.activeEnemies.indexOf(enemy);
  857. if (index !== -1) {
  858. this.activeEnemies.splice(index, 1);
  859. }
  860. // 清理敌人身上的所有灼烧效果,防止定时器继续运行
  861. try {
  862. const burnEffect = enemy.getComponent(BurnEffect);
  863. if (burnEffect) {
  864. console.log(`[EnemyController] 清理敌人身上的灼烧效果`);
  865. // 先停止灼烧效果,再移除组件
  866. burnEffect.stopBurnEffect();
  867. enemy.removeComponent(BurnEffect);
  868. }
  869. } catch (error) {
  870. console.warn(`[EnemyController] 清理灼烧效果时出错:`, error);
  871. }
  872. // 敌人死亡时不在这里更新数量显示,由GameManager统一管理
  873. // 延迟销毁敌人,避免在物理碰撞监听器中直接销毁导致的刚体激活错误
  874. this.scheduleOnce(() => {
  875. if (enemy && enemy.isValid) {
  876. enemy.destroy();
  877. }
  878. }, 0);
  879. }
  880. }
  881. // 墙体受到伤害 - 现在委托给Wall组件
  882. damageWall(damage: number) {
  883. if (this.wallComponent) {
  884. this.wallComponent.takeDamage(damage);
  885. } else {
  886. console.warn('[EnemyController] 墙体组件未找到,无法处理伤害');
  887. }
  888. }
  889. // 游戏结束
  890. gameOver() {
  891. // 停止游戏,但不清除敌人
  892. this.stopGame(false);
  893. // 通过事件系统触发游戏失败
  894. const eventBus = EventBus.getInstance();
  895. eventBus.emit(GameEvents.GAME_DEFEAT);
  896. }
  897. update(dt: number) {
  898. if (!this.gameStarted) return;
  899. // 更新所有敌人
  900. for (let i = this.activeEnemies.length - 1; i >= 0; i--) {
  901. const enemy = this.activeEnemies[i];
  902. if (!enemy || !enemy.isValid) {
  903. this.activeEnemies.splice(i, 1);
  904. continue;
  905. }
  906. // 敌人更新由各自的组件处理
  907. // 不再需要检查敌人是否到达墙体,因为敌人到达游戏区域后会自动攻击
  908. // 敌人的攻击逻辑已经在EnemyInstance中处理
  909. }
  910. }
  911. public getCurrentWallHealth(): number {
  912. return this.wallComponent ? this.wallComponent.getCurrentHealth() : 0;
  913. }
  914. public forceEnemyAttack() {
  915. const activeEnemies = this.getActiveEnemies();
  916. for (const enemy of activeEnemies) {
  917. const enemyComp = enemy.getComponent(EnemyInstance);
  918. if (enemyComp) {
  919. // 直接调用damageWall方法进行测试
  920. this.damageWall(enemyComp.attackPower);
  921. }
  922. }
  923. }
  924. /** 供 EnemyInstance 在 onDestroy 中调用 */
  925. public notifyEnemyDead(enemyNode?: Node) {
  926. // 如果正在清理敌人,不触发任何游戏事件
  927. if (this.isClearing) {
  928. if (enemyNode) {
  929. const idx = this.activeEnemies.indexOf(enemyNode);
  930. if (idx !== -1) {
  931. this.activeEnemies.splice(idx, 1);
  932. }
  933. }
  934. return;
  935. }
  936. if (enemyNode) {
  937. const idx = this.activeEnemies.indexOf(enemyNode);
  938. if (idx !== -1) {
  939. this.activeEnemies.splice(idx, 1);
  940. } else {
  941. console.log(`[EnemyController] 警告:尝试移除的敌人不在activeEnemies数组中!`);
  942. }
  943. // 移除EnemyController内部的击杀计数,统一由GameManager管理
  944. // this.currentWaveEnemiesKilled++;
  945. // 敌人死亡通知时不更新数量显示,由GameManager统一管理
  946. }
  947. // 直接通过事件总线发送ENEMY_KILLED事件,避免通过GamePause的双重调用
  948. const eventBus = EventBus.getInstance();
  949. eventBus.emit('ENEMY_KILLED');
  950. }
  951. /**
  952. * 加载敌人骨骼动画
  953. */
  954. private loadEnemyAnimation(enemyNode: Node, enemyConfig: EnemyConfig) {
  955. console.log(`[EnemyController] 开始加载敌人动画,敌人ID: ${enemyConfig?.id}, 敌人名称: ${enemyConfig?.name}`);
  956. if (!enemyConfig || !enemyConfig.visualConfig) {
  957. console.warn('[EnemyController] 敌人配置或视觉配置缺失,使用默认动画');
  958. return;
  959. }
  960. let spinePath: string | undefined = enemyConfig.visualConfig?.spritePath;
  961. console.log(`[EnemyController] 敌人 ${enemyConfig.id} 的spritePath: ${spinePath}`);
  962. if (!spinePath) {
  963. console.warn('[EnemyController] 敌人精灵路径缺失');
  964. return;
  965. }
  966. if (spinePath.startsWith('@EnemyAni')) {
  967. spinePath = spinePath.replace('@EnemyAni', 'Animation/EnemyAni');
  968. }
  969. if (spinePath.startsWith('@')) {
  970. spinePath = spinePath.substring(1);
  971. }
  972. // 对于Animation/EnemyAni路径,需要添加子目录和文件名
  973. // 例如:Animation/EnemyAni/007 -> Animation/EnemyAni/007/007
  974. if (spinePath.startsWith('Animation/EnemyAni/')) {
  975. const parts = spinePath.split('/');
  976. if (parts.length === 3) {
  977. const animId = parts[2];
  978. spinePath = `${spinePath}/${animId}`;
  979. }
  980. }
  981. // 移除Animation/前缀,因为BundleLoader会从Bundle中加载
  982. if (spinePath.startsWith('Animation/')) {
  983. spinePath = spinePath.replace('Animation/', '');
  984. }
  985. console.log(`[EnemyController] 最终加载路径: ${spinePath}`);
  986. BundleLoader.loadSkeletonData(spinePath).then((skeletonData) => {
  987. if (!skeletonData) {
  988. console.warn(`加载敌人Spine动画失败: ${spinePath}`);
  989. return;
  990. }
  991. // 获取EnemySprite子节点
  992. const enemySprite = enemyNode.getChildByName('EnemySprite');
  993. if (!enemySprite) {
  994. console.error('[EnemyController] 未找到EnemySprite子节点,无法设置骨骼动画');
  995. return;
  996. }
  997. let skeleton = enemySprite.getComponent(sp.Skeleton);
  998. if (!skeleton) {
  999. skeleton = enemySprite.addComponent(sp.Skeleton);
  1000. }
  1001. skeleton.skeletonData = skeletonData;
  1002. const anims = enemyConfig.visualConfig.animations;
  1003. const walkName = anims?.walk ?? 'walk';
  1004. const idleName = anims?.idle ?? 'idle';
  1005. if (skeleton.findAnimation(walkName)) {
  1006. skeleton.setAnimation(0, walkName, true);
  1007. } else if (skeleton.findAnimation(idleName)) {
  1008. skeleton.setAnimation(0, idleName, true);
  1009. } else {
  1010. console.warn(`[EnemyController] 未找到合适的动画,walk: ${walkName}, idle: ${idleName}`);
  1011. }
  1012. });
  1013. }
  1014. // 更新敌人数量显示
  1015. public updateEnemyCountLabel(killedCount?: number) {
  1016. if (!this.enemyCountLabelNode) {
  1017. console.warn('[EnemyController] enemyCountLabelNode 未找到,无法更新敌人数量显示');
  1018. return;
  1019. }
  1020. const label = this.enemyCountLabelNode.getComponent(Label);
  1021. if (label) {
  1022. // 计算剩余敌人数量:总数 - 击杀数量
  1023. let remaining: number;
  1024. if (killedCount !== undefined) {
  1025. // 使用传入的击杀数量计算剩余数量
  1026. remaining = Math.max(0, this.currentWaveTotalEnemies - killedCount);
  1027. } else {
  1028. // 如果没有传入击杀数量,则显示当前波次总敌人数(初始状态)
  1029. remaining = this.currentWaveTotalEnemies;
  1030. }
  1031. label.string = remaining.toString();
  1032. } else {
  1033. console.warn('[EnemyController] enemyCountLabelNode 上未找到 Label 组件');
  1034. }
  1035. }
  1036. public startWave(waveNum: number, totalWaves: number, totalEnemies: number, waveEnemyConfigs?: any[], waveHealthMultiplier?: number) {
  1037. this.currentWave = waveNum;
  1038. this.totalWaves = totalWaves;
  1039. this.currentWaveTotalEnemies = totalEnemies;
  1040. this.currentWaveEnemiesSpawned = 0; // 重置已生成敌人计数器
  1041. this.currentWaveEnemyConfigs = waveEnemyConfigs || []; // 存储当前波次的敌人配置
  1042. this.currentWaveHealthMultiplier = waveHealthMultiplier || 1.0; // 设置当前波次的血量系数
  1043. // 移除EnemyController内部的击杀计数重置,统一由GameManager管理
  1044. // this.currentWaveEnemiesKilled = 0;
  1045. // 修复问题1:根据波次配置设置生成间隔
  1046. if (this.currentWaveEnemyConfigs.length > 0) {
  1047. // 使用第一个敌人配置的spawnInterval,如果有多个配置可以取平均值或最小值
  1048. const firstEnemyConfig = this.currentWaveEnemyConfigs[0];
  1049. if (firstEnemyConfig && typeof firstEnemyConfig.spawnInterval === 'number') {
  1050. this.spawnInterval = firstEnemyConfig.spawnInterval;
  1051. console.log(`[EnemyController] 设置生成间隔为: ${this.spawnInterval}`);
  1052. }
  1053. }
  1054. console.log(`[EnemyController] 开始波次 ${waveNum}/${totalWaves},需要生成 ${totalEnemies} 个敌人`);
  1055. console.log(`[EnemyController] 波次敌人配置:`, this.currentWaveEnemyConfigs);
  1056. console.log(`[EnemyController] 当前生成间隔: ${this.spawnInterval}`);
  1057. this.updateWaveLabel();
  1058. this.updateEnemyCountLabel();
  1059. // 移除startWaveUI的隐藏,因为现在使用Toast预制体
  1060. // if (this.startWaveUI) this.startWaveUI.active = false;
  1061. }
  1062. private updateWaveLabel() {
  1063. // 更新当前波数显示
  1064. if (this.currentWaveNumberLabelNode) {
  1065. const currentLabel = this.currentWaveNumberLabelNode.getComponent(Label);
  1066. if (currentLabel) {
  1067. currentLabel.string = this.currentWave.toString();
  1068. } else {
  1069. console.warn('[EnemyController] currentWaveNumberLabelNode 上未找到 Label 组件');
  1070. }
  1071. } else {
  1072. console.warn('[EnemyController] currentWaveNumberLabelNode 未找到,无法更新当前波次标签');
  1073. }
  1074. // 更新总波数显示
  1075. if (this.totalWaveNumberLabelNode) {
  1076. const totalLabel = this.totalWaveNumberLabelNode.getComponent(Label);
  1077. if (totalLabel) {
  1078. totalLabel.string = this.totalWaves.toString();
  1079. } else {
  1080. console.warn('[EnemyController] totalWaveNumberLabelNode 上未找到 Label 组件');
  1081. }
  1082. } else {
  1083. console.warn('[EnemyController] totalWaveNumberLabelNode 未找到,无法更新总波次标签');
  1084. }
  1085. }
  1086. /** 显示每波开始提示,随后开启敌人生成 */
  1087. public showStartWavePromptUI(duration: number = 2) {
  1088. // 通过事件系统检查游戏是否已经结束
  1089. let gameOver = false;
  1090. EventBus.getInstance().emit(GameEvents.GAME_CHECK_OVER, (isOver: boolean) => {
  1091. gameOver = isOver;
  1092. });
  1093. if (gameOver) {
  1094. return;
  1095. }
  1096. // 暂停敌人生成,确保在Toast显示期间不会生成敌人
  1097. this.pauseSpawning();
  1098. // 使用事件机制显示Toast
  1099. const toastText = `第${this.currentWave}波敌人来袭!`;
  1100. EventBus.getInstance().emit(GameEvents.SHOW_TOAST, {
  1101. message: toastText,
  1102. duration: duration
  1103. });
  1104. // 等待Toast消息播放结束后再开始生成敌人
  1105. if (duration > 0) {
  1106. this.scheduleOnce(() => {
  1107. // 再次检查游戏状态,确保游戏未结束
  1108. let gameOverCheck = false;
  1109. EventBus.getInstance().emit(GameEvents.GAME_CHECK_OVER, (isOver: boolean) => {
  1110. gameOverCheck = isOver;
  1111. });
  1112. if (gameOverCheck) {
  1113. return;
  1114. }
  1115. // Toast播放完毕,开始生成敌人
  1116. this.startGame();
  1117. }, duration);
  1118. } else {
  1119. // 如果duration为0,立即开始游戏
  1120. this.startGame();
  1121. }
  1122. }
  1123. /**
  1124. * 重置到初始状态
  1125. * 用于游戏重新开始时重置所有敌人相关状态
  1126. */
  1127. public resetToInitialState(): void {
  1128. // 停止游戏并清理所有敌人
  1129. this.stopGame(true);
  1130. // 重置波次信息
  1131. this.currentWave = 1;
  1132. this.totalWaves = 1;
  1133. this.currentWaveTotalEnemies = 0;
  1134. this.currentWaveEnemiesKilled = 0;
  1135. this.currentWaveEnemiesSpawned = 0;
  1136. this.currentWaveEnemyConfigs = [];
  1137. // 重置血量倍率
  1138. this.currentHealthMultiplier = 1.0;
  1139. // 清空暂停状态
  1140. this.pausedEnemyStates.clear();
  1141. // 重置UI显示
  1142. this.updateEnemyCountLabel();
  1143. this.updateWaveLabel();
  1144. }
  1145. /**
  1146. * 处理更新敌人计数事件
  1147. */
  1148. private onUpdateEnemyCountEvent(killedCount: number) {
  1149. this.updateEnemyCountLabel(killedCount);
  1150. }
  1151. /**
  1152. * 处理启动波次事件
  1153. */
  1154. private onStartWaveEvent(data: { wave: number; totalWaves: number; enemyCount: number; waveEnemyConfigs: any[]; healthMultiplier?: number }) {
  1155. this.startWave(data.wave, data.totalWaves, data.enemyCount, data.waveEnemyConfigs, data.healthMultiplier);
  1156. }
  1157. /**
  1158. * 处理启动游戏事件
  1159. */
  1160. private onStartGameEvent() {
  1161. this.startGame();
  1162. }
  1163. /**
  1164. * 处理显示波次提示事件
  1165. */
  1166. private onShowStartWavePromptEvent() {
  1167. this.showStartWavePromptUI();
  1168. }
  1169. onDestroy() {
  1170. // 清理事件监听
  1171. const eventBus = EventBus.getInstance();
  1172. eventBus.off(GameEvents.GAME_PAUSE, this.onGamePauseEvent, this);
  1173. eventBus.off(GameEvents.GAME_PAUSE_SKILL_SELECTION, this.onGamePauseEvent, this);
  1174. eventBus.off(GameEvents.GAME_PAUSE_BLOCK_SELECTION, this.onGamePauseEvent, this);
  1175. eventBus.off(GameEvents.GAME_RESUME, this.onGameResumeEvent, this);
  1176. eventBus.off(GameEvents.GAME_RESUME_SKILL_SELECTION, this.onGameResumeSkillSelectionEvent, this);
  1177. eventBus.off(GameEvents.GAME_RESUME_BLOCK_SELECTION, this.onGameResumeBlockSelectionEvent, this);
  1178. eventBus.off(GameEvents.GAME_SUCCESS, this.onGameEndEvent, this);
  1179. eventBus.off(GameEvents.GAME_DEFEAT, this.onGameEndEvent, this);
  1180. eventBus.off(GameEvents.CLEAR_ALL_GAME_OBJECTS, this.onClearAllGameObjectsEvent, this);
  1181. eventBus.off(GameEvents.CLEAR_ALL_ENEMIES, this.onClearAllEnemiesEvent, this);
  1182. eventBus.off(GameEvents.RESET_ENEMY_CONTROLLER, this.onResetEnemyControllerEvent, this);
  1183. // 清理新增的事件监听
  1184. eventBus.off(GameEvents.ENEMY_UPDATE_COUNT, this.onUpdateEnemyCountEvent, this);
  1185. eventBus.off(GameEvents.ENEMY_START_WAVE, this.onStartWaveEvent, this);
  1186. eventBus.off(GameEvents.ENEMY_START_GAME, this.onStartGameEvent, this);
  1187. eventBus.off(GameEvents.ENEMY_SHOW_START_WAVE_PROMPT, this.onShowStartWavePromptEvent, this);
  1188. // 清空状态
  1189. this.pausedEnemyStates.clear();
  1190. }
  1191. }