import { _decorator, Component, instantiate, Node, UITransform, Vec3, view, } from "cc"; import { DialogManager } from "../../manager/DialogManager"; import { DialogBubble } from "./DialogBubble"; import { Dialog } from "../../data/DialogData"; const { ccclass, property } = _decorator; @ccclass("ChatContentsLayout") export class ChatContentsLayout extends Component { manager: DialogManager = null; @property(Node) initPos: Node = null; @property(DialogBubble) lBubble: DialogBubble = null; @property(DialogBubble) rBubble: DialogBubble = null; bubbles: DialogBubble[] = []; cachedBubbles: DialogBubble[] = []; fixMaxWidth: number; // 用于跟踪延时任务,支持中断清理 private delayedTasks: number[] = []; protected start(): void { this.lBubble.node.active = false; this.rBubble.node.active = false; this.fixMaxWidth = view.getVisibleSize().width * 0.8; } protected onEnable(): void { if (!this.manager) this.manager = DialogManager.getInstance(); this.manager.layoutout = this; } UpdateDialog(dialogs: Dialog[]) { // 清理所有正在进行的延时任务 this.clearDelayedTasks(); // 隐藏当前使用的气泡并放入缓存 for (let i = 0; i < this.bubbles.length; i++) { if (this.bubbles[i].node) { this.bubbles[i].node.active = false; this.cachedBubbles.push(this.bubbles[i]); } } this.bubbles = []; this.updateDialogLayoutWithDelay(dialogs); // 清理多余的缓存气泡,避免内存泄漏 this.cleanupExcessCachedBubbles(); } private updateDialogLayout(dialogs: Dialog[]) { let initPosY: number = this.initPos.position.y; let pendingUpdates = 0; const updateNextBubble = (index: number) => { if (index < 0) { return; } const dialog = dialogs[index]; let newBubble: DialogBubble = null; // 尝试从缓存中获取合适的气泡 const isPlayerBubble = dialog.isPlayer; for (let j = 0; j < this.cachedBubbles.length; j++) { const cached = this.cachedBubbles[j]; if (cached && cached.node) { // 检查气泡类型是否匹配(通过位置判断左右气泡) const isRightBubble = cached.node.position.x > 0; if ( (isPlayerBubble && isRightBubble) || (!isPlayerBubble && !isRightBubble) ) { newBubble = cached; this.cachedBubbles.splice(j, 1); break; } } } // 如果缓存中没有合适的气泡,创建新的 if (!newBubble) { let newBubbleNode = isPlayerBubble ? instantiate(this.rBubble.node) : instantiate(this.lBubble.node); newBubble = newBubbleNode.getComponent(DialogBubble); newBubble.init(650); newBubbleNode.setParent(this.node); } newBubble.node.active = true; let pos = newBubble.node.position; newBubble.node.position = new Vec3(pos.x, initPosY, pos.z); pendingUpdates++; newBubble.updateBubbleContent( dialog.content, dialog.isPlayer, (actualHeight: number) => { initPosY += actualHeight + 40; pendingUpdates--; // 当当前气泡更新完成后,处理下一个 if (pendingUpdates === 0) { updateNextBubble(index - 1); } }, dialog.isLoading || false ); this.bubbles.push(newBubble); }; // 从最后一个对话开始处理(倒序) updateNextBubble(dialogs.length - 1); } private cleanupExcessCachedBubbles() { const maxCachedBubbles = 20; // 最大缓存数量 if (this.cachedBubbles.length > maxCachedBubbles) { const excessCount = this.cachedBubbles.length - maxCachedBubbles; for (let i = 0; i < excessCount; i++) { const bubble = this.cachedBubbles.shift(); if (bubble && bubble.node) { bubble.node.destroy(); } } } } private clearDelayedTasks() { // 清理所有正在进行的延时任务 for (const taskId of this.delayedTasks) { clearTimeout(taskId); } this.delayedTasks = []; } private groupConsecutiveNonPlayerDialogs(dialogs: Dialog[]): Dialog[][] { const groups: Dialog[][] = []; let currentGroup: Dialog[] = []; for (const dialog of dialogs) { if (dialog.isPlayer) { // 遇到player消息,结束当前非player组 if (currentGroup.length > 0) { groups.push([...currentGroup]); currentGroup = []; } // player消息单独成组 groups.push([dialog]); } else { // 非player消息加入当前组 currentGroup.push(dialog); } } // 处理最后一组非player消息 if (currentGroup.length > 0) { groups.push(currentGroup); } return groups; } private updateDialogLayoutWithDelay(dialogs: Dialog[]) { const groups = this.groupConsecutiveNonPlayerDialogs(dialogs); let initPosY: number = this.initPos.position.y; // 倒序处理组,保持原有的显示顺序 this.processGroupsWithDelay(groups, groups.length - 1, initPosY); } private processGroupsWithDelay( groups: Dialog[][], groupIndex: number, currentPosY: number ) { if (groupIndex < 0) return; const currentGroup = groups[groupIndex]; // 直接渲染组,延时逻辑已移到单条消息级别 this.renderDialogGroup(currentGroup, currentPosY, (newPosY) => { // 递归处理下一组,传递更新后的位置 this.processGroupsWithDelay(groups, groupIndex - 1, newPosY); }); } private renderDialogGroup( dialogs: Dialog[], startPosY: number, onComplete: (finalPosY: number) => void ) { let currentPosY = startPosY; const updateNextBubble = (index: number) => { if (index < 0) { onComplete(currentPosY); return; } const dialog = dialogs[index]; // 计算当前消息的延时:非player消息且不是组内最后一条时添加延时 const isNonPlayerMessage = !dialog.isPlayer; const isLastInGroup = index === dialogs.length - 1; const delay = isNonPlayerMessage && !isLastInGroup ? Math.random() * 1000 + 500 : 0; // 500-1500ms随机延时 const renderCurrentBubble = () => { let newBubble: DialogBubble = null; // 尝试从缓存中获取合适的气泡 const isPlayerBubble = dialog.isPlayer; for (let j = 0; j < this.cachedBubbles.length; j++) { const cached = this.cachedBubbles[j]; if (cached && cached.node) { const isRightBubble = cached.node.position.x > 0; if ( (isPlayerBubble && isRightBubble) || (!isPlayerBubble && !isRightBubble) ) { newBubble = cached; this.cachedBubbles.splice(j, 1); break; } } } // 如果缓存中没有合适的气泡,创建新的 if (!newBubble) { let newBubbleNode = isPlayerBubble ? instantiate(this.rBubble.node) : instantiate(this.lBubble.node); newBubble = newBubbleNode.getComponent(DialogBubble); newBubble.init(650); newBubbleNode.setParent(this.node); } newBubble.node.active = true; let pos = newBubble.node.position; newBubble.node.position = new Vec3(pos.x, currentPosY, pos.z); newBubble.updateBubbleContent( dialog.content, dialog.isPlayer, (actualHeight: number) => { currentPosY += actualHeight + 40; // 渲染完当前气泡后,处理下一个 updateNextBubble(index - 1); }, dialog.isLoading || false ); this.bubbles.push(newBubble); }; if (delay > 0) { // 添加延时任务ID到跟踪数组 const taskId = setTimeout(() => { // 从跟踪数组中移除已完成的任务 const taskIndex = this.delayedTasks.indexOf(taskId); if (taskIndex > -1) { this.delayedTasks.splice(taskIndex, 1); } renderCurrentBubble(); }, delay); this.delayedTasks.push(taskId); } else { // 立即渲染 renderCurrentBubble(); } }; // 从组内最后一个对话开始处理(倒序) updateNextBubble(dialogs.length - 1); } }