EnemyController.ts 29 KB

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