GroundBurnArea.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. import { _decorator, Component, Node, Vec3, find, BoxCollider2D, RigidBody2D, Collider2D, Contact2DType, IPhysics2DContact, ERigidBody2DType } from 'cc';
  2. import { sp } from 'cc';
  3. import EventBus, { GameEvents } from '../../Core/EventBus';
  4. import { EnemyAttackStateManager } from '../EnemyAttackStateManager';
  5. import { GroundBurnAreaManager } from './GroundBurnAreaManager';
  6. import { PhysicsManager } from '../../Core/PhysicsManager';
  7. const { ccclass, property } = _decorator;
  8. /**
  9. * 地面燃烧区域组件
  10. * 用于在地面创建固定范围的燃烧区域,对进入区域的敌人造成持续伤害
  11. */
  12. @ccclass('GroundBurnArea')
  13. export class GroundBurnArea extends Component {
  14. private _duration: number = 0;
  15. private _damage: number = 0;
  16. private _tickInterval: number = 0;
  17. private _isActive: boolean = false;
  18. private _burnEffectNode: Node | null = null; // 燃烧特效节点
  19. private _enemiesInArea: Set<Node> = new Set(); // 区域内的敌人
  20. private _damageTimer: number = 0; // 伤害计时器
  21. /**
  22. * 组件加载时的初始化
  23. */
  24. protected onLoad(): void {
  25. // 立即禁用RigidBody2D组件,防止在物理世界未准备好时激活
  26. const rigidBody = this.node.getComponent(RigidBody2D);
  27. if (rigidBody) {
  28. rigidBody.enabled = false;
  29. }
  30. // 也禁用BoxCollider2D组件,确保完全避免物理系统访问
  31. const collider = this.node.getComponent(BoxCollider2D);
  32. if (collider) {
  33. collider.enabled = false;
  34. }
  35. }
  36. /**
  37. * 初始化地面燃烧区域
  38. * @param position 燃烧区域位置
  39. * @param duration 持续时间(秒)
  40. * @param damage 每次伤害值
  41. * @param tickInterval 伤害间隔(秒)
  42. */
  43. public initGroundBurnArea(position: Vec3, duration: number, damage: number, tickInterval: number): void {
  44. this._duration = duration;
  45. this._damage = damage;
  46. this._tickInterval = tickInterval;
  47. this._isActive = true;
  48. this._damageTimer = 0;
  49. // 设置节点位置
  50. this.node.setWorldPosition(position);
  51. // 获取预制体中的火焰特效节点
  52. this.setupFlameEffect();
  53. // 先禁用RigidBody2D组件,避免在物理世界未准备好时激活
  54. const rigidBody = this.node.getComponent(RigidBody2D);
  55. if (rigidBody) {
  56. rigidBody.enabled = false;
  57. }
  58. // 设置碰撞器
  59. this.setupCollider();
  60. console.log(`[GroundBurnArea] 初始化地面燃烧区域 - 位置: (${position.x.toFixed(1)}, ${position.y.toFixed(1)}), 持续时间: ${duration}秒, 伤害: ${damage}, 间隔: ${tickInterval}秒`);
  61. // 启动燃烧区域
  62. this.startBurnArea();
  63. }
  64. /**
  65. * 设置碰撞器
  66. */
  67. private setupCollider(): void {
  68. // 延迟设置碰撞器,确保物理系统已初始化
  69. this.scheduleOnce(() => {
  70. this.trySetupCollider();
  71. }, 0.2); // 增加延迟时间到0.2秒
  72. }
  73. private trySetupCollider(): void {
  74. // 检查物理系统是否已初始化
  75. const physicsManager = PhysicsManager.getInstance();
  76. if (!physicsManager) {
  77. console.warn('[GroundBurnArea] 物理系统未初始化,延迟重试');
  78. // 延迟重试
  79. this.scheduleOnce(() => {
  80. this.trySetupCollider();
  81. }, 0.1);
  82. return;
  83. }
  84. const physicsSystem = physicsManager.getSystem();
  85. if (!physicsSystem || !physicsSystem.enable) {
  86. console.warn('[GroundBurnArea] 物理系统未启用,延迟重试');
  87. // 延迟重试
  88. this.scheduleOnce(() => {
  89. this.trySetupCollider();
  90. }, 0.1);
  91. return;
  92. }
  93. // 获取碰撞器组件
  94. const collider = this.node.getComponent(BoxCollider2D);
  95. if (!collider) {
  96. console.error('[GroundBurnArea] 未找到 BoxCollider2D 组件');
  97. return;
  98. }
  99. // 重新启用碰撞器组件
  100. collider.enabled = true;
  101. // 获取刚体组件并确保启用碰撞监听
  102. const rigidBody = this.node.getComponent(RigidBody2D);
  103. if (rigidBody) {
  104. try {
  105. // 保持预制体中的刚体类型配置,不在代码中修改
  106. rigidBody.enabledContactListener = true;
  107. // 重新启用RigidBody2D组件
  108. rigidBody.enabled = true;
  109. } catch (error) {
  110. console.error('[GroundBurnArea] 设置刚体组件时出错:', error);
  111. // 如果设置失败,再次延迟重试
  112. this.scheduleOnce(() => {
  113. this.trySetupCollider();
  114. }, 0.1);
  115. return;
  116. }
  117. }
  118. // 监听碰撞事件(移除之前的监听器避免重复)
  119. collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
  120. collider.off(Contact2DType.END_CONTACT, this.onEndContact, this);
  121. collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
  122. collider.on(Contact2DType.END_CONTACT, this.onEndContact, this);
  123. console.log(`[GroundBurnArea] 碰撞器设置完成,碰撞器启用: ${collider.enabled}, 刚体启用: ${rigidBody?.enabled}`);
  124. // 验证碰撞器设置
  125. if (!collider.enabled) {
  126. console.error('[GroundBurnArea] 碰撞器未启用!');
  127. }
  128. if (rigidBody && !rigidBody.enabled) {
  129. console.error('[GroundBurnArea] 刚体未启用!');
  130. }
  131. if (rigidBody && !rigidBody.enabledContactListener) {
  132. console.error('[GroundBurnArea] 刚体碰撞监听未启用!');
  133. }
  134. }
  135. /**
  136. * 敌人进入燃烧区域
  137. */
  138. private onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null): void {
  139. const contactNode = otherCollider.node;
  140. const enemyRoot = this.resolveEnemyRootNode(contactNode);
  141. console.log(`[GroundBurnArea] 碰撞检测 - 子节点: ${contactNode.name}, 根节点: ${enemyRoot?.name || 'null'}`);
  142. // 使用根节点进行判定与记录
  143. if (enemyRoot) {
  144. const attackStateManager = EnemyAttackStateManager.getInstance();
  145. if (attackStateManager && !attackStateManager.isEnemyAttackable(enemyRoot)) {
  146. console.log(`[GroundBurnArea] 敌人不可攻击(隐身等),不加入区域: ${enemyRoot.name}`);
  147. return;
  148. }
  149. this._enemiesInArea.add(enemyRoot);
  150. console.log(`[GroundBurnArea] 敌人进入燃烧区域: ${enemyRoot.name}, 区域内敌人数量: ${this._enemiesInArea.size}`);
  151. } else {
  152. console.log(`[GroundBurnArea] 非敌人节点进入燃烧区域: ${contactNode.name}`);
  153. }
  154. }
  155. /**
  156. * 敌人离开燃烧区域
  157. */
  158. private onEndContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null): void {
  159. const contactNode = otherCollider.node;
  160. const enemyRoot = this.resolveEnemyRootNode(contactNode);
  161. if (enemyRoot) {
  162. this._enemiesInArea.delete(enemyRoot);
  163. console.log(`[GroundBurnArea] 敌人离开燃烧区域: ${enemyRoot.name}, 区域内敌人数量: ${this._enemiesInArea.size}`);
  164. }
  165. }
  166. private resolveEnemyRootNode(node: Node): Node | null {
  167. if (!node) return null;
  168. // 碰到 EnemySprite 时取父节点(根节点)
  169. if (node.name === 'EnemySprite' && node.parent && node.parent.getComponent('EnemyInstance')) {
  170. return node.parent;
  171. }
  172. // 兼容直接碰到敌人根节点的情况
  173. if (node.getComponent('EnemyInstance')) {
  174. return node;
  175. }
  176. return null;
  177. }
  178. /**
  179. * 检查是否是敌人节点
  180. */
  181. private isEnemyNode(node: Node): boolean {
  182. if (!node || !node.name) {
  183. return false;
  184. }
  185. // 检查是否为EnemySprite子节点
  186. if (node.name === 'EnemySprite' && node.parent) {
  187. const hasEnemyComponent = node.parent.getComponent('EnemyInstance') !== null;
  188. console.log(`[GroundBurnArea] EnemySprite检测 - 节点: ${node.name}, 父节点: ${node.parent.name}, 有EnemyInstance组件: ${hasEnemyComponent}`);
  189. return hasEnemyComponent;
  190. }
  191. // 检查节点名称
  192. const nameCheck = (
  193. node.name.includes('Enemy') ||
  194. node.name.includes('enemy') ||
  195. node.name.includes('僵尸') ||
  196. node.name.includes('Zombie') ||
  197. node.name.includes('zombie')
  198. );
  199. // 检查父节点名称
  200. const parentCheck = (
  201. node.parent?.name === 'enemyContainer' ||
  202. node.parent?.name === 'EnemyContainer' ||
  203. node.parent?.name?.includes('enemy') ||
  204. node.parent?.name?.includes('Enemy')
  205. );
  206. // 检查是否有EnemyInstance组件
  207. const componentCheck = node.getComponent('EnemyInstance') !== null;
  208. const isEnemy = nameCheck || parentCheck || componentCheck;
  209. console.log(`[GroundBurnArea] 敌人检测 - 节点: ${node.name}, 父节点: ${node.parent?.name || 'null'}, 名称检查: ${nameCheck}, 父节点检查: ${parentCheck}, 组件检查: ${componentCheck}, 是敌人: ${isEnemy}`);
  210. return isEnemy;
  211. }
  212. /**
  213. * 启动燃烧区域
  214. */
  215. private startBurnArea(): void {
  216. if (!this._isActive) {
  217. return;
  218. }
  219. console.log(`[GroundBurnArea] 启动地面燃烧区域 - 持续时间: ${this._duration}秒`);
  220. // 启动更新循环
  221. this.schedule(this.updateBurnArea, 0.1); // 每0.1秒更新一次
  222. // 设置区域销毁定时器
  223. this.scheduleOnce(() => {
  224. this.destroyBurnArea();
  225. }, this._duration);
  226. }
  227. /**
  228. * 更新燃烧区域
  229. */
  230. private updateBurnArea(deltaTime: number): void {
  231. if (!this._isActive) {
  232. return;
  233. }
  234. // 更新伤害计时器
  235. this._damageTimer += deltaTime;
  236. // 检查是否到了造成伤害的时间
  237. if (this._damageTimer >= this._tickInterval) {
  238. console.log(`[GroundBurnArea] 燃烧区域伤害检查 - 区域内敌人数量: ${this._enemiesInArea.size}, 伤害值: ${this._damage}`);
  239. this.dealDamageToEnemiesInArea();
  240. this._damageTimer = 0;
  241. }
  242. // 清理无效的敌人节点
  243. this.cleanupInvalidEnemies();
  244. }
  245. /**
  246. * 对区域内的敌人造成伤害
  247. */
  248. private dealDamageToEnemiesInArea(): void {
  249. if (this._enemiesInArea.size === 0) {
  250. return;
  251. }
  252. console.log(`[GroundBurnArea] 对区域内 ${this._enemiesInArea.size} 个敌人造成燃烧伤害: ${this._damage}`);
  253. const eventBus = EventBus.getInstance();
  254. const attackStateManager = EnemyAttackStateManager.getInstance();
  255. // 对每个区域内的敌人造成伤害
  256. for (const enemyNode of this._enemiesInArea) {
  257. if (enemyNode && enemyNode.isValid) {
  258. // 检查敌人是否处于漂移状态
  259. const enemyInstance = enemyNode.getComponent('EnemyInstance') as any;
  260. if (enemyInstance && enemyInstance.isDrifting()) {
  261. console.log(`[GroundBurnArea] 敌人 ${enemyNode.name} 处于漂移状态,跳过燃烧伤害`);
  262. continue;
  263. }
  264. // 检查敌人是否可攻击(隐身等状态不可受伤害)
  265. if (attackStateManager && !attackStateManager.isEnemyAttackable(enemyNode)) {
  266. console.log(`[GroundBurnArea] 敌人不可攻击,跳过燃烧伤害: ${enemyNode.name}`);
  267. continue;
  268. }
  269. const damageData = {
  270. enemyNode: enemyNode,
  271. damage: this._damage,
  272. isCritical: false,
  273. source: 'GroundBurnArea'
  274. };
  275. eventBus.emit(GameEvents.APPLY_DAMAGE_TO_ENEMY, damageData);
  276. }
  277. }
  278. }
  279. /**
  280. * 清理无效的敌人节点
  281. */
  282. private cleanupInvalidEnemies(): void {
  283. const invalidEnemies: Node[] = [];
  284. for (const enemyNode of this._enemiesInArea) {
  285. if (!enemyNode || !enemyNode.isValid) {
  286. invalidEnemies.push(enemyNode);
  287. }
  288. }
  289. // 移除无效的敌人
  290. for (const invalidEnemy of invalidEnemies) {
  291. this._enemiesInArea.delete(invalidEnemy);
  292. }
  293. }
  294. /**
  295. * 设置火焰特效
  296. */
  297. private setupFlameEffect(): void {
  298. // 查找预制体中的 FlameEffect 子节点
  299. const flameEffectNode = this.node.getChildByName('FlameEffect');
  300. if (flameEffectNode) {
  301. this._burnEffectNode = flameEffectNode;
  302. // 启动火焰特效动画
  303. const skeleton = flameEffectNode.getComponent(sp.Skeleton);
  304. if (skeleton) {
  305. skeleton.setAnimation(0, 'animation', true);
  306. console.log('[GroundBurnArea] 火焰特效节点已找到并开始播放动画');
  307. } else {
  308. console.warn('[GroundBurnArea] FlameEffect 节点缺少 sp.Skeleton 组件');
  309. }
  310. } else {
  311. console.warn('[GroundBurnArea] 未找到 FlameEffect 子节点,请检查预制体配置');
  312. }
  313. }
  314. /**
  315. * 设置燃烧特效节点
  316. */
  317. public setBurnEffectNode(effectNode: Node): void {
  318. this._burnEffectNode = effectNode;
  319. }
  320. /**
  321. * 销毁燃烧区域
  322. */
  323. public destroyBurnArea(): void {
  324. console.log(`[GroundBurnArea] 销毁地面燃烧区域`);
  325. this._isActive = false;
  326. this.unscheduleAllCallbacks();
  327. // 清空敌人列表
  328. this._enemiesInArea.clear();
  329. // 销毁燃烧特效节点
  330. if (this._burnEffectNode && this._burnEffectNode.isValid) {
  331. console.log(`[GroundBurnArea] 销毁燃烧特效节点`);
  332. this._burnEffectNode.destroy();
  333. this._burnEffectNode = null;
  334. }
  335. // 通知管理器移除此燃烧区域
  336. const manager = GroundBurnAreaManager.getInstance();
  337. if (manager) {
  338. manager.removeBurnArea(this.node);
  339. }
  340. // 销毁节点
  341. if (this.node && this.node.isValid) {
  342. this.node.destroy();
  343. }
  344. }
  345. /**
  346. * 获取燃烧区域是否激活
  347. */
  348. public isActive(): boolean {
  349. return this._isActive;
  350. }
  351. /**
  352. * 获取区域内敌人数量
  353. */
  354. public getEnemyCount(): number {
  355. return this._enemiesInArea.size;
  356. }
  357. /**
  358. * 组件销毁时清理资源
  359. */
  360. protected onDestroy(): void {
  361. this._isActive = false;
  362. this.unscheduleAllCallbacks();
  363. // 清空敌人列表
  364. this._enemiesInArea.clear();
  365. // 清理燃烧特效节点
  366. if (this._burnEffectNode && this._burnEffectNode.isValid) {
  367. console.log(`[GroundBurnArea] 组件销毁时清理燃烧特效节点`);
  368. this._burnEffectNode.destroy();
  369. this._burnEffectNode = null;
  370. }
  371. // 通知管理器移除此燃烧区域
  372. const manager = GroundBurnAreaManager.getInstance();
  373. if (manager) {
  374. manager.removeBurnArea(this.node);
  375. }
  376. console.log(`[GroundBurnArea] 地面燃烧区域组件已销毁`);
  377. }
  378. }