GroundBurnArea.ts 14 KB

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