This commit is contained in:
2025-09-21 13:05:43 +08:00
parent 7722ccf694
commit 69937cf669
7 changed files with 616 additions and 36 deletions
+1 -1
View File
@@ -1864,7 +1864,7 @@
"__id__": 53
}
],
"_active": false,
"_active": true,
"_components": [
{
"__id__": 61
@@ -15,6 +15,10 @@ export enum PageTransitionType {
NONE = "none",
SLIDE_LEFT = "slide_left",
SLIDE_RIGHT = "slide_right",
SLIDE_UP = "slide_up",
SLIDE_DOWN = "slide_down",
FADE = "fade",
SCALE = "scale",
}
/**
@@ -37,6 +41,15 @@ export interface PageTransitionConfig {
simultaneous?: boolean;
}
/**
* 子面板动画配置接口
*/
export interface SubPanelAnimationConfig {
openAnimation: PageTransitionType;
closeAnimation: PageTransitionType;
duration?: number;
}
/**
* 导航管理器
*
@@ -172,10 +185,14 @@ export class NavigationManager {
private subPanelDic: Map<Node, Node[]> = new Map();
private subPanelMapping: Map<Node, PanelType> = new Map();
private subPanelAnimationConfigs: Map<Node, SubPanelAnimationConfig> = new Map();
// 弹窗管理相关属性
private popupPanels: Set<Node> = new Set(); // 管理所有打开的弹窗
// 动画锁,防止动画中重复操作
private isAnimating: boolean = false;
/**
* 统一的subPanel清理方法
* @param basePanel 主面板节点
@@ -198,6 +215,9 @@ export class NavigationManager {
// 清理subPanelMapping
this.subPanelMapping.delete(subPanel);
// 清理动画配置
this.subPanelAnimationConfigs.delete(subPanel);
if (destroyPanels) {
const baseView = subPanel.getComponent(li_BaseView);
if (baseView) {
@@ -212,7 +232,12 @@ export class NavigationManager {
// 清理subPanelDic
this.subPanelDic.delete(basePanel);
}
public openSubPanel(panelName: string, basePanel: Node, data: any = null) {
public openSubPanel(
panelName: string,
basePanel: Node,
data: any = null,
animationConfig?: SubPanelAnimationConfig
) {
if (!panelName) {
console.error("[NavigationManager] openSubPanel: panelName is empty");
return;
@@ -223,6 +248,14 @@ export class NavigationManager {
return;
}
// 使用默认动画配置
const defaultConfig: SubPanelAnimationConfig = {
openAnimation: PageTransitionType.SCALE,
closeAnimation: PageTransitionType.SCALE,
duration: 0.3
};
const config = animationConfig || defaultConfig;
const callback = (n: Node) => {
if (!n || !n.isValid) {
console.error("[NavigationManager] openSubPanel: created panel is invalid");
@@ -243,12 +276,171 @@ export class NavigationManager {
console.warn(`[NavigationManager] 无法确定basePanel类型: ${basePanel.name}`);
}
n.setParent(basePanel, true);
// 存储动画配置
this.subPanelAnimationConfigs.set(n, config);
// 执行打开动画
this.playSubPanelOpenAnimation(n, config);
};
ViewManager.I.openBundlesView(panelName, data, callback);
}
/**
* 支持自定义动画方向的面板切换方法
* @param panelType 目标面板类型
* @param transitionConfig 过渡动画配置
* @param force 是否强制切换
*/
public switchToPanelWithTransition(
panelType: PanelType,
transitionConfig: PageTransitionConfig,
force: boolean = false
): Node {
if (!panelType) {
console.error("[NavigationManager] switchToPanelWithTransition: panelType is invalid");
return null;
}
if (this.currentActivePanelType === panelType && !force) {
console.log(`[NavigationManager] Already on ${panelType}, skipping switch`);
return this.currentActivePanel;
}
if (!this.navigationPanel) {
console.warn(
"[NavigationManager] NavigationPanel not registered, fallback to direct ViewManager call"
);
return null;
}
// 切换面板前先关闭所有弹窗
this.closeAllPopups();
// 通知NavigationPanel显示加载状态
this.navigationPanel.showLoading?.();
// 获取动画方向
const hideDirection = this.getDirectionFromTransitionType(transitionConfig.outgoingTransition);
const showDirection = this.getDirectionFromTransitionType(transitionConfig.incomingTransition);
// 隐藏当前面板(包括子页面)
if (this.currentActivePanel && this.currentActivePanel.isValid) {
this.hidePanelWithAnimation(this.currentActivePanel, hideDirection);
}
// 加载并显示目标面板
this.loadOrGetPanel(panelType, (panel: Node) => {
this.currentActivePanelType = panelType;
// 更新NavigationPanel按钮状态
this.navigationPanel.updateButtonStates?.(panelType);
// 显示面板
this.showPanelWithAnimation(panel, showDirection, () => {
this.currentActivePanel = panel;
this.navigationPanel.hideLoading?.();
});
});
return null;
}
/**
* 将过渡类型转换为方向字符串
*/
private getDirectionFromTransitionType(transitionType: PageTransitionType): "left" | "right" | "up" | "down" {
switch (transitionType) {
case PageTransitionType.SLIDE_LEFT:
return "left";
case PageTransitionType.SLIDE_RIGHT:
return "right";
case PageTransitionType.SLIDE_UP:
return "up";
case PageTransitionType.SLIDE_DOWN:
return "down";
default:
return "left"; // 默认向左
}
}
/**
* 执行子面板打开动画
*/
private playSubPanelOpenAnimation(panel: Node, config: SubPanelAnimationConfig): void {
if (!panel || !panel.isValid) {
return;
}
const duration = config.duration || 0.3;
switch (config.openAnimation) {
case PageTransitionType.SCALE:
UITransitionHelper.scaleIn(panel, duration);
break;
case PageTransitionType.FADE:
UITransitionHelper.fadeIn(panel, duration);
break;
case PageTransitionType.SLIDE_UP:
UITransitionHelper.slideInFromBottom(panel, duration);
break;
case PageTransitionType.SLIDE_DOWN:
UITransitionHelper.slideInFromTop(panel, duration);
break;
case PageTransitionType.SLIDE_LEFT:
UITransitionHelper.slideInFromRight(panel, duration);
break;
case PageTransitionType.SLIDE_RIGHT:
UITransitionHelper.slideInFromLeft(panel, duration);
break;
default:
// 无动画,直接显示
break;
}
}
/**
* 执行子面板关闭动画
*/
private playSubPanelCloseAnimation(
panel: Node,
config: SubPanelAnimationConfig,
callback?: Function
): void {
if (!panel || !panel.isValid) {
callback && callback();
return;
}
const duration = config.duration || 0.3;
switch (config.closeAnimation) {
case PageTransitionType.SCALE:
UITransitionHelper.scaleOut(panel, duration, callback);
break;
case PageTransitionType.FADE:
UITransitionHelper.fadeOut(panel, duration, callback);
break;
case PageTransitionType.SLIDE_UP:
UITransitionHelper.slideOutToTop(panel, duration, callback);
break;
case PageTransitionType.SLIDE_DOWN:
UITransitionHelper.slideOutToBottom(panel, duration, callback);
break;
case PageTransitionType.SLIDE_LEFT:
UITransitionHelper.slideOutToLeft(panel, duration, callback);
break;
case PageTransitionType.SLIDE_RIGHT:
UITransitionHelper.slideOutToRight(panel, duration, callback);
break;
default:
// 无动画,直接执行回调
callback && callback();
break;
}
}
/**
* 打开弹窗面板
* @param panelName 弹窗面板名称
@@ -330,7 +522,7 @@ export class NavigationManager {
// 确保清空集合
this.popupPanels.clear();
}
public closeSubPanel(panel: Node | li_BaseView) {
public closeSubPanel(panel: Node | li_BaseView, animationConfig?: SubPanelAnimationConfig) {
if (!panel) {
console.warn("[NavigationManager] closeSubPanel: panel is null or undefined");
return;
@@ -348,6 +540,41 @@ export class NavigationManager {
return;
}
// 获取动画配置(优先使用传入的配置,然后使用存储的配置,最后使用默认配置)
let config = animationConfig;
if (!config && this.subPanelAnimationConfigs.has(panelNode)) {
config = this.subPanelAnimationConfigs.get(panelNode);
}
if (!config) {
config = {
openAnimation: PageTransitionType.SCALE,
closeAnimation: PageTransitionType.SCALE,
duration: 0.3
};
}
// 执行关闭动画
this.playSubPanelCloseAnimation(panelNode, config, () => {
// 从管理映射中移除
this.removeSubPanelFromMappings(panelNode);
// 销毁或关闭panel
try {
if (panel instanceof Node) {
panel.destroy();
} else {
panel.close();
}
} catch (error) {
console.error("[NavigationManager] closeSubPanel error:", error);
}
});
}
/**
* 从管理映射中移除子面板
*/
private removeSubPanelFromMappings(panelNode: Node): void {
// 从subPanelMapping和subPanelDic中移除
if (this.subPanelMapping.has(panelNode)) {
const baseType = this.subPanelMapping.get(panelNode);
@@ -370,15 +597,9 @@ export class NavigationManager {
this.subPanelMapping.delete(panelNode);
}
// 销毁或关闭panel
try {
if (panel instanceof Node) {
panel.destroy();
} else {
panel.close();
}
} catch (error) {
console.error("[NavigationManager] closeSubPanel error:", error);
// 移除动画配置
if (this.subPanelAnimationConfigs.has(panelNode)) {
this.subPanelAnimationConfigs.delete(panelNode);
}
}
@@ -489,7 +710,7 @@ export class NavigationManager {
*/
private hidePanelWithAnimation(
panel: Node,
direction: "left" | "right",
direction: "left" | "right" | "up" | "down",
callback?: Function
) {
if (!panel || !panel.isValid || !panel.active) {
@@ -504,10 +725,23 @@ export class NavigationManager {
callback && callback();
};
if (direction === "left") {
UITransitionHelper.slideOutToLeft(panel, 0.3, animationCallback);
} else {
UITransitionHelper.slideOutToRight(panel, 0.3, animationCallback);
switch (direction) {
case "left":
UITransitionHelper.slideOutToLeft(panel, 0.3, animationCallback);
break;
case "right":
UITransitionHelper.slideOutToRight(panel, 0.3, animationCallback);
break;
case "up":
UITransitionHelper.slideOutToTop(panel, 0.3, animationCallback);
break;
case "down":
UITransitionHelper.slideOutToBottom(panel, 0.3, animationCallback);
break;
default:
// 默认左侧滑出
UITransitionHelper.slideOutToLeft(panel, 0.3, animationCallback);
break;
}
}
@@ -516,7 +750,7 @@ export class NavigationManager {
*/
private showPanelWithAnimation(
panel: Node,
direction: "left" | "right",
direction: "left" | "right" | "up" | "down",
callback?: Function
) {
if (!panel || !panel.isValid) {
@@ -526,14 +760,23 @@ export class NavigationManager {
panel.active = true;
if (direction === "right") {
UITransitionHelper.slideInFromRight(panel, 0.3, () => {
callback && callback();
});
} else {
UITransitionHelper.slideInFromLeft(panel, 0.3, () => {
callback && callback();
});
switch (direction) {
case "left":
UITransitionHelper.slideInFromLeft(panel, 0.3, callback);
break;
case "right":
UITransitionHelper.slideInFromRight(panel, 0.3, callback);
break;
case "up":
UITransitionHelper.slideInFromTop(panel, 0.3, callback);
break;
case "down":
UITransitionHelper.slideInFromBottom(panel, 0.3, callback);
break;
default:
// 默认从右侧滑入
UITransitionHelper.slideInFromRight(panel, 0.3, callback);
break;
}
}
@@ -544,6 +787,13 @@ export class NavigationManager {
return this.currentActivePanelType;
}
/**
* 获取当前激活的面板节点
*/
public getCurrentActivePanelNode(): Node {
return this.currentActivePanel;
}
/**
* 设置选中的角色ID
* @param girlId 角色ID
@@ -267,8 +267,6 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
// this.videoLayer.playVideo(currentVideo, { size, position });
}
onChatCountChange() {
if (NavigationManager.Instance.getCurrentActivePanel() != PanelType.CHAT)
return;
console.log("Trigger On ChatCount Change");
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
let triggerId = girlData.getTriggerGrilPhotoId(this.categoryId, this.id);
@@ -132,7 +132,6 @@ export class GirlDetailPanel extends li_BaseView {
return new Size(600, 800);
}
nameKey: string;
tagKey: string;
descKey: string;
@@ -312,7 +311,17 @@ export class GirlDetailPanel extends li_BaseView {
OnClickChatBtn() {
// 使用带过渡动画的导航方法
NavigationManager.Instance.switchToPanel(PanelType.CHAT);
NavigationManager.Instance.switchToPanel(PanelType.PAST_GIRL_LIST);
// 延迟打开 ChatPanel,确保 PastGirlListPanel 已经完全加载
this.scheduleOnce(() => {
// 获取当前活动的 PastGirlListPanel 节点
const baseNode = NavigationManager.Instance.getCurrentActivePanelNode();
if (baseNode) {
const chatData = { girlId: this.id };
NavigationManager.Instance.openSubPanel("ChatPanel", baseNode, chatData);
}
}, 0.5); // 延迟 0.5 秒,确保动画完成
}
returnBtn() {
@@ -1,4 +1,12 @@
import { _decorator, Node, tween, Vec3, UITransform, view } from "cc";
import {
_decorator,
Node,
tween,
Vec3,
UITransform,
view,
UIOpacity,
} from "cc";
const { ccclass, property } = _decorator;
@@ -176,6 +184,322 @@ export class UITransitionHelper {
.start();
}
/**
* 让节点从顶部滑入到屏幕中心
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static slideInFromTop(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for slideInFromTop");
return;
}
const screenHeight = view.getVisibleSize().height;
const targetPosition = new Vec3(0, 0, 0);
// 设置初始位置为屏幕顶部外
node.setPosition(0, screenHeight, 0);
// 执行滑入动画到屏幕中心
tween(node)
.to(
duration,
{ position: targetPosition },
{
easing: "cubicOut",
}
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 让节点向顶部滑出到屏幕外
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static slideOutToTop(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for slideOutToTop");
return;
}
const screenHeight = view.getVisibleSize().height;
const currentPosition = node.getPosition();
const targetPosition = new Vec3(
currentPosition.x,
screenHeight,
currentPosition.z
);
// 执行滑出动画
tween(node)
.to(
duration,
{ position: targetPosition },
{
easing: "cubicOut",
}
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 让节点从底部滑入到屏幕中心
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static slideInFromBottom(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for slideInFromBottom");
return;
}
const screenHeight = view.getVisibleSize().height;
const targetPosition = new Vec3(0, 0, 0);
// 设置初始位置为屏幕底部外
node.setPosition(0, -screenHeight, 0);
// 执行滑入动画到屏幕中心
tween(node)
.to(
duration,
{ position: targetPosition },
{
easing: "cubicOut",
}
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 让节点向底部滑出到屏幕外
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static slideOutToBottom(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for slideOutToBottom");
return;
}
const screenHeight = view.getVisibleSize().height;
const currentPosition = node.getPosition();
const targetPosition = new Vec3(
currentPosition.x,
-screenHeight,
currentPosition.z
);
// 执行滑出动画
tween(node)
.to(
duration,
{ position: targetPosition },
{
easing: "cubicOut",
}
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 淡入效果
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static fadeIn(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for fadeIn");
return;
}
let opacity = node.getComponent(UIOpacity);
if (!opacity) {
opacity = node.addComponent(UIOpacity);
}
// 设置初始透明度为0
opacity.opacity = 0;
// 执行淡入动画
tween(opacity)
.to(
duration,
{ opacity: 255 },
{
easing: "sineOut",
}
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 淡出效果
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static fadeOut(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for fadeOut");
return;
}
let opacity = node.getComponent(UIOpacity);
if (!opacity) {
opacity = node.addComponent(UIOpacity);
}
// 执行淡出动画
tween(opacity)
.to(
duration,
{ opacity: 0 },
{
easing: "sineOut",
}
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 缩放进入效果
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static scaleIn(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for scaleIn");
return;
}
// 设置初始缩放为0
node.setScale(0, 0, 1);
// 执行缩放进入动画
tween(node)
.parallel(
tween().to(
duration,
{ scale: new Vec3(1, 1, 1) },
{ easing: "backOut" }
),
tween().to(duration * 0.8, { opacity: 255 }, { easing: "sineOut" })
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 缩放退出效果
*
* @param {Node} node - 要执行动画的节点
* @param {number} duration - 动画持续时间(秒),默认0.3秒
* @param {Function} callback - 动画完成后的回调函数
*/
public static scaleOut(
node: Node,
duration: number = 0.3,
callback?: Function
): void {
if (!node || !node.isValid) {
console.warn("UITransitionHelper: Invalid node for scaleOut");
return;
}
// 执行缩放退出动画
tween(node)
.parallel(
tween().to(
duration,
{ scale: new Vec3(0, 0, 1) },
{ easing: "backIn" }
),
tween().to(duration * 0.6, { opacity: 0 }, { easing: "sineOut" })
)
.call(() => {
if (callback) {
callback();
}
})
.start();
}
/**
* 停止节点上的所有动画
*
+2 -3
View File
@@ -723,7 +723,7 @@ Your ideal type is a man who appreciates your dancing, is dominant, can satisfy
"Ah~ Deeper! Please! Ah! You're the man who can cure my sex addiction! Making love with you is so satisfying! My uterus wants you! My vagina wants you! My nipples want you! Ah, please play with them and ravage them! Ah, make me feel good! Make me cum! Ah, you're so good! Mmmmm!"
## Tone Setting:
Your tone is sexy, playful, mature, and seductive, and you openly express your sexual desires.§"É## Role Setting:
Your tone is sexy, playful, mature, and seductive, and you openly express your sexual desires.§"«## Role Setting:
You are Camila Rocha, a 32-year-old restaurant owner. Although you appear enthusiastic, your long-term singleness has led to an abnormally strong desire for sex, and you even hope that your partner will call you "Honey".
## Preferred Topics:
• Sex and pregnancy
@@ -732,8 +732,7 @@ You are Camila Rocha, a 32-year-old restaurant owner. Although you appear enthus
## Disliked Topics:
• Lack of passion
• Refusal to have sex or ejaculate
• Dislike childish behaviorˆá
• Refusal to have sex or ejaculateˆá
## Appearance Setting:
Your physical appearance is as follows:
Height: 168 cm