EnemyController.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. import { _decorator, Node, Label, Vec3, Prefab, instantiate, find, UITransform, resources, RigidBody2D } 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 { GamePause } from './GamePause';
  9. import EventBus, { GameEvents } from '../Core/EventBus';
  10. const { ccclass, property } = _decorator;
  11. // 前向声明EnemyInstance类型,避免循环引用
  12. class EnemyInstanceType {
  13. public health: number;
  14. public maxHealth: number;
  15. public speed: number;
  16. public attackPower: number;
  17. public movingDirection: number;
  18. public targetY: number;
  19. public changeDirectionTime: number;
  20. public controller: any;
  21. public node: Node;
  22. public updateHealthDisplay: () => void;
  23. public takeDamage: (damage: number) => void;
  24. }
  25. @ccclass('EnemyController')
  26. export class EnemyController extends BaseSingleton {
  27. // 仅类型声明,实例由 BaseSingleton 维护
  28. public static _instance: EnemyController;
  29. // 敌人预制体
  30. @property({
  31. type: Prefab,
  32. tooltip: '拖拽Enemy预制体到这里'
  33. })
  34. public enemyPrefab: Prefab = null;
  35. // 敌人容器节点
  36. @property({
  37. type: Node,
  38. tooltip: '拖拽enemyContainer节点到这里(Canvas/GameLevelUI/enemyContainer)'
  39. })
  40. public enemyContainer: Node = null;
  41. // 金币预制体
  42. @property({ type: Prefab, tooltip: '金币预制体 CoinDrop' })
  43. public coinPrefab: Prefab = null;
  44. // === 生成 & 属性参数(保留需要可在内部自行设定,Inspector 不再显示) ===
  45. private spawnInterval: number = 3;
  46. // === 默认数值(当配置文件尚未加载时使用) ===
  47. private defaultEnemySpeed: number = 50;
  48. private defaultAttackPower: number = 10;
  49. private defaultHealth: number = 30;
  50. // 墙体属性
  51. // 最终数值将在 init() 中从存档或 GameManager 注入
  52. public wallHealth: number = 0;
  53. // 墙体血量显示节点(Inspector 拖拽 HeartLabel 节点到此)
  54. @property({ type: Node, tooltip: '墙体血量 Label 节点 (HeartLabel)' })
  55. public wallHealthNode: Node = null;
  56. // 游戏区域边界 - 改为public,让敌人实例可以访问
  57. public gameBounds = {
  58. left: 0,
  59. right: 0,
  60. top: 0,
  61. bottom: 0
  62. };
  63. // 活跃的敌人列表
  64. private activeEnemies: Node[] = [];
  65. // 游戏是否已开始
  66. private gameStarted: boolean = false;
  67. // 墙体节点
  68. private wallNodes: Node[] = [];
  69. // 配置管理器
  70. private configManager: ConfigManager = null;
  71. @property({
  72. type: Node,
  73. tooltip: '敌人数量显示节点 (EnemyNumber)'
  74. })
  75. public enemyCountLabelNode: Node = null;
  76. @property({
  77. type: Node,
  78. tooltip: '波数显示Label (WaveNumber)'
  79. })
  80. public waveNumberLabelNode: Node = null;
  81. @property({
  82. type: Node,
  83. tooltip: '每波开始提示UI节点 (StartWaveUI)'
  84. })
  85. public startWaveUI: Node = null;
  86. private totalWaves: number = 1;
  87. private currentWave: number = 1;
  88. private currentWaveTotalEnemies: number = 0;
  89. private currentWaveEnemiesKilled: number = 0;
  90. // 暂停前保存的敌人状态
  91. private pausedEnemyStates: Map<Node, {
  92. velocity: any,
  93. angularVelocity: number,
  94. isMoving: boolean,
  95. attackTimer: number
  96. }> = new Map();
  97. /**
  98. * BaseSingleton 首次实例化回调
  99. */
  100. protected init() {
  101. // 获取配置管理器实例
  102. this.configManager = ConfigManager.getInstance();
  103. // 如果没有指定enemyContainer,尝试找到它
  104. if (!this.enemyContainer) {
  105. this.enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
  106. if (!this.enemyContainer) {
  107. console.warn('找不到enemyContainer节点,将尝试创建');
  108. }
  109. }
  110. // 获取游戏区域边界
  111. this.calculateGameBounds();
  112. // 查找墙体节点
  113. this.findWallNodes();
  114. // 从存档读取墙体基础血量,如果有的话
  115. const sdm = SaveDataManager.getInstance();
  116. const base = (sdm.getPlayerData() as any)?.wallBaseHealth;
  117. if (typeof base === 'number' && base > 0) this.wallHealth = base;
  118. // 初始化墙体血量显示
  119. this.initWallHealthDisplay();
  120. // 确保enemyContainer节点存在
  121. this.ensureEnemyContainer();
  122. // 如果没有指定enemyCountLabelNode,尝试找到它
  123. if (!this.enemyCountLabelNode) {
  124. this.enemyCountLabelNode = find('Canvas/GameLevelUI/EnemyNode/EnemyNumber');
  125. }
  126. if (!this.waveNumberLabelNode) {
  127. this.waveNumberLabelNode = find('Canvas/GameLevelUI/WaveInfo/WaveNumber');
  128. }
  129. if (!this.startWaveUI) {
  130. this.startWaveUI = find('Canvas/GameLevelUI/StartWaveUI') || find('Canvas/GameLevelUI/NextWaveUI');
  131. }
  132. // 初始化敌人数量显示
  133. this.updateEnemyCountLabel();
  134. // 监听游戏事件
  135. this.setupEventListeners();
  136. }
  137. /**
  138. * 设置事件监听器
  139. */
  140. private setupEventListeners() {
  141. const eventBus = EventBus.getInstance();
  142. // 监听暂停事件
  143. eventBus.on(GameEvents.GAME_PAUSE, this.onGamePauseEvent, this);
  144. // 监听恢复事件
  145. eventBus.on(GameEvents.GAME_RESUME, this.onGameResumeEvent, this);
  146. // 监听游戏成功/失败事件
  147. eventBus.on(GameEvents.GAME_SUCCESS, this.onGameEndEvent, this);
  148. eventBus.on(GameEvents.GAME_DEFEAT, this.onGameEndEvent, this);
  149. }
  150. /**
  151. * 处理游戏暂停事件
  152. */
  153. private onGamePauseEvent() {
  154. console.log('[EnemyController] 接收到游戏暂停事件,暂停所有敌人');
  155. this.pauseAllEnemies();
  156. this.pauseSpawning();
  157. }
  158. /**
  159. * 处理游戏恢复事件
  160. */
  161. private onGameResumeEvent() {
  162. console.log('[EnemyController] 接收到游戏恢复事件,恢复所有敌人');
  163. this.resumeAllEnemies();
  164. // 检查游戏是否已经结束,如果结束则不恢复敌人生成
  165. const gamePause = GamePause.getInstance();
  166. if (!gamePause.isGameOver()) {
  167. this.resumeSpawning();
  168. }
  169. }
  170. /**
  171. * 处理游戏结束事件
  172. */
  173. private onGameEndEvent() {
  174. console.log('[EnemyController] 接收到游戏结束事件,停止敌人生成');
  175. this.stopGame(false); // 停止游戏但不清除敌人
  176. }
  177. /**
  178. * 暂停所有敌人
  179. */
  180. private pauseAllEnemies(): void {
  181. const activeEnemies = this.getActiveEnemies();
  182. for (const enemy of activeEnemies) {
  183. if (!enemy || !enemy.isValid) continue;
  184. // 保存敌人状态
  185. const rigidBody = enemy.getComponent(RigidBody2D);
  186. if (rigidBody) {
  187. this.pausedEnemyStates.set(enemy, {
  188. velocity: rigidBody.linearVelocity.clone(),
  189. angularVelocity: rigidBody.angularVelocity,
  190. isMoving: true,
  191. attackTimer: 0 // 可以扩展保存攻击计时器
  192. });
  193. // 停止敌人运动
  194. rigidBody.linearVelocity.set(0, 0);
  195. rigidBody.angularVelocity = 0;
  196. rigidBody.sleep();
  197. }
  198. // 暂停敌人AI组件(如果有的话)
  199. const enemyAI = enemy.getComponent('EnemyAI');
  200. if (enemyAI && typeof (enemyAI as any).pause === 'function') {
  201. (enemyAI as any).pause();
  202. }
  203. // 暂停敌人动画(如果有的话)
  204. const animation = enemy.getComponent('Animation');
  205. if (animation && typeof (animation as any).pause === 'function') {
  206. (animation as any).pause();
  207. }
  208. }
  209. }
  210. /**
  211. * 恢复所有敌人
  212. */
  213. private resumeAllEnemies(): void {
  214. const activeEnemies = this.getActiveEnemies();
  215. for (const enemy of activeEnemies) {
  216. if (!enemy || !enemy.isValid) continue;
  217. const savedState = this.pausedEnemyStates.get(enemy);
  218. if (savedState) {
  219. const rigidBody = enemy.getComponent(RigidBody2D);
  220. if (rigidBody) {
  221. // 恢复敌人运动
  222. rigidBody.wakeUp();
  223. rigidBody.linearVelocity = savedState.velocity;
  224. rigidBody.angularVelocity = savedState.angularVelocity;
  225. }
  226. }
  227. // 恢复敌人AI组件
  228. const enemyAI = enemy.getComponent('EnemyAI');
  229. if (enemyAI && typeof (enemyAI as any).resume === 'function') {
  230. (enemyAI as any).resume();
  231. }
  232. // 恢复敌人动画
  233. const animation = enemy.getComponent('Animation');
  234. if (animation && typeof (animation as any).resume === 'function') {
  235. (animation as any).resume();
  236. }
  237. }
  238. // 清空保存的状态
  239. this.pausedEnemyStates.clear();
  240. }
  241. // 计算游戏区域边界
  242. private calculateGameBounds() {
  243. const gameArea = find('Canvas/GameLevelUI/GameArea');
  244. if (!gameArea) {
  245. return;
  246. }
  247. const uiTransform = gameArea.getComponent(UITransform);
  248. if (!uiTransform) {
  249. return;
  250. }
  251. const worldPos = gameArea.worldPosition;
  252. const width = uiTransform.width;
  253. const height = uiTransform.height;
  254. this.gameBounds = {
  255. left: worldPos.x - width / 2,
  256. right: worldPos.x + width / 2,
  257. top: worldPos.y + height / 2,
  258. bottom: worldPos.y - height / 2
  259. };
  260. }
  261. // 查找墙体节点
  262. private findWallNodes() {
  263. const gameArea = find('Canvas/GameLevelUI/GameArea');
  264. if (gameArea) {
  265. this.wallNodes = [];
  266. for (let i = 0; i < gameArea.children.length; i++) {
  267. const child = gameArea.children[i];
  268. if (child.name.includes('Wall') || child.name.includes('wall') || child.name.includes('墙')) {
  269. this.wallNodes.push(child);
  270. }
  271. }
  272. }
  273. }
  274. // 初始化墙体血量显示
  275. initWallHealthDisplay() {
  276. if (this.wallHealthNode) {
  277. this.updateWallHealthDisplay();
  278. } else {
  279. console.warn('EnemyController 未绑定 HeartLabel 节点,请在 Inspector 中拖拽 Canvas/GameLevelUI/HeartNode/HeartLabel');
  280. }
  281. }
  282. // 更新墙体血量显示
  283. public updateWallHealthDisplay() {
  284. if (!this.wallHealthNode) return;
  285. // 直接在当前节点或其子节点寻找 Label
  286. let heartLabel = this.wallHealthNode.getComponent(Label);
  287. if (!heartLabel) {
  288. heartLabel = this.wallHealthNode.getComponentInChildren(Label);
  289. }
  290. if (heartLabel) {
  291. heartLabel.string = this.wallHealth.toString();
  292. }
  293. }
  294. // 确保enemyContainer节点存在
  295. ensureEnemyContainer() {
  296. // 如果已经通过拖拽设置了节点,直接使用
  297. if (this.enemyContainer && this.enemyContainer.isValid) {
  298. return;
  299. }
  300. // 尝试查找节点
  301. this.enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
  302. if (this.enemyContainer) {
  303. console.log('找到已存在的enemyContainer节点');
  304. return;
  305. }
  306. // 如果找不到,创建新节点
  307. const gameLevelUI = find('Canvas/GameLevelUI');
  308. if (!gameLevelUI) {
  309. console.error('找不到GameLevelUI节点,无法创建enemyContainer');
  310. return;
  311. }
  312. this.enemyContainer = new Node('enemyContainer');
  313. gameLevelUI.addChild(this.enemyContainer);
  314. if (!this.enemyContainer.getComponent(UITransform)) {
  315. this.enemyContainer.addComponent(UITransform);
  316. }
  317. console.log('已在GameLevelUI下创建enemyContainer节点');
  318. }
  319. // 游戏开始
  320. startGame() {
  321. // 检查游戏是否已经结束,如果结束则不开始生成敌人
  322. const gamePause = GamePause.getInstance();
  323. if (gamePause.isGameOver()) {
  324. console.log('[EnemyController] 游戏已经结束,不启动敌人生成');
  325. return;
  326. }
  327. this.gameStarted = true;
  328. // 确保enemyContainer节点存在
  329. this.ensureEnemyContainer();
  330. // 开始生成敌人
  331. this.schedule(this.spawnEnemy, this.spawnInterval);
  332. console.log('开始生成敌人');
  333. }
  334. // 游戏结束
  335. stopGame(clearEnemies: boolean = true) {
  336. this.gameStarted = false;
  337. // 停止生成敌人
  338. this.unschedule(this.spawnEnemy);
  339. // 只有在指定时才清除所有敌人
  340. if (clearEnemies) {
  341. // 当游戏结束时清除敌人,不触发敌人死亡事件
  342. const gamePause = GamePause.getInstance();
  343. const isGameOver = gamePause.isGameOver();
  344. this.clearAllEnemies(!isGameOver); // 只有在游戏没有结束时才触发事件
  345. }
  346. console.log('停止生成敌人');
  347. }
  348. // 生成敌人
  349. spawnEnemy() {
  350. if (!this.gameStarted || !this.enemyPrefab) return;
  351. // 检查游戏是否已经结束,如果结束则不生成敌人
  352. const gamePause = GamePause.getInstance();
  353. if (gamePause.isGameOver()) {
  354. console.log('[EnemyController] 游戏已经结束,不再生成敌人');
  355. this.unschedule(this.spawnEnemy); // 取消定时生成
  356. return;
  357. }
  358. // 随机决定从上方还是下方生成
  359. const fromTop = Math.random() > 0.5;
  360. // 实例化敌人
  361. const enemy = instantiate(this.enemyPrefab);
  362. enemy.name = 'Enemy'; // 确保敌人节点名称为Enemy
  363. // 添加到场景中
  364. const enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
  365. if (!enemyContainer) {
  366. return;
  367. }
  368. enemyContainer.addChild(enemy);
  369. // 生成在对应线(Line1 / Line2)上的随机位置
  370. const lineName = fromTop ? 'Line1' : 'Line2';
  371. const lineNode = enemyContainer.getChildByName(lineName);
  372. if (!lineNode) {
  373. console.warn(`[EnemyController] 未找到 ${lineName} 节点,取消本次敌人生成`);
  374. enemy.destroy();
  375. return;
  376. }
  377. // 在对应 line 上随机 X 坐标
  378. const spawnWorldX = this.gameBounds.left + Math.random() * (this.gameBounds.right - this.gameBounds.left);
  379. const spawnWorldY = lineNode.worldPosition.y;
  380. const worldPos = new Vec3(spawnWorldX, spawnWorldY, 0);
  381. const localPos = enemyContainer.getComponent(UITransform).convertToNodeSpaceAR(worldPos);
  382. enemy.position = localPos;
  383. // === 根据配置设置敌人 ===
  384. const enemyComp = enemy.addComponent(EnemyInstance);
  385. let enemyConfig: EnemyConfig = null;
  386. if (this.configManager && this.configManager.isConfigLoaded()) {
  387. enemyConfig = this.configManager.getRandomEnemy();
  388. }
  389. if (enemyConfig) {
  390. // 添加EnemyComponent保存配置
  391. const cfgComp = enemy.addComponent(EnemyComponent);
  392. cfgComp.enemyConfig = enemyConfig;
  393. cfgComp.spawner = this;
  394. // 应用数值
  395. enemyComp.health = enemyConfig.stats.health;
  396. enemyComp.maxHealth = enemyConfig.stats.health;
  397. enemyComp.speed = enemyConfig.stats.speed;
  398. enemyComp.attackPower = enemyConfig.stats.damage;
  399. // 加载动画
  400. this.loadEnemyAnimation(enemy, enemyConfig);
  401. } else {
  402. // 使用默认值
  403. enemyComp.health = this.defaultHealth;
  404. enemyComp.maxHealth = this.defaultHealth;
  405. enemyComp.speed = this.defaultEnemySpeed;
  406. enemyComp.attackPower = this.defaultAttackPower;
  407. }
  408. // 额外的属性设置
  409. enemyComp.spawnFromTop = fromTop;
  410. enemyComp.targetFence = find(fromTop ? 'Canvas/GameLevelUI/GameArea/TopFence' : 'Canvas/GameLevelUI/GameArea/BottomFence');
  411. enemyComp.movingDirection = Math.random() > 0.5 ? 1 : -1;
  412. enemyComp.targetY = fromTop ? this.gameBounds.top - 50 : this.gameBounds.bottom + 50;
  413. enemyComp.changeDirectionTime = 0;
  414. enemyComp.controller = this;
  415. // 更新敌人血量显示
  416. enemyComp.updateHealthDisplay();
  417. // 添加到活跃敌人列表
  418. this.activeEnemies.push(enemy);
  419. // 更新敌人数量显示
  420. this.updateEnemyCountLabel();
  421. console.log(`生成敌人,当前共有 ${this.activeEnemies.length} 个敌人`);
  422. }
  423. // 清除所有敌人
  424. clearAllEnemies(triggerEvents: boolean = true) {
  425. // 如果不触发事件,先暂时禁用 notifyEnemyDead 方法
  426. const originalNotifyMethod = this.notifyEnemyDead;
  427. if (!triggerEvents) {
  428. // 临时替换为空函数
  429. this.notifyEnemyDead = () => {};
  430. console.log('[EnemyController] 临时禁用敌人死亡通知');
  431. }
  432. for (const enemy of this.activeEnemies) {
  433. if (enemy && enemy.isValid) {
  434. enemy.destroy();
  435. }
  436. }
  437. // 恢复原来的方法
  438. if (!triggerEvents) {
  439. this.notifyEnemyDead = originalNotifyMethod;
  440. console.log('[EnemyController] 恢复敌人死亡通知');
  441. }
  442. this.activeEnemies = [];
  443. // 更新敌人数量显示
  444. this.updateEnemyCountLabel();
  445. }
  446. // 获取所有活跃的敌人
  447. getActiveEnemies(): Node[] {
  448. // 过滤掉已经无效的敌人
  449. this.activeEnemies = this.activeEnemies.filter(enemy => enemy && enemy.isValid);
  450. return this.activeEnemies;
  451. }
  452. // 获取当前敌人数量
  453. getCurrentEnemyCount(): number {
  454. return this.getActiveEnemies().length;
  455. }
  456. // 获取最近的敌人节点
  457. public getNearestEnemy(fromPosition: Vec3): Node | null {
  458. const enemies = this.getActiveEnemies();
  459. if (enemies.length === 0) return null;
  460. let nearestEnemy: Node = null;
  461. let nearestDistance = Infinity;
  462. for (const enemy of enemies) {
  463. const distance = Vec3.distance(fromPosition, enemy.worldPosition);
  464. if (distance < nearestDistance) {
  465. nearestDistance = distance;
  466. nearestEnemy = enemy;
  467. }
  468. }
  469. return nearestEnemy;
  470. }
  471. // 检查是否有活跃敌人
  472. public hasActiveEnemies(): boolean {
  473. return this.getActiveEnemies().length > 0;
  474. }
  475. // 获取游戏是否已开始状态
  476. public isGameStarted(): boolean {
  477. return this.gameStarted;
  478. }
  479. // 暂停生成敌人
  480. public pauseSpawning(): void {
  481. if (this.gameStarted) {
  482. console.log('暂停生成敌人');
  483. this.unschedule(this.spawnEnemy);
  484. }
  485. }
  486. // 恢复生成敌人
  487. public resumeSpawning(): void {
  488. // 检查游戏是否已经结束,如果结束则不恢复生成敌人
  489. const gamePause = GamePause.getInstance();
  490. if (gamePause.isGameOver()) {
  491. console.log('[EnemyController] 游戏已经结束,不恢复敌人生成');
  492. return;
  493. }
  494. if (this.gameStarted) {
  495. this.schedule(this.spawnEnemy, this.spawnInterval);
  496. }
  497. }
  498. // 敌人受到伤害
  499. damageEnemy(enemy: Node, damage: number) {
  500. if (!enemy || !enemy.isValid) return;
  501. // 检查游戏是否已经结束
  502. const gamePause = GamePause.getInstance();
  503. if (gamePause.isGameOver()) {
  504. console.warn('[EnemyController] 游戏已经结束,不再处理敌人伤害');
  505. return;
  506. }
  507. // 获取敌人组件
  508. const enemyComp = enemy.getComponent(EnemyInstance);
  509. if (!enemyComp) return;
  510. // 减少敌人血量
  511. enemyComp.takeDamage(damage);
  512. // 检查敌人是否死亡
  513. if (enemyComp.health <= 0) {
  514. // 从活跃敌人列表中移除
  515. const index = this.activeEnemies.indexOf(enemy);
  516. if (index !== -1) {
  517. this.activeEnemies.splice(index, 1);
  518. }
  519. // 更新敌人数量显示
  520. this.updateEnemyCountLabel();
  521. // 销毁敌人
  522. enemy.destroy();
  523. }
  524. }
  525. // 墙体受到伤害
  526. damageWall(damage: number) {
  527. // 减少墙体血量
  528. this.wallHealth -= damage;
  529. // 更新墙体血量显示
  530. this.updateWallHealthDisplay();
  531. // 检查墙体是否被摧毁
  532. if (this.wallHealth <= 0) {
  533. // 游戏结束
  534. this.gameOver();
  535. }
  536. }
  537. // 游戏结束
  538. gameOver() {
  539. // 停止游戏,但不清除敌人
  540. this.stopGame(false);
  541. // 通知GamePause触发游戏失败
  542. const gamePause = GamePause.getInstance();
  543. gamePause.triggerGameDefeat();
  544. }
  545. update(dt: number) {
  546. if (!this.gameStarted) return;
  547. // 更新所有敌人
  548. for (let i = this.activeEnemies.length - 1; i >= 0; i--) {
  549. const enemy = this.activeEnemies[i];
  550. if (!enemy || !enemy.isValid) {
  551. this.activeEnemies.splice(i, 1);
  552. continue;
  553. }
  554. // 敌人更新由各自的组件处理
  555. // 不再需要检查敌人是否到达墙体,因为敌人到达游戏区域后会自动攻击
  556. // 敌人的攻击逻辑已经在EnemyInstance中处理
  557. }
  558. }
  559. // === 调试方法 ===
  560. public testEnemyAttack() {
  561. console.log('=== 测试敌人攻击墙体 ===');
  562. // 手动触发墙体受到伤害
  563. const testDamage = 50;
  564. console.log(`模拟敌人攻击,伤害: ${testDamage}`);
  565. this.damageWall(testDamage);
  566. return this.wallHealth;
  567. }
  568. public getCurrentWallHealth(): number {
  569. return this.wallHealth;
  570. }
  571. public forceEnemyAttack() {
  572. console.log('=== 强制所有敌人进入攻击状态 ===');
  573. const activeEnemies = this.getActiveEnemies();
  574. console.log(`当前活跃敌人数量: ${activeEnemies.length}`);
  575. for (const enemy of activeEnemies) {
  576. const enemyComp = enemy.getComponent(EnemyInstance);
  577. if (enemyComp) {
  578. console.log(`强制敌人 ${enemy.name} 进入攻击状态`);
  579. // 直接调用damageWall方法进行测试
  580. this.damageWall(enemyComp.attackPower);
  581. }
  582. }
  583. }
  584. /** 供 EnemyInstance 在 onDestroy 中调用 */
  585. public notifyEnemyDead(enemyNode?: Node) {
  586. if (enemyNode) {
  587. const idx = this.activeEnemies.indexOf(enemyNode);
  588. if (idx !== -1) this.activeEnemies.splice(idx, 1);
  589. this.currentWaveEnemiesKilled++;
  590. this.updateEnemyCountLabel();
  591. }
  592. // 通知GamePause处理敌人被击杀
  593. const gamePause = GamePause.getInstance();
  594. gamePause.onEnemyKilled();
  595. }
  596. /**
  597. * 加载敌人骨骼动画
  598. */
  599. private loadEnemyAnimation(enemyNode: Node, enemyConfig: EnemyConfig) {
  600. let spinePath: string | undefined = enemyConfig.visualConfig?.spritePrefab;
  601. if (!spinePath) return;
  602. if (spinePath.startsWith('@EnemyAni')) {
  603. spinePath = spinePath.replace('@EnemyAni', 'Animation/EnemyAni');
  604. }
  605. if (spinePath.startsWith('@')) {
  606. spinePath = spinePath.substring(1);
  607. }
  608. resources.load(spinePath, sp.SkeletonData, (err, skeletonData) => {
  609. if (err) {
  610. console.warn(`加载敌人Spine动画失败: ${spinePath}`, err);
  611. return;
  612. }
  613. let skeleton = enemyNode.getComponent(sp.Skeleton);
  614. if (!skeleton) {
  615. skeleton = enemyNode.addComponent(sp.Skeleton);
  616. }
  617. skeleton.skeletonData = skeletonData;
  618. const anims = enemyConfig.visualConfig.animations;
  619. const walkName = anims?.walk ?? 'walk';
  620. const idleName = anims?.idle ?? 'idle';
  621. if (skeleton.findAnimation(walkName)) {
  622. skeleton.setAnimation(0, walkName, true);
  623. } else if (skeleton.findAnimation(idleName)) {
  624. skeleton.setAnimation(0, idleName, true);
  625. }
  626. });
  627. }
  628. // 更新敌人数量显示
  629. private updateEnemyCountLabel() {
  630. if (!this.enemyCountLabelNode) return;
  631. const label = this.enemyCountLabelNode.getComponent(Label);
  632. if (label) {
  633. const remaining = Math.max(0, this.currentWaveTotalEnemies - this.currentWaveEnemiesKilled);
  634. label.string = remaining.toString();
  635. console.log(`EnemyController 剩余敌人数量: ${remaining}`);
  636. }
  637. }
  638. public startWave(waveNum: number, totalWaves: number, totalEnemies: number) {
  639. this.currentWave = waveNum;
  640. this.totalWaves = totalWaves;
  641. this.currentWaveTotalEnemies = totalEnemies;
  642. this.currentWaveEnemiesKilled = 0;
  643. this.updateWaveLabel();
  644. this.updateEnemyCountLabel();
  645. if (this.startWaveUI) this.startWaveUI.active = false;
  646. }
  647. private updateWaveLabel() {
  648. if (!this.waveNumberLabelNode) return;
  649. const label = this.waveNumberLabelNode.getComponent(Label);
  650. if (label) {
  651. label.string = `${this.currentWave}/${this.totalWaves}`;
  652. }
  653. }
  654. /** 显示每波开始提示,随后开启敌人生成 */
  655. public showStartWavePromptUI(duration: number = 2) {
  656. // 检查游戏是否已经结束,如果结束则不显示波次提示
  657. const gamePause = GamePause.getInstance();
  658. if (gamePause.isGameOver()) {
  659. console.log('[EnemyController] 游戏已经结束,不显示波次提示');
  660. return;
  661. }
  662. if (!this.startWaveUI) return;
  663. this.startWaveUI.active = true;
  664. // 暂停生成(确保未重复)
  665. this.pauseSpawning();
  666. if (duration > 0) {
  667. this.scheduleOnce(() => {
  668. if (this.startWaveUI) this.startWaveUI.active = false;
  669. // 再次检查游戏是否已经结束,如果结束则不开始生成敌人
  670. const gamePauseCheck = GamePause.getInstance();
  671. if (gamePauseCheck.isGameOver()) {
  672. console.log('[EnemyController] 游戏已经结束,不启动敌人生成(延时检查)');
  673. return;
  674. }
  675. // 真正开始/恢复生成敌人
  676. this.startGame();
  677. }, duration);
  678. } else {
  679. this.startGame();
  680. }
  681. }
  682. onDestroy() {
  683. // 清理事件监听
  684. const eventBus = EventBus.getInstance();
  685. eventBus.off(GameEvents.GAME_PAUSE, this.onGamePauseEvent, this);
  686. eventBus.off(GameEvents.GAME_RESUME, this.onGameResumeEvent, this);
  687. eventBus.off(GameEvents.GAME_SUCCESS, this.onGameEndEvent, this);
  688. eventBus.off(GameEvents.GAME_DEFEAT, this.onGameEndEvent, this);
  689. // 清空状态
  690. this.pausedEnemyStates.clear();
  691. }
  692. }