HomeNotices.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import { AppKernel } from '@scripts/app/AppKernel';
  2. import { CURRENCY_TYPE } from '@scripts/define/Define';
  3. import { IBigWinItem } from '@scripts/store/mainStore/data';
  4. import { formatMaskNickName, formatSCNum } from '@scripts/utils/Helper';
  5. import { _decorator, Component, Input, input, instantiate, isValid, KeyCode, Label, Node, NodePool, tween, Tween, UITransform, Vec3 } from 'cc';
  6. const { ccclass, property } = _decorator;
  7. const MIN_MESSAGE_GAP = 50;
  8. const SCROLL_SPEED = 120;
  9. const NEXT_NOTICE_DELAY = 2;
  10. interface INoticeNode {
  11. label: Label,
  12. trans: UITransform,
  13. }
  14. @ccclass('HomeNotices')
  15. export class HomeNotices extends Component {
  16. @property(Node)
  17. itemNode: Node = null;
  18. @property(UITransform)
  19. content: UITransform = null;
  20. private readonly pool = new NodePool('HomeNotices');
  21. private readonly activeItems: Node[] = [];
  22. private readonly noticeNodes = new WeakMap<Node, INoticeNode>();
  23. private timerID: number = 0;
  24. private waitingForNext = false;
  25. private readonly playNextNotice = () => {
  26. this.waitingForNext = false;
  27. this.startNotify();
  28. };
  29. protected onLoad(): void {
  30. if (this.itemNode) this.itemNode.active = false;
  31. this.node.active = false;
  32. // input.on(Input.EventType.KEY_DOWN, (event) => {
  33. // if (event.keyCode === KeyCode.KEY_W) {
  34. // let data: INoticeData = {
  35. // nickName: `User${Math.floor(Math.random() * 8999 + 1000)}`,
  36. // value: Math.random() * 10000
  37. // }
  38. // const mainStore = AppKernel.stores.mainStore;
  39. // mainStore['_noticesList'].push(data);
  40. // this.startNotify();
  41. // }
  42. // })
  43. }
  44. protected onDestroy(): void {
  45. this.unschedule(this.playNextNotice);
  46. for (let index = 0; index < this.activeItems.length; index += 1) {
  47. Tween.stopAllByTarget(this.activeItems[index]);
  48. }
  49. this.activeItems.length = 0;
  50. this.stopTimer();
  51. }
  52. private stopTimer() {
  53. if (this.timerID === 0) return;
  54. clearTimeout(this.timerID);
  55. this.timerID = 0;
  56. }
  57. startNotify(): void {
  58. // 外部刷新或旧消息播放结束不能跳过下一条消息的等待时间。
  59. if (this.waitingForNext) return;
  60. this.stopTimer();
  61. const mainStore = AppKernel.stores.mainStore;
  62. const data = mainStore.getNoticeData();
  63. if (!data) {
  64. // 队列已空且没有正在移动的消息时,隐藏整个公告区域。
  65. if (this.activeItems.length === 0) {
  66. this.node.active = false;
  67. }
  68. this.timerID = setTimeout(async () => {
  69. this.timerID = 0;
  70. await AppKernel.stores.mainStore.reqBigWinNotices()
  71. if (isValid(this.node)) this.startNotify();
  72. }, 5000);
  73. return;
  74. }
  75. this.node.active = true;
  76. this.onNotify(data);
  77. }
  78. onNotify(data: IBigWinItem): void {
  79. if (!this.itemNode || !this.content) return;
  80. const msg = `${formatMaskNickName(data.username)} won ${CURRENCY_TYPE.SC} ${formatSCNum(data.amount)}`;
  81. const item = this.pool.get() || instantiate(this.itemNode);
  82. const noticeNode = this.getNoticeNode(item);
  83. const lblNotices = noticeNode.label;
  84. const itemTransform = noticeNode.trans;
  85. Tween.stopAllByTarget(item);
  86. item.setParent(this.content.node);
  87. item.active = true;
  88. lblNotices.string = msg;
  89. lblNotices.updateRenderData(true);
  90. const itemWidth = itemTransform.width * Math.abs(item.scale.x);
  91. if (itemWidth <= 0) {
  92. this.recycle(item);
  93. return;
  94. }
  95. const startX = this.getStartX(this.content.width);
  96. const endX = -itemWidth;
  97. item.setPosition(startX, 0, item.position.z);
  98. this.activeItems.push(item);
  99. // 消息右边缘进入 content 右边界,表示整条消息已经滑入。
  100. // 超长消息也以尾部进入作为判定,不要求全文同时出现在可视区。
  101. const fullyEnteredTime = Math.max(0, startX + itemWidth - this.content.width) / SCROLL_SPEED;
  102. this.waitingForNext = true;
  103. this.unschedule(this.playNextNotice);
  104. this.scheduleOnce(this.playNextNotice, fullyEnteredTime + NEXT_NOTICE_DELAY);
  105. tween(item)
  106. .to((startX - endX) / SCROLL_SPEED, {
  107. x: endX,
  108. }, { easing: 'linear' })
  109. .call(() => {
  110. this.recycle(item);
  111. this.startNotify();
  112. })
  113. .start();
  114. }
  115. private getNoticeNode(item: Node) {
  116. let notices = this.noticeNodes.get(item);
  117. if (!notices) {
  118. notices = {
  119. label: item.getComponent(Label),
  120. trans: item.getComponent(UITransform),
  121. }
  122. this.noticeNodes.set(item, notices);
  123. }
  124. return notices;
  125. }
  126. private getStartX(contentWidth: number): number {
  127. let startX = contentWidth;
  128. for (let index = 0; index < this.activeItems.length; index += 1) {
  129. const item = this.activeItems[index];
  130. if (!item.isValid) continue;
  131. const transform = this.noticeNodes.get(item)?.trans;
  132. if (!transform) continue;
  133. const rightX = item.position.x + transform.width * Math.abs(item.scale.x);
  134. startX = Math.max(startX, rightX + MIN_MESSAGE_GAP);
  135. }
  136. return startX;
  137. }
  138. private recycle(item: Node): void {
  139. Tween.stopAllByTarget(item);
  140. const index = this.activeItems.indexOf(item);
  141. if (index !== -1) this.activeItems.splice(index, 1);
  142. if (!item.isValid) return;
  143. item.active = false;
  144. item.removeFromParent();
  145. this.pool.put(item);
  146. }
  147. async updateNotices() {
  148. await AppKernel.stores.mainStore.reqBigWinNotices();
  149. if (!isValid(this.node)) return;
  150. this.startNotify();
  151. }
  152. }