import { _decorator, Label, Node, Sprite, VideoPlayer, UITransform, Size, Vec3, tween, view, screen, } from "cc"; import li_BaseView from "db://assets/Scripts/Main/Common/li_BaseView"; import ResManager from "db://assets/Scripts/Main/Manager/ResManager"; import { DetailImageItem } from "../../uiitems/DetailImageItem"; import { NavigationManager, PageTransitionType, PanelType, } from "../../manager/NavigationManager"; import { DetailImageItemPool } from "../../manager/DetailImageItemPool"; import LanguageUtils from "../../../Main/Common/LanguageUtils"; import Utils from "../../../Main/Common/Utils"; import { InnerMsgCode } from "../../../Main/Config/InnerMsgCode"; import { SimpleToggle } from "../components/SimpleToggle"; 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"; import { logger } from "db://assets/Scripts/Main/Common/Logger"; const { ccclass, property } = _decorator; @ccclass("GirlDetailPanel") export class GirlDetailPanel extends li_BaseView { @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) videoToggle: SimpleToggle = null; @property(Node) starParent: Node; @property(Label) tags: Label; @property(Label) descAge: Label; @property(Label) desc: Label; @property(Node) chatBtn: Node; @property(Node) descContent: Node; id = 0; @property(DetailImageItem) imgItemInst: DetailImageItem; @property(Node) imgsLayout: Node; videoAreaPos: Vec3; @property(Label) girlPrice: Label; @property(Node) sendMsgText: Node; private _nodeTab: any = {}; ImgVidSelect: Node; ImgVidFakePos: Node; // 跟随控制相关属性 private isFollowing: boolean = true; // 当前是否处于跟随状态 private lastFakePosPosition: Vec3 = new Vec3(); // 记录上次的位置 protected onEnable(): void { this.refresh(); } onLoadCT() { super.onLoadCT(); Utils.parseNode(this.node, this._nodeTab); this.ImgVidSelect = this._nodeTab.ImgVidSelect; this.ImgVidFakePos = this._nodeTab.ImgVidFakePos; // 初始化位置跟随 if (this.ImgVidFakePos) { this.lastFakePosPosition = this.ImgVidFakePos.position.clone(); } DetailImageItemPool.Instance.setTemplate(this.imgItemInst.node); Utils.addInnerEL(InnerMsgCode.LanguageChange, this, () => { this.girlName.string = LanguageUtils.getText(this.nameKey); let desc = ""; const tags = LanguageUtils.getText(this.tagKey).split("|"); for (let i = 0; i < tags.length; i++) { if (i != 0) desc += "\n"; desc += tags[i]; } this.tags.string = desc; this.desc.string = LanguageUtils.getText(this.descKey); }); this.imgToggle.callback = this.bindImgToggle.bind(this); this.videoToggle.callback = this.bindVideoToggle.bind(this); } setVideEnable(enable: boolean) { if (!this.videoLayer) { logger.error("GirlDetailPanel: SceneBgVideoLayer 不可用"); return; } if (enable) { // 恢复播放 this.videoLayer.resume(); logger.log("GirlDetailPanel: 开启视频播放"); } else { // 暂停播放 this.videoLayer.pause(); logger.log("GirlDetailPanel: 暂停视频播放"); } } /** * 获取目标视频尺寸 * 从 videoArea 获取实际需要填充的区域尺寸 */ private getTargetVideoSize(): Size | null { // 从 videoArea 获取目标尺寸 if (this.videoArea) { return new Size(this.videoArea.width, this.videoArea.height); } // 如果没有 videoArea,使用默认尺寸 logger.warn("GirlDetailPanel: videoArea 未设置,使用默认尺寸"); return new Size(600, 800); } nameKey: string; tagKey: string; descKey: string; category: number; async refresh() { this.id = NavigationManager.Instance.getSelectedGirlId(); this.category = NavigationManager.Instance.getSelectedCategoryId(); this.imgItemInst.node.active = false; this.videoAreaPos = new Vec3(540, 1384 - 60); const pos = new Vec3( this.descContent.position.x, this.descContent.position.y, this.descContent.position.z ); this.descContent.position = new Vec3(pos.x, pos.y - 1000, pos.z); //this.node.scale = Vec3.ZERO; tween(this.descContent) .to(0.3, { position: pos }, { easing: "quadOut" }) .start(); // 检查 SceneBgVideoLayer 是否可用 if (!this.videoLayer) { logger.error("GirlDetailPanel: SceneBgVideoLayer.handle 未初始化"); } else { logger.log("GirlDetailPanel: 成功获取 SceneBgVideoLayer 实例"); // 初始化视频显示位置 //this.initVideoPosition(); } const girlData = DataManager.I.getDataById(DataId.Girl); const isRelease = girlData.getIsRelease(this.category.toString(), this.id); const isFree = girlData.isGirlFreeType(this.category.toString(), this.id); if (isRelease || isFree) { // 先设置选中的角色ID this.girlPrice.node.active = false; this.sendMsgText.active = true; } else { this.girlPrice.node.active = true; this.sendMsgText.active = false; this.girlPrice.string = girlData .getGrilOriginalPrice(this.category.toString(), this.id) .toString(); } // 请求详细数据 const reqData = { id: this.id, }; let res = await GirlService.I.reqGetGirlDetail(reqData); if (res && res.code === proto.cs.EnmRetCode.SUCCESS) { // 保存数据 girlData.setGirlDetailData(this.category.toString(), res.data); // 根据数据,刷新界面 this.nameKey = girlData.getGrilName(this.category.toString(), this.id); this.girlName.string = LanguageUtils.getText(this.nameKey); this.descKey = girlData.getGrilDesc(this.category.toString(), this.id); this.desc.string = LanguageUtils.getText(this.descKey); let desc = ""; this.tagKey = girlData.getGrilTagKey(this.category.toString(), this.id); const tags = LanguageUtils.getText(this.tagKey).split("|"); for (let i = 0; i < tags.length; i++) { if (i != 0) desc += "\n"; desc += tags[i]; } this.tags.string = desc; const star = girlData.getStar(this.category.toString(), this.id); const stars = this.starParent.getComponentsInChildren(Sprite); logger.log("好感度:" + star); for (let i = 0; i < stars.length; i++) { const element = stars[i]; if (star > i) { ResManager.I.changeResourceSpriteFrame(element, "ui/common/heart"); } else { ResManager.I.changeResourceSpriteFrame( element, "ui/common/heart_empty" ); } } let age = girlData.getGrilAge(this.category.toString(), this.id); this.descAge.string = LanguageUtils.getText("girldetailpanel.age") + age.toString(); const firstPath = girlData.getFirstGrilVideoPath( this.category.toString(), this.id ); // 使用 SceneBgVideoLayer 加载并播放视频 if (this.videoLayer && firstPath) { logger.log(`GirlDetailPanel: 加载视频 ${firstPath}`); // 获取 videoArea 的尺寸和位置 const targetSize = this.videoArea ? new Size(this.videoArea.width, this.videoArea.height) : new Size(600, 800); // 获取 videoArea 的中心位置,确保使用正确的世界坐标 // 使用新的统一 playVideo 方法 this.videoLayer.playVideo(firstPath, { loop: true, size: targetSize, position: this.videoAreaPos, }); } let resIds = girlData.getAllDisplayGrilPhotoId( this.category.toString(), this.id ); this.refreshImgs( proto.cs.EnmResType.ERT_Image, this.category, this.id, resIds ); } } bindImgToggle() { const girlData = DataManager.I.getDataById(DataId.Girl); let resIds = girlData.getAllDisplayGrilPhotoId( this.category.toString(), this.id ); this.refreshImgs( proto.cs.EnmResType.ERT_Image, this.category, this.id, resIds ); } bindVideoToggle() { const girlData = DataManager.I.getDataById(DataId.Girl); let resIds = girlData.getAllDisplayGrilVideoId( this.category.toString(), this.id ); this.refreshImgs( proto.cs.EnmResType.ERT_Video, this.category, this.id, resIds ); } refreshImgs( type: proto.cs.EnmResType, category: number, id: number, resIds: number[] ) { for (let i = this.imgsLayout.children.length - 1; i >= 0; i--) { DetailImageItemPool.Instance.put(this.imgsLayout.children[i]); } let count = resIds.length; for (let i = 0; i < count; i++) { let newNode = DetailImageItemPool.Instance.get(); if (newNode) { let item = newNode.getComponent(DetailImageItem); let resId = resIds[i]; item.refresh(this, type, category, id, resId); newNode.active = true; this.imgsLayout.addChild(newNode); } } } OnClickChatBtn() { const girlData = DataManager.I.getDataById(DataId.Girl); const isRelease = girlData.getIsRelease(this.category.toString(), this.id); const isFree = girlData.isGirlFreeType(this.category.toString(), this.id); if (isRelease || isFree) { // 使用带过渡动画的导航方法 NavigationManager.Instance.switchToPanel( PanelType.PAST_GIRL_LIST, false, (base) => { const chatData = { girlId: this.id, base: base }; NavigationManager.Instance.openSubPanel("ChatPanel", base, chatData, { openAnimation: PageTransitionType.SLIDE_LEFT, closeAnimation: PageTransitionType.SLIDE_RIGHT, }); } ); } else { //未解锁 NavigationManager.Instance.openPopupPanel("GirlListPopupPanel", { base: this, category: this.category, id: this.id, }); } } returnBtn() { NavigationManager.Instance.switchToPanel(PanelType.THEME); } /** * 面板关闭时的清理工作 */ onClose() { // 停止视频播放,释放资源 if (this.videoLayer) { //this.videoLayer.stop(); //logger.log("GirlDetailPanel: 停止视频播放"); } super.onClose(); } /** * 检测节点是否在屏幕可见区域内 * @param node 要检测的节点 * @returns 是否在屏幕内 */ private isNodeInScreen(node: Node): boolean { if (!node) return false; const uiTransform = node.getComponent(UITransform); if (!uiTransform) return false; // 获取屏幕尺寸 const screenSize = screen.windowSize; const designResolution = view.getDesignResolutionSize(); // 获取节点的世界坐标 const worldPos = uiTransform.convertToWorldSpaceAR(Vec3.ZERO); // 获取节点的尺寸 const nodeSize = uiTransform.contentSize; // 计算节点的边界 const left = worldPos.x - nodeSize.width * uiTransform.anchorX; const right = worldPos.x + nodeSize.width * (1 - uiTransform.anchorX); const bottom = worldPos.y - nodeSize.height * uiTransform.anchorY; const top = worldPos.y + nodeSize.height * (1 - uiTransform.anchorY); // 检测是否完全在屏幕内 const isInScreen = left >= 0 && right <= designResolution.width && bottom >= 0 && top <= designResolution.height - 160; return isInScreen; } /** * 实时更新ImgVidSelect跟随ImgVidFakePos的逻辑 */ lateUpdate(dt: number) { if (!this.ImgVidSelect || !this.ImgVidFakePos) { return; } // 检测ImgVidFakePos是否在屏幕内 const isInScreen = this.isNodeInScreen(this.ImgVidFakePos); if (isInScreen) { // 在屏幕内,开始或继续跟随 if (!this.isFollowing) { this.isFollowing = true; logger.log("ImgVidSelect开始跟随ImgVidFakePos"); } // 更新ImgVidSelect的位置到ImgVidFakePos的位置 this.ImgVidSelect.worldPosition = this.ImgVidFakePos.worldPosition.clone(); this.lastFakePosPosition = this.ImgVidFakePos.worldPosition.clone(); } else { // 超出屏幕,停止跟随 if (this.isFollowing) { this.isFollowing = false; logger.log("ImgVidSelect停止跟随ImgVidFakePos(超出屏幕)"); // ImgVidSelect保持在最后的跟随位置 } } } /** * 面板销毁时的清理工作 */ onDestroy() { // 确保视频资源被释放 if (this.videoLayer) { //this.videoLayer.stop(); //logger.log("GirlDetailPanel: 销毁时停止视频播放"); } // 回收所有使用中的节点到对象池 for (let i = this.imgsLayout.children.length - 1; i >= 0; i--) { DetailImageItemPool.Instance.put(this.imgsLayout.children[i]); } super.onDestroy(); } }