import { AppKernel } from '@scripts/app/AppKernel'; import { CURRENCY_TYPE } from '@scripts/define/Define'; import { IBigWinItem } from '@scripts/store/mainStore/data'; import { formatMaskNickName, formatSCNum } from '@scripts/utils/Helper'; import { _decorator, Component, Input, input, instantiate, isValid, KeyCode, Label, Node, NodePool, tween, Tween, UITransform, Vec3 } from 'cc'; const { ccclass, property } = _decorator; const MIN_MESSAGE_GAP = 50; const SCROLL_SPEED = 120; const NEXT_NOTICE_DELAY = 2; interface INoticeNode { label: Label, trans: UITransform, } @ccclass('HomeNotices') export class HomeNotices extends Component { @property(Node) itemNode: Node = null; @property(UITransform) content: UITransform = null; private readonly pool = new NodePool('HomeNotices'); private readonly activeItems: Node[] = []; private readonly noticeNodes = new WeakMap(); private timerID: number = 0; private waitingForNext = false; private readonly playNextNotice = () => { this.waitingForNext = false; this.startNotify(); }; protected onLoad(): void { if (this.itemNode) this.itemNode.active = false; this.node.active = false; // input.on(Input.EventType.KEY_DOWN, (event) => { // if (event.keyCode === KeyCode.KEY_W) { // let data: INoticeData = { // nickName: `User${Math.floor(Math.random() * 8999 + 1000)}`, // value: Math.random() * 10000 // } // const mainStore = AppKernel.stores.mainStore; // mainStore['_noticesList'].push(data); // this.startNotify(); // } // }) } protected onDestroy(): void { this.unschedule(this.playNextNotice); for (let index = 0; index < this.activeItems.length; index += 1) { Tween.stopAllByTarget(this.activeItems[index]); } this.activeItems.length = 0; this.stopTimer(); } private stopTimer() { if (this.timerID === 0) return; clearTimeout(this.timerID); this.timerID = 0; } startNotify(): void { // 外部刷新或旧消息播放结束不能跳过下一条消息的等待时间。 if (this.waitingForNext) return; this.stopTimer(); const mainStore = AppKernel.stores.mainStore; const data = mainStore.getNoticeData(); if (!data) { // 队列已空且没有正在移动的消息时,隐藏整个公告区域。 if (this.activeItems.length === 0) { this.node.active = false; } this.timerID = setTimeout(async () => { this.timerID = 0; await AppKernel.stores.mainStore.reqBigWinNotices() if (isValid(this.node)) this.startNotify(); }, 5000); return; } this.node.active = true; this.onNotify(data); } onNotify(data: IBigWinItem): void { if (!this.itemNode || !this.content) return; const msg = `${formatMaskNickName(data.username)} won ${CURRENCY_TYPE.SC} ${formatSCNum(data.amount)}`; const item = this.pool.get() || instantiate(this.itemNode); const noticeNode = this.getNoticeNode(item); const lblNotices = noticeNode.label; const itemTransform = noticeNode.trans; Tween.stopAllByTarget(item); item.setParent(this.content.node); item.active = true; lblNotices.string = msg; lblNotices.updateRenderData(true); const itemWidth = itemTransform.width * Math.abs(item.scale.x); if (itemWidth <= 0) { this.recycle(item); return; } const startX = this.getStartX(this.content.width); const endX = -itemWidth; item.setPosition(startX, 0, item.position.z); this.activeItems.push(item); // 消息右边缘进入 content 右边界,表示整条消息已经滑入。 // 超长消息也以尾部进入作为判定,不要求全文同时出现在可视区。 const fullyEnteredTime = Math.max(0, startX + itemWidth - this.content.width) / SCROLL_SPEED; this.waitingForNext = true; this.unschedule(this.playNextNotice); this.scheduleOnce(this.playNextNotice, fullyEnteredTime + NEXT_NOTICE_DELAY); tween(item) .to((startX - endX) / SCROLL_SPEED, { x: endX, }, { easing: 'linear' }) .call(() => { this.recycle(item); this.startNotify(); }) .start(); } private getNoticeNode(item: Node) { let notices = this.noticeNodes.get(item); if (!notices) { notices = { label: item.getComponent(Label), trans: item.getComponent(UITransform), } this.noticeNodes.set(item, notices); } return notices; } private getStartX(contentWidth: number): number { let startX = contentWidth; for (let index = 0; index < this.activeItems.length; index += 1) { const item = this.activeItems[index]; if (!item.isValid) continue; const transform = this.noticeNodes.get(item)?.trans; if (!transform) continue; const rightX = item.position.x + transform.width * Math.abs(item.scale.x); startX = Math.max(startX, rightX + MIN_MESSAGE_GAP); } return startX; } private recycle(item: Node): void { Tween.stopAllByTarget(item); const index = this.activeItems.indexOf(item); if (index !== -1) this.activeItems.splice(index, 1); if (!item.isValid) return; item.active = false; item.removeFromParent(); this.pool.put(item); } async updateNotices() { await AppKernel.stores.mainStore.reqBigWinNotices(); if (!isValid(this.node)) return; this.startNotify(); } }