refactor(missionCard): 重构面板滑动逻辑为底部划出模式

重构了任务卡牌面板的布局与动画系统:
1. 移除原水平轮播逻辑,改为覆盖面板从底部划入/划出的交互
2. 统一使用场景读取的真实坐标作为显示/隐藏基准位置
3. 重命名相关变量与注释,匹配新的底部划出交互逻辑
4. 调整所有面板切换按钮的注释描述,贴合新交互行为
This commit is contained in:
pan
2026-08-05 14:45:22 +08:00
parent 95969781fb
commit fb3cf4c619

View File

@@ -179,14 +179,14 @@ export class MissionCardComp extends CCComp {
/** 是否已召唤过英雄(用于控制其他面板按钮是否可点击) */
private hasCalledHero: boolean = false;
// ======================== 面板轮播状态 ========================
// ======================== 面板划出状态 ========================
/** 面板滑动间距(单面板宽度,运行时由 initPanelLayout 推导) */
private panelSlideWidth: number = 720;
/** 当前激活的面板索引0=英雄, 1=装备, 2=技能, 3=药品 */
private currentPanelIndex: number = 0;
/** 各面板基准 Y 坐标缓存 */
private panelBaseYs: number[] = [];
/** 面板显示位置(英雄面板基准位置,运行时从场景读取) */
private panelShowPos: Vec3 = new Vec3();
/** 各覆盖面板(装备/技能/药品)的隐藏 Y 坐标(屏幕外底部,从场景初始位置读取) */
private panelHideYs: number[] = [];
/** 是否已初始化面板布局 */
private hasInitPanelLayout: boolean = false;
/** 是否已缓存卡牌面板基准缩放 */
@@ -197,10 +197,8 @@ export class MissionCardComp extends CCComp {
private cardsShowScale: Vec3 = new Vec3(1, 1, 1);
/** 卡牌面板收起态缩放scale=0 隐藏) */
private cardsHideScale: Vec3 = new Vec3(0, 0, 1);
/** 卡牌面板显示位置 Y 坐标 */
private readonly cardPanelShowY: number = 0;
/** 卡牌面板隐藏位置 Y 坐标(向下 700 */
private readonly cardPanelHideY: number = -700;
/** 卡牌面板隐藏 Y 坐标(场景初始位置,屏幕外底部,由 initPanelLayout 缓存) */
private cardsPanHideY: number = 0;
/** 卡牌面板显示/隐藏动画时长 */
private readonly cardPanelAnimDuration: number = 0.25;
@@ -504,7 +502,7 @@ export class MissionCardComp extends CCComp {
}
/**
* 技能面板按钮回调:滑动到技能面板(index=2)。
* 技能面板按钮回调:技能面板从底部划出(index=2)。
*/
private onShowSkillsClick() {
oops.audio.playEffect("music/button");
@@ -514,7 +512,7 @@ export class MissionCardComp extends CCComp {
// ======================== 商店面板 ========================
/**
* 商店面板按钮回调:滑动到药品商店面板(index=3)。
* 商店面板按钮回调:药品商店面板从底部划出(index=3)。
*/
private onShowShopClick() {
oops.audio.playEffect("music/button");
@@ -854,7 +852,7 @@ export class MissionCardComp extends CCComp {
this.playButtonResetAnim(this.cards_chou);
}
// ======================== 面板轮播系统 ========================
// ======================== 面板底部划出系统 ========================
/** 面板节点数组(顺序:英雄→装备→技能→药品) */
private getPanelNodes(): Node[] {
@@ -867,46 +865,51 @@ export class MissionCardComp extends CCComp {
}
/**
* 初始化面板轮播布局:
* 1. 缓存各面板基准 Y 坐标
* 2. 推导面板滑动间距(取场景设计分辨率宽度或默认 720)。
* 3. 将所有面板排列为水平队列初始显示英雄面板index=0
* 初始化面板布局:
* 1. 以英雄面板位置为基准显示位置(英雄面板固定不动)
* 2. 缓存各覆盖面板(装备/技能/药品)的初始位置作为隐藏位置(屏幕外底部)。
* 3. 将覆盖面板归位到隐藏位置初始显示英雄面板index=0
*/
private initPanelLayout() {
if (this.hasInitPanelLayout) return;
const panels = this.getPanelNodes();
this.panelBaseYs = [];
this.panelHideYs = [];
// 以第一个有效面板的位置为基准,推导滑动间距
let refX = 0;
for (const p of panels) {
if (p && p.isValid) {
refX = p.getPosition().x;
break;
}
const heroNode = panels[0];
if (heroNode && heroNode.isValid) {
heroNode.active = true;
this.panelShowPos.set(heroNode.getPosition());
}
this.panelSlideWidth = Math.abs(refX) * 2 || 720;
if (this.panelSlideWidth < 400) this.panelSlideWidth = 720;
for (let i = 0; i < panels.length; i++) {
// 覆盖面板缓存场景初始位置屏幕外底部作为隐藏位置X 对齐显示位置
for (let i = 1; i < panels.length; i++) {
const node = panels[i];
if (!node || !node.isValid) {
this.panelBaseYs.push(0);
this.panelHideYs.push(this.panelShowPos.y);
continue;
}
node.active = true;
const pos = node.getPosition();
this.panelBaseYs.push(pos.y);
// 初始位置:当前面板(英雄)居中,其余按序排列
const targetX = (i - this.currentPanelIndex) * this.panelSlideWidth;
node.setPosition(targetX, pos.y, pos.z);
this.panelHideYs.push(pos.y);
// index 0 为英雄面板,隐藏列表从 index 1 起
node.setPosition(this.panelShowPos.x, pos.y, pos.z);
}
// 卡牌面板与其他覆盖面板同级缓存场景初始位置屏幕外底部作为隐藏位置X 对齐显示位置
if (this.cardPanNode && this.cardPanNode.isValid) {
const pos = this.cardPanNode.getPosition();
this.cardsPanHideY = pos.y;
this.cardPanNode.setPosition(this.panelShowPos.x, pos.y, pos.z);
}
this.hasInitPanelLayout = true;
this.updatePanelButtonStates();
}
/**
* 滑动到指定面板
* 切换到指定面板
* 英雄面板index=0固定不动
* 选中的覆盖面板从底部划入到显示位置,其余覆盖面板划出回底部隐藏位置。
* @param index 0=英雄, 1=装备, 2=技能, 3=药品
*/
private slideToPanel(index: number) {
@@ -916,14 +919,14 @@ export class MissionCardComp extends CCComp {
this.currentPanelIndex = index;
const duration = 0.25;
for (let i = 0; i < panels.length; i++) {
// 覆盖面板从 index 1 开始index 0 英雄面板固定不动)
for (let i = 1; i < panels.length; i++) {
const node = panels[i];
if (!node || !node.isValid) continue;
const targetX = (i - index) * this.panelSlideWidth;
const baseY = this.panelBaseYs[i] ?? node.getPosition().y;
const targetY = i === index ? this.panelShowPos.y : (this.panelHideYs[i - 1] ?? node.getPosition().y);
Tween.stopAllByTarget(node);
tween(node)
.to(duration, { position: new Vec3(targetX, baseY, 0) }, { easing: 'quadOut' })
.to(duration, { position: new Vec3(this.panelShowPos.x, targetY, 0) }, { easing: 'quadOut' })
.start();
}
@@ -960,7 +963,7 @@ export class MissionCardComp extends CCComp {
}
/**
* 英雄面板按钮回调:滑动到英雄面板(index=0)。
* 英雄面板按钮回调:收起覆盖面板,回到英雄面板(index=0)。
*/
private onShowHerosClick() {
oops.audio.playEffect("music/button");
@@ -1020,7 +1023,7 @@ export class MissionCardComp extends CCComp {
}
/**
* 显示卡牌面板:从底部向上滑入到 Y=0
* 显示卡牌面板:从底部向上滑入到面板显示位置panelShowPos
*/
private onShowCardsClick() {
oops.audio.playEffect("music/button");
@@ -1028,24 +1031,24 @@ export class MissionCardComp extends CCComp {
this.cardPanNode.active = true;
Tween.stopAllByTarget(this.cardPanNode);
tween(this.cardPanNode)
.to(this.cardPanelAnimDuration, { position: new Vec3(0, this.cardPanelShowY, 0) }, { easing: 'quadOut' })
.to(this.cardPanelAnimDuration, { position: new Vec3(this.panelShowPos.x, this.panelShowPos.y, 0) }, { easing: 'quadOut' })
.start();
}
/**
* 隐藏卡牌面板:向下滑出到 Y=-700
* 隐藏卡牌面板:向下滑出到其隐藏位置cardsPanHideY屏幕外底部
*/
private onCloseCardsClick() {
oops.audio.playEffect("music/button");
this.hideCardsPanel();
}
/** 隐藏卡牌面板:向下滑出到 Y=-700(不含按钮音效,供系统逻辑复用) */
/** 隐藏卡牌面板:向下滑出到隐藏位置(不含按钮音效,供系统逻辑复用) */
private hideCardsPanel() {
if (!this.cardPanNode || !this.cardPanNode.isValid) return;
Tween.stopAllByTarget(this.cardPanNode);
tween(this.cardPanNode)
.to(this.cardPanelAnimDuration, { position: new Vec3(0, this.cardPanelHideY, 0) }, { easing: 'quadIn' })
.to(this.cardPanelAnimDuration, { position: new Vec3(this.panelShowPos.x, this.cardsPanHideY, 0) }, { easing: 'quadIn' })
.call(() => {
if (this.cardPanNode && this.cardPanNode.isValid) {
this.cardPanNode.active = false;
@@ -1057,7 +1060,7 @@ export class MissionCardComp extends CCComp {
// ======================== 装备商店面板 ========================
/**
* 装备面板按钮回调:滑动到装备商店面板(index=1)。
* 装备面板按钮回调:装备商店面板从底部划出(index=1)。
*/
private onShowEquipsClick() {
oops.audio.playEffect("music/button");
@@ -1451,8 +1454,8 @@ export class MissionCardComp extends CCComp {
/**
* 进入准备阶段:
* - 初始化面板轮播布局(如尚未初始化)。
* - 滑动到英雄面板index=0作为默认展示。
* - 初始化面板布局(如尚未初始化)。
* - 收起覆盖面板回到英雄面板index=0作为默认展示。
* - 激活 showHeros 按钮可见性。
* - 显示卡牌面板(从底部滑入)。
*/
@@ -1469,10 +1472,10 @@ export class MissionCardComp extends CCComp {
}
// 刷新所有刷新按钮的费用显示(含刷新石/金币切换与置灰状态)
this.updateDrawCostUI();
// 显示卡牌面板(默认进入准备阶段时展开)
// 显示卡牌面板(默认进入准备阶段时展开,位置与其他面板保持一致
if (this.cardPanNode && this.cardPanNode.isValid) {
this.cardPanNode.active = true;
this.cardPanNode.setPosition(0, this.cardPanelShowY, 0);
this.cardPanNode.setPosition(this.panelShowPos.x, this.panelShowPos.y, 0);
mLogger.log(this.debugMode, "MissionCardComp", "enterPreparePhase cardPanNode", {
active: this.cardPanNode.active,
position: this.cardPanNode.position,
@@ -1485,7 +1488,7 @@ export class MissionCardComp extends CCComp {
cards_nodeChildren: this.cards_node?.children.length
});
}
// 滑动到英雄面板
// 收起覆盖面板,回到英雄面板
this.slideToPanel(0);
mLogger.log(this.debugMode, "MissionCardComp", "enterPreparePhase herosPanNode", {
@@ -1493,7 +1496,7 @@ export class MissionCardComp extends CCComp {
herosPanNodePos: this.herosPanNode?.position,
herosPanNodeWorldPos: this.herosPanNode?.worldPosition,
currentPanelIndex: this.currentPanelIndex,
panelSlideWidth: this.panelSlideWidth
panelShowPos: this.panelShowPos
});
}