优化聊天部分
This commit is contained in:
@@ -29,6 +29,9 @@ export class ChatContentsLayout extends Component {
|
||||
|
||||
fixMaxWidth: number;
|
||||
|
||||
// 用于跟踪延时任务,支持中断清理
|
||||
private delayedTasks: number[] = [];
|
||||
|
||||
protected start(): void {
|
||||
this.lBubble.node.active = false;
|
||||
this.rBubble.node.active = false;
|
||||
@@ -42,6 +45,9 @@ export class ChatContentsLayout extends Component {
|
||||
}
|
||||
|
||||
UpdateDialog(dialogs: Dialog[]) {
|
||||
// 清理所有正在进行的延时任务
|
||||
this.clearDelayedTasks();
|
||||
|
||||
// 隐藏当前使用的气泡并放入缓存
|
||||
for (let i = 0; i < this.bubbles.length; i++) {
|
||||
if (this.bubbles[i].node) {
|
||||
@@ -51,7 +57,7 @@ export class ChatContentsLayout extends Component {
|
||||
}
|
||||
this.bubbles = [];
|
||||
|
||||
this.updateDialogLayout(dialogs);
|
||||
this.updateDialogLayoutWithDelay(dialogs);
|
||||
|
||||
// 清理多余的缓存气泡,避免内存泄漏
|
||||
this.cleanupExcessCachedBubbles();
|
||||
@@ -93,7 +99,7 @@ export class ChatContentsLayout extends Component {
|
||||
? instantiate(this.rBubble.node)
|
||||
: instantiate(this.lBubble.node);
|
||||
newBubble = newBubbleNode.getComponent(DialogBubble);
|
||||
newBubble.init();
|
||||
newBubble.init(650);
|
||||
newBubbleNode.setParent(this.node);
|
||||
}
|
||||
|
||||
@@ -104,6 +110,7 @@ export class ChatContentsLayout extends Component {
|
||||
pendingUpdates++;
|
||||
newBubble.updateBubbleContent(
|
||||
dialog.content,
|
||||
dialog.isPlayer,
|
||||
(actualHeight: number) => {
|
||||
initPosY += actualHeight + 40;
|
||||
pendingUpdates--;
|
||||
@@ -135,4 +142,154 @@ export class ChatContentsLayout extends Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,21 @@ import {
|
||||
Node,
|
||||
Overflow,
|
||||
Size,
|
||||
Sprite,
|
||||
UITransform,
|
||||
view,
|
||||
} from "cc";
|
||||
import Tools from "../../utils/tools";
|
||||
import { GirlData } from "../../data/GirlData";
|
||||
import { DataId, DataManager } from "../../data/DataManager";
|
||||
import { NavigationManager } from "../../manager/NavigationManager";
|
||||
import ResManager from "../../../Main/Manager/ResManager";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("DialogBubble")
|
||||
export class DialogBubble extends Component {
|
||||
@property(Sprite)
|
||||
avatar: Sprite = null;
|
||||
@property(UITransform)
|
||||
bg: UITransform = null;
|
||||
@property(Label)
|
||||
@@ -21,8 +28,12 @@ export class DialogBubble extends Component {
|
||||
contentT: UITransform = null;
|
||||
|
||||
fixMaxWidth: number;
|
||||
private loadingAnimationId: number = null;
|
||||
|
||||
uiTransform: UITransform;
|
||||
|
||||
init(maxWidth?: number) {
|
||||
this.uiTransform = this.node.getComponent(UITransform);
|
||||
|
||||
// 如果没有提供maxWidth,使用屏幕宽度的80%
|
||||
if (maxWidth === undefined) {
|
||||
this.fixMaxWidth = view.getVisibleSize().width * 0.8;
|
||||
@@ -33,6 +44,7 @@ export class DialogBubble extends Component {
|
||||
|
||||
updateBubbleContent(
|
||||
str: string,
|
||||
isPlayer: boolean = false,
|
||||
callback?: (actualHeight: number) => void,
|
||||
isLoading: boolean = false
|
||||
) {
|
||||
@@ -80,7 +92,7 @@ export class DialogBubble extends Component {
|
||||
// 设置内容区域宽度,让Label自动计算高度
|
||||
this.contentT.setContentSize(new Size(contentWidth, 0));
|
||||
this.bg.setContentSize(new Size(contentWidth + 37, 0));
|
||||
|
||||
this.uiTransform.setContentSize(new Size(contentWidth + 37, 0));
|
||||
// 强制更新Label的渲染数据以获取正确的高度
|
||||
this.content.updateRenderData(true);
|
||||
|
||||
@@ -95,6 +107,9 @@ export class DialogBubble extends Component {
|
||||
// 根据内容大小调整背景大小,添加适当的内边距
|
||||
//const bgHeight = actualHeight + 17;
|
||||
this.bg.setContentSize(new Size(actualSize.x + 40, actualSize.y + 30));
|
||||
this.uiTransform.setContentSize(
|
||||
new Size(actualSize.x + 40, actualSize.y + 30)
|
||||
);
|
||||
|
||||
// 回调通知实际高度
|
||||
if (callback) {
|
||||
@@ -104,6 +119,19 @@ export class DialogBubble extends Component {
|
||||
|
||||
// 返回预估的背景高度(用于同步计算)
|
||||
const estimatedHeight = Math.max(lines.length * 35 + 20, 60); // 最小高度60
|
||||
|
||||
//设置头像
|
||||
if (isPlayer) {
|
||||
//获取玩家头像,等接入luffa
|
||||
} else {
|
||||
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
|
||||
const path = girlData.getGrilAvatar(
|
||||
NavigationManager.Instance.getSelectedCategoryId().toString(),
|
||||
NavigationManager.Instance.getSelectedGirlId()
|
||||
);
|
||||
ResManager.I.changeBundleSpriteFrame(this.avatar, path, "Girls");
|
||||
}
|
||||
|
||||
return estimatedHeight;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import { SceneBgVideoLayer } from "../../../Sub/UI/SceneBgVideoLayer";
|
||||
import { NavigationManager, PanelType } from "../../manager/NavigationManager";
|
||||
import { GirlService } from "../../network/services/GirlService";
|
||||
import proto from "db://assets/Scripts/proto/proto.pb.js";
|
||||
import { TipsPanel } from "./TipsPanel";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("ChatPanel")
|
||||
@@ -389,7 +390,10 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
|
||||
public async OnClickSend() {
|
||||
const str = this.editBox.string;
|
||||
if (!str || str == "") return;
|
||||
if (!str || str == "") {
|
||||
TipsPanel.show("请输入内容");
|
||||
return;
|
||||
}
|
||||
|
||||
// 通过ChatController发送消息
|
||||
const succeed = await this.chatController.sendMessage(str);
|
||||
@@ -437,6 +441,7 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
|
||||
onChatLimitReached(): void {
|
||||
this.payToTalkPanel.show(this.categoryId, this.id);
|
||||
TipsPanel.show(LanguageUtils.getText("chat_error_code_1001"));
|
||||
}
|
||||
/**
|
||||
* 情绪状态更新回调 (实现IChatPanelCallback接口)
|
||||
|
||||
Reference in New Issue
Block a user