Chat相关表现&逻辑分离,新增ChatController
新增情绪机器人逻辑, 新增情绪切换视频逻辑
This commit is contained in:
@@ -6,11 +6,12 @@ import {
|
||||
Label,
|
||||
Sprite,
|
||||
UITransform,
|
||||
VideoPlayer,
|
||||
} from "cc";
|
||||
// 首先加载 polyfills 以确保兼容性
|
||||
import "../../utils/polyfills";
|
||||
import { DialogManager } from "../../manager/DialogManager";
|
||||
import { ChatAIService } from "../../core/ChatAIService";
|
||||
import { ChatController, IChatPanelCallback } from "../../core/ChatController";
|
||||
import li_BaseView from "db://assets/Scripts/Main/Common/li_BaseView";
|
||||
import Utils from "db://assets/Scripts/Main/Common/Utils";
|
||||
import { InnerMsgCode } from "db://assets/Scripts/Main/Config/InnerMsgCode";
|
||||
@@ -20,15 +21,15 @@ import GameRootUI from "db://assets/Scripts/Main/Common/GameRootUI";
|
||||
import { ImagePopup } from "../components/ImagePopup";
|
||||
|
||||
import ResManager from "db://assets/Scripts/Main/Manager/ResManager";
|
||||
import { VideoRoleType } from "db://assets/Scripts/Main/Common/GlobalValue";
|
||||
import ConfigManager from "../../manager/ConfigManager";
|
||||
import LanguageUtils from "../../../Main/Common/LanguageUtils";
|
||||
import { UITransitionHelper } from "../../utils/UITransitionHelper";
|
||||
import { VideoEmotion, purchase } from "../../../schema/schema";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("ChatPanel")
|
||||
export class ChatPanel extends li_BaseView {
|
||||
export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
manager: DialogManager = null;
|
||||
private chatController: ChatController = new ChatController();
|
||||
|
||||
@property(EditBox)
|
||||
editBox: EditBox = null;
|
||||
@@ -39,6 +40,9 @@ export class ChatPanel extends li_BaseView {
|
||||
@property(Sprite)
|
||||
girlImg: Sprite = null;
|
||||
|
||||
@property(VideoPlayer)
|
||||
girlVideo: VideoPlayer = null;
|
||||
|
||||
@property(ChatContentsLayout)
|
||||
layout: ChatContentsLayout = null;
|
||||
|
||||
@@ -49,6 +53,8 @@ export class ChatPanel extends li_BaseView {
|
||||
private _nodeTab: any = {};
|
||||
|
||||
nameKey: string;
|
||||
private commercialVideos: purchase.CommercialVideo[] = [];
|
||||
private currentEmotion: VideoEmotion | null = null;
|
||||
|
||||
private onLanguageChangeCallback = () => {
|
||||
if (!this.girlName) return;
|
||||
@@ -57,9 +63,9 @@ export class ChatPanel extends li_BaseView {
|
||||
|
||||
openUIDataCT(data) {
|
||||
// 处理新的数据格式,支持过渡动画参数
|
||||
if (typeof data === 'object' && data.roleId !== undefined) {
|
||||
if (typeof data === "object" && data.roleId !== undefined) {
|
||||
this.id = data.roleId;
|
||||
|
||||
|
||||
// 如果标记了需要滑入动画,则执行动画
|
||||
if (data.withSlideTransition && this.node && this.node.isValid) {
|
||||
// 延迟一帧执行动画,确保节点已正确加载到场景中
|
||||
@@ -71,10 +77,10 @@ export class ChatPanel extends li_BaseView {
|
||||
// 兼容原来的数字格式
|
||||
this.id = data;
|
||||
}
|
||||
|
||||
// 设置当前聊天的角色ID
|
||||
|
||||
// 初始化ChatController
|
||||
if (this.id && this.id > 0) {
|
||||
ChatAIService.Instance.setCurrentRole(this.id);
|
||||
this.chatController.initialize(this.id, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +89,17 @@ export class ChatPanel extends li_BaseView {
|
||||
|
||||
this.register();
|
||||
this.refresh(this.id);
|
||||
|
||||
// Add video loaded event callback
|
||||
if (this.girlVideo) {
|
||||
this.girlVideo.node.on(
|
||||
VideoPlayer.EventType.READY_TO_PLAY,
|
||||
() => {
|
||||
this.adjustVideoScale();
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
}
|
||||
register() {
|
||||
Utils.addInnerEL(
|
||||
@@ -96,6 +113,12 @@ export class ChatPanel extends li_BaseView {
|
||||
this,
|
||||
this.onLanguageChangeCallback
|
||||
);
|
||||
|
||||
Utils.addInnerEL(
|
||||
InnerMsgCode.Chat_EmotionInitialized,
|
||||
this,
|
||||
this.onEmotionInitialized
|
||||
);
|
||||
}
|
||||
onDestroy(): void {
|
||||
Utils.removeInnerEL(
|
||||
@@ -108,12 +131,37 @@ export class ChatPanel extends li_BaseView {
|
||||
this,
|
||||
this.onLanguageChangeCallback
|
||||
);
|
||||
|
||||
Utils.removeInnerEL(
|
||||
InnerMsgCode.Chat_EmotionInitialized,
|
||||
this,
|
||||
this.onEmotionInitialized
|
||||
);
|
||||
|
||||
// Cleanup video event listeners
|
||||
if (this.girlVideo && this.girlVideo.node) {
|
||||
this.girlVideo.node.off(
|
||||
VideoPlayer.EventType.READY_TO_PLAY,
|
||||
this.adjustVideoScale,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
// 销毁ChatController
|
||||
if (this.chatController) {
|
||||
this.chatController.destroy();
|
||||
}
|
||||
}
|
||||
refresh(id: number) {
|
||||
this.id = id;
|
||||
const data = ConfigManager.tables.TbGirls.get(this.id);
|
||||
const dataDetail = ConfigManager.tables.TbGirlsDetail.get(this.id);
|
||||
if (!data) return;
|
||||
|
||||
// 通过ChatController获取角色数据
|
||||
const roleData = this.chatController.getRoleData();
|
||||
if (!roleData || !roleData.basic) return;
|
||||
|
||||
const data = roleData.basic;
|
||||
const dataDetail = roleData.detail;
|
||||
|
||||
this.nameKey = data.nameKey;
|
||||
this.girlName.string = LanguageUtils.getText(data.nameKey);
|
||||
ResManager.I.changeBundleSpriteFrame(
|
||||
@@ -129,15 +177,172 @@ export class ChatPanel extends li_BaseView {
|
||||
);
|
||||
}
|
||||
);
|
||||
let uiTransform: UITransform =
|
||||
this._nodeTab.BgFrame.getComponent(UITransform);
|
||||
|
||||
Utils.sendInnerMsg(InnerMsgCode.SimplePlayVide, {
|
||||
path: dataDetail.pics[0],
|
||||
size: uiTransform.contentSize,
|
||||
bgFrameNode: this._nodeTab.BgFrame,
|
||||
});
|
||||
// Store commercialVideos for emotion-based switching
|
||||
if (dataDetail && dataDetail.commercialVideos) {
|
||||
this.commercialVideos = dataDetail.commercialVideos;
|
||||
console.log(`Loaded ${this.commercialVideos.length} videos for role ${this.id}`);
|
||||
|
||||
// Load initial video based on current emotion
|
||||
if (this.commercialVideos.length > 0 && this.girlVideo) {
|
||||
// Get current emotion from ChatController
|
||||
let currentEmotion = VideoEmotion.calm_down; // Default fallback
|
||||
|
||||
try {
|
||||
if (this.chatController) {
|
||||
currentEmotion = this.chatController.getCurrentEmotion();
|
||||
console.log(`Current emotion for role ${this.id}: ${VideoEmotion[currentEmotion]}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to get current emotion during initialization, using calm_down as default:", error);
|
||||
}
|
||||
|
||||
// Find video matching current emotion or fallback
|
||||
const initialVideo = this.commercialVideos.find(video => video.emotion === currentEmotion)
|
||||
|| this.commercialVideos.find(video => video.emotion === VideoEmotion.calm_down)
|
||||
|| this.commercialVideos[0]; // Final fallback to first video
|
||||
|
||||
console.log(`Loading initial video: ${initialVideo.path} (emotion: ${VideoEmotion[initialVideo.emotion]})`);
|
||||
|
||||
// Set current emotion to the loaded video's emotion
|
||||
this.currentEmotion = initialVideo.emotion;
|
||||
|
||||
ResManager.I.changeBundleVideo(
|
||||
this.girlVideo,
|
||||
initialVideo.path,
|
||||
"Chat18x"
|
||||
);
|
||||
// Also immediately try to adjust scale (in case already loaded)
|
||||
this.adjustVideoScale();
|
||||
}
|
||||
} else {
|
||||
this.commercialVideos = [];
|
||||
this.currentEmotion = null; // Reset current emotion when no videos available
|
||||
console.log("No commercialVideos data available for role", this.id);
|
||||
}
|
||||
|
||||
// if (dataDetail && dataDetail.commercialVideos && dataDetail.commercialVideos.length > 0) {
|
||||
// let uiTransform: UITransform =
|
||||
// this._nodeTab.BgFrame.getComponent(UITransform);
|
||||
|
||||
// Utils.sendInnerMsg(InnerMsgCode.SimplePlayVide, {
|
||||
// path: dataDetail.commercialVideos[0].path,
|
||||
// size: uiTransform.contentSize,
|
||||
// bgFrameNode: this._nodeTab.BgFrame,
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
adjustVideoScale() {
|
||||
if (!this.girlVideo || !this.girlVideo.node.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tryAdjustScale = () => {
|
||||
const parentTransform =
|
||||
this.girlVideo.node.parent.getComponent(UITransform);
|
||||
const videoTransform = this.girlVideo.node.getComponent(UITransform);
|
||||
|
||||
if (parentTransform && videoTransform && videoTransform.height > 0) {
|
||||
const scale = parentTransform.height / videoTransform.height;
|
||||
this.girlVideo.node.setScale(scale, scale, 1);
|
||||
console.log(
|
||||
`ChatPanel Video scaled to: ${scale}, parent height: ${parentTransform.height}, video height: ${videoTransform.height}`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Immediately try once
|
||||
if (!tryAdjustScale()) {
|
||||
// If failed, delay and try again
|
||||
this.scheduleOnce(() => {
|
||||
if (!tryAdjustScale()) {
|
||||
// Try again with longer delay
|
||||
this.scheduleOnce(() => {
|
||||
tryAdjustScale();
|
||||
}, 0.5);
|
||||
}
|
||||
}, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
setVideoEnable(enable: boolean) {
|
||||
if (!this.girlVideo) return;
|
||||
|
||||
this.girlVideo.enabled = enable;
|
||||
if (enable) {
|
||||
this.girlVideo.play();
|
||||
}
|
||||
}
|
||||
|
||||
private switchVideoByEmotion(emotion: VideoEmotion): void {
|
||||
if (!this.girlVideo || !this.commercialVideos || this.commercialVideos.length === 0) {
|
||||
console.warn("Cannot switch video: missing video player or commercialVideos data");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if emotion has changed
|
||||
if (this.currentEmotion === emotion) {
|
||||
console.log(`Emotion ${VideoEmotion[emotion]} unchanged, skipping video switch`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find video matching the emotion
|
||||
const matchingVideo = this.commercialVideos.find(video => video.emotion === emotion);
|
||||
|
||||
if (matchingVideo) {
|
||||
console.log(`Switching to video for emotion ${VideoEmotion[emotion]}: ${matchingVideo.path} (from ${this.currentEmotion !== null ? VideoEmotion[this.currentEmotion] : 'null'})`);
|
||||
|
||||
// Load the new video
|
||||
ResManager.I.changeBundleVideo(
|
||||
this.girlVideo,
|
||||
matchingVideo.path,
|
||||
"Chat18x"
|
||||
);
|
||||
|
||||
// Update current emotion state
|
||||
this.currentEmotion = emotion;
|
||||
|
||||
// Adjust scale and enable playback
|
||||
this.adjustVideoScale();
|
||||
this.setVideoEnable(true);
|
||||
} else {
|
||||
console.warn(`No video found for emotion ${VideoEmotion[emotion]}, falling back to first available video`);
|
||||
|
||||
// Fallback to first video if no match found
|
||||
if (this.commercialVideos.length > 0) {
|
||||
const fallbackVideo = this.commercialVideos[0];
|
||||
console.log(`Loading fallback video: ${fallbackVideo.path} (emotion: ${VideoEmotion[fallbackVideo.emotion]})`);
|
||||
|
||||
ResManager.I.changeBundleVideo(
|
||||
this.girlVideo,
|
||||
fallbackVideo.path,
|
||||
"Chat18x"
|
||||
);
|
||||
|
||||
// Update current emotion state to the fallback video's emotion
|
||||
this.currentEmotion = fallbackVideo.emotion;
|
||||
|
||||
this.adjustVideoScale();
|
||||
this.setVideoEnable(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onEmotionInitialized(data: {roleId: number, emotion: VideoEmotion}): void {
|
||||
// 检查是否是当前角色
|
||||
if (data.roleId === this.id) {
|
||||
console.log(`Emotion initialized for current role ${this.id}: ${VideoEmotion[data.emotion]}`);
|
||||
|
||||
// 切换到对应的视频
|
||||
this.switchVideoByEmotion(data.emotion);
|
||||
} else {
|
||||
console.log(`Emotion initialized for role ${data.roleId} (not current role ${this.id}), ignoring`);
|
||||
}
|
||||
}
|
||||
|
||||
onDialogUpdate() {
|
||||
if (this.layout) {
|
||||
this.layout.UpdateDialog(DialogManager.getInstance().getDialogs());
|
||||
@@ -152,24 +357,90 @@ export class ChatPanel extends li_BaseView {
|
||||
public async OnClickSend() {
|
||||
const str = this.editBox.string;
|
||||
if (!str || str == "") return;
|
||||
console.log("post:" + str);
|
||||
|
||||
// 清空输入框
|
||||
this.editBox.string = "";
|
||||
|
||||
DialogManager.getInstance().updateDialog(true, str, true);
|
||||
|
||||
// 使用新的sendMessage方法,传入角色ID
|
||||
const ret = await ChatAIService.Instance.sendMessage(this.id, str);
|
||||
|
||||
if (this.manager) {
|
||||
this.manager.updateDialog(false, ret);
|
||||
}
|
||||
console.log("ret:" + ret);
|
||||
// 通过ChatController发送消息
|
||||
await this.chatController.sendMessage(str);
|
||||
|
||||
//测试
|
||||
//this.popUpImage.refresh("Image/1/blur_naked_1");
|
||||
}
|
||||
|
||||
// === IChatPanelCallback 接口实现 ===
|
||||
|
||||
/**
|
||||
* 消息发送开始回调
|
||||
* @param message 用户发送的消息
|
||||
*/
|
||||
public onMessageSent(message: string): void {
|
||||
console.log("Message sent:", message);
|
||||
// 可以在这里添加发送中的UI状态显示,比如显示loading等
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收到AI回复回调
|
||||
* @param response AI的回复内容
|
||||
*/
|
||||
public onMessageReceived(response: string): void {
|
||||
console.log("Message received:", response);
|
||||
// 可以在这里添加接收到回复的UI效果,比如播放声音等
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话更新回调
|
||||
*/
|
||||
public onDialogUpdated(): void {
|
||||
// 更新对话显示
|
||||
this.onDialogUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理回调
|
||||
* @param error 错误信息
|
||||
*/
|
||||
public onError(error: Error): void {
|
||||
console.error("ChatPanel error:", error);
|
||||
// 可以在这里显示错误提示给用户
|
||||
}
|
||||
|
||||
/**
|
||||
* 情绪状态更新回调 (实现IChatPanelCallback接口)
|
||||
* @param emotion 当前情绪状态
|
||||
*/
|
||||
public onEmotionUpdated(emotion: VideoEmotion): void {
|
||||
console.log(`ChatPanel: Emotion updated to ${VideoEmotion[emotion]} (${emotion})`);
|
||||
|
||||
// Switch video based on the new emotion
|
||||
this.switchVideoByEmotion(emotion);
|
||||
|
||||
// Additional UI updates can be added here based on emotion
|
||||
// 例如:改变角色表情、背景色、播放相应的动画等
|
||||
switch (emotion) {
|
||||
case VideoEmotion.calm_down:
|
||||
// 平静状态的UI更新
|
||||
console.log("UI state: Calm");
|
||||
break;
|
||||
case VideoEmotion.arousal:
|
||||
// 兴奋状态的UI更新
|
||||
console.log("UI state: Arousal");
|
||||
break;
|
||||
case VideoEmotion.desire:
|
||||
// 渴望状态的UI更新
|
||||
console.log("UI state: Desire");
|
||||
break;
|
||||
case VideoEmotion.passion:
|
||||
// 激情状态的UI更新
|
||||
console.log("UI state: Passion");
|
||||
break;
|
||||
case VideoEmotion.orgasm:
|
||||
// 高潮状态的UI更新
|
||||
console.log("UI state: Orgasm");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
returnBtn() {
|
||||
this.onClose();
|
||||
GameRootUI.I.showDefaultView();
|
||||
|
||||
@@ -16,7 +16,7 @@ import LanguageUtils from "../../../Main/Common/LanguageUtils";
|
||||
import Utils from "../../../Main/Common/Utils";
|
||||
import { InnerMsgCode } from "../../../Main/Config/InnerMsgCode";
|
||||
import { SimpleToggle } from "../components/SimpleToggle";
|
||||
import { GirlDetail } from "../../../schema/schema";
|
||||
import { GirlDetail, purchase } from "../../../schema/schema";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@@ -151,31 +151,37 @@ export class GirlDetailPanel extends li_BaseView {
|
||||
}
|
||||
this.tags.string = desc;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
this.starParent.children[i].active = i < data.starCount;
|
||||
this.starParent.children[i].active = i < 5;
|
||||
}
|
||||
this.descAge.string = data.age.toString();
|
||||
this.descAge.string =
|
||||
LanguageUtils.getText("girldetailpanel.age") + data.age.toString();
|
||||
this.desc.string = dataDetail.detailDesc;
|
||||
|
||||
ResManager.I.changeBundleVideo(
|
||||
this.avatarVideo,
|
||||
dataDetail.vids[0],
|
||||
dataDetail.commercialVideos[0].path,
|
||||
"Chat18x"
|
||||
);
|
||||
|
||||
// 也立即尝试调整(以防已经加载完成)
|
||||
this.adjustVideoScale();
|
||||
this.vids = dataDetail.vids.slice(0);
|
||||
this.refreshImgs(dataDetail.pics, true);
|
||||
this.vids = dataDetail.commercialVideos.slice(0);
|
||||
const paths = dataDetail.commercialImages.map((a) => a.path);
|
||||
this.refreshImgs(paths, true);
|
||||
}
|
||||
|
||||
vids: string[];
|
||||
vids: purchase.CommercialVideo[];
|
||||
bindImgToggle() {
|
||||
const dataDetail = ConfigManager.tables.TbGirlsDetail.get(this.id);
|
||||
this.refreshImgs(dataDetail.pics, true);
|
||||
const paths = dataDetail.commercialImages.map((a) => a.path);
|
||||
|
||||
this.refreshImgs(paths, true);
|
||||
}
|
||||
|
||||
bindVideoToggle() {
|
||||
this.refreshImgs(this.vids, false);
|
||||
const paths = this.vids.map((a) => a.path);
|
||||
|
||||
this.refreshImgs(paths, false);
|
||||
}
|
||||
|
||||
refreshImgs(paths: string[], isImg: boolean) {
|
||||
|
||||
Reference in New Issue
Block a user