设置页、弹窗逻辑、跳转逻辑

视频播放逻辑优化
This commit is contained in:
2025-09-11 00:03:22 +08:00
parent a6b15778a8
commit 7dedbf124c
24 changed files with 2338 additions and 1381 deletions
+333 -245
View File
@@ -1,269 +1,357 @@
import { _decorator, instantiate, isValid, Label, Node,VideoClip, Sprite, UITransform, Vec3, VideoPlayer, view } from 'cc';
import Utils from '../../Main/Common/Utils';
import { InnerMsgCode } from '../../Main/Config/InnerMsgCode';
import li_BaseView from '../../Main/Common/li_BaseView';
import ResManager from '../../Main/Manager/ResManager';
import GlobalValue, { VideoRoleType } from '../../Main/Common/GlobalValue';
import { SDKManager } from '../../Main/Channel/SDKManager';
import { BgVideo } from '../../Main/Channel/BgVideo';
import {
_decorator,
isValid,
Node,
VideoClip,
UITransform,
Vec3,
VideoPlayer,
Size,
} from "cc";
import Utils from "../../Main/Common/Utils";
import li_BaseView from "../../Main/Common/li_BaseView";
import ResManager from "../../Main/Manager/ResManager";
const { ccclass, property } = _decorator;
//场景背景层 - 视频模式
@ccclass('SceneBgVideoLayer')
@ccclass("SceneBgVideoLayer")
export class SceneBgVideoLayer extends li_BaseView {
private _nodeTab: any = {};
private _data;
private _nodeTab: any = {};
private _data;
static handle: SceneBgVideoLayer | null = null;
static handle: SceneBgVideoLayer | null = null;
private remoteLoopPath: string = "" //远程循环路径
private curVideoType: VideoRoleType = VideoRoleType.None;
private sceneBg: Sprite = null;
private videoInited: boolean = false; //视频是否初始化完成
private vpWait: BgVideo | null = null; //播表情视频时暂停的背景视频
private emoReady: BgVideo | null = null; //已准备好播放的表情
private videoPlayer: VideoPlayer = null;
private isPlaying: boolean = false;
private currentVideoData: any = null;
private videoPlayer: VideoPlayer = null;
//----重写父类接口---------------------------------------
onLoadCT() {
Utils.parseNode(this.node, this._nodeTab);
this.videoPlayer = this._nodeTab.videoPlayer.getComponent(VideoPlayer);
if (!this.videoPlayer) {
console.error("SceneBgVideoLayer: video节点未找到或没有VideoPlayer组件");
}
this.initVideoEvents();
SceneBgVideoLayer.handle = this;
}
openUIDataCT(data: any): void {
this._data = data;
}
/**获取videoplayer副本 */
getVideoClone() {
let vp = instantiate(this._nodeTab.videoRole)
this.node.addChild(vp)
let bv = vp.getComponent(BgVideo)!
return bv
/**初始化视频事件监听 */
private initVideoEvents() {
if (!this.videoPlayer) return;
// 移除之前的事件监听器,避免重复绑定
this.videoPlayer.node.off(
VideoPlayer.EventType.READY_TO_PLAY,
this.onVideoReadyToPlay,
this
);
//this.videoPlayer.node.off(VideoPlayer.EventType.VIDEO_PLAYER_EVENT, this.onVideoEvent, this);
// 添加新的事件监听
this.videoPlayer.node.on(
VideoPlayer.EventType.READY_TO_PLAY,
this.onVideoReadyToPlay,
this
);
// this.videoPlayer.node.on(VideoPlayer.EventType.VIDEO_PLAYER_EVENT, this.onVideoEvent, this);
}
//================== 公共 API 方法 ==================
/**
* 播放视频
* @param path 视频路径
* @param loop 是否循环播放
*/
playVideo(path: string, loop: boolean = false) {
if (!this.videoPlayer) {
console.error("SceneBgVideoLayer: VideoPlayer未初始化");
return;
}
//----重写父类接口---------------------------------------
onLoadCT() {
this.registerListenner();
Utils.parseNode(this.node, this._nodeTab)
this.sceneBg = this._nodeTab.sceneBg.getComponent(Sprite)!;
this.videoPlayer = this._nodeTab.videoRole.getComponent(VideoPlayer);
if (GlobalValue.NpcVideoMod) {
this.setVideoInited(true, true)
// 加载本地视频资源
ResManager.I.changeBundleVideo(
this.videoPlayer,
path,
"Girls",
(res: VideoClip) => {
this.videoPlayer.loop = loop;
this.videoPlayer.play();
this.isPlaying = true;
console.log(`SceneBgVideoLayer: 播放视频 ${path}, 循环: ${loop}`);
}
);
}
/**
* 切换视频(停止当前视频并播放新视频)
* @param path 视频路径
* @param loop 是否循环播放
*/
switchVideo(path: string, loop: boolean = false) {
this.stop();
this.playVideo(path, loop);
}
/**
* 暂停视频
*/
pause() {
if (this.videoPlayer && this.isPlaying) {
this.videoPlayer.pause();
console.log("SceneBgVideoLayer: 暂停视频");
}
}
/**
* 继续播放视频
*/
resume() {
if (this.videoPlayer && !this.videoPlayer.isPlaying) {
this.videoPlayer.play();
console.log("SceneBgVideoLayer: 继续播放视频");
}
}
/**
* 停止视频
*/
stop() {
if (this.videoPlayer) {
this.videoPlayer.stop();
this.isPlaying = false;
console.log("SceneBgVideoLayer: 停止视频");
}
}
/**
* 设置视频尺寸
* @param width 宽度
* @param height 高度
*/
setVideoSize(width: number, height: number) {
if (this.videoPlayer) {
const uiTransform = this.videoPlayer.node.getComponent(UITransform);
if (uiTransform) {
uiTransform.setContentSize(width, height);
console.log(`SceneBgVideoLayer: 设置视频尺寸 ${width}x${height}`);
}
}
}
/**
* 设置视频位置
* @param x X坐标
* @param y Y坐标
* @param z Z坐标
*/
setVideoPosition(x: number, y: number, z: number = 0) {
if (this.videoPlayer) {
this.videoPlayer.node.setPosition(x, y, z);
console.log(`SceneBgVideoLayer: 设置视频位置 (${x}, ${y}, ${z})`);
}
}
/**
* 设置视频世界位置
* @param x X坐标
* @param y Y坐标
* @param z Z坐标
*/
setVideoWorldPosition(x: number, y: number, z: number = 0) {
if (this.videoPlayer) {
this.videoPlayer.node.setWorldPosition(x, y, z);
console.log(`SceneBgVideoLayer: 设置视频世界位置 (${x}, ${y}, ${z})`);
}
}
/**
* 设置视频锚点
* @param anchorX X轴锚点 (0-1)
* @param anchorY Y轴锚点 (0-1)
*/
setVideoAnchor(anchorX: number, anchorY: number) {
if (this.videoPlayer) {
const uiTransform = this.videoPlayer.node.getComponent(UITransform);
if (uiTransform) {
uiTransform.setAnchorPoint(anchorX, anchorY);
console.log(`SceneBgVideoLayer: 设置视频锚点 (${anchorX}, ${anchorY})`);
}
}
}
/**
* 设置视频缩放
* @param scale 缩放值(数字或Vec3
*/
setVideoScale(scale: number | Vec3) {
if (this.videoPlayer) {
if (typeof scale === "number") {
this.videoPlayer.node.setScale(new Vec3(scale, scale, 1));
console.log(`SceneBgVideoLayer: 设置视频缩放 ${scale}`);
} else {
this.videoPlayer.node.setScale(scale);
console.log(
`SceneBgVideoLayer: 设置视频缩放 (${scale.x}, ${scale.y}, ${scale.z})`
);
}
}
}
/**
* 调整视频以填满指定区域
* @param targetSize 目标尺寸
* @param position 可选的位置
*/
adjustToFillArea(targetSize: Size, position?: Vec3) {
if (!this.videoPlayer) return;
const tryAdjust = () => {
const uiTransform = this.videoPlayer.node.getComponent(UITransform);
if (!uiTransform) return false;
const videoSize = uiTransform.contentSize;
if (videoSize.width > 0 && videoSize.height > 0) {
// 计算宽度和高度的缩放比例
const scaleX = targetSize.width / videoSize.width;
const scaleY = targetSize.height / videoSize.height;
// 选择较大的缩放比例以填满整个区域
const scale = Math.max(scaleX, scaleY);
this.videoPlayer.node.setScale(new Vec3(scale, scale, 1));
if (position) {
this.videoPlayer.node.setPosition(position);
}
SceneBgVideoLayer.handle = this;
}
openUIDataCT(data: any): void {
this._data = data
}
console.log(`SceneBgVideoLayer: 自适应填充区域,缩放: ${scale}`);
return true;
}
return false;
};
registerListenner() {
Utils.addInnerEL(InnerMsgCode.SimplePlayVide,this,this.playLocalLoopVideo);
Utils.addInnerEL(InnerMsgCode.SceneLayerBgUp, this, this.resiveBgUp)
Utils.addInnerEL(InnerMsgCode.BgVideo_Ready, this, this.resiveBgVideoReady)
Utils.addInnerEL(InnerMsgCode.BgVideo_End, this, this.resiveBgVideoEnd)
}
//刷新场景背景消息
resiveBgUp(data) {
if (!GlobalValue.NpcVideoMod) {
return
}
if (data) {
if (data.videoPath) {
console.log("bgvideo 收到播放视频消息:", data)
if (data.videoType == VideoRoleType.Idle) {
this.playLoopVideo(data.videoPath, false)
} else {
this.playOnceVideo(data.videoPath)
}
}
if (!this.videoInited && data.bgPath) {
ResManager.I.changeBundleSpriteFrame(this.sceneBg, data.bgPath, "Raw", ()=>{
//调整背景图适配
Utils.adjustBgPixelRatio(this.sceneBg.node, 1)
})
}
}
}
/**视频准备好消息 */
resiveBgVideoReady(data:any) {
if (!this.videoInited) {
this.setVideoInited(true)
}
if (data.compo) {
if (data.compo.vtype == VideoRoleType.emo) {
this.setEmoReadyAudio(data.compo)
if (this.curVideoType == VideoRoleType.emo) {
this.doPlayEmoReadyAudio()
}
} else if (data.compo.vtype == VideoRoleType.Idle) {
this.curVideoType = VideoRoleType.Idle;
}
}
}
/**视频播放完毕消息 */
resiveBgVideoEnd(data:any) {
if (data.compo) {
if (data.compo.vtype == VideoRoleType.emo) {
// this.playLoopVideo(this.remoteLoopPath, true)
this.doEmoFinish()
} else if (data.compo.vtype == VideoRoleType.Idle) {
//主视频播完后,检测一下有没有要播的表情视频
if (this.doPlayEmoReadyAudio()) {
this.setBgAudioPause(data.compo)
}
}
}
}
playLocalLoopVideo(data:any) {
ResManager.I.changeBundleVideo(this.videoPlayer,data.path,"Chat18x",(res:VideoClip)=>{
this.videoPlayer.play();
// 移除之前的事件监听器,避免重复绑定
this.videoPlayer.node.off(VideoPlayer.EventType.READY_TO_PLAY, this.onVideoReadyToPlay, this);
// 添加视频准备完成事件监听
this.videoPlayer.node.on(VideoPlayer.EventType.READY_TO_PLAY, this.onVideoReadyToPlay, this);
// 保存当前数据供回调使用
this.currentVideoData = data;
// 也立即尝试调整(以防已经准备好)
this.adjustVideoToFillFrame(data.size, data.bgFrameNode);
});
}
private currentVideoData: any = null;
private onVideoReadyToPlay = () => {
if (this.currentVideoData) {
this.adjustVideoToFillFrame(this.currentVideoData.size, this.currentVideoData.bgFrameNode);
}
}
adjustVideoToFillFrame(targetSize: any, bgFrameNode?: Node) {
if (!this.videoPlayer) return;
const tryAdjust = () => {
let uiT: UITransform = this.videoPlayer.node.getComponent(UITransform);
const videoSize = uiT.contentSize;
console.log('Video size:', videoSize, 'Target size:', targetSize);
if (videoSize.width > 0 && videoSize.height > 0) {
// 计算宽度和高度的缩放比例
const scaleX = targetSize.width / videoSize.width;
const scaleY = targetSize.height / videoSize.height;
// 选择较大的缩放比例以填满整个区域(可能超出但不会有黑边)
const scale = Math.max(scaleX, scaleY);
console.log(`Video scaling: scaleX=${scaleX}, scaleY=${scaleY}, final scale=${scale}`);
this.videoPlayer.node.setScale(new Vec3(scale, scale, 1));
// 只有传入bgFrameNode时才调整位置
if (bgFrameNode && isValid(bgFrameNode) && bgFrameNode.position) {
this.videoPlayer.node.setPosition(
bgFrameNode.position.x,
bgFrameNode.position.y,
bgFrameNode.position.z
);
console.log(`Video positioned to bgFrame center: ${bgFrameNode.position}`);
}
return true;
}
return false;
};
// 立即尝试一次
// 立即尝试一次
if (!tryAdjust()) {
// 如果失败,延迟尝试
this.scheduleOnce(() => {
if (!tryAdjust()) {
// 如果失败,延迟尝试
this.scheduleOnce(() => {
if (!tryAdjust()) {
// 再次延迟尝试
this.scheduleOnce(() => {
tryAdjust();
}, 0.5);
}
}, 0.1);
this.scheduleOnce(() => {
tryAdjust();
}, 0.5);
}
}, 0.1);
}
}
/**播放循环视频 */
playLoopVideo(path: string, force = false) {
if (this.remoteLoopPath == path && !force) {
return
}
console.log("bgvideo 播放循环视频", path)
this.remoteLoopPath = path
SDKManager.PlayBgAudio(path, VideoRoleType.Idle, true)
}
//================== 事件回调 ==================
/**播放单次视频 */
playOnceVideo(path: string) {
console.log("bgvideo 播放单次视频", path)
SDKManager.AddEmoAudio(path, VideoRoleType.emo, false)
}
private onVideoReadyToPlay = () => {
console.log("SceneBgVideoLayer: 视频准备完成");
this.onVideoReady();
};
private onVideoEvent = (event: any, eventType: string) => {
switch (eventType) {
case "completed":
console.log("SceneBgVideoLayer: 视频播放完成");
this.isPlaying = false;
this.onVideoEnd();
break;
case "error":
console.error("SceneBgVideoLayer: 视频播放错误");
this.isPlaying = false;
this.onVideoError();
break;
}
};
//设置视频是否初始化完成
setVideoInited(inited: boolean, force: boolean = false) {
if (this.videoInited == inited && !force) {
return
}
console.log("bgvideo setVideoInited", inited)
this.videoInited = inited
this._nodeTab.sceneBg.active = !inited
}
/**
* 视频准备完成回调(可重写)
*/
protected onVideoReady() {
// 由子类重写或外部监听
}
//设置暂停播放的主视频
setBgAudioPause(wait:BgVideo) {
console.log("bgvideo 主视频暂停播放")
this.vpWait = wait
this.vpWait.pauseVideo()
}
replayBgAudioPause() {
console.log("bgvideo 主视频继续播放")
if (this.vpWait) {
this.vpWait.replayVideo()
}
}
/**
* 视频播放完成回调(可重写)
*/
protected onVideoEnd() {
// 由子类重写或外部监听
}
//设置准备好要播放的表情视频
setEmoReadyAudio(au: BgVideo) {
console.log("bgvideo 设置ready视频", au)
if (this.emoReady) {
this.emoReady.recycleVideo()
}
this.emoReady = au;
this.emoReady.pauseVideo()
}
//移除准备好要播放的表情视频
removeEmoReadyAudio() {
console.log("bgvideo 移除ready视频")
if (this.emoReady) {
this.emoReady.recycleVideo()
}
this.emoReady = null
}
//播放准备好要播放的表情视频
doPlayEmoReadyAudio():boolean {
if (this.emoReady) {
console.log("bgvideo 播放ready视频")
this.emoReady.replayVideo()
this.emoReady.moveIn()
this.curVideoType = VideoRoleType.emo
return true
}
return false
}
//表情视频播放结束
doEmoFinish() {
console.log("bgvideo ready视频播放结束")
this.removeEmoReadyAudio()
this.replayBgAudioPause()
this.curVideoType = VideoRoleType.Idle
}
/**
* 视频播放错误回调(可重写)
*/
protected onVideoError() {
// 由子类重写或外部监听
}
//================== 兼容性方法 ==================
/**
* 播放本地循环视频(保留以保证兼容性)
* @deprecated 使用 playVideo(path, true) 替代
*/
playLocalLoopVideo(data: any) {
if (data?.path) {
this.playVideo(data.path, true);
// 保存当前数据供回调使用
this.currentVideoData = data;
// 如果有尺寸和位置信息,进行调整
if (data.size) {
this.adjustToFillArea(data.size, data.bgFrameNode?.position);
}
}
}
/**
* 调整视频填充框架(保留以保证兼容性)
* @deprecated 使用 adjustToFillArea 替代
*/
adjustVideoToFillFrame(targetSize: any, bgFrameNode?: Node) {
let position: Vec3 | undefined;
if (bgFrameNode && isValid(bgFrameNode) && bgFrameNode.position) {
position = bgFrameNode.position;
}
this.adjustToFillArea(targetSize, position);
}
//================== 其他方法 ==================
/**
* 获取当前播放状态
*/
getIsPlaying(): boolean {
return this.isPlaying;
}
/**
* 获取VideoPlayer组件引用
*/
getVideoPlayer(): VideoPlayer | null {
return this.videoPlayer;
}
/**
* 销毁时清理资源
*/
onDestroy() {
if (this.videoPlayer) {
this.videoPlayer.node.off(
VideoPlayer.EventType.READY_TO_PLAY,
this.onVideoReadyToPlay,
this
);
//this.videoPlayer.node.off(VideoPlayer.EventType.VIDEO_PLAYER_EVENT, this.onVideoEvent, this);
}
SceneBgVideoLayer.handle = null;
}
}
+1
View File
@@ -525,6 +525,7 @@ export class ChatModel {
* @returns 是否可以继续聊天
*/
public canChat(roleId?: number): boolean {
//return false;
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
const roleData = this.rolesData.get(this.currentGirlId);
@@ -25,6 +25,7 @@ export class ImagePopup extends Component {
start() {
this.node.setPosition(new Vec3(-1500, 493, 0));
GButton.BandClick(this.image.node, this.openImage, this);
this.node.active = false;
}
onDestroy() {
@@ -36,6 +37,7 @@ export class ImagePopup extends Component {
resId: number;
//url: string;
refresh(categoryId: string, girlId: number, resId: number, clear = false) {
this.node.active = true;
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
this.categoryId = categoryId;
this.girlId = girlId;
+109 -40
View File
@@ -7,6 +7,7 @@ import {
Sprite,
UITransform,
VideoPlayer,
Size,
} from "cc";
// 首先加载 polyfills 以确保兼容性
import "../../utils/polyfills";
@@ -27,6 +28,7 @@ import { VideoEmotion, purchase } from "../../../schema/schema";
import { PayToTalkSubpanel } from "./PayToTalkSubpanel";
import { DataId, DataManager } from "../../data/DataManager";
import { GirlData } from "../../data/GirlData";
import { SceneBgVideoLayer } from "../../../Sub/UI/SceneBgVideoLayer";
const { ccclass, property } = _decorator;
@ccclass("ChatPanel")
@@ -46,8 +48,16 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
// @property(Sprite)
// girlImg: Sprite = null;
@property(VideoPlayer)
girlVideo: VideoPlayer = null;
// 移除直接的 VideoPlayer 引用,改用 SceneBgVideoLayer 单例
// @property(VideoPlayer)
// girlVideo: VideoPlayer = null;
@property(UITransform)
videoArea: UITransform = null;
/** 获取 SceneBgVideoLayer 单例实例 */
private get videoLayer(): SceneBgVideoLayer | null {
return SceneBgVideoLayer.handle;
}
@property(ChatContentsLayout)
layout: ChatContentsLayout = null;
@@ -119,17 +129,38 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
this.register();
this.refresh();
this.payToTalkPanel.node.active = false;
// Add video loaded event callback
if (this.girlVideo) {
this.girlVideo.node.on(
VideoPlayer.EventType.READY_TO_PLAY,
() => {
this.adjustVideoScale();
},
this
);
// 检查 SceneBgVideoLayer 是否可用
if (!this.videoLayer) {
console.error("ChatPanel: SceneBgVideoLayer.handle 未初始化");
} else {
console.log("ChatPanel: 成功获取 SceneBgVideoLayer 实例");
this.initVideoPosition();
}
}
/**
* 初始化视频位置和属性
*/
private initVideoPosition() {
if (!this.videoLayer || !this.videoArea) return;
// 设置视频锚点为中心 (0.5, 0.5),这样视频的中心点就是定位点
this.videoLayer.setVideoAnchor(0.5, 0.5);
// 使用 videoArea 的世界坐标位置
const videoAreaWorldPos = this.videoArea.node.getWorldPosition();
this.videoLayer.setVideoWorldPosition(
this.videoLayer.node.worldPosition.x,
videoAreaWorldPos.y,
videoAreaWorldPos.z
);
this.videoLayer.setVideoScale(1);
console.log(
`ChatPanel: 初始化视频世界位置到 (${videoAreaWorldPos.x}, ${videoAreaWorldPos.y})`
);
}
register() {
Utils.addInnerEL(
InnerMsgCode.Chat_DialogRefresh,
@@ -179,8 +210,8 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
);
// Cleanup video event listeners
if (this.girlVideo && this.girlVideo.node) {
this.girlVideo.node.off(
if (this.videoLayer && this.videoLayer.node) {
this.videoLayer.node.off(
VideoPlayer.EventType.READY_TO_PLAY,
this.adjustVideoScale,
this
@@ -224,7 +255,7 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
const chatModel = this.chatController.getChatModel();
// Load initial video based on current emotion
if (this.girlVideo) {
if (this.videoLayer) {
// Get current emotion from ChatController
const currentEmotion = this.chatController.getCurrentEmotion();
console.log(
@@ -241,46 +272,81 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
`Loading initial video: ${initialVideo} (emotion: ${VideoEmotion[initialVideo]})`
);
// 获取 videoArea 的尺寸
const targetSize = this.videoArea
? new Size(this.videoArea.width, this.videoArea.height)
: new Size(600, 800);
const videoData = {
path: initialVideo,
size: targetSize,
bgFrameNode: this.videoArea ? this.videoArea.node : null,
};
// Set current emotion to the loaded video's emotion
this.currentEmotion = this.chatController.getCurrentEmotion();
ResManager.I.changeBundleVideo(this.girlVideo, initialVideo, "Girls");
// Also immediately try to adjust scale (in case already loaded)
this.adjustVideoScale();
this.videoLayer.playLocalLoopVideo(videoData);
// 延迟调整视频缩放
this.scheduleOnce(() => {
this.adjustVideoScale();
}, 0.1);
}
} else {
this.commercialVideos = [];
this.currentEmotion = null; // Reset current emotion when no videos available
console.log("No commercialVideos data available for role", this.id);
console.log("No SceneBgVideoLayer available for role", this.id);
}
}
adjustVideoScale() {
if (!this.girlVideo || !this.girlVideo.node.parent) {
if (!this.videoLayer || !this.videoArea) {
console.error("ChatPanel: SceneBgVideoLayer 或 videoArea 不可用");
return;
}
const tryAdjustScale = () => {
const parentTransform =
this.girlVideo.node.parent.getComponent(UITransform);
const videoTransform = this.girlVideo.node.getComponent(UITransform);
const videoPlayer = this.videoLayer.getVideoPlayer();
if (!videoPlayer) return false;
const videoTransform = videoPlayer.node.getComponent(UITransform);
if (videoTransform && videoTransform.height > 0) {
// 设置视频锚点为中心 (0.5, 0.5),确保视频的中心点就是定位点
this.videoLayer.setVideoAnchor(0.5, 0.5);
// 计算缩放比例,使视频高度填满 videoArea
const scale = this.videoArea.height / videoTransform.height;
// 设置视频缩放
this.videoLayer.setVideoScale(scale);
// 获取 videoArea 的世界坐标中心点
const videoAreaWorldPos = this.videoArea.node.getWorldPosition();
// 使用世界坐标设置视频位置,确保正确对齐
this.videoLayer.setVideoWorldPosition(
this.videoLayer.node.worldPosition.x,
videoAreaWorldPos.y,
videoAreaWorldPos.z
);
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}`
`ChatPanel: 视频缩放到 ${scale}, videoArea高度: ${this.videoArea.height}, 视频高度: ${videoTransform.height}`
);
console.log(
`ChatPanel: 视频世界位置设置为 (${videoAreaWorldPos.x}, ${videoAreaWorldPos.y})`
);
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);
@@ -292,22 +358,28 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
let triggerId = girlData.getTriggerGrilPhotoId(this.categoryId, this.id);
if (triggerId == 0) triggerId = 100040103;
if (triggerId == 0) return;
this.popUpImage.refresh(this.categoryId, this.id, triggerId);
}
setVideoEnable(enable: boolean) {
if (!this.girlVideo) return;
if (!this.videoLayer) {
console.error("ChatPanel: SceneBgVideoLayer 不可用");
return;
}
this.girlVideo.enabled = enable;
if (enable) {
this.girlVideo.play();
this.videoLayer.resume();
console.log("ChatPanel: 恢复视频播放");
} else {
this.videoLayer.pause();
console.log("ChatPanel: 暂停视频播放");
}
}
private switchVideoByEmotion(emotion: VideoEmotion): void {
if (!this.girlVideo) {
if (!this.videoLayer) {
console.warn("Cannot switch video: missing video player");
return;
}
@@ -337,7 +409,8 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
);
// Load the new video
ResManager.I.changeBundleVideo(this.girlVideo, matchingVideo, "Girls");
this.videoLayer.playVideo(matchingVideo, true);
//ResManager.I.changeBundleVideo(this.girlVideo, matchingVideo, "Girls");
// Update current emotion state
this.currentEmotion = emotion;
@@ -357,11 +430,7 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
`Loading fallback video: ${fallbackVideo} (emotion: ${VideoEmotion[fallbackVideo]})`
);
ResManager.I.changeBundleVideo(
this.girlVideo,
fallbackVideo,
"Chat18x"
);
this.videoLayer.playVideo(fallbackVideo, true);
// Update current emotion state to the fallback video's emotion
this.currentEmotion = this.chatController.getCurrentEmotion();
@@ -6,6 +6,7 @@ import {
VideoPlayer,
instantiate,
UITransform,
Size,
} from "cc";
import li_BaseView from "db://assets/Scripts/Main/Common/li_BaseView";
import ResManager from "db://assets/Scripts/Main/Manager/ResManager";
@@ -20,18 +21,25 @@ import { DataManager, DataId } from "../../data/DataManager";
import { GirlData } from "../../data/GirlData";
import { GirlService } from "db://assets/Scripts/chat18x/network/services/GirlService";
import proto from "db://assets/Scripts/proto/proto.pb.js";
import { SceneBgVideoLayer } from "../../../Sub/UI/SceneBgVideoLayer";
const { ccclass, property } = _decorator;
@ccclass("GirlDetailPanel")
export class GirlDetailPanel extends li_BaseView {
@property(Sprite)
avatar: Sprite;
@property(VideoPlayer)
avatarVideo: VideoPlayer;
@property(UITransform)
videoArea: UITransform;
// 移除直接的 VideoPlayer 引用,改用 SceneBgVideoLayer 单例
// @property(VideoPlayer)
// avatarVideo: VideoPlayer;
@property(Label)
girlName: Label;
/** 获取 SceneBgVideoLayer 单例实例 */
private get videoLayer(): SceneBgVideoLayer | null {
return SceneBgVideoLayer.handle;
}
@property(SimpleToggle)
imgToggle: SimpleToggle = null;
@property(SimpleToggle)
@@ -82,38 +90,76 @@ export class GirlDetailPanel extends li_BaseView {
this.imgToggle.callback = this.bindImgToggle.bind(this);
this.videoToggle.callback = this.bindVideoToggle.bind(this);
// 添加视频加载完成回调
this.avatarVideo.node.on(
VideoPlayer.EventType.READY_TO_PLAY,
() => {
this.adjustVideoScale();
},
this
);
// 检查 SceneBgVideoLayer 是否可用
if (!this.videoLayer) {
console.error("GirlDetailPanel: SceneBgVideoLayer.handle 未初始化");
} else {
console.log("GirlDetailPanel: 成功获取 SceneBgVideoLayer 实例");
// 初始化视频显示位置
this.initVideoPosition();
}
}
setVideEnable(enable: boolean) {
this.avatarVideo.enabled = enable;
if (!this.videoLayer) {
console.error("GirlDetailPanel: SceneBgVideoLayer 不可用");
return;
}
if (enable) {
this.avatarVideo.play();
// 恢复播放
this.videoLayer.resume();
console.log("GirlDetailPanel: 开启视频播放");
} else {
// 暂停播放
this.videoLayer.pause();
console.log("GirlDetailPanel: 暂停视频播放");
}
}
adjustVideoScale() {
if (!this.avatarVideo || !this.avatarVideo.node.parent) {
if (!this.videoLayer) {
console.error("GirlDetailPanel: SceneBgVideoLayer 不可用");
return;
}
// 获取 videoArea 的尺寸
if (!this.videoArea) {
console.error("GirlDetailPanel: videoArea 未设置");
return;
}
// 参考原有逻辑,计算缩放比例使视频高度填满 videoArea
const tryAdjustScale = () => {
const parentTransform =
this.avatarVideo.node.parent.getComponent(UITransform);
const videoTransform = this.avatarVideo.node.getComponent(UITransform);
const videoPlayer = this.videoLayer.getVideoPlayer();
if (!videoPlayer) return false;
const videoTransform = videoPlayer.node.getComponent(UITransform);
if (videoTransform && videoTransform.height > 0) {
// 设置视频锚点为中心 (0.5, 0.5),确保视频的中心点就是定位点
this.videoLayer.setVideoAnchor(0.5, 0.5);
// 计算缩放比例,使视频高度填满 videoArea(保持原有逻辑)
const scale = this.videoArea.height / videoTransform.height;
// 设置视频缩放
this.videoLayer.setVideoScale(scale);
// 获取 videoArea 的世界坐标中心点
const videoAreaWorldPos = this.videoArea.node.getWorldPosition();
// 使用世界坐标设置视频位置,确保正确对齐
this.videoLayer.setVideoWorldPosition(
videoAreaWorldPos.x,
videoAreaWorldPos.y,
videoAreaWorldPos.z
);
if (parentTransform && videoTransform && videoTransform.height > 0) {
const scale = parentTransform.height / videoTransform.height;
this.avatarVideo.node.setScale(scale, scale, 1);
console.log(
`Video scaled to: ${scale}, parent height: ${parentTransform.height}, video height: ${videoTransform.height}`
`GirlDetailPanel: 视频缩放到 ${scale}, videoArea高度: ${this.videoArea.height}, 视频高度: ${videoTransform.height}`
);
console.log(
`GirlDetailPanel: 视频世界位置设置为 (${videoAreaWorldPos.x}, ${videoAreaWorldPos.y})`
);
return true;
}
@@ -122,7 +168,7 @@ export class GirlDetailPanel extends li_BaseView {
// 立即尝试一次
if (!tryAdjustScale()) {
// 如果失败,延迟尝试
// 如果失败,延迟尝试(保持原有的重试机制)
this.scheduleOnce(() => {
if (!tryAdjustScale()) {
// 再次延迟尝试
@@ -133,6 +179,21 @@ export class GirlDetailPanel extends li_BaseView {
}, 0.1);
}
}
/**
* 获取目标视频尺寸
* 从 videoArea 获取实际需要填充的区域尺寸
*/
private getTargetVideoSize(): Size | null {
// 从 videoArea 获取目标尺寸
if (this.videoArea) {
return new Size(this.videoArea.width, this.videoArea.height);
}
// 如果没有 videoArea,使用默认尺寸
console.warn("GirlDetailPanel: videoArea 未设置,使用默认尺寸");
return new Size(600, 800);
}
nameKey: string;
tagKey: string;
descKey: string;
@@ -173,14 +234,41 @@ export class GirlDetailPanel extends li_BaseView {
this.category.toString(),
this.id
);
ResManager.I.changeBundleVideo(this.avatarVideo, firstPath, "Girls");
// 使用 SceneBgVideoLayer 加载并播放视频
if (this.videoLayer && firstPath) {
console.log(`GirlDetailPanel: 加载视频 ${firstPath}`);
// 获取 videoArea 的尺寸和位置
const targetSize = this.videoArea
? new Size(this.videoArea.width, this.videoArea.height)
: new Size(600, 800);
// 获取 videoArea 的中心位置作为参考节点
const bgFrameNode = this.videoArea ? this.videoArea.node : null;
// 使用 playLocalLoopVideo 方法,保证兼容性
const videoData = {
path: firstPath,
size: targetSize,
bgFrameNode: bgFrameNode, // 传递 videoArea 节点作为位置参考
};
this.videoLayer.playLocalLoopVideo(videoData);
// 延迟调整视频缩放和位置,确保视频加载完成
this.scheduleOnce(() => {
this.adjustVideoScale();
}, 0.1);
} else if (!firstPath) {
console.warn(
`GirlDetailPanel: 未找到视频路径 category=${this.category}, id=${this.id}`
);
}
let resIds = girlData.getAllDisplayGrilPhotoId(
this.category.toString(),
this.id
);
// 也立即尝试调整(以防已经加载完成)
this.adjustVideoScale();
this.refreshImgs(
proto.cs.EnmResType.ERT_Image,
this.category,
@@ -239,6 +327,28 @@ export class GirlDetailPanel extends li_BaseView {
}
}
/**
* 初始化视频位置和属性
*/
private initVideoPosition() {
if (!this.videoLayer || !this.videoArea) return;
// 设置视频锚点为中心 (0.5, 0.5),这样视频的中心点就是定位点
this.videoLayer.setVideoAnchor(0.5, 0.5);
// 使用 videoArea 的世界坐标位置
const videoAreaWorldPos = this.videoArea.node.getWorldPosition();
this.videoLayer.setVideoWorldPosition(
videoAreaWorldPos.x,
videoAreaWorldPos.y,
videoAreaWorldPos.z
);
this.videoLayer.setVideoScale(1);
console.log(
`GirlDetailPanel: 初始化视频世界位置到 (${videoAreaWorldPos.x}, ${videoAreaWorldPos.y})`
);
}
OnClickChatBtn() {
// 使用带过渡动画的导航方法
NavigationManager.Instance.navigateToChatWithTransition(
@@ -254,4 +364,30 @@ export class GirlDetailPanel extends li_BaseView {
NavigationManager.Instance.navigateToGirlList(this.category);
//ViewManager.I.openBundlesView("GirlListPanel");
}
/**
* 面板关闭时的清理工作
*/
onClose() {
// 停止视频播放,释放资源
if (this.videoLayer) {
//this.videoLayer.stop();
//console.log("GirlDetailPanel: 停止视频播放");
}
super.onClose();
}
/**
* 面板销毁时的清理工作
*/
onDestroy() {
// 确保视频资源被释放
if (this.videoLayer) {
//this.videoLayer.stop();
//console.log("GirlDetailPanel: 销毁时停止视频播放");
}
super.onDestroy();
}
}
@@ -1,4 +1,4 @@
import { _decorator, Button, Component, Node, Label } from "cc";
import { _decorator, Button, Component, Node, Label, View } from "cc";
import li_BaseView from "../../../Main/Common/li_BaseView";
import Utils from "../../../Main/Common/Utils";
import { GButton } from "../../../Main/Common/GButton";
@@ -10,6 +10,8 @@ import { GirlData } from "../../data/GirlData";
import { WalletData } from "../../data/WalletData";
import proto from "db://assets/Scripts/proto/proto.pb.js";
import LanguageUtils from "../../../Main/Common/LanguageUtils";
import { ViewManager } from "../../../Main/Manager/ViewManager";
import { TipsPanel } from "./TipsPanel";
const { ccclass, property } = _decorator;
@ccclass("GirlListPopupPanel")
@@ -74,6 +76,11 @@ export class GirlListPopupPanel extends li_BaseView {
// 刷新界面
this.base.refreshBuy(true);
this.close();
} else {
//ViewManager.I.openBundlesView("PurchasePanel");
this.close();
TipsPanel.show("Insufficient balance, need to purchase");
}
// TipsPanel.show("Insufficient balance, need to purchase");
}
}
@@ -10,6 +10,7 @@ import { WalletData } from "../../data/WalletData";
import proto from "db://assets/Scripts/proto/proto.pb.js";
import LanguageUtils from "../../../Main/Common/LanguageUtils";
import { ImagePopup } from "../components/ImagePopup";
import { TipsPanel } from "./TipsPanel";
const { ccclass, property } = _decorator;
@ccclass("PopupGirlDetailPanel")
@@ -103,6 +104,8 @@ export class PopupGirlDetailPanel extends li_BaseView {
this.base.refreshSelf();
}
this.close();
} else {
TipsPanel.show("Insufficient balance, need to purchase");
}
}
}
@@ -28,6 +28,7 @@ import { AccountData } from "../../data/AccountData";
import { ShopData } from "../../data/ShopData";
import proto from "db://assets/Scripts/proto/proto.pb.js";
import GameRootUI from "../../../Main/Common/GameRootUI";
const { ccclass, property } = _decorator;
@@ -109,7 +110,7 @@ export class ThemePanel extends li_BaseView {
ResManager.I.changeBundleSpriteFrame(
this.recSprite,
avatarPath,
"Chat18x",
"Girls",
() => {
let sizeTran = this.recSprite.node.parent.getComponent(UITransform);
Utils.adjustBgPixelRatioToSize(
@@ -163,7 +164,7 @@ export class ThemePanel extends li_BaseView {
) == 0;
if (isRelease || isFree) {
NavigationManager.Instance.navigateToGirlDetail(this.recId);
ViewManager.I.closeView(this.node);
GameRootUI.I.hideDefaultView();
} else {
//未解锁
ViewManager.I.openBundlesPopupView("GirlListPopupPanel", {
@@ -0,0 +1,111 @@
import { _decorator, Component, Node, Label, tween, Vec3, UIOpacity } from "cc";
import li_BaseView from "../../../Main/Common/li_BaseView";
import Utils from "../../../Main/Common/Utils";
import { ViewManager } from "../../../Main/Manager/ViewManager";
import { GButton } from "../../../Main/Common/GButton";
const { ccclass, property } = _decorator;
interface TipsPanelData {
content: string;
duration?: number;
autoClose?: boolean;
}
@ccclass("TipsPanel")
export class TipsPanel extends li_BaseView {
private _nodeTab: any = {};
private _data: TipsPanelData;
private contentLabel: Label;
static show(content: string, duration: number = 1) {
ViewManager.I.openBundlesPopupView("TipsPanel", {
content: content,
duration: duration,
autoClose: true,
});
}
onLoadCT() {
Utils.parseNode(this.node, this._nodeTab);
this.contentLabel = this._nodeTab.text.getComponent(Label);
this.setupUI();
// 如果数据已经传入,立即更新内容
if (this._data) {
this.updateContent();
}
}
openUIData(data: TipsPanelData) {
this._data = data;
// 如果UI已经初始化,立即更新内容
if (this.contentLabel) {
this.updateContent();
}
}
private setupUI() {
if (this._nodeTab.mask) {
GButton.BandClick(this._nodeTab.mask, this.onMaskClick, this);
}
this.node.setScale(Vec3.ZERO);
const uiOpacity =
this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
uiOpacity.opacity = 0;
this.playShowAnimation();
}
private updateContent() {
if (this.contentLabel && this._data) {
this.contentLabel.string = this._data.content;
if (this._data.autoClose !== false) {
const duration = this._data.duration || 1;
this.scheduleOnce(this.autoClose, duration);
}
}
}
private playShowAnimation() {
const uiOpacity = this.node.getComponent(UIOpacity);
tween(this.node)
.to(0.3, { scale: Vec3.ONE }, { easing: "backOut" })
.start();
tween(uiOpacity).to(0.3, { opacity: 255 }).start();
}
private playHideAnimation(callback?: () => void) {
const uiOpacity = this.node.getComponent(UIOpacity);
tween(this.node)
.to(0.2, { scale: new Vec3(0.8, 0.8, 1) })
.start();
tween(uiOpacity)
.to(0.2, { opacity: 0 })
.call(() => {
callback && callback();
})
.start();
}
private onMaskClick() {
this.closePanel();
}
private autoClose() {
this.closePanel();
}
private closePanel() {
this.playHideAnimation(() => {
this.close();
});
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "3a01052c-1b9f-4ad4-b5a4-ecda9b0c1bbe",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -17,6 +17,7 @@ import { GirlDetailPanel } from "../ui/panels/GirlDetailPanel";
import proto from "db://assets/Scripts/proto/proto.pb.js";
import { DataManager, DataId } from "../data/DataManager";
import { GirlData } from "../data/GirlData";
import { TipsPanel } from "../ui/panels/TipsPanel";
const { ccclass, property } = _decorator;
@@ -133,12 +133,12 @@ export class GirlListItem extends Component {
// this.price.node.active = priceType == PriceType.pay;
// this.freeNode.active = priceType == PriceType.free;
//this.tryNode.active = priceType == PriceType.first_time_free;
let listAvatarPath = girlData.getGrilListAvatar(
let listAvatarPath = girlData.getGrilAvatar(
this.category.toString(),
this.id
);
listAvatarPath = listAvatarPath.replace("Avatar", "avatar"); //临时
//listAvatarPath = listAvatarPath.replace("Avatar", "avatar"); //临时
ResManager.I.changeBundleSpriteFrame(
this.avatar,
listAvatarPath,