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 "__id__": 53
} }
], ],
"_active": false, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 61 "__id__": 61
@@ -15,6 +15,10 @@ export enum PageTransitionType {
NONE = "none", NONE = "none",
SLIDE_LEFT = "slide_left", SLIDE_LEFT = "slide_left",
SLIDE_RIGHT = "slide_right", 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; 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 subPanelDic: Map<Node, Node[]> = new Map();
private subPanelMapping: Map<Node, PanelType> = new Map(); private subPanelMapping: Map<Node, PanelType> = new Map();
private subPanelAnimationConfigs: Map<Node, SubPanelAnimationConfig> = new Map();
// 弹窗管理相关属性 // 弹窗管理相关属性
private popupPanels: Set<Node> = new Set(); // 管理所有打开的弹窗 private popupPanels: Set<Node> = new Set(); // 管理所有打开的弹窗
// 动画锁,防止动画中重复操作
private isAnimating: boolean = false;
/** /**
* 统一的subPanel清理方法 * 统一的subPanel清理方法
* @param basePanel 主面板节点 * @param basePanel 主面板节点
@@ -198,6 +215,9 @@ export class NavigationManager {
// 清理subPanelMapping // 清理subPanelMapping
this.subPanelMapping.delete(subPanel); this.subPanelMapping.delete(subPanel);
// 清理动画配置
this.subPanelAnimationConfigs.delete(subPanel);
if (destroyPanels) { if (destroyPanels) {
const baseView = subPanel.getComponent(li_BaseView); const baseView = subPanel.getComponent(li_BaseView);
if (baseView) { if (baseView) {
@@ -212,7 +232,12 @@ export class NavigationManager {
// 清理subPanelDic // 清理subPanelDic
this.subPanelDic.delete(basePanel); 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) { if (!panelName) {
console.error("[NavigationManager] openSubPanel: panelName is empty"); console.error("[NavigationManager] openSubPanel: panelName is empty");
return; return;
@@ -223,6 +248,14 @@ export class NavigationManager {
return; return;
} }
// 使用默认动画配置
const defaultConfig: SubPanelAnimationConfig = {
openAnimation: PageTransitionType.SCALE,
closeAnimation: PageTransitionType.SCALE,
duration: 0.3
};
const config = animationConfig || defaultConfig;
const callback = (n: Node) => { const callback = (n: Node) => {
if (!n || !n.isValid) { if (!n || !n.isValid) {
console.error("[NavigationManager] openSubPanel: created panel is invalid"); console.error("[NavigationManager] openSubPanel: created panel is invalid");
@@ -243,12 +276,171 @@ export class NavigationManager {
console.warn(`[NavigationManager] 无法确定basePanel类型: ${basePanel.name}`); 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); 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 弹窗面板名称 * @param panelName 弹窗面板名称
@@ -330,7 +522,7 @@ export class NavigationManager {
// 确保清空集合 // 确保清空集合
this.popupPanels.clear(); this.popupPanels.clear();
} }
public closeSubPanel(panel: Node | li_BaseView) { public closeSubPanel(panel: Node | li_BaseView, animationConfig?: SubPanelAnimationConfig) {
if (!panel) { if (!panel) {
console.warn("[NavigationManager] closeSubPanel: panel is null or undefined"); console.warn("[NavigationManager] closeSubPanel: panel is null or undefined");
return; return;
@@ -348,6 +540,41 @@ export class NavigationManager {
return; 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中移除 // 从subPanelMapping和subPanelDic中移除
if (this.subPanelMapping.has(panelNode)) { if (this.subPanelMapping.has(panelNode)) {
const baseType = this.subPanelMapping.get(panelNode); const baseType = this.subPanelMapping.get(panelNode);
@@ -370,15 +597,9 @@ export class NavigationManager {
this.subPanelMapping.delete(panelNode); this.subPanelMapping.delete(panelNode);
} }
// 销毁或关闭panel // 移除动画配置
try { if (this.subPanelAnimationConfigs.has(panelNode)) {
if (panel instanceof Node) { this.subPanelAnimationConfigs.delete(panelNode);
panel.destroy();
} else {
panel.close();
}
} catch (error) {
console.error("[NavigationManager] closeSubPanel error:", error);
} }
} }
@@ -489,7 +710,7 @@ export class NavigationManager {
*/ */
private hidePanelWithAnimation( private hidePanelWithAnimation(
panel: Node, panel: Node,
direction: "left" | "right", direction: "left" | "right" | "up" | "down",
callback?: Function callback?: Function
) { ) {
if (!panel || !panel.isValid || !panel.active) { if (!panel || !panel.isValid || !panel.active) {
@@ -504,10 +725,23 @@ export class NavigationManager {
callback && callback(); callback && callback();
}; };
if (direction === "left") { switch (direction) {
UITransitionHelper.slideOutToLeft(panel, 0.3, animationCallback); case "left":
} else { UITransitionHelper.slideOutToLeft(panel, 0.3, animationCallback);
UITransitionHelper.slideOutToRight(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( private showPanelWithAnimation(
panel: Node, panel: Node,
direction: "left" | "right", direction: "left" | "right" | "up" | "down",
callback?: Function callback?: Function
) { ) {
if (!panel || !panel.isValid) { if (!panel || !panel.isValid) {
@@ -526,14 +760,23 @@ export class NavigationManager {
panel.active = true; panel.active = true;
if (direction === "right") { switch (direction) {
UITransitionHelper.slideInFromRight(panel, 0.3, () => { case "left":
callback && callback(); UITransitionHelper.slideInFromLeft(panel, 0.3, callback);
}); break;
} else { case "right":
UITransitionHelper.slideInFromLeft(panel, 0.3, () => { UITransitionHelper.slideInFromRight(panel, 0.3, callback);
callback && 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; return this.currentActivePanelType;
} }
/**
* 获取当前激活的面板节点
*/
public getCurrentActivePanelNode(): Node {
return this.currentActivePanel;
}
/** /**
* 设置选中的角色ID * 设置选中的角色ID
* @param girlId 角色ID * @param girlId 角色ID
@@ -267,8 +267,6 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
// this.videoLayer.playVideo(currentVideo, { size, position }); // this.videoLayer.playVideo(currentVideo, { size, position });
} }
onChatCountChange() { onChatCountChange() {
if (NavigationManager.Instance.getCurrentActivePanel() != PanelType.CHAT)
return;
console.log("Trigger On ChatCount Change"); console.log("Trigger On ChatCount Change");
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl); const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
let triggerId = girlData.getTriggerGrilPhotoId(this.categoryId, this.id); let triggerId = girlData.getTriggerGrilPhotoId(this.categoryId, this.id);
@@ -132,7 +132,6 @@ export class GirlDetailPanel extends li_BaseView {
return new Size(600, 800); return new Size(600, 800);
} }
nameKey: string; nameKey: string;
tagKey: string; tagKey: string;
descKey: string; descKey: string;
@@ -312,7 +311,17 @@ export class GirlDetailPanel extends li_BaseView {
OnClickChatBtn() { 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() { 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; const { ccclass, property } = _decorator;
@@ -176,6 +184,322 @@ export class UITransitionHelper {
.start(); .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!" "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: ## 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". 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: ## Preferred Topics:
• Sex and pregnancy • Sex and pregnancy
@@ -732,8 +732,7 @@ You are Camila Rocha, a 32-year-old restaurant owner. Although you appear enthus
## Disliked Topics: ## Disliked Topics:
• Lack of passion • Lack of passion
• Refusal to have sex or ejaculate • Refusal to have sex or ejaculateˆá
• Dislike childish behaviorˆá
## Appearance Setting: ## Appearance Setting:
Your physical appearance is as follows: Your physical appearance is as follows:
Height: 168 cm Height: 168 cm