BulletController.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. import { _decorator, Component, Node, Vec2, Vec3, find, RigidBody2D, Collider2D, Contact2DType, IPhysics2DContact } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. @ccclass('BulletController')
  4. export class BulletController extends Component {
  5. @property
  6. public speed: number = 300;
  7. @property
  8. public damage: number = 10;
  9. @property
  10. public lifetime: number = 5;
  11. private targetEnemy: Node = null;
  12. private rigidBody: RigidBody2D = null;
  13. private lifeTimer: number = 0;
  14. private initialDirection: Vec2 = null; // 存储初始方向
  15. private direction: Vec3 = null;
  16. start() {
  17. console.log('🚀 子弹开始初始化...');
  18. // 调试碰撞组信息
  19. this.debugCollisionGroups();
  20. // 获取刚体组件
  21. this.rigidBody = this.node.getChildByName('Icon')?.getComponent(RigidBody2D);
  22. if (!this.rigidBody) {
  23. console.error('❌ 子弹找不到Icon子节点的RigidBody2D组件');
  24. return;
  25. }
  26. console.log('✅ 子弹刚体组件获取成功');
  27. // 重置刚体的预设速度
  28. this.rigidBody.linearVelocity = new Vec2(0, 0);
  29. console.log('🔧 已重置刚体预设速度');
  30. // 设置刚体参数
  31. this.rigidBody.gravityScale = 0; // 不受重力影响
  32. this.rigidBody.linearDamping = 0; // 无阻尼
  33. this.rigidBody.angularDamping = 0; // 无角阻尼
  34. this.rigidBody.allowSleep = false; // 不允许休眠
  35. // 设置子弹生命周期
  36. this.lifeTimer = this.lifetime;
  37. // 延迟一帧后再设置方向和速度,确保节点完全初始化
  38. this.scheduleOnce(() => {
  39. this.initializeBulletDirection();
  40. }, 0.02);
  41. // 注册碰撞事件
  42. const collider = this.node.getChildByName('Icon')?.getComponent(Collider2D);
  43. if (collider) {
  44. collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
  45. console.log('✅ 子弹碰撞事件已注册');
  46. } else {
  47. console.error('❌ 子弹找不到Icon子节点的Collider2D组件');
  48. }
  49. console.log('🚀 子弹初始化完成');
  50. }
  51. // 初始化子弹方向
  52. private initializeBulletDirection() {
  53. console.log('🎯 开始初始化子弹方向...');
  54. // 如果没有设置目标,寻找最近的敌人
  55. if (!this.targetEnemy) {
  56. this.findNearestEnemy();
  57. }
  58. // 设置初始方向
  59. if (this.targetEnemy) {
  60. console.log('🎯 设置子弹朝向目标敌人');
  61. this.setDirectionToTarget();
  62. } else {
  63. // 没有敌人目标时,设置随机方向
  64. console.log('🎲 没有敌人目标,设置随机发射方向');
  65. const randomAngle = Math.random() * Math.PI * 2;
  66. this.direction = new Vec3(Math.cos(randomAngle), Math.sin(randomAngle), 0);
  67. console.log('随机方向:', this.direction);
  68. }
  69. // 设置初始速度
  70. this.updateVelocity();
  71. // 再次确认速度设置
  72. this.scheduleOnce(() => {
  73. if (this.rigidBody) {
  74. const currentVelocity = this.rigidBody.linearVelocity;
  75. console.log('🔍 最终确认子弹速度:', currentVelocity);
  76. const speed = Math.sqrt(currentVelocity.x * currentVelocity.x + currentVelocity.y * currentVelocity.y);
  77. console.log('🔍 速度大小:', speed);
  78. if (speed < 50) {
  79. console.warn('⚠️ 子弹速度过低,重新设置...');
  80. this.updateVelocity();
  81. }
  82. }
  83. }, 0.1);
  84. }
  85. // 寻找最近的敌人
  86. private findNearestEnemy() {
  87. console.log('🔍 子弹开始寻找最近的敌人...');
  88. // 主要路径:Canvas/GameLevelUI/enemyContainer
  89. const primaryPath = 'Canvas/GameLevelUI/enemyContainer';
  90. let enemyContainer = find(primaryPath);
  91. if (enemyContainer) {
  92. console.log('✅ 找到主要敌人容器:', primaryPath);
  93. } else {
  94. console.log('❌ 主要路径未找到,尝试备用路径...');
  95. // 备用路径
  96. const backupPaths = [
  97. 'Canvas/enemyContainer',
  98. 'Canvas/EnemyContainer',
  99. 'Canvas/GameLevelUI/EnemyContainer',
  100. 'Canvas/GameArea/enemyContainer'
  101. ];
  102. for (const path of backupPaths) {
  103. enemyContainer = find(path);
  104. if (enemyContainer) {
  105. console.log('✅ 找到备用敌人容器:', path);
  106. break;
  107. }
  108. }
  109. }
  110. if (!enemyContainer) {
  111. console.log('❌ 未找到任何敌人容器,尝试在整个场景中搜索敌人节点...');
  112. // 如果找不到容器,尝试直接搜索敌人节点
  113. const allEnemies = this.findAllEnemyNodes();
  114. if (allEnemies.length > 0) {
  115. this.selectNearestFromEnemies(allEnemies);
  116. } else {
  117. console.log('❌ 场景中没有找到任何敌人,子弹将随机发射');
  118. }
  119. return;
  120. }
  121. // 打印容器详细信息
  122. console.log('敌人容器详细信息:', {
  123. name: enemyContainer.name,
  124. childrenCount: enemyContainer.children.length,
  125. children: enemyContainer.children.map(child => ({
  126. name: child.name,
  127. active: child.active,
  128. hasEnemyInstance: !!child.getComponent('EnemyInstance'),
  129. hasEnemyComponent: !!child.getComponent('EnemyComponent')
  130. }))
  131. });
  132. // 在容器中查找敌人 - 使用更宽泛的匹配条件
  133. const enemies = enemyContainer.children.filter(child => {
  134. if (!child.active) return false; // 跳过非激活的节点
  135. const name = child.name.toLowerCase();
  136. const hasEnemyComponent = child.getComponent('EnemyInstance') !== null ||
  137. child.getComponent('EnemyComponent') !== null;
  138. return name === 'enemy' ||
  139. name.includes('enemy') ||
  140. name.includes('敌人') ||
  141. hasEnemyComponent ||
  142. name.startsWith('enemy') ||
  143. name.endsWith('enemy');
  144. });
  145. console.log(`在容器中找到 ${enemies.length} 个敌人:`, enemies.map(e => ({
  146. name: e.name,
  147. position: e.worldPosition
  148. })));
  149. if (enemies.length === 0) {
  150. console.log('❌ 敌人容器中没有找到敌人,尝试将所有子节点作为潜在目标...');
  151. // 如果没有找到明确的敌人,尝试将所有活动的子节点作为目标
  152. const allActiveChildren = enemyContainer.children.filter(child => child.active);
  153. if (allActiveChildren.length > 0) {
  154. console.log(`🔍 将 ${allActiveChildren.length} 个活动子节点作为潜在目标`);
  155. this.selectNearestFromEnemies(allActiveChildren);
  156. } else {
  157. console.log('❌ 容器中没有任何活动节点,子弹将随机发射');
  158. }
  159. return;
  160. }
  161. this.selectNearestFromEnemies(enemies);
  162. }
  163. // 在整个场景中搜索敌人节点
  164. private findAllEnemyNodes(): Node[] {
  165. const canvas = find('Canvas');
  166. if (!canvas) return [];
  167. const enemies: Node[] = [];
  168. this.searchEnemiesRecursive(canvas, enemies);
  169. console.log(`在整个场景中找到 ${enemies.length} 个敌人`);
  170. return enemies;
  171. }
  172. // 递归搜索敌人节点
  173. private searchEnemiesRecursive(node: Node, enemies: Node[]) {
  174. // 检查当前节点是否为敌人
  175. if (node.active) {
  176. const name = node.name.toLowerCase();
  177. const hasEnemyComponent = node.getComponent('EnemyInstance') !== null ||
  178. node.getComponent('EnemyComponent') !== null;
  179. if (name === 'enemy' ||
  180. name.includes('enemy') ||
  181. name.includes('敌人') ||
  182. hasEnemyComponent ||
  183. name.startsWith('enemy') ||
  184. name.endsWith('enemy')) {
  185. enemies.push(node);
  186. console.log(`🎯 找到敌人节点: ${node.name} (路径: ${this.getNodePath(node)})`);
  187. }
  188. }
  189. // 递归搜索子节点
  190. for (const child of node.children) {
  191. this.searchEnemiesRecursive(child, enemies);
  192. }
  193. }
  194. // 获取节点的完整路径
  195. private getNodePath(node: Node): string {
  196. let path = node.name;
  197. let current = node;
  198. while (current.parent) {
  199. current = current.parent;
  200. path = current.name + '/' + path;
  201. }
  202. return path;
  203. }
  204. // 从敌人列表中选择最近的
  205. private selectNearestFromEnemies(enemies: Node[]) {
  206. let nearestEnemy: Node = null;
  207. let nearestDistance = Infinity;
  208. const bulletPos = this.node.worldPosition;
  209. console.log('子弹当前位置:', bulletPos);
  210. for (const enemy of enemies) {
  211. try {
  212. const enemyPos = enemy.worldPosition;
  213. const distance = Vec3.distance(bulletPos, enemyPos);
  214. console.log(`敌人 ${enemy.name} 距离: ${distance.toFixed(1)}`);
  215. if (distance < nearestDistance) {
  216. nearestDistance = distance;
  217. nearestEnemy = enemy;
  218. }
  219. } catch (error) {
  220. console.error('计算敌人距离时出错:', error);
  221. }
  222. }
  223. if (nearestEnemy) {
  224. this.targetEnemy = nearestEnemy;
  225. console.log(`✅ 选定目标敌人: ${nearestEnemy.name}, 距离: ${nearestDistance.toFixed(1)}`);
  226. } else {
  227. console.log('❌ 无法确定最近敌人,子弹将随机发射');
  228. }
  229. }
  230. // 设置朝向目标的方向
  231. private setDirectionToTarget() {
  232. if (!this.targetEnemy) return;
  233. try {
  234. const targetPos = this.targetEnemy.worldPosition;
  235. const currentPos = this.node.worldPosition;
  236. console.log('子弹位置:', currentPos);
  237. console.log('目标敌人位置:', targetPos);
  238. this.direction = targetPos.subtract(currentPos).normalize();
  239. console.log('计算出的发射方向:', this.direction);
  240. } catch (error) {
  241. console.error('❌ 计算子弹方向时出错:', error);
  242. // 备用方案:随机方向
  243. const randomAngle = Math.random() * Math.PI * 2;
  244. this.direction = new Vec3(Math.cos(randomAngle), Math.sin(randomAngle), 0);
  245. console.log('使用备用随机方向:', this.direction);
  246. }
  247. }
  248. // 更新子弹速度方向
  249. updateVelocity() {
  250. if (!this.rigidBody) {
  251. console.error('❌ 刚体组件不存在,无法设置速度');
  252. return;
  253. }
  254. if (!this.direction) {
  255. console.error('❌ 子弹方向未设置,无法更新速度');
  256. return;
  257. }
  258. // 确保方向向量已归一化
  259. const normalizedDirection = this.direction.clone().normalize();
  260. // 计算速度向量
  261. const velocity = new Vec2(
  262. normalizedDirection.x * this.speed,
  263. normalizedDirection.y * this.speed
  264. );
  265. console.log('🎯 设置子弹速度:', {
  266. direction: normalizedDirection,
  267. speed: this.speed,
  268. velocity: velocity
  269. });
  270. // 强制设置速度
  271. this.rigidBody.linearVelocity = velocity;
  272. // 立即验证速度是否设置成功
  273. const actualVelocity = this.rigidBody.linearVelocity;
  274. const actualSpeed = Math.sqrt(actualVelocity.x * actualVelocity.x + actualVelocity.y * actualVelocity.y);
  275. console.log('✅ 子弹速度验证:', {
  276. 设置的速度: velocity,
  277. 实际速度: actualVelocity,
  278. 速度大小: actualSpeed.toFixed(2)
  279. });
  280. if (Math.abs(actualSpeed - this.speed) > 10) {
  281. console.warn('⚠️ 速度设置可能失败,实际速度与期望不符');
  282. // 尝试再次设置
  283. this.scheduleOnce(() => {
  284. this.rigidBody.linearVelocity = velocity;
  285. console.log('🔄 重新设置子弹速度');
  286. }, 0.01);
  287. }
  288. }
  289. // 碰撞检测
  290. onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
  291. const otherNode = otherCollider.node;
  292. console.log('💥 子弹发生碰撞:', {
  293. 子弹: selfCollider.node.name,
  294. 碰撞对象: otherNode.name,
  295. 碰撞对象路径: this.getNodePath(otherNode),
  296. 碰撞组: otherCollider.group
  297. });
  298. // 检查是否击中敌人 - 使用更灵活的匹配条件
  299. const isEnemy = this.isEnemyNode(otherNode);
  300. if (isEnemy) {
  301. console.log('🎯 子弹击中敌人:', otherNode.name);
  302. // 尝试对敌人造成伤害
  303. this.damageEnemy(otherNode);
  304. // 销毁子弹
  305. console.log('💥 子弹击中目标,即将销毁');
  306. this.node.destroy();
  307. } else {
  308. console.log('🚫 子弹击中非敌人对象:', otherNode.name);
  309. // 如果击中墙体或其他障碍物,也销毁子弹
  310. if (otherNode.name.includes('Wall') || otherNode.name.includes('wall')) {
  311. console.log('🧱 子弹击中墙体,销毁子弹');
  312. this.node.destroy();
  313. }
  314. }
  315. }
  316. // 判断节点是否为敌人
  317. private isEnemyNode(node: Node): boolean {
  318. const name = node.name.toLowerCase();
  319. const hasEnemyComponent = node.getComponent('EnemyInstance') !== null ||
  320. node.getComponent('EnemyComponent') !== null;
  321. const isEnemyByName = name === 'enemy' ||
  322. name.includes('enemy') ||
  323. name.includes('敌人') ||
  324. name.startsWith('enemy') ||
  325. name.endsWith('enemy');
  326. console.log('🔍 敌人判定:', {
  327. 节点名称: node.name,
  328. 名称匹配: isEnemyByName,
  329. 组件匹配: hasEnemyComponent,
  330. 最终判定: isEnemyByName || hasEnemyComponent
  331. });
  332. return isEnemyByName || hasEnemyComponent;
  333. }
  334. // 对敌人造成伤害
  335. private damageEnemy(enemyNode: Node) {
  336. console.log('⚔️ 开始对敌人造成伤害:', enemyNode.name);
  337. // 方法1: 尝试通过EnemyController造成伤害
  338. const enemyController = find('Canvas/GameLevelUI/EnemyController')?.getComponent('EnemyController') as any;
  339. if (enemyController && typeof enemyController.damageEnemy === 'function') {
  340. try {
  341. enemyController.damageEnemy(enemyNode, this.damage);
  342. console.log('✅ 通过EnemyController造成伤害:', this.damage);
  343. return;
  344. } catch (error) {
  345. console.error('❌ EnemyController.damageEnemy调用失败:', error);
  346. }
  347. }
  348. // 方法2: 尝试直接调用敌人组件的受伤方法
  349. const enemyInstance = enemyNode.getComponent('EnemyInstance') as any;
  350. if (enemyInstance) {
  351. if (typeof enemyInstance.takeDamage === 'function') {
  352. try {
  353. enemyInstance.takeDamage(this.damage);
  354. console.log('✅ 通过EnemyInstance.takeDamage造成伤害:', this.damage);
  355. return;
  356. } catch (error) {
  357. console.error('❌ EnemyInstance.takeDamage调用失败:', error);
  358. }
  359. }
  360. if (typeof enemyInstance.damage === 'function') {
  361. try {
  362. enemyInstance.damage(this.damage);
  363. console.log('✅ 通过EnemyInstance.damage造成伤害:', this.damage);
  364. return;
  365. } catch (error) {
  366. console.error('❌ EnemyInstance.damage调用失败:', error);
  367. }
  368. }
  369. }
  370. // 方法3: 尝试其他可能的敌人组件
  371. const enemyComponent = enemyNode.getComponent('EnemyComponent') as any;
  372. if (enemyComponent && typeof enemyComponent.takeDamage === 'function') {
  373. try {
  374. enemyComponent.takeDamage(this.damage);
  375. console.log('✅ 通过EnemyComponent.takeDamage造成伤害:', this.damage);
  376. return;
  377. } catch (error) {
  378. console.error('❌ EnemyComponent.takeDamage调用失败:', error);
  379. }
  380. }
  381. // 方法4: 简单的生命值减少(如果敌人有health属性)
  382. if (enemyInstance && typeof enemyInstance.health === 'number') {
  383. enemyInstance.health -= this.damage;
  384. console.log('✅ 直接减少敌人生命值:', {
  385. 伤害: this.damage,
  386. 剩余生命: enemyInstance.health
  387. });
  388. // 如果生命值归零,尝试销毁敌人
  389. if (enemyInstance.health <= 0) {
  390. console.log('💀 敌人生命值归零,尝试销毁');
  391. if (typeof enemyInstance.die === 'function') {
  392. enemyInstance.die();
  393. } else {
  394. enemyNode.destroy();
  395. }
  396. }
  397. return;
  398. }
  399. console.warn('⚠️ 无法找到合适的方法对敌人造成伤害');
  400. console.log('敌人节点信息:', {
  401. name: enemyNode.name,
  402. components: enemyNode.components.map(c => c.constructor.name),
  403. EnemyInstance: !!enemyNode.getComponent('EnemyInstance'),
  404. EnemyComponent: !!enemyNode.getComponent('EnemyComponent')
  405. });
  406. }
  407. update(dt: number) {
  408. // 更新生命周期
  409. this.lifeTimer -= dt;
  410. if (this.lifeTimer <= 0) {
  411. this.node.destroy();
  412. return;
  413. }
  414. // 定期检查速度是否正确(每0.1秒检查一次)
  415. if (this.rigidBody && this.direction) {
  416. const currentVelocity = this.rigidBody.linearVelocity;
  417. const currentSpeed = Math.sqrt(currentVelocity.x * currentVelocity.x + currentVelocity.y * currentVelocity.y);
  418. // 如果速度过低,重新设置
  419. if (currentSpeed < this.speed * 0.8) {
  420. console.log('🔧 检测到子弹速度过低,重新设置...');
  421. this.updateVelocity();
  422. }
  423. }
  424. // 注意:不再每帧更新速度方向,子弹将沿初始方向直线移动
  425. }
  426. // 调试碰撞组信息
  427. private debugCollisionGroups() {
  428. const iconNode = this.node.getChildByName('Icon');
  429. if (iconNode) {
  430. const collider = iconNode.getComponent(Collider2D);
  431. const rigidBody = iconNode.getComponent(RigidBody2D);
  432. console.log('🔧 子弹碰撞组调试信息:', {
  433. 节点名称: iconNode.name,
  434. 碰撞体组: collider?.group,
  435. 刚体组: rigidBody?.group,
  436. 预期组: 3, // BULLET组
  437. 组名: 'BULLET'
  438. });
  439. // 检查碰撞矩阵
  440. const bulletGroup = 3; // BULLET
  441. const enemyGroup = 4; // ENEMY
  442. console.log('🎯 碰撞组配置:', {
  443. 子弹组: bulletGroup,
  444. 敌人组: enemyGroup,
  445. 提示: '子弹组3应该能与敌人组4碰撞'
  446. });
  447. }
  448. }
  449. }