BulletController.ts 9.9 KB

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