MouseIconView.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. import { MathUtils } from "../../../SubGamePublic/PG_Public/Script/lib/utils/MathUtils";
  2. import MouseAvatar from "./MouseAvatar";
  3. import { MouseConst } from "./MouseConst";
  4. import MouseDecor from "./MouseDecor";
  5. import MouseIconCover from "./MouseIconCover";
  6. import MouseIconDetail from "./MouseIconDetail";
  7. import MouseIconItem from "./MouseIconItem";
  8. import MouseSuper from "./MouseSuper";
  9. const { ccclass, property, menu } = cc._decorator;
  10. const TopCount: number = 1; // 网格上面的格子数量
  11. const PREPARE_TWEEN_TAG = 101;
  12. const SCROLL_TWEEN_TAG = 102;
  13. @ccclass
  14. @menu("Game/Mouse/MouseIconView")
  15. export default class MouseIconView extends cc.Component {
  16. @property(cc.Prefab)
  17. iconItemPrefab: cc.Prefab = null;
  18. @property([sp.SkeletonData])
  19. iconSpines: sp.SkeletonData[] = [];
  20. @property(cc.Node)
  21. particleAnimNodeArr: cc.Node = null;
  22. @property(cc.Node)
  23. underlayNormal: cc.Node = null;
  24. @property(cc.Node)
  25. underlaySuper: cc.Node = null;
  26. @property(sp.Skeleton)
  27. spinSplash: sp.Skeleton = null;
  28. @property(cc.ParticleSystem)
  29. spinParticle: cc.ParticleSystem = null;
  30. @property(MouseAvatar)
  31. mouseAvatar: MouseAvatar = null;
  32. @property(MouseSuper)
  33. mouseSuper: MouseSuper = null;
  34. @property(MouseIconCover)
  35. mouseIconCover: MouseIconCover = null;
  36. @property(MouseDecor)
  37. mouseDecor: MouseDecor = null;
  38. @property(MouseIconDetail)
  39. mouseIconDetail: MouseIconDetail = null;
  40. @property(cc.Node)
  41. iconRoot: cc.Node = null;
  42. @property(cc.Node)
  43. nodeClick: cc.Node = null;
  44. _callback: Function = null;
  45. _gameResult: NSlots.ICommonResult = null;
  46. _iconItemMap: MouseIconItem[][] = []; //客户端显示第0行0列在左下角,服务端是在左上角
  47. _startRunAudioId: number = 0;
  48. SCALE_OFFSET = 150; // 放大/缩小偏移
  49. // =============== 滚动参数 ==================
  50. // 滚动圈数
  51. TURBO_ROLL_CIRCLES = [1, 1, 1]; // Turbo转圈数
  52. NORMAL_ROLL_CIRCLES = [1, 2, 3]; // 正常转圈数
  53. SUPER_ROLL_CIRCLES = [20, 21, 22]; // 超级转圈数
  54. rollSpeed = 4000;
  55. backOutHeight = 213;
  56. startPosYArr = this.getRowPosY();//各行icon初始的y坐标
  57. topPosY = MouseConst.IconSize.height * (TopCount + 1); //最上面看不见的位置
  58. endPosY = - MouseConst.IconSize.height * 2;//最下面看不见的位置,底部下面一个格子处
  59. oneRoundTime = Math.abs(this.endPosY - this.topPosY) / this.rollSpeed; // 滚动一圈的时间
  60. backOutTime = this.backOutHeight / this.rollSpeed;
  61. backOutTimeMutiple = 5; //缓冲系数
  62. // =============== 滚动参数 ==================
  63. isStartSuper = false;
  64. isSpinReady = false;
  65. gameFrameView: NSlots.GameFrameView = null;
  66. start() {
  67. this.gameFrameView = this.node.parent.getComponent('MouseGameFrameView');
  68. this.initView();
  69. }
  70. initView() {
  71. for (let i = 0; i < MouseConst.GAME_COLUMN; i++) {
  72. for (let j = MouseConst.GAME_ROW + TopCount - 1; j >= 0; j--) { // 下面的盖在上面
  73. let iconItem = cc.instantiate(this.iconItemPrefab).getComponent(MouseIconItem);
  74. iconItem.node.x = MouseConst.IconSize.width * (i - 1);
  75. iconItem.node.y = MouseConst.IconSize.height * (j - 1);
  76. iconItem.node.parent = this.iconRoot;
  77. iconItem.node.active = j < MouseConst.GAME_ROW; // 防止顶部冒出
  78. iconItem.init(this, i, j);
  79. iconItem.setItemInfo(this.getRandomId());
  80. this._iconItemMap[i] = this._iconItemMap[i] || [];
  81. this._iconItemMap[i][j] = iconItem;
  82. }
  83. }
  84. this.mouseIconCover.initView(this);
  85. }
  86. /**
  87. * 获取随机id
  88. */
  89. getRandomId() {
  90. return MathUtils.getRandomFromArray(MouseConst.NormalIds);
  91. }
  92. /**
  93. * 获取图标动画
  94. */
  95. getIconSpineById(spineIndex: number) {
  96. return this.iconSpines[spineIndex];
  97. }
  98. /**
  99. * 获取每行Y轴坐标
  100. */
  101. getRowPosY() {
  102. let ys: number[] = [];
  103. for (let index = 0; index < MouseConst.GAME_ROW + TopCount; index++) {
  104. ys[index] = MouseConst.IconSize.height * (index - 1);
  105. }
  106. return ys;
  107. }
  108. /**
  109. * 服务端顺序( 左-右 上-下)
  110. */
  111. getIconItemByIndex(index: number) {
  112. let x = index % MouseConst.GAME_COLUMN;
  113. let y = MouseConst.GAME_ROW - 1 - Math.floor(index / MouseConst.GAME_COLUMN);
  114. return this._iconItemMap[x][y];
  115. }
  116. /**
  117. * 本地行列转服务端index
  118. */
  119. getServerIndex(col: number, row: number) {
  120. return (MouseConst.GAME_ROW - 1 - row) * MouseConst.GAME_COLUMN + col;
  121. }
  122. /**
  123. * 转动结果id
  124. */
  125. getTrueId(col: number, row: number) {
  126. return this._gameResult.itemInfo[this.getServerIndex(col, row)];
  127. }
  128. /**
  129. * 开始动效
  130. */
  131. playStartRunAnim() {
  132. cc.warn("====>playStartRunAnim");
  133. this.spinParticle.node.active = true;
  134. this.spinParticle.resetSystem();
  135. this.spinSplash.node.active = true;
  136. this.spinSplash.setAnimation(0, "animation", false);
  137. this.spinSplash.setCompleteListener(() => {
  138. this.spinSplash.setCompleteListener(null);
  139. this.spinSplash.node.active = false;
  140. this.spinParticle.node.active = false;
  141. });
  142. }
  143. /**
  144. * 预滚动(等后台数据)
  145. */
  146. onSpinPre() {
  147. cc.Tween.stopAllByTag(PREPARE_TWEEN_TAG);
  148. cc.Tween.stopAllByTag(SCROLL_TWEEN_TAG);
  149. this.nodeClick.active = false;
  150. // 滚动开始音效
  151. AudioPlayer.playEffect(MouseConst.Sound.spinRun, true, 1, false, (audioID: number) => {
  152. this._startRunAudioId = audioID;
  153. });
  154. // 开始动效
  155. this.playStartRunAnim();
  156. let __run = (iconItem: MouseIconItem, col: number, row: number) => {
  157. iconItem.node.active = true;
  158. let stepFirstTime = Math.abs(this.endPosY - this.startPosYArr[row]) / this.rollSpeed;
  159. // 开始
  160. let start = cc.tween().to(stepFirstTime, { y: this.endPosY }).call(() => { iconItem.node.y = this.topPosY; });
  161. // 循环
  162. let repeatRoll = cc.tween().repeat(Number.MAX_VALUE, cc.tween().to(this.oneRoundTime, { y: this.endPosY }).call(() => {
  163. iconItem.node.y = this.topPosY;
  164. }));
  165. // =============== End =====================
  166. cc.tween(iconItem.node).tag(PREPARE_TWEEN_TAG).then(start).then(repeatRoll).start();
  167. };
  168. this._iconItemMap.forEach((icons, col) => {
  169. icons.forEach((icon, row) => {
  170. __run(icon, col, row);
  171. });
  172. });
  173. }
  174. /**
  175. * 数据已就绪
  176. */
  177. onSpinReady(result: any, callback: Function, hasSuperSpin?: boolean) {
  178. this._callback = callback;
  179. this._gameResult = result;
  180. this.isStartSuper = hasSuperSpin;
  181. this.isSpinReady = true;
  182. cc.Tween.stopAllByTag(PREPARE_TWEEN_TAG);
  183. cc.Tween.stopAllByTag(SCROLL_TWEEN_TAG);
  184. this.nodeClick.active = true;
  185. this.resetAllIconItemPos();
  186. if (this._startRunAudioId == 0) {
  187. // 滚动开始音效
  188. AudioPlayer.playEffect(MouseConst.Sound.spinRun, true, 1, false, (audioID: number) => {
  189. this._startRunAudioId = audioID;
  190. });
  191. }
  192. if (this.gameFrameView.isSuperSpin) {
  193. this.playStartRunAnim();
  194. }
  195. this.runSpin(hasSuperSpin, this.gameFrameView.bottomView.isTurbo);
  196. }
  197. runSpin(hasSuperSpin?: boolean, isTurbo?: boolean) {
  198. let __run = (iconItem: MouseIconItem, col: number, row: number) => {
  199. iconItem.node.active = true;
  200. let rpeatCount = hasSuperSpin ? this.SUPER_ROLL_CIRCLES[col] : (isTurbo ? this.TURBO_ROLL_CIRCLES[col] : this.NORMAL_ROLL_CIRCLES[col]);
  201. let stepFirstTime = Math.abs(this.endPosY - this.startPosYArr[row]) / this.rollSpeed;
  202. let stepEndPosY = this.startPosYArr[row] + this.backOutHeight;
  203. let stepEndTime = Math.abs(stepEndPosY - this.topPosY) / this.rollSpeed;
  204. let easeKind = row >= MouseConst.GAME_ROW ? 'sineIn' : 'backOut';
  205. // =============== 转动的四个步骤 =====================
  206. // 开始
  207. let start = cc.tween().to(stepFirstTime, { y: this.endPosY }).call(() => { iconItem.node.y = this.topPosY; });
  208. // 循环
  209. let repeatRoll = cc.tween().repeat(rpeatCount, cc.tween().to(this.oneRoundTime, { y: this.endPosY }).call(() => {
  210. iconItem.node.y = this.topPosY;
  211. })).call(() => {
  212. if (row >= MouseConst.GAME_ROW) {
  213. // 屏幕外, 忽略
  214. } else {
  215. // 设置服务端下发的值
  216. let self = this
  217. iconItem.setItemInfo(self.getTrueId(col, row));
  218. // 同步到覆盖层
  219. self.mouseIconCover.syncItemInfo(col, row, iconItem.iconId);
  220. }
  221. });
  222. // 结束
  223. let end = cc.tween().to(stepEndTime, { y: stepEndPosY }).call(() => {
  224. if (row == 0) {
  225. //音效有延时, 提前播放
  226. this.playReelStopEffect(col);
  227. } else if (row >= MouseConst.GAME_ROW) {
  228. iconItem.node.active = false;
  229. }
  230. });
  231. // 回弹
  232. let back = cc.tween().to(this.backOutTime * this.backOutTimeMutiple, { y: this.startPosYArr[row] }, { easing: easeKind })
  233. .call(() => this.onReelStop(row, col));
  234. // =============== End =====================
  235. cc.tween(iconItem.node).tag(SCROLL_TWEEN_TAG).then(start).then(repeatRoll).then(end).then(back).start();
  236. };
  237. this._iconItemMap.forEach((icons, col) => {
  238. icons.forEach((icon, row) => {
  239. if (this.gameFrameView.isSuperSpin && col == 1) {
  240. // 超级转中间位置固定
  241. } else {
  242. __run(icon, col, row);
  243. }
  244. });
  245. });
  246. }
  247. stopSpin() {
  248. cc.warn("=============>stopSpin");
  249. cc.Tween.stopAllByTag(PREPARE_TWEEN_TAG);
  250. cc.Tween.stopAllByTag(SCROLL_TWEEN_TAG);
  251. for (let i = 0; i < MouseConst.GAME_COLUMN; i++) {
  252. this.playReelStopEffect(i);
  253. for (let j = 0; j < MouseConst.GAME_ROW + TopCount; j++) {
  254. let iconItem = this._iconItemMap[i][j];
  255. iconItem.node.y = MouseConst.IconSize.height * (j - 1);
  256. if (j >= MouseConst.GAME_ROW) {
  257. iconItem.node.active = false;
  258. } else {
  259. // 设置服务端下发的值
  260. iconItem.setItemInfo(this.getTrueId(i, j));
  261. // 同步到覆盖层
  262. this.mouseIconCover.syncItemInfo(i, j, iconItem.iconId);
  263. }
  264. }
  265. }
  266. this.onReelStop(MouseConst.GAME_ROW - 1, MouseConst.GAME_COLUMN - 1);
  267. }
  268. /**
  269. * 一列停止音效
  270. */
  271. playReelStopEffect(col: number) {
  272. if (this.gameFrameView.isSuperSpin) {
  273. if (col != 1) {
  274. AudioPlayer.playEffect(`sound/superReelStop${col + 1}`, false);
  275. }
  276. // 超级转中每列停止随机老鼠声音
  277. AudioPlayer.playEffect(`sound/mouse${MathUtils.makeRandomInt(9, 1)}`, false);
  278. // for (let i = 0; i < MouseConst.GAME_ROW; i++) {
  279. // let iconId = this.getTrueId(col, i);
  280. // if (iconId == MouseConst.CT_WILD) { // 有老鼠
  281. // AudioPlayer.playEffect(`sound/mouse${MathUtils.makeRandomInt(9, 1)}`, false);
  282. // return;
  283. // }
  284. // }
  285. } else {
  286. AudioPlayer.playEffect(`sound/reelStop${col + 1}`, false);
  287. }
  288. }
  289. // 滚轮停下来
  290. onReelStop(row: number, col: number) {
  291. if (row == MouseConst.GAME_ROW - 1 && col == MouseConst.GAME_COLUMN - 1) {
  292. cc.error("stop effect: " + this._startRunAudioId);
  293. AudioPlayer.stopEffect(this._startRunAudioId);
  294. this.nodeClick.active = false;
  295. this.scheduleOnce(() => this.onSpinOver(), 0.5);
  296. }
  297. }
  298. /**
  299. * 转动结束
  300. */
  301. onSpinOver() {
  302. // 如有wild, 先播放wild动画,再播放连线
  303. this.playWildParticle(() => {
  304. this.playShineArea();
  305. // 结束回调
  306. this._callback?.();
  307. });
  308. }
  309. /**
  310. * 播放所有wild粒子动画
  311. */
  312. playWildParticle(cb: Function) {
  313. // 超级转老鼠已经在房顶了
  314. if (this.gameFrameView.isSuperSpin || !this._gameResult.itemInfo.includes(MouseConst.CT_WILD)) {
  315. cb?.();
  316. return;
  317. }
  318. // Wild粒子音效
  319. AudioPlayer.playEffect(MouseConst.Sound.wildStart, false);
  320. this.mouseIconCover.node.active = true;
  321. let collectParticleStart = 0;
  322. let collectParticleEnd = 0;
  323. for (let i = 0; i < this._gameResult.itemInfo.length; i++) {
  324. let iconItem = this.mouseIconCover.getIconItemByIndex(i);
  325. if (iconItem.iconId == MouseConst.CT_WILD) {
  326. this.getIconItemByIndex(i).node.active = false;
  327. iconItem.playWildAnim();
  328. // 收集粒子动画
  329. let particleAnimNode = this.particleAnimNodeArr.getChildByName('pos_' + i);
  330. particleAnimNode.position = iconItem.node.position;
  331. particleAnimNode.active = true;
  332. particleAnimNode.getComponent(cc.ParticleSystem).resetSystem();
  333. collectParticleStart++;
  334. cc.tween(particleAnimNode)
  335. .bezierTo(0.6, cc.v2(particleAnimNode.x - 50, particleAnimNode.y + 30), cc.v2(particleAnimNode.x - 100, particleAnimNode.y), this.mouseAvatar.getBottomPos())
  336. .call(() => {
  337. particleAnimNode.active = false;
  338. collectParticleEnd++;
  339. if (collectParticleStart == collectParticleEnd) {
  340. // 粒子结束音效
  341. AudioPlayer.playEffect(MouseConst.Sound.wildEnd, false);
  342. this.mouseAvatar.playWildAnim();
  343. this.scheduleOnce(() => {
  344. cb?.();
  345. }, 0.5);
  346. }
  347. })
  348. .start();
  349. }
  350. }
  351. }
  352. /**
  353. * shine
  354. */
  355. playShineArea() {
  356. // let shineCount = this._gameResult?.shineArea?.length || 0;
  357. // if (shineCount == 0)
  358. // return;
  359. // 后台协议有变化
  360. let hasShine = (this._gameResult?.awardTotalTimes || 0) > 0;
  361. if (!hasShine)
  362. return;
  363. // 播放shine的时候就要隐藏中间三个老鼠
  364. if (this.gameFrameView.isSuperSpin) {
  365. this.mouseSuper.onExitSuper();
  366. this.mouseAvatar.onExitSuper();
  367. }
  368. // 中奖音效
  369. AudioPlayer.playEffect(MouseConst.Sound.win, false);
  370. // 老鼠音效
  371. for (let i = 0; i < this._gameResult.shineArea.length; i++) {
  372. if (this._gameResult.shineArea[i] && this._gameResult.itemInfo[i] == MouseConst.CT_WILD) {
  373. AudioPlayer.playEffect(`sound/winLineMouse${MouseConst.getRandomInt(1, 3)}`, false);
  374. break;
  375. }
  376. }
  377. this.mouseIconCover.playShineArea(this._gameResult);
  378. }
  379. // ====================== 超级转 =======================
  380. isVisibleIcon(row: number) {
  381. return row >= 0 && row <= MouseConst.GAME_ROW - 1;
  382. }
  383. /**
  384. * 触发超级转动画
  385. */
  386. playSuperAnim(cb: Function) {
  387. // 超级转触发音效
  388. AudioPlayer.playEffect(MouseConst.Sound.superPre, false);
  389. this.mouseDecor.onEnterSuper();
  390. // 老鼠摇晃
  391. this.mouseAvatar.playSuperAnim();
  392. // 窗口变小
  393. cc.tween(this.node).to(MouseConst.SUPER_ENTER_TIME, { scale: 0.85, y: this.node.y - this.SCALE_OFFSET }, { easing: 'sineIn' })
  394. .call(() => { cb?.(); }).start();
  395. }
  396. /**
  397. * 进入超级转
  398. */
  399. onEnterSuper() {
  400. this.mouseAvatar.onEnterSuper();
  401. this.scheduleOnce(() => {
  402. // 超级转音效(中间3个老鼠)
  403. AudioPlayer.playEffect(MouseConst.Sound.superStart, false);
  404. this.underlayNormal.active = false;
  405. this.underlaySuper.active = true;
  406. this.mouseSuper.onEnterSuper();
  407. }, 1); // 1s 以同步老鼠窜天的动作
  408. }
  409. /**
  410. * 退出超级转
  411. */
  412. onExitSuper(cb: Function) {
  413. this.scheduleOnce(() => {
  414. this.mouseDecor.onExitSuper();
  415. cc.tween(this.node).to(MouseConst.SUPER_EXIT_TIME, { scale: 1, y: this.node.y + this.SCALE_OFFSET })
  416. .call(() => {
  417. this.underlayNormal.active = true;
  418. this.underlaySuper.active = false;
  419. cb?.();
  420. })
  421. .start();
  422. }, 1);
  423. }
  424. onClickIconView() {
  425. if (!this.nodeClick.active)
  426. return;
  427. cc.warn("=============>onClickIconView");
  428. this.nodeClick.active = false;
  429. if (!this.gameFrameView.isGaming || this.gameFrameView.bottomView.isTurbo || !this.isSpinReady)
  430. return;
  431. if (this.isStartSuper || this.gameFrameView.isSuperSpin) {
  432. return;
  433. }
  434. this.stopSpin();
  435. }
  436. resetIcons() {
  437. for (let i = 0; i < MouseConst.GAME_COLUMN; i++) {
  438. for (let j = 0; j < MouseConst.GAME_ROW + TopCount; j++) {
  439. this._iconItemMap[i][j].node.active = true;
  440. }
  441. }
  442. }
  443. resetAllIconItemPos() {
  444. for (let i = 0; i < MouseConst.GAME_COLUMN; i++) {
  445. for (let j = 0; j < MouseConst.GAME_ROW + TopCount; j++) {
  446. this._iconItemMap[i][j].node.y = MouseConst.IconSize.height * (j - 1);
  447. }
  448. }
  449. }
  450. // =====================
  451. showIconDetail(iconId: number, col: number, row: number, target: cc.Node) {
  452. this.mouseIconDetail.show(this, iconId, col, row);
  453. }
  454. resetView() {
  455. cc.error("========== MouseIconView ResetView==========");
  456. this._callback = null;
  457. this._gameResult = null;
  458. this._startRunAudioId = 0;
  459. this.isStartSuper = false;
  460. this.isSpinReady = false;
  461. this.unscheduleAllCallbacks();
  462. this.nodeClick.active = false;
  463. this.mouseIconCover.resetView(); // IconCover会调用IconView的resetIcons
  464. this.mouseIconDetail.resetView();
  465. }
  466. }