BulletController.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import { _decorator, Component, Node, Vec2, Vec3, find, RigidBody2D, Collider2D, Contact2DType, IPhysics2DContact, instantiate, Prefab, UITransform } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. /**
  4. * 子弹控制器 - 安全版本
  5. *
  6. * 设计原则:
  7. * 1. 在子弹添加到场景之前设置所有物理属性
  8. * 2. 延迟启用物理组件,避免m_world为null错误
  9. * 3. 确保物理组件在正确的时机激活
  10. */
  11. @ccclass('BulletController')
  12. export class BulletController extends Component {
  13. @property
  14. public speed: number = 300;
  15. @property
  16. public damage: number = 10;
  17. @property
  18. public lifetime: number = 5;
  19. private targetEnemy: Node = null;
  20. private rigidBody: RigidBody2D = null;
  21. private lifeTimer: number = 0;
  22. private direction: Vec3 = null;
  23. private firePosition: Vec3 = null;
  24. private isInitialized: boolean = false;
  25. private needsPhysicsSetup: boolean = false;
  26. /**
  27. * 设置子弹的发射位置
  28. * @param position 发射位置(世界坐标)
  29. */
  30. public setFirePosition(position: Vec3) {
  31. this.firePosition = position.clone();
  32. this.needsPhysicsSetup = true;
  33. console.log('🎯 子弹发射位置已设置:', this.firePosition);
  34. }
  35. start() {
  36. console.log('🚀 子弹start()开始...');
  37. // 设置生命周期
  38. this.lifeTimer = this.lifetime;
  39. // 如果需要设置物理属性,延迟处理
  40. if (this.needsPhysicsSetup) {
  41. // 使用更短的延迟,确保物理世界准备好
  42. this.scheduleOnce(() => {
  43. this.setupPhysics();
  44. }, 0.05);
  45. }
  46. console.log('✅ 子弹start()完成');
  47. }
  48. /**
  49. * 设置物理属性
  50. */
  51. private setupPhysics() {
  52. console.log('🔧 开始设置子弹物理属性...');
  53. // 获取物理组件
  54. this.rigidBody = this.node.getComponent(RigidBody2D);
  55. const collider = this.node.getComponent(Collider2D);
  56. if (!this.rigidBody) {
  57. console.error('❌ 子弹预制体缺少RigidBody2D组件');
  58. return;
  59. }
  60. if (!collider) {
  61. console.error('❌ 子弹预制体缺少Collider2D组件');
  62. return;
  63. }
  64. console.log('✅ 物理组件获取成功');
  65. // 确保物理组件设置正确
  66. this.rigidBody.enabledContactListener = true;
  67. this.rigidBody.gravityScale = 0; // 不受重力影响
  68. this.rigidBody.linearDamping = 0; // 无阻尼
  69. this.rigidBody.angularDamping = 0; // 无角阻尼
  70. // 绑定碰撞事件
  71. collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
  72. console.log('✅ 碰撞事件已绑定');
  73. // 设置子弹位置
  74. if (this.firePosition) {
  75. this.setPositionInGameArea();
  76. }
  77. // 初始化方向和速度
  78. this.initializeDirection();
  79. this.isInitialized = true;
  80. console.log('✅ 子弹物理属性设置完成');
  81. }
  82. /**
  83. * 在GameArea中设置子弹位置
  84. */
  85. private setPositionInGameArea() {
  86. const gameArea = find('Canvas/GameLevelUI/GameArea');
  87. if (gameArea) {
  88. const gameAreaTransform = gameArea.getComponent(UITransform);
  89. if (gameAreaTransform) {
  90. const localPos = gameAreaTransform.convertToNodeSpaceAR(this.firePosition);
  91. this.node.position = localPos;
  92. console.log('✅ 子弹位置已设置:', localPos);
  93. }
  94. }
  95. }
  96. /**
  97. * 初始化子弹方向
  98. */
  99. private initializeDirection() {
  100. console.log('🎯 开始初始化子弹方向...');
  101. // 寻找最近的敌人
  102. this.findNearestEnemy();
  103. // 设置方向
  104. if (this.targetEnemy) {
  105. this.setDirectionToTarget();
  106. } else {
  107. // 随机方向
  108. const randomAngle = Math.random() * Math.PI * 2;
  109. this.direction = new Vec3(Math.cos(randomAngle), Math.sin(randomAngle), 0);
  110. console.log('🎲 使用随机方向');
  111. }
  112. // 应用速度
  113. this.applyVelocity();
  114. }
  115. // 寻找最近的敌人
  116. private findNearestEnemy() {
  117. const enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
  118. if (!enemyContainer) {
  119. console.log('❌ 未找到敌人容器');
  120. return;
  121. }
  122. const enemies = enemyContainer.children.filter(child =>
  123. child.active &&
  124. (child.name.toLowerCase().includes('enemy') ||
  125. child.getComponent('EnemyInstance') !== null)
  126. );
  127. if (enemies.length === 0) {
  128. console.log('❌ 没有找到敌人');
  129. return;
  130. }
  131. let nearestEnemy: Node = null;
  132. let nearestDistance = Infinity;
  133. const bulletPos = this.node.worldPosition;
  134. for (const enemy of enemies) {
  135. const distance = Vec3.distance(bulletPos, enemy.worldPosition);
  136. if (distance < nearestDistance) {
  137. nearestDistance = distance;
  138. nearestEnemy = enemy;
  139. }
  140. }
  141. if (nearestEnemy) {
  142. this.targetEnemy = nearestEnemy;
  143. console.log(`🎯 锁定目标: ${nearestEnemy.name}`);
  144. }
  145. }
  146. // 设置朝向目标的方向
  147. private setDirectionToTarget() {
  148. if (!this.targetEnemy) return;
  149. const targetPos = this.targetEnemy.worldPosition;
  150. const currentPos = this.node.worldPosition;
  151. this.direction = targetPos.subtract(currentPos).normalize();
  152. console.log('🎯 方向已设置,朝向目标敌人');
  153. }
  154. // 应用速度
  155. private applyVelocity() {
  156. if (!this.rigidBody || !this.direction) {
  157. console.error('❌ 无法应用速度:缺少刚体或方向');
  158. return;
  159. }
  160. const velocity = new Vec2(
  161. this.direction.x * this.speed,
  162. this.direction.y * this.speed
  163. );
  164. this.rigidBody.linearVelocity = velocity;
  165. console.log('✅ 子弹速度已应用:', velocity);
  166. }
  167. // 碰撞检测
  168. onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
  169. const otherNode = otherCollider.node;
  170. console.log('💥 子弹碰撞:', otherNode.name);
  171. // 检查是否击中敌人
  172. if (this.isEnemyNode(otherNode)) {
  173. console.log('🎯 击中敌人:', otherNode.name);
  174. this.damageEnemy(otherNode);
  175. this.node.destroy();
  176. } else if (otherNode.name.toLowerCase().includes('wall')) {
  177. console.log('🧱 击中墙体,销毁子弹');
  178. this.node.destroy();
  179. }
  180. }
  181. // 判断是否为敌人节点
  182. private isEnemyNode(node: Node): boolean {
  183. const name = node.name.toLowerCase();
  184. return name.includes('enemy') ||
  185. name.includes('敌人') ||
  186. node.getComponent('EnemyInstance') !== null ||
  187. node.getComponent('EnemyComponent') !== null;
  188. }
  189. // 对敌人造成伤害
  190. private damageEnemy(enemyNode: Node) {
  191. console.log('⚔️ 对敌人造成伤害:', this.damage);
  192. // 尝试调用敌人的受伤方法
  193. const enemyInstance = enemyNode.getComponent('EnemyInstance') as any;
  194. if (enemyInstance) {
  195. if (typeof enemyInstance.takeDamage === 'function') {
  196. enemyInstance.takeDamage(this.damage);
  197. return;
  198. }
  199. if (typeof enemyInstance.health === 'number') {
  200. enemyInstance.health -= this.damage;
  201. if (enemyInstance.health <= 0) {
  202. enemyNode.destroy();
  203. }
  204. return;
  205. }
  206. }
  207. // 备用方案:通过EnemyController
  208. const enemyController = find('Canvas/GameLevelUI/EnemyController')?.getComponent('EnemyController') as any;
  209. if (enemyController && typeof enemyController.damageEnemy === 'function') {
  210. enemyController.damageEnemy(enemyNode, this.damage);
  211. }
  212. }
  213. update(dt: number) {
  214. // 只有在初始化完成后才进行更新
  215. if (!this.isInitialized) return;
  216. // 生命周期倒计时
  217. this.lifeTimer -= dt;
  218. if (this.lifeTimer <= 0) {
  219. this.node.destroy();
  220. }
  221. }
  222. }