update chat exp
This commit is contained in:
@@ -33,7 +33,7 @@ export interface IChatPanelCallback {
|
|||||||
/**
|
/**
|
||||||
* 对话更新回调
|
* 对话更新回调
|
||||||
*/
|
*/
|
||||||
onDialogUpdated(): void;
|
onDialogUpdated(isPlayer: boolean): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 聊天次数用尽回调
|
* 聊天次数用尽回调
|
||||||
@@ -135,7 +135,7 @@ export class ChatController {
|
|||||||
this.loadDialogsFromHistory(roleId);
|
this.loadDialogsFromHistory(roleId);
|
||||||
|
|
||||||
// 同步到DialogManager
|
// 同步到DialogManager
|
||||||
this.syncDialogData();
|
//this.syncDialogData();
|
||||||
|
|
||||||
console.log(`ChatController initialized with role ${roleId}`);
|
console.log(`ChatController initialized with role ${roleId}`);
|
||||||
} else {
|
} else {
|
||||||
@@ -162,7 +162,7 @@ export class ChatController {
|
|||||||
this.loadDialogsFromHistory(roleId);
|
this.loadDialogsFromHistory(roleId);
|
||||||
|
|
||||||
// 同步对话数据到DialogManager
|
// 同步对话数据到DialogManager
|
||||||
this.syncDialogData();
|
//this.syncDialogData();
|
||||||
|
|
||||||
console.log(`ChatController switched to role ${roleId}`);
|
console.log(`ChatController switched to role ${roleId}`);
|
||||||
return true;
|
return true;
|
||||||
@@ -196,16 +196,15 @@ export class ChatController {
|
|||||||
|
|
||||||
// 更新对话显示 - 用户消息
|
// 更新对话显示 - 用户消息
|
||||||
this.dialogManager?.updateDialog(true, message, true);
|
this.dialogManager?.updateDialog(true, message, true);
|
||||||
this.callback?.onDialogUpdated();
|
this.callback?.onDialogUpdated(true);
|
||||||
|
|
||||||
// 通知界面消息发送开始
|
// 通知界面消息发送开始
|
||||||
this.callback?.onMessageSent(message);
|
this.callback?.onMessageSent(message);
|
||||||
|
|
||||||
// 显示加载中的对话
|
// 显示加载中的对话
|
||||||
this.dialogManager?.addLoadingDialog();
|
//this.dialogManager?.addLoadingDialog();
|
||||||
|
|
||||||
console.log(`Sending message to role ${roleId}: ${message}`);
|
console.log(`Sending message to role ${roleId}: ${message}`);
|
||||||
|
|
||||||
// 发送消息给AI服务
|
// 发送消息给AI服务
|
||||||
const response = await ChatAIService.Instance.sendMessage(
|
const response = await ChatAIService.Instance.sendMessage(
|
||||||
roleId,
|
roleId,
|
||||||
@@ -214,14 +213,14 @@ export class ChatController {
|
|||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
// 移除加载中的对话
|
// 移除加载中的对话
|
||||||
this.dialogManager?.removeLoadingDialog();
|
//this.dialogManager?.removeLoadingDialog();
|
||||||
|
|
||||||
// 添加AI回复到模型 (保持完整消息)
|
// 添加AI回复到模型 (保持完整消息)
|
||||||
this.chatModel.addDialog(false, response);
|
this.chatModel.addDialog(false, response);
|
||||||
|
|
||||||
// 更新对话显示 - AI回复 (使用分段显示)
|
// 更新对话显示 - AI回复 (使用分段显示)
|
||||||
this.dialogManager?.updateDialogWithSegments(false, response);
|
this.dialogManager?.updateDialogWithSegments(false, response);
|
||||||
this.callback?.onDialogUpdated();
|
this.callback?.onDialogUpdated(false);
|
||||||
|
|
||||||
// 通知界面收到回复
|
// 通知界面收到回复
|
||||||
this.callback?.onMessageReceived(response);
|
this.callback?.onMessageReceived(response);
|
||||||
@@ -235,7 +234,7 @@ export class ChatController {
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// 移除加载中的对话
|
// 移除加载中的对话
|
||||||
this.dialogManager?.removeLoadingDialog();
|
//this.dialogManager?.removeLoadingDialog();
|
||||||
|
|
||||||
const error = new Error("AI返回了空响应");
|
const error = new Error("AI返回了空响应");
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
@@ -243,7 +242,7 @@ export class ChatController {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 移除加载中的对话
|
// 移除加载中的对话
|
||||||
this.dialogManager?.removeLoadingDialog();
|
//this.dialogManager?.removeLoadingDialog();
|
||||||
|
|
||||||
ErrorHandler.Instance.handleApiError(
|
ErrorHandler.Instance.handleApiError(
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
export interface Dialog {
|
export interface Dialog {
|
||||||
isPlayer: boolean;
|
isPlayer: boolean;
|
||||||
content: string;
|
content: string;
|
||||||
isLoading?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DiaLogData {
|
export class DiaLogData {
|
||||||
public Dialogs: Dialog[] = [];
|
public playerDialog: Dialog = null;
|
||||||
|
public aiDialogs: Dialog[] = [];
|
||||||
|
|
||||||
public cleanDialog() {
|
public cleanDialog() {
|
||||||
this.Dialogs = [];
|
this.playerDialog = null;
|
||||||
|
this.aiDialogs = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public pushDialog(
|
public pushDialog(
|
||||||
@@ -16,15 +17,17 @@ export class DiaLogData {
|
|||||||
str: string,
|
str: string,
|
||||||
isLoading: boolean = false
|
isLoading: boolean = false
|
||||||
) {
|
) {
|
||||||
if (!this.Dialogs) this.Dialogs = [];
|
if (isPlayer) {
|
||||||
this.Dialogs.push({
|
this.playerDialog = { isPlayer: true, content: str };
|
||||||
isPlayer: isPlayer,
|
} else {
|
||||||
content: str,
|
this.aiDialogs.push({ isPlayer: false, content: str });
|
||||||
isLoading: isLoading,
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public GetDialogs() {
|
public GetPlayerDialog() {
|
||||||
return this.Dialogs;
|
return this.playerDialog;
|
||||||
|
}
|
||||||
|
public GetAIDialog() {
|
||||||
|
return this.aiDialogs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,11 +123,6 @@ export class DialogManager {
|
|||||||
segments: string[],
|
segments: string[],
|
||||||
isPlayer: boolean
|
isPlayer: boolean
|
||||||
): void {
|
): void {
|
||||||
// let currentDelay = 500; // 基础延迟 500ms
|
|
||||||
// 每个后续段落增加随机延迟 (800-1500ms)
|
|
||||||
|
|
||||||
// currentDelay += 800 + Math.random() * 700;
|
|
||||||
|
|
||||||
segments.forEach((segment, index) => {
|
segments.forEach((segment, index) => {
|
||||||
this.dialogData.pushDialog(isPlayer, segment);
|
this.dialogData.pushDialog(isPlayer, segment);
|
||||||
});
|
});
|
||||||
@@ -143,28 +138,15 @@ export class DialogManager {
|
|||||||
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除加载中的对话
|
|
||||||
* 在收到AI回复或出错时调用
|
|
||||||
*/
|
|
||||||
public removeLoadingDialog(): void {
|
|
||||||
const dialogs = this.dialogData.GetDialogs();
|
|
||||||
const loadingIndex = dialogs.findIndex((dialog) => dialog.isLoading);
|
|
||||||
if (loadingIndex !== -1) {
|
|
||||||
dialogs.splice(loadingIndex, 1);
|
|
||||||
console.log("Loading dialog removed");
|
|
||||||
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public GetPlayerDialog() {
|
||||||
* 获取所有对话记录
|
return this.dialogData.GetPlayerDialog();
|
||||||
*
|
|
||||||
* @returns {Dialog[]} 对话记录数组
|
|
||||||
*/
|
|
||||||
public getDialogs() {
|
|
||||||
return this.dialogData.GetDialogs();
|
|
||||||
}
|
}
|
||||||
|
public GetAIDialog() {
|
||||||
|
return this.dialogData.GetAIDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空当前对话记录
|
* 清空当前对话记录
|
||||||
@@ -172,7 +154,6 @@ export class DialogManager {
|
|||||||
public clearDialogs(): void {
|
public clearDialogs(): void {
|
||||||
this.dialogData.cleanDialog();
|
this.dialogData.cleanDialog();
|
||||||
console.log("All dialogs cleared");
|
console.log("All dialogs cleared");
|
||||||
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -185,7 +166,6 @@ export class DialogManager {
|
|||||||
this.dialogData.pushDialog(
|
this.dialogData.pushDialog(
|
||||||
dialog.isPlayer,
|
dialog.isPlayer,
|
||||||
dialog.content,
|
dialog.content,
|
||||||
dialog.isLoading || false
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
console.log(`DialogManager: Set ${dialogs.length} dialogs`);
|
console.log(`DialogManager: Set ${dialogs.length} dialogs`);
|
||||||
@@ -201,25 +181,4 @@ export class DialogManager {
|
|||||||
console.log("DialogManager: Synced dialogs from ChatModel");
|
console.log("DialogManager: Synced dialogs from ChatModel");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取对话记录数量
|
|
||||||
*/
|
|
||||||
public getDialogCount(): number {
|
|
||||||
return this.dialogData.GetDialogs().length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取最后一条对话记录
|
|
||||||
*/
|
|
||||||
public getLastDialog(): Dialog | null {
|
|
||||||
const dialogs = this.dialogData.GetDialogs();
|
|
||||||
return dialogs.length > 0 ? dialogs[dialogs.length - 1] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否有对话记录
|
|
||||||
*/
|
|
||||||
public hasDialogs(): boolean {
|
|
||||||
return this.dialogData.GetDialogs().length > 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
instantiate,
|
instantiate,
|
||||||
Node,
|
Node,
|
||||||
|
ScrollView,
|
||||||
UITransform,
|
UITransform,
|
||||||
Vec3,
|
Vec3,
|
||||||
view,
|
view,
|
||||||
@@ -16,122 +17,198 @@ const { ccclass, property } = _decorator;
|
|||||||
export class ChatContentsLayout extends Component {
|
export class ChatContentsLayout extends Component {
|
||||||
manager: DialogManager = null;
|
manager: DialogManager = null;
|
||||||
|
|
||||||
@property(Node)
|
|
||||||
initPos: Node = null;
|
|
||||||
|
|
||||||
@property(DialogBubble)
|
@property(DialogBubble)
|
||||||
lBubble: DialogBubble = null;
|
lBubble: DialogBubble = null;
|
||||||
@property(DialogBubble)
|
@property(DialogBubble)
|
||||||
rBubble: DialogBubble = null;
|
rBubble: DialogBubble = null;
|
||||||
|
|
||||||
bubbles: DialogBubble[] = [];
|
// 当前显示的气泡
|
||||||
cachedBubbles: DialogBubble[] = [];
|
private bubbles: DialogBubble[] = [];
|
||||||
|
// 缓存池
|
||||||
|
private cachedBubbles: DialogBubble[] = [];
|
||||||
|
// 等待回复气泡的引用
|
||||||
|
private waitingBubble: DialogBubble = null;
|
||||||
|
// 延时任务ID数组
|
||||||
|
private delayedTasks: number[] = [];
|
||||||
|
// ScrollView组件引用
|
||||||
|
@property(ScrollView)
|
||||||
|
private scrollView: ScrollView = null;
|
||||||
|
|
||||||
fixMaxWidth: number;
|
fixMaxWidth: number;
|
||||||
|
|
||||||
// 用于跟踪延时任务,支持中断清理
|
|
||||||
private delayedTasks: number[] = [];
|
|
||||||
|
|
||||||
protected start(): void {
|
protected start(): void {
|
||||||
this.lBubble.node.active = false;
|
this.lBubble.node.active = false;
|
||||||
this.rBubble.node.active = false;
|
this.rBubble.node.active = false;
|
||||||
this.fixMaxWidth = view.getVisibleSize().width * 0.8;
|
this.fixMaxWidth = 730;
|
||||||
|
|
||||||
|
// 获取父节点的ScrollView组件
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onEnable(): void {
|
protected onEnable(): void {
|
||||||
if (!this.manager) this.manager = DialogManager.getInstance();
|
if (!this.manager) this.manager = DialogManager.getInstance();
|
||||||
|
|
||||||
this.manager.layoutout = this;
|
this.manager.layoutout = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateDialog(dialogs: Dialog[]) {
|
/**
|
||||||
// 清理所有正在进行的延时任务
|
* 更新玩家输入的对话
|
||||||
|
* @param dialog 玩家输入的对话数据
|
||||||
|
*/
|
||||||
|
updatePlayerDialog(dialog: Dialog): void {
|
||||||
|
// 清理所有现有气泡到缓存池
|
||||||
|
this.clearAllBubbles();
|
||||||
|
|
||||||
|
// 清理所有延时任务
|
||||||
this.clearDelayedTasks();
|
this.clearDelayedTasks();
|
||||||
|
|
||||||
// 隐藏当前使用的气泡并放入缓存
|
// 创建玩家气泡(插入到首位,Layout会让它显示在最下面)
|
||||||
for (let i = 0; i < this.bubbles.length; i++) {
|
const playerBubble = this.createOrGetBubble(true);
|
||||||
if (this.bubbles[i].node) {
|
playerBubble.node.active = true;
|
||||||
this.bubbles[i].node.active = false;
|
playerBubble.updateBubbleContent(dialog.content, true);
|
||||||
this.cachedBubbles.push(this.bubbles[i]);
|
this.bubbles.push(playerBubble);
|
||||||
|
|
||||||
|
// 创建等待回复气泡(插入到首位,显示在玩家消息上面)
|
||||||
|
this.waitingBubble = this.createOrGetBubble(false);
|
||||||
|
this.waitingBubble.node.active = true;
|
||||||
|
this.waitingBubble.updateBubbleContent("...", false, undefined, true);
|
||||||
|
this.waitingBubble.node.setSiblingIndex(0);
|
||||||
|
this.bubbles.push(this.waitingBubble);
|
||||||
|
this.node.position = Vec3.ZERO;
|
||||||
|
// 滚动到底部(等待下一帧Layout更新完成)
|
||||||
|
this.scheduleOnce(() => {
|
||||||
|
this.scrollView.scrollToBottom();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新AI回复的对话
|
||||||
|
* @param dialogs AI回复的对话数据数组
|
||||||
|
*/
|
||||||
|
updateAIDialogs(dialogs: Dialog[]): void {
|
||||||
|
// 清除等待回复气泡
|
||||||
|
if (this.waitingBubble) {
|
||||||
|
this.removeWaitingBubble();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理之前的延时任务
|
||||||
|
this.clearDelayedTasks();
|
||||||
|
|
||||||
|
// 逐个添加AI对话,带延时
|
||||||
|
this.addAIDialogsWithDelay(dialogs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理所有气泡到缓存池
|
||||||
|
*/
|
||||||
|
private clearAllBubbles(): void {
|
||||||
|
for (const bubble of this.bubbles) {
|
||||||
|
if (bubble && bubble.node) {
|
||||||
|
bubble.node.active = false;
|
||||||
|
this.cachedBubbles.push(bubble);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.bubbles = [];
|
this.bubbles = [];
|
||||||
|
this.waitingBubble = null;
|
||||||
|
|
||||||
this.updateDialogLayoutWithDelay(dialogs);
|
// 清理多余的缓存气泡
|
||||||
|
|
||||||
// 清理多余的缓存气泡,避免内存泄漏
|
|
||||||
this.cleanupExcessCachedBubbles();
|
this.cleanupExcessCachedBubbles();
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateDialogLayout(dialogs: Dialog[]) {
|
/**
|
||||||
let initPosY: number = this.initPos.position.y;
|
* 从缓存池获取或创建新的气泡
|
||||||
let pendingUpdates = 0;
|
* @param isPlayer 是否为玩家气泡
|
||||||
|
* @returns DialogBubble实例
|
||||||
const updateNextBubble = (index: number) => {
|
*/
|
||||||
if (index < 0) {
|
private createOrGetBubble(isPlayer: boolean): DialogBubble {
|
||||||
return;
|
// 尝试从缓存中获取合适的气泡
|
||||||
}
|
for (let i = 0; i < this.cachedBubbles.length; i++) {
|
||||||
|
const cached = this.cachedBubbles[i];
|
||||||
const dialog = dialogs[index];
|
if (cached && cached.node) {
|
||||||
let newBubble: DialogBubble = null;
|
// 通过原始模板判断气泡类型
|
||||||
|
const isRightBubble = cached.node.position.x > 0;
|
||||||
// 尝试从缓存中获取合适的气泡
|
if ((isPlayer && isRightBubble) || (!isPlayer && !isRightBubble)) {
|
||||||
const isPlayerBubble = dialog.isPlayer;
|
this.cachedBubbles.splice(i, 1);
|
||||||
for (let j = 0; j < this.cachedBubbles.length; j++) {
|
return cached;
|
||||||
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) {
|
const templateBubble = isPlayer ? this.rBubble : this.lBubble;
|
||||||
let newBubbleNode = isPlayerBubble
|
const newBubbleNode = instantiate(templateBubble.node);
|
||||||
? instantiate(this.rBubble.node)
|
const newBubble = newBubbleNode.getComponent(DialogBubble);
|
||||||
: instantiate(this.lBubble.node);
|
newBubble.init(this.fixMaxWidth);
|
||||||
newBubble = newBubbleNode.getComponent(DialogBubble);
|
|
||||||
newBubble.init(650);
|
|
||||||
newBubbleNode.setParent(this.node);
|
|
||||||
}
|
|
||||||
|
|
||||||
newBubble.node.active = true;
|
// 设置为子节点,具体位置由调用者决定
|
||||||
let pos = newBubble.node.position;
|
newBubbleNode.setParent(this.node);
|
||||||
newBubble.node.position = new Vec3(pos.x, initPosY, pos.z);
|
|
||||||
|
|
||||||
pendingUpdates++;
|
return newBubble;
|
||||||
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; // 最大缓存数量
|
* 移除等待回复气泡
|
||||||
|
*/
|
||||||
|
private removeWaitingBubble(): void {
|
||||||
|
if (this.waitingBubble && this.waitingBubble.node) {
|
||||||
|
this.waitingBubble.node.active = false;
|
||||||
|
this.cachedBubbles.push(this.waitingBubble);
|
||||||
|
|
||||||
|
// 从当前气泡列表中移除
|
||||||
|
const index = this.bubbles.indexOf(this.waitingBubble);
|
||||||
|
if (index > -1) {
|
||||||
|
this.bubbles.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.waitingBubble = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带延时地添加AI对话
|
||||||
|
* @param dialogs AI对话数组
|
||||||
|
* @param index 当前处理的对话索引
|
||||||
|
*/
|
||||||
|
private addAIDialogsWithDelay(dialogs: Dialog[], index: number): void {
|
||||||
|
if (index >= dialogs.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialog = dialogs[index];
|
||||||
|
const delay = index > 0 ? Math.random() * 1000 + 500 : 0; // 第一条立即显示,后续延时500-1500ms
|
||||||
|
|
||||||
|
const addCurrentDialog = () => {
|
||||||
|
const aiBubble = this.createOrGetBubble(false);
|
||||||
|
aiBubble.node.active = true;
|
||||||
|
aiBubble.updateBubbleContent(dialog.content, false);
|
||||||
|
// AI消息插入到首位,显示在最上面
|
||||||
|
aiBubble.node.setSiblingIndex(0);
|
||||||
|
this.scheduleOnce(() => {
|
||||||
|
this.scrollView.scrollToBottom();
|
||||||
|
}, 0);
|
||||||
|
this.bubbles.push(aiBubble);
|
||||||
|
|
||||||
|
// 递归处理下一条对话
|
||||||
|
this.addAIDialogsWithDelay(dialogs, index + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (delay > 0) {
|
||||||
|
const taskId = setTimeout(() => {
|
||||||
|
// 从任务列表中移除
|
||||||
|
const taskIndex = this.delayedTasks.indexOf(taskId);
|
||||||
|
if (taskIndex > -1) {
|
||||||
|
this.delayedTasks.splice(taskIndex, 1);
|
||||||
|
}
|
||||||
|
addCurrentDialog();
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
this.delayedTasks.push(taskId);
|
||||||
|
} else {
|
||||||
|
addCurrentDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理多余的缓存气泡
|
||||||
|
*/
|
||||||
|
private cleanupExcessCachedBubbles(): void {
|
||||||
|
const maxCachedBubbles = 20;
|
||||||
if (this.cachedBubbles.length > maxCachedBubbles) {
|
if (this.cachedBubbles.length > maxCachedBubbles) {
|
||||||
const excessCount = this.cachedBubbles.length - maxCachedBubbles;
|
const excessCount = this.cachedBubbles.length - maxCachedBubbles;
|
||||||
for (let i = 0; i < excessCount; i++) {
|
for (let i = 0; i < excessCount; i++) {
|
||||||
@@ -143,153 +220,35 @@ export class ChatContentsLayout extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearDelayedTasks() {
|
/**
|
||||||
// 清理所有正在进行的延时任务
|
* 清理所有延时任务
|
||||||
|
*/
|
||||||
|
private clearDelayedTasks(): void {
|
||||||
for (const taskId of this.delayedTasks) {
|
for (const taskId of this.delayedTasks) {
|
||||||
clearTimeout(taskId);
|
clearTimeout(taskId);
|
||||||
}
|
}
|
||||||
this.delayedTasks = [];
|
this.delayedTasks = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private groupConsecutiveNonPlayerDialogs(dialogs: Dialog[]): Dialog[][] {
|
/**
|
||||||
const groups: Dialog[][] = [];
|
* 保持向后兼容性的方法
|
||||||
let currentGroup: Dialog[] = [];
|
* @param dialogs 对话数组
|
||||||
|
* @deprecated 请使用 updatePlayerDialog 和 updateAIDialogs 替代
|
||||||
|
*/
|
||||||
|
UpdateDialog(dialogs: Dialog[]): void {
|
||||||
|
// 为了保持兼容性,我们假设这是一个完整的对话更新
|
||||||
|
// 清理所有现有气泡
|
||||||
|
this.clearAllBubbles();
|
||||||
|
this.clearDelayedTasks();
|
||||||
|
|
||||||
for (const dialog of dialogs) {
|
// 逐个添加对话,从最后一个开始倒序插入(保持显示顺序)
|
||||||
if (dialog.isPlayer) {
|
for (let i = dialogs.length - 1; i >= 0; i--) {
|
||||||
// 遇到player消息,结束当前非player组
|
const dialog = dialogs[i];
|
||||||
if (currentGroup.length > 0) {
|
const bubble = this.createOrGetBubble(dialog.isPlayer);
|
||||||
groups.push([...currentGroup]);
|
bubble.node.active = true;
|
||||||
currentGroup = [];
|
bubble.updateBubbleContent(dialog.content, dialog.isPlayer, undefined);
|
||||||
}
|
bubble.node.setSiblingIndex(0);
|
||||||
// player消息单独成组
|
this.bubbles.push(bubble);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -377,9 +377,15 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDialogUpdate() {
|
onDialogUpdate(isPlayer: boolean) {
|
||||||
if (this.layout) {
|
if (this.layout) {
|
||||||
this.layout.UpdateDialog(DialogManager.getInstance().getDialogs());
|
if (isPlayer) {
|
||||||
|
this.layout.updatePlayerDialog(
|
||||||
|
DialogManager.getInstance().GetPlayerDialog()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.layout.updateAIDialogs(DialogManager.getInstance().GetAIDialog());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected onEnable(): void {
|
protected onEnable(): void {
|
||||||
@@ -425,9 +431,9 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
|||||||
/**
|
/**
|
||||||
* 对话更新回调
|
* 对话更新回调
|
||||||
*/
|
*/
|
||||||
public onDialogUpdated(): void {
|
public onDialogUpdated(isPlayer: boolean): void {
|
||||||
// 更新对话显示
|
// 更新对话显示
|
||||||
this.onDialogUpdate();
|
this.onDialogUpdate(isPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user