GameCtl.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. import { _decorator, BoxCollider, Camera, Component, director, EventTouch, find, Game, geometry, Layers, Layout, Node, PhysicsSystem, Quat, RigidBody, Size, sp, tween, UITransform, v3, Vec3 } from 'cc';
  2. import { gui } from '../../core/ui/ui';
  3. import { UI_Idioms } from '../ui/UI_Idioms/UI_Idioms';
  4. import { UI_Main } from '../ui/UI_Main/UI_Main';
  5. import { Container_Manager } from './Container_Manager';
  6. import { Cube_Infor, Cube_State } from './Cube_Infor';
  7. import { UI_LatticeFull } from '../ui/UI_LatticeFull/UI_LatticeFull';
  8. import { UI_Lock } from '../ui/UI_Lock/UI_Lock';
  9. import { Layout_Main } from '../ui/UI_Main/Layout_Main';
  10. import { Toast } from '../../core/util_class/Toast';
  11. import ch_audio from '../../ch/audio/audio';
  12. import { Hall } from '../hall/Hall';
  13. const { ccclass, property } = _decorator;
  14. @ccclass('GameCtl')
  15. export class GameCtl extends Component {
  16. static instance: GameCtl = null;
  17. @property(Camera)
  18. camera: Camera = null;
  19. @property([Node])
  20. Ani: Node[] = [];
  21. @property(Node)
  22. Mid: Node[] = [];
  23. Container: Container_Manager;
  24. onLoad() {
  25. GameCtl.instance = this;
  26. this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
  27. this.node.on(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
  28. this.node.on(Node.EventType.TOUCH_END, this.onTouchEnd, this);
  29. this.Container = find('Container').getComponent(Container_Manager);
  30. }
  31. update(deltaTime: number) {
  32. }
  33. onTouchStart(event: EventTouch) {
  34. }
  35. onTouchMove(event: EventTouch) {
  36. }
  37. onTouchEnd(event: EventTouch) {
  38. if (this.Container.canTouch) {
  39. this.shootRay(event);
  40. }
  41. }
  42. shootRay(event: EventTouch) {
  43. if (!this.camera) return;
  44. let ray = new geometry.Ray();
  45. this.camera.screenPointToRay(event.getLocationX(), event.getLocationY(), ray);
  46. if (PhysicsSystem.instance.raycast(ray)) {
  47. const results = PhysicsSystem.instance.raycastResults;
  48. // 筛选出不在 "IGNORE_RAYCAST" 层的物体
  49. let filteredResults = results.filter(e => e.collider.node.layer !== (1 << Layers.nameToLayer('IGNORE_RAYCAST')));
  50. // 根据距离排序,确保第 0 个是最近的物体
  51. filteredResults.sort((a, b) => a.distance - b.distance);
  52. console.log(filteredResults);
  53. if (filteredResults.length > 0) {
  54. const collider = filteredResults[0].collider;
  55. const node = collider.node;
  56. // 检查物体是否可消除
  57. if (node.getComponent(Cube_Infor)?.state === Cube_State.live) {
  58. ch_audio.getInstance().playOneShot('sound/click_Cube')
  59. if (Hall.getInstance().firstEnter) {
  60. if (this.Container.nodeReferences.length > 4) {
  61. gui.get(UI_Main).getLayout<Layout_Main>().Hands[this.Container.nodeReferences.indexOf(node)].active = false;
  62. }
  63. }
  64. this.entryContainer(node);
  65. } else if (node.name === 'Lock') {
  66. this.UnLock(node);
  67. }
  68. }
  69. }
  70. console.log('发射了射线');
  71. }
  72. entryContainer(node: Node) {
  73. // 判断容器剩余容量
  74. let startIndex = -1;
  75. let targetPos = new Vec3();
  76. let txt_length = node.getComponent(Cube_Infor).Text.length;
  77. console.log(txt_length);
  78. // 判断字长 并 判断是否还有空间可放置
  79. switch (txt_length) {
  80. case 1:
  81. for (let i = 0; i < this.Container.unlock_Num; i++) {
  82. if (this.Container.node_isIdiom[i] === false) {
  83. startIndex = i; // 找到连续的三个 false,记录起始位置
  84. break; // 找到第一个符合条件的位置后停止
  85. }
  86. }
  87. if (startIndex !== -1) { // 如果找到了一个值为 false 的元素
  88. targetPos = this.Container.nodes[startIndex].getWorldPosition().clone();
  89. this.Container.node_isIdiom[startIndex] = true;
  90. console.log(targetPos);
  91. } else {
  92. Toast.makeText(gui.getLayerNode(5), "剩余槽位无法放下该词块").show();
  93. }
  94. break;
  95. case 2: {
  96. for (let i = 0; i < this.Container.unlock_Num - 1; i++) {
  97. if (this.Container.node_isIdiom[i] === false &&
  98. this.Container.node_isIdiom[i + 1] === false) {
  99. startIndex = i; // 找到连续的两个 false,记录起始位置
  100. break; // 找到第一个符合条件的位置后停止
  101. }
  102. }
  103. if (startIndex !== -1) {
  104. console.log("找到连续的两个 false,起始索引是:", startIndex);
  105. // 可以在此使用 startIndex 进行后续操作,例如:
  106. let pos1 = this.Container.nodes[startIndex].getWorldPosition();
  107. let pos2 = this.Container.nodes[startIndex + 1].getWorldPosition();
  108. this.Container.node_isIdiom[startIndex] = true;
  109. this.Container.node_isIdiom[startIndex + 1] = true;
  110. targetPos.set(
  111. (pos1.x + pos2.x) / 2,
  112. (pos1.y + pos2.y) / 2,
  113. (pos1.z + pos2.z) / 2
  114. );
  115. console.log(targetPos);
  116. } else {
  117. Toast.makeText(gui.getLayerNode(5), "剩余槽位无法放下该词块").show();
  118. }
  119. break;
  120. }
  121. case 3: {
  122. for (let i = 0; i < this.Container.unlock_Num - 2; i++) {
  123. if (this.Container.node_isIdiom[i] === false &&
  124. this.Container.node_isIdiom[i + 1] === false &&
  125. this.Container.node_isIdiom[i + 2] === false) {
  126. startIndex = i; // 找到连续的三个 false,记录起始位置
  127. break; // 找到第一个符合条件的位置后停止
  128. }
  129. }
  130. if (startIndex !== -1) {
  131. // 找到连续三个 false,startIndex 即为最前面的索引
  132. console.log("找到连续的三个 false,起始索引是:", startIndex);
  133. // 可以在此使用 startIndex 进行后续操作,例如:
  134. let pos1 = this.Container.nodes[startIndex].getWorldPosition();
  135. let pos2 = this.Container.nodes[startIndex + 1].getWorldPosition();
  136. let pos3 = this.Container.nodes[startIndex + 2].getWorldPosition();
  137. this.Container.node_isIdiom[startIndex] = true;
  138. this.Container.node_isIdiom[startIndex + 1] = true;
  139. this.Container.node_isIdiom[startIndex + 2] = true;
  140. targetPos.set(
  141. (pos1.x + pos2.x + pos3.x) / 3,
  142. (pos1.y + pos2.y + pos3.y) / 3,
  143. (pos1.z + pos2.z + pos3.z) / 3
  144. );
  145. console.log(targetPos);
  146. } else {
  147. Toast.makeText(gui.getLayerNode(5), "剩余槽位无法放下该词块").show();
  148. }
  149. break;
  150. }
  151. default:
  152. return;
  153. }
  154. if (startIndex !== -1) {
  155. // node.getComponent(Cube_Infor).lock = true;
  156. //生成一个新方块在场上剩余方块的方块的下方
  157. this.Container.instantiateNewCube();
  158. node.getComponent(Cube_Infor).state = Cube_State.wait;
  159. node.getComponent(Cube_Infor).rigidbody.type = RigidBody.Type.STATIC; // 禁用重力
  160. let targetRotation = new Quat();
  161. let rotationX = node.eulerAngles.x;
  162. // 由于你初始是绕 X 轴旋转了 -90度,所以判断区间可以参考这个旋转角度
  163. if (rotationX >= -180 && rotationX < 0) {
  164. Quat.fromEuler(targetRotation, -90, 0, 0);
  165. } else {
  166. Quat.fromEuler(targetRotation, 90, 0, 0);
  167. }
  168. this.Container.canTouch = false;
  169. tween(node)
  170. .to(0.2, { position: new Vec3(targetPos.x, targetPos.y, targetPos.z + 0.4), rotation: targetRotation })
  171. .call(() => {
  172. this.Container.idiom_combine.set(node.getComponent(Cube_Infor), startIndex);
  173. // 执行判断成语合成逻辑
  174. let matchedCubes: Cube_Infor[] = [];
  175. let flag = this.Container.checkIdiom_Combine(node.getComponent(Cube_Infor), matchedCubes);
  176. if (flag) {
  177. // 执行合成动画
  178. console.log("匹配的成语方块:", matchedCubes);
  179. this.combine_ani(matchedCubes[0], matchedCubes[1]);
  180. } else {
  181. //高亮
  182. gui.get(UI_Idioms).light_Show(node.getComponent(Cube_Infor));
  183. //判断槽子满了吗
  184. let count = 0;
  185. for (let element of this.Container.node_isIdiom) {
  186. if (element == true) {
  187. count++;
  188. }
  189. }
  190. if (count == this.Container.unlock_Num) {
  191. gui.show(UI_LatticeFull);
  192. } else if (count >= this.Container.unlock_Num - 2 && this.Container.unlock_Num != 9) {
  193. if (!this.Container.is_Show_UI_Lock) {
  194. gui.show(UI_Lock);
  195. this.Container.is_Show_UI_Lock = true;
  196. }
  197. }
  198. this.Container.canTouch = true;
  199. }
  200. })
  201. .start();
  202. }
  203. }
  204. //合成动画
  205. combine_ani(cube1: Cube_Infor, cube2: Cube_Infor) {
  206. this.Container.canTouch = true;
  207. for (let i = this.Container.idiom_combine.get(cube1); i < this.Container.idiom_combine.get(cube1) + cube1.Text.length; i++) {
  208. this.Container.node_isIdiom[i] = false;
  209. }
  210. this.Container.idiom_combine.delete(cube1);
  211. for (let i = this.Container.idiom_combine.get(cube2); i < this.Container.idiom_combine.get(cube2) + cube2.Text.length; i++) {
  212. this.Container.node_isIdiom[i] = false;
  213. }
  214. this.Container.idiom_combine.delete(cube2);
  215. // 取消相关字的高亮显示
  216. gui.get(UI_Idioms).light_Hide(cube1, cube2);
  217. gui.get(UI_Main).evt.emit(gui.get(UI_Main).evt.key.update_remain);
  218. this.adjustContainer();
  219. let index1 = 0;
  220. if (cube1.Text.length == 1) {
  221. index1 = 0;
  222. } else if (cube1.Text.length == 2) {
  223. index1 = 2;
  224. } else if (cube1.Text.length == 3) {
  225. index1 = 4;
  226. }
  227. let index2 = 0;
  228. if (cube2.Text.length == 1) {
  229. index2 = 5;
  230. } else if (cube2.Text.length == 2) {
  231. index2 = 3;
  232. } else if (cube2.Text.length == 3) {
  233. index2 = 1;
  234. }
  235. // 检测容器中哪些字需要高亮显示
  236. console.log(this.Container.node_isIdiom); // 在所有动画结束后执行
  237. this.Container.idioms = this.Container.idioms.filter(c => c.idiom !== cube1.Text + cube2.Text);
  238. // 创建第一个 tween 动画
  239. ch_audio.getInstance().playOneShot('sound/fly_Cube')
  240. const tween1 = tween(cube1.node)
  241. .to(0.25, { position: new Vec3(this.Mid[0].position.x, this.Mid[0].position.y, this.Mid[0].position.z) }).call(() => {
  242. cube1.node.layer = 1 << Layers.nameToLayer('Cube2');
  243. cube1.node.walk((child) => {
  244. child.layer = 1 << Layers.nameToLayer('Cube2');
  245. })
  246. this.playAnim();
  247. ch_audio.getInstance().playOneShot('sound/eliminate')
  248. // this.Container.skeleton2.getComponent(sp.Skeleton).setAnimation(0, 'anim2', false);
  249. })
  250. .to(0.3, { position: new Vec3(this.Ani[index1].position.x, this.Ani[index1].position.y, this.Ani[index1].position.z + 0.4) })
  251. .call(() => {
  252. setTimeout(() => {
  253. cube1.state = Cube_State.dead;
  254. this.Container.recycleCube(cube1.node);
  255. }, 600.0);
  256. });
  257. // 创建第二个 tween 动画
  258. const tween2 = tween(cube2.node)
  259. .to(0.3, { position: new Vec3(this.Mid[1].position.x, this.Mid[1].position.y, this.Mid[1].position.z) }).call(() => {
  260. cube2.node.layer = 1 << Layers.nameToLayer('Cube2');
  261. cube2.node.walk((child) => {
  262. child.layer = 1 << Layers.nameToLayer('Cube2');
  263. })
  264. })
  265. .to(0.25, { position: new Vec3(this.Ani[index2].position.x, this.Ani[index2].position.y, this.Ani[index2].position.z + 0.4) })
  266. .call(() => {
  267. setTimeout(() => {
  268. cube2.state = Cube_State.dead;
  269. this.Container.recycleCube(cube2.node);
  270. }, 600.0);
  271. });
  272. // 使用 tween 的并行组合功能
  273. tween(cube1.node)
  274. .parallel(tween1, tween2) // 并行执行 tween1 和 tween2
  275. .call(() => {
  276. Hall.getInstance().player.set_combine_num(1);
  277. if (Hall.getInstance().firstEnter) {
  278. if (this.Container.nodeReferences.length == 4) {
  279. this.Container.nodeReferences.forEach(node => {
  280. node.getComponent(Cube_Infor).state=Cube_State.live;
  281. });
  282. }
  283. }
  284. })
  285. .start();
  286. }
  287. adjustContainer() {
  288. const container = this.Container;
  289. // 新的 node_isIdiom 状态数组
  290. const newNodeIsIdiom = Array(container.node_isIdiom.length).fill(false);
  291. // 新的 idiom_combine 映射表
  292. const newIdiomCombine = new Map<Cube_Infor, number>();
  293. // 遍历 idiom_combine,重新计算位置
  294. for (const [cube, startIndex] of container.idiom_combine.entries()) {
  295. const txtLength = cube.Text.length; // 获取字长
  296. let targetIndex = -1; // 目标位置起始索引
  297. let targetPos = new Vec3();
  298. switch (txtLength) {
  299. case 1: {
  300. // 找到一个空位
  301. for (let i = 0; i < container.unlock_Num; i++) {
  302. if (!newNodeIsIdiom[i]) {
  303. targetIndex = i;
  304. break;
  305. }
  306. }
  307. if (targetIndex !== -1) {
  308. targetPos = container.nodes[targetIndex].getWorldPosition().clone();
  309. newNodeIsIdiom[targetIndex] = true; // 占用位置
  310. }
  311. break;
  312. }
  313. case 2: {
  314. // 找到连续两个空位
  315. for (let i = 0; i < container.unlock_Num - 1; i++) {
  316. if (!newNodeIsIdiom[i] && !newNodeIsIdiom[i + 1]) {
  317. targetIndex = i;
  318. break;
  319. }
  320. }
  321. if (targetIndex !== -1) {
  322. const pos1 = container.nodes[targetIndex].getWorldPosition();
  323. const pos2 = container.nodes[targetIndex + 1].getWorldPosition();
  324. targetPos.set(
  325. (pos1.x + pos2.x) / 2,
  326. (pos1.y + pos2.y) / 2,
  327. (pos1.z + pos2.z) / 2
  328. );
  329. newNodeIsIdiom[targetIndex] = true;
  330. newNodeIsIdiom[targetIndex + 1] = true;
  331. }
  332. break;
  333. }
  334. case 3: {
  335. // 找到连续三个空位
  336. for (let i = 0; i < container.unlock_Num - 2; i++) {
  337. if (
  338. !newNodeIsIdiom[i] &&
  339. !newNodeIsIdiom[i + 1] &&
  340. !newNodeIsIdiom[i + 2]
  341. ) {
  342. targetIndex = i;
  343. break;
  344. }
  345. }
  346. if (targetIndex !== -1) {
  347. const pos1 = container.nodes[targetIndex].getWorldPosition();
  348. const pos2 = container.nodes[targetIndex + 1].getWorldPosition();
  349. const pos3 = container.nodes[targetIndex + 2].getWorldPosition();
  350. targetPos.set(
  351. (pos1.x + pos2.x + pos3.x) / 3,
  352. (pos1.y + pos2.y + pos3.y) / 3,
  353. (pos1.z + pos2.z + pos3.z) / 3
  354. );
  355. newNodeIsIdiom[targetIndex] = true;
  356. newNodeIsIdiom[targetIndex + 1] = true;
  357. newNodeIsIdiom[targetIndex + 2] = true;
  358. }
  359. break;
  360. }
  361. default:
  362. return;
  363. }
  364. // 移动 Cube_Infor 到目标位置
  365. if (targetIndex !== -1) {
  366. tween(cube.node)
  367. .to(0.3, { position: new Vec3(targetPos.x, targetPos.y, targetPos.z + 0.4) })
  368. .start();
  369. newIdiomCombine.set(cube, targetIndex); // 更新新映射
  370. }
  371. }
  372. // 更新 container 状态
  373. container.node_isIdiom = newNodeIsIdiom;
  374. container.idiom_combine = newIdiomCombine;
  375. console.log("调整后的容器状态:", container.node_isIdiom);
  376. console.log("更新后的 idiom_combine:", [...container.idiom_combine.entries()]);
  377. }
  378. async UnLock(node: Node) {
  379. const res = await chsdk.playRewardAd('解锁槽位');
  380. if (res) {
  381. for (let i = 0; i < 2; i++) {
  382. if (this.Container.Lock_node[i].active) {
  383. this.Container.Lock_node[i].active = false;
  384. this.Container.unlock_Num += 1;
  385. gui.get(UI_Main).evt.emit(gui.get(UI_Main).evt.key.resume);
  386. return;
  387. }
  388. }
  389. }
  390. }
  391. playAnim() {
  392. const skeleton = this.Container.skeleton1.getComponent(sp.Skeleton);
  393. // 播放 anim_2, anim_3, anim_4,同时使用不同的轨道
  394. skeleton.setAnimation(0, 'anim_2', false); // 轨道 0 播放 anim_2
  395. skeleton.setAnimation(1, 'anim_3', false); // 轨道 1 播放 anim_3
  396. skeleton.setAnimation(2, 'anim_4', false); // 轨道 2 播放 anim_4
  397. // 监听所有轨道完成,最后播放 anim_1
  398. let completedTracks = new Set<number>(); // 记录完成的轨道
  399. skeleton.setCompleteListener((trackEntry: sp.spine.TrackEntry) => {
  400. completedTracks.add(trackEntry.trackIndex); // 将完成的轨道加入记录
  401. console.log(`轨道 ${trackEntry.trackIndex} 的动画完成`);
  402. // 如果轨道 0、1、2 都完成,则播放 anim_1
  403. if (completedTracks.has(0) && completedTracks.has(1) && completedTracks.has(2)) {
  404. console.log('所有动画完成,播放 anim_1');
  405. // 播放 anim_1 并监听其完成事件
  406. skeleton.setAnimation(0, 'anim_1', false); // 非循环播放 anim_1
  407. skeleton.setCompleteListener((trackEntry: sp.spine.TrackEntry) => {
  408. if (trackEntry.animation.name === 'anim_1') {
  409. console.log('anim_1 播放完成');
  410. skeleton.setCompleteListener(null); // 移除监听器,防止重复触发
  411. }
  412. });
  413. }
  414. });
  415. }
  416. }