GameCtl.ts 20 KB

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