BallController.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import { _decorator, Component, Node, Vec2, Vec3, UITransform, Collider2D, Contact2DType, IPhysics2DContact, RigidBody2D, Prefab, instantiate, find, CircleCollider2D } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. @ccclass('BallController')
  4. export class BallController extends Component {
  5. // 球的预制体
  6. @property({
  7. type: Prefab,
  8. tooltip: '拖拽Ball预制体到这里'
  9. })
  10. public ballPrefab: Prefab = null;
  11. // 球的移动速度
  12. @property
  13. public speed: number = 10;
  14. // 当前活动的球
  15. private activeBall: Node = null;
  16. // 球的方向向量
  17. private direction: Vec2 = new Vec2();
  18. // GameArea区域边界
  19. private gameBounds = {
  20. left: 0,
  21. right: 0,
  22. top: 0,
  23. bottom: 0
  24. };
  25. // 球的半径
  26. private radius: number = 0;
  27. // 是否已初始化
  28. private initialized: boolean = false;
  29. // 子弹预制体
  30. @property({
  31. type: Prefab,
  32. tooltip: '拖拽子弹预制体到这里'
  33. })
  34. public bulletPrefab: Prefab = null;
  35. // 子弹速度
  36. @property
  37. public bulletSpeed: number = 50;
  38. start() {
  39. // 计算游戏边界
  40. this.calculateGameBounds();
  41. }
  42. // 计算游戏边界(使用GameArea节点)
  43. calculateGameBounds() {
  44. // 获取GameArea节点
  45. const gameArea = find('Canvas/GameArea');
  46. if (!gameArea) {
  47. console.error('找不到GameArea节点');
  48. return;
  49. }
  50. const gameAreaUI = gameArea.getComponent(UITransform);
  51. if (!gameAreaUI) {
  52. console.error('GameArea节点没有UITransform组件');
  53. return;
  54. }
  55. // 获取GameArea的尺寸
  56. const areaWidth = gameAreaUI.width;
  57. const areaHeight = gameAreaUI.height;
  58. // 获取GameArea的世界坐标位置
  59. const worldPos = gameArea.worldPosition;
  60. // 计算GameArea的世界坐标边界
  61. this.gameBounds.left = worldPos.x - areaWidth / 2;
  62. this.gameBounds.right = worldPos.x + areaWidth / 2;
  63. this.gameBounds.bottom = worldPos.y - areaHeight / 2;
  64. this.gameBounds.top = worldPos.y + areaHeight / 2;
  65. console.log('GameArea Bounds:', this.gameBounds);
  66. }
  67. // 创建小球
  68. createBall() {
  69. if (!this.ballPrefab) {
  70. console.error('未设置Ball预制体');
  71. return;
  72. }
  73. // 如果已经有活动的球,先销毁它
  74. if (this.activeBall && this.activeBall.isValid) {
  75. this.activeBall.destroy();
  76. }
  77. // 实例化小球
  78. this.activeBall = instantiate(this.ballPrefab);
  79. // 将小球添加到GameArea中
  80. const gameArea = find('Canvas/GameArea');
  81. if (gameArea) {
  82. gameArea.addChild(this.activeBall);
  83. } else {
  84. this.node.addChild(this.activeBall);
  85. }
  86. // 随机位置(在GameArea范围内)
  87. this.positionBallRandomly();
  88. // 设置球的半径
  89. const transform = this.activeBall.getComponent(UITransform);
  90. if (transform) {
  91. this.radius = transform.width / 2;
  92. }
  93. // 确保有碰撞组件
  94. this.setupCollider();
  95. // 初始化方向
  96. this.initializeDirection();
  97. this.initialized = true;
  98. }
  99. // 随机位置小球
  100. positionBallRandomly() {
  101. if (!this.activeBall) return;
  102. const transform = this.activeBall.getComponent(UITransform);
  103. const ballRadius = transform ? transform.width / 2 : 25;
  104. // 计算可生成的范围(考虑小球半径,避免生成在边缘)
  105. const minX = this.gameBounds.left + ballRadius + 20; // 额外偏移,避免生成在边缘
  106. const maxX = this.gameBounds.right - ballRadius - 20;
  107. const minY = this.gameBounds.bottom + ballRadius + 20;
  108. const maxY = this.gameBounds.top - ballRadius - 20;
  109. // 随机生成位置
  110. const randomX = Math.random() * (maxX - minX) + minX;
  111. const randomY = Math.random() * (maxY - minY) + minY;
  112. // 将世界坐标转换为相对于GameArea的本地坐标
  113. const gameArea = find('Canvas/GameArea');
  114. if (gameArea) {
  115. const localPos = gameArea.getComponent(UITransform).convertToNodeSpaceAR(new Vec3(randomX, randomY, 0));
  116. this.activeBall.position = localPos;
  117. } else {
  118. // 直接设置位置(不太准确,但作为后备)
  119. this.activeBall.position = new Vec3(randomX - this.gameBounds.left, randomY - this.gameBounds.bottom, 0);
  120. }
  121. }
  122. // 设置碰撞组件
  123. setupCollider() {
  124. if (!this.activeBall) return;
  125. // 确保小球有刚体组件
  126. let rigidBody = this.activeBall.getComponent(RigidBody2D);
  127. if (!rigidBody) {
  128. rigidBody = this.activeBall.addComponent(RigidBody2D);
  129. rigidBody.type = 2; // Dynamic
  130. rigidBody.gravityScale = 0; // 不受重力影响
  131. rigidBody.enabledContactListener = true; // 启用碰撞监听
  132. rigidBody.bullet = true; // 高精度碰撞检测
  133. rigidBody.fixedRotation = false; // 允许旋转
  134. rigidBody.allowSleep = true;
  135. }
  136. // 注册碰撞事件
  137. this.activeBall.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
  138. }
  139. // 初始化方向
  140. initializeDirection() {
  141. // 随机初始方向
  142. const angle = Math.random() * Math.PI * 2; // 0-2π之间的随机角度
  143. this.direction.x = Math.cos(angle);
  144. this.direction.y = Math.sin(angle);
  145. this.direction.normalize();
  146. console.log('Ball initialized with direction:', this.direction);
  147. }
  148. // 初始化球的参数 - 公开方法,供GameManager调用
  149. initialize() {
  150. this.calculateGameBounds();
  151. this.createBall();
  152. }
  153. update(dt: number) {
  154. // 如果使用物理引擎,不要手动更新位置
  155. if (!this.initialized || !this.activeBall || !this.activeBall.isValid) return;
  156. // 使用刚体组件控制小球移动,而不是直接设置位置
  157. const rigidBody = this.activeBall.getComponent(RigidBody2D);
  158. if (rigidBody) {
  159. // 设置线性速度而不是位置
  160. rigidBody.linearVelocity = new Vec2(
  161. this.direction.x * this.speed,
  162. this.direction.y * this.speed
  163. );
  164. }
  165. }
  166. // 碰撞回调中,只需要更新方向向量,物理引擎会处理实际的反弹
  167. onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
  168. // 获取碰撞点和法线
  169. if (!contact) return;
  170. // 获取碰撞点的世界坐标
  171. const worldManifold = contact.getWorldManifold();
  172. if (!worldManifold) return;
  173. // 获取碰撞法线
  174. const normal = worldManifold.normal;
  175. // 更新方向向量,但让物理引擎处理实际的反弹
  176. this.direction = this.calculateReflection(this.direction, normal);
  177. // 如果碰撞的是方块,发射子弹
  178. if (otherCollider.node.name.includes('Block')) {
  179. this.fireBullet(otherCollider.node);
  180. }
  181. }
  182. // 计算反射向量
  183. calculateReflection(direction: Vec2, normal: Vec2): Vec2 {
  184. // 使用反射公式: R = V - 2(V·N)N
  185. const dot = direction.x * normal.x + direction.y * normal.y;
  186. const reflection = new Vec2(
  187. direction.x - 2 * dot * normal.x,
  188. direction.y - 2 * dot * normal.y
  189. );
  190. reflection.normalize();
  191. return reflection;
  192. }
  193. // 发射子弹
  194. fireBullet(blockNode: Node) {
  195. console.log('发射子弹!击中方块:', blockNode.name);
  196. // 检查是否有子弹预制体
  197. if (!this.bulletPrefab) {
  198. console.error('未设置子弹预制体');
  199. return;
  200. }
  201. // 查找Weapon节点
  202. const weaponNode = blockNode.getChildByName('Weapon');
  203. if (!weaponNode) {
  204. console.warn(`方块 ${blockNode.name} 没有Weapon子节点`);
  205. return;
  206. }
  207. // 实例化子弹
  208. const bullet = instantiate(this.bulletPrefab);
  209. // 将子弹添加到GameArea中
  210. const gameArea = find('Canvas/GameArea');
  211. if (gameArea) {
  212. gameArea.addChild(bullet);
  213. // 设置子弹初始位置为Weapon节点的位置
  214. const weaponWorldPos = weaponNode.worldPosition;
  215. bullet.worldPosition = weaponWorldPos;
  216. // 计算子弹方向 - 从Weapon指向小球
  217. const ballPos = this.activeBall.worldPosition;
  218. const direction = new Vec2(
  219. ballPos.x - weaponWorldPos.x,
  220. ballPos.y - weaponWorldPos.y
  221. );
  222. direction.normalize();
  223. // 添加刚体组件控制子弹移动
  224. let rigidBody = bullet.getComponent(RigidBody2D);
  225. if (!rigidBody) {
  226. rigidBody = bullet.addComponent(RigidBody2D);
  227. rigidBody.type = 2; // Dynamic
  228. rigidBody.gravityScale = 0; // 不受重力影响
  229. rigidBody.fixedRotation = true; // 固定旋转
  230. rigidBody.allowSleep = false; // 不允许休眠
  231. }
  232. // 设置子弹速度
  233. rigidBody.linearVelocity = new Vec2(
  234. direction.x * this.bulletSpeed,
  235. direction.y * this.bulletSpeed
  236. );
  237. // 确保子弹有碰撞体
  238. let collider = bullet.getComponent(CircleCollider2D);
  239. if (!collider) {
  240. collider = bullet.addComponent(CircleCollider2D);
  241. collider.radius = 5; // 设置适当的半径
  242. collider.tag = 3; // 子弹标签
  243. collider.group = 1; // 与其他物体同组
  244. collider.sensor = false;
  245. collider.friction = 0;
  246. collider.restitution = 1;
  247. }
  248. // 添加自动销毁逻辑
  249. this.scheduleOnce(() => {
  250. if (bullet && bullet.isValid) {
  251. bullet.destroy();
  252. }
  253. }, 5); // 5秒后销毁子弹
  254. }
  255. }
  256. }