feat(map): 重构任务面板为轮播切换模式
重构任务面板的显示逻辑,将原有单面板独立显隐改为水平轮播切换: 1. 新增面板轮播状态管理相关字段与方法 2. 替换原有单独显隐逻辑为统一滑动切换逻辑 3. 调整技能面板初始显示规则,改为始终激活仅通过滑动控制可见性 4. 统一所有面板的打开/关闭操作,优化交互一致性
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -183,6 +183,19 @@ export class MissionCardComp extends CCComp {
|
||||
private purchasedEquipUuids: Set<number> = new Set();
|
||||
/** 已购买商品的 UUID 集合(跨波次保持,防止重复购买) */
|
||||
private purchasedItemUuids: Set<number> = new Set();
|
||||
|
||||
// ======================== 面板轮播状态 ========================
|
||||
|
||||
/** 面板滑动间距(单面板宽度,运行时由 initPanelLayout 推导) */
|
||||
private panelSlideWidth: number = 720;
|
||||
/** 当前激活的面板索引:0=英雄, 1=装备, 2=技能, 3=药品 */
|
||||
private currentPanelIndex: number = 0;
|
||||
/** 各面板基准 Y 坐标缓存 */
|
||||
private panelBaseYs: number[] = [];
|
||||
/** 是否已初始化面板布局 */
|
||||
private hasInitPanelLayout: boolean = false;
|
||||
/** 面板是否处于隐藏状态(被 close 按钮关闭) */
|
||||
private panelsHidden: boolean = false;
|
||||
/** 是否已缓存卡牌面板基准缩放 */
|
||||
private hasCachedCardsBaseScale: boolean = false;
|
||||
/** 卡牌面板基准缩放(从场景读取) */
|
||||
@@ -207,12 +220,14 @@ export class MissionCardComp extends CCComp {
|
||||
* 2. 缓存 3 个 CardComp 子控制器引用。
|
||||
* 3. 计算并设置槽位水平布局。
|
||||
* 4. 初始化卡牌面板缩放参数。
|
||||
* 5. 初始化面板轮播布局。
|
||||
*/
|
||||
onLoad() {
|
||||
this.bindEvents();
|
||||
this.cacheCardComps();
|
||||
this.layoutCardSlots();
|
||||
this.initCardsPanelPos();
|
||||
this.initPanelLayout();
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "onLoad init", {
|
||||
slots: this.cardComps.length,
|
||||
});
|
||||
@@ -504,16 +519,12 @@ export class MissionCardComp extends CCComp {
|
||||
* 首次进入准备阶段时自动抽取一次技能卡:
|
||||
* - 仅在任务开始时调用一次。
|
||||
* - 后续抽卡只能通过技能刷新按钮触发。
|
||||
* - 游戏刚开始时不激活技能面板(等召唤第一个英雄后才开放)。
|
||||
* - 面板轮播模式下技能面板始终 active,仅通过滑动切换可见性。
|
||||
*/
|
||||
private initSkillCardsOnce() {
|
||||
if (this.skillCardComps.length === 0) {
|
||||
this.cacheCardComps();
|
||||
}
|
||||
// 游戏刚开始时不显示技能卡牌面板(等召唤英雄后才开放)
|
||||
if (this.skill_card_node) {
|
||||
this.skill_card_node.active = false;
|
||||
}
|
||||
const cards = this.buildSkillDrawCards();
|
||||
this.dispatchCardsToSkillSlots(cards);
|
||||
this.playSkillCardEnterAnim();
|
||||
@@ -525,66 +536,31 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
|
||||
/**
|
||||
* 技能卡池显隐切换按钮回调:
|
||||
* - 仅切换 skill_card_node 的 active 状态,不执行抽卡。
|
||||
* - 后续刷新需通过技能刷新按钮(skill_refresh)触发。
|
||||
* - 打开前关闭其他面板,确保同时只显示一个。
|
||||
* 技能面板按钮回调:滑动到技能面板(index=2)。
|
||||
*/
|
||||
private onShowSkillsClick() {
|
||||
if (!this.skill_card_node || !this.skill_card_node.isValid) return;
|
||||
oops.audio.playEffect("music/button");
|
||||
const visible = !this.skill_card_node.active;
|
||||
if (visible) {
|
||||
this.closeAllPanels();
|
||||
}
|
||||
this.skill_card_node.active = visible;
|
||||
const activeChild = this.showSkills?.getChildByName("active");
|
||||
if (activeChild) {
|
||||
activeChild.active = visible;
|
||||
}
|
||||
this.slideToPanel(2);
|
||||
}
|
||||
|
||||
/** 关闭技能卡池按钮回调 */
|
||||
/** 关闭技能面板按钮回调:隐藏所有面板 */
|
||||
private onCloseSkillsClick() {
|
||||
if (!this.skill_card_node || !this.skill_card_node.isValid) return;
|
||||
oops.audio.playEffect("music/button");
|
||||
this.skill_card_node.active = false;
|
||||
const activeChild = this.showSkills?.getChildByName("active");
|
||||
if (activeChild) {
|
||||
activeChild.active = false;
|
||||
}
|
||||
this.hideAllPanels();
|
||||
}
|
||||
|
||||
// ======================== 商店面板 ========================
|
||||
|
||||
/**
|
||||
* 商店显隐切换按钮回调:
|
||||
* 切换 shopPanNode 的 active 状态。
|
||||
* 打开前关闭其他面板,确保同时只显示一个。
|
||||
* 商店面板按钮回调:滑动到药品商店面板(index=3)。
|
||||
*/
|
||||
private onShowShopClick() {
|
||||
if (!this.shopPanNode || !this.shopPanNode.isValid) return;
|
||||
oops.audio.playEffect("music/button");
|
||||
const visible = !this.shopPanNode.active;
|
||||
if (visible) {
|
||||
this.closeAllPanels();
|
||||
}
|
||||
this.shopPanNode.active = visible;
|
||||
const activeChild = this.showShop?.getChildByName("active");
|
||||
if (activeChild) {
|
||||
activeChild.active = visible;
|
||||
}
|
||||
this.slideToPanel(3);
|
||||
}
|
||||
|
||||
/** 关闭商店按钮回调 */
|
||||
/** 关闭商店面板按钮回调:隐藏所有面板 */
|
||||
private onCloseShopClick() {
|
||||
if (!this.shopPanNode || !this.shopPanNode.isValid) return;
|
||||
oops.audio.playEffect("music/button");
|
||||
this.shopPanNode.active = false;
|
||||
const activeChild = this.showShop?.getChildByName("active");
|
||||
if (activeChild) {
|
||||
activeChild.active = false;
|
||||
}
|
||||
this.hideAllPanels();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -872,129 +848,162 @@ export class MissionCardComp extends CCComp {
|
||||
this.playButtonResetAnim(this.cards_chou);
|
||||
}
|
||||
|
||||
// ======================== 面板轮播系统 ========================
|
||||
|
||||
/** 面板节点数组(顺序:英雄→装备→技能→药品) */
|
||||
private getPanelNodes(): Node[] {
|
||||
return [this.cards_node, this.equipsPanNode, this.skill_card_node, this.shopPanNode];
|
||||
}
|
||||
|
||||
/** 面板显示按钮数组(同上排序) */
|
||||
private getPanelShowButtons(): Node[] {
|
||||
return [this.showHeros, this.showEquips, this.showSkills, this.showShop];
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化面板轮播布局:
|
||||
* 1. 缓存各面板基准 Y 坐标。
|
||||
* 2. 推导面板滑动间距(取场景设计分辨率宽度或默认 720)。
|
||||
* 3. 将所有面板排列为水平队列,初始显示英雄面板(index=0)。
|
||||
*/
|
||||
private initPanelLayout() {
|
||||
if (this.hasInitPanelLayout) return;
|
||||
const panels = this.getPanelNodes();
|
||||
this.panelBaseYs = [];
|
||||
|
||||
// 以第一个有效面板的位置为基准,推导滑动间距
|
||||
let refX = 0;
|
||||
for (const p of panels) {
|
||||
if (p && p.isValid) {
|
||||
refX = p.getPosition().x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.panelSlideWidth = Math.abs(refX) * 2 || 720;
|
||||
if (this.panelSlideWidth < 400) this.panelSlideWidth = 720;
|
||||
|
||||
for (let i = 0; i < panels.length; i++) {
|
||||
const node = panels[i];
|
||||
if (!node || !node.isValid) {
|
||||
this.panelBaseYs.push(0);
|
||||
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.hasInitPanelLayout = true;
|
||||
this.updatePanelButtonStates();
|
||||
}
|
||||
|
||||
/**
|
||||
* 滑动到指定面板。
|
||||
* @param index 0=英雄, 1=装备, 2=技能, 3=药品
|
||||
*/
|
||||
private slideToPanel(index: number) {
|
||||
const panels = this.getPanelNodes();
|
||||
if (index < 0 || index >= panels.length) return;
|
||||
|
||||
// 如果面板被隐藏(close),先恢复显示
|
||||
if (this.panelsHidden) {
|
||||
this.panelsHidden = false;
|
||||
for (const p of panels) {
|
||||
if (p && p.isValid) p.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.currentPanelIndex = index;
|
||||
const duration = 0.25;
|
||||
|
||||
for (let i = 0; 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;
|
||||
Tween.stopAllByTarget(node);
|
||||
tween(node)
|
||||
.to(duration, { position: new Vec3(targetX, baseY, 0) }, { easing: 'quadOut' })
|
||||
.start();
|
||||
}
|
||||
|
||||
this.updatePanelButtonStates();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新面板 tab 按钮的 active 子节点状态。
|
||||
* 当前激活面板对应的按钮 active 高亮,其余关闭。
|
||||
*/
|
||||
private updatePanelButtonStates() {
|
||||
const buttons = this.getPanelShowButtons();
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
const btn = buttons[i];
|
||||
if (!btn || !btn.isValid) continue;
|
||||
const activeChild = btn.getChildByName("active");
|
||||
if (activeChild) {
|
||||
activeChild.active = (i === this.currentPanelIndex && !this.panelsHidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏所有面板(通过 close 按钮关闭)。
|
||||
*/
|
||||
private hideAllPanels() {
|
||||
oops.audio.playEffect("music/button");
|
||||
this.panelsHidden = true;
|
||||
const panels = this.getPanelNodes();
|
||||
for (const p of panels) {
|
||||
if (p && p.isValid) p.active = false;
|
||||
}
|
||||
this.updatePanelButtonStates();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新面板按钮的可交互状态。
|
||||
* 游戏刚开始时(未召唤英雄),只有英雄面板可打开;
|
||||
* 召唤第一个英雄后,其他面板才允许点击。
|
||||
* 通过 nock_node(阻挡层)控制按钮是否可点击,而非隐藏按钮本身。
|
||||
* 通过 nock_node(阻挡层)控制按钮是否可点击。
|
||||
*/
|
||||
private updatePanelButtonsInteractable() {
|
||||
const interactable = this.hasCalledHero;
|
||||
// nock_node 是覆盖在其他按钮上的阻挡层:
|
||||
// 未召唤英雄时激活(阻挡点击),召唤英雄后隐藏(允许点击)
|
||||
if (this.nock_node && this.nock_node.isValid) {
|
||||
this.nock_node.active = !interactable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭所有面板(英雄卡池、技能卡池、装备商店、商品商店)。
|
||||
* 用于打开新面板前确保同时只显示一个面板。
|
||||
* 同时关闭所有按钮的 active 子节点状态。
|
||||
*/
|
||||
private closeAllPanels() {
|
||||
// 关闭英雄卡池
|
||||
if (this.cards_node && this.cards_node.isValid) {
|
||||
this.cards_node.active = false;
|
||||
}
|
||||
if (this.showHeros && this.showHeros.isValid) {
|
||||
const activeChild = this.showHeros.getChildByName("active");
|
||||
if (activeChild) activeChild.active = false;
|
||||
}
|
||||
// 关闭技能卡池
|
||||
if (this.skill_card_node && this.skill_card_node.isValid) {
|
||||
this.skill_card_node.active = false;
|
||||
}
|
||||
if (this.showSkills && this.showSkills.isValid) {
|
||||
const activeChild = this.showSkills.getChildByName("active");
|
||||
if (activeChild) activeChild.active = false;
|
||||
}
|
||||
// 关闭装备商店
|
||||
if (this.equipsPanNode && this.equipsPanNode.isValid) {
|
||||
this.equipsPanNode.active = false;
|
||||
}
|
||||
if (this.showEquips && this.showEquips.isValid) {
|
||||
const activeChild = this.showEquips.getChildByName("active");
|
||||
if (activeChild) activeChild.active = false;
|
||||
}
|
||||
// 关闭商品商店
|
||||
if (this.shopPanNode && this.shopPanNode.isValid) {
|
||||
this.shopPanNode.active = false;
|
||||
}
|
||||
if (this.showShop && this.showShop.isValid) {
|
||||
const activeChild = this.showShop.getChildByName("active");
|
||||
if (activeChild) activeChild.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 英雄卡池显隐切换按钮回调:
|
||||
* - 切换 cards_node 的 active 状态。
|
||||
* - 显示时激活 showHeros 下的 "active" 子节点,隐藏时关闭该子节点。
|
||||
* - 打开前关闭其他面板,确保同时只显示一个。
|
||||
* 英雄面板按钮回调:滑动到英雄面板(index=0)。
|
||||
*/
|
||||
private onShowHerosClick() {
|
||||
if (!this.cards_node || !this.cards_node.isValid) return;
|
||||
oops.audio.playEffect("music/button");
|
||||
const visible = !this.cards_node.active;
|
||||
if (visible) {
|
||||
this.closeAllPanels();
|
||||
}
|
||||
this.cards_node.active = visible;
|
||||
const activeChild = this.showHeros?.getChildByName("active");
|
||||
if (activeChild) {
|
||||
activeChild.active = visible;
|
||||
}
|
||||
this.slideToPanel(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭英雄卡池按钮回调:
|
||||
* - 仅负责收起 cards_node,不切换展开态。
|
||||
* - 同步关闭 showHeros 下的 "active" 子节点,保持与 toggle 状态一致。
|
||||
* - 同步关闭已打开的英雄信息面板(HInfo),避免卡池收起后信息面板孤立悬浮。
|
||||
* 关闭英雄面板按钮回调:隐藏所有面板。
|
||||
* 同步关闭已打开的英雄信息面板(HInfo),避免卡池收起后信息面板孤立悬浮。
|
||||
*/
|
||||
private onCloseHerosClick() {
|
||||
if (!this.cards_node || !this.cards_node.isValid) return;
|
||||
oops.audio.playEffect("music/button");
|
||||
this.cards_node.active = false;
|
||||
const activeChild = this.showHeros?.getChildByName("active");
|
||||
if (activeChild) {
|
||||
activeChild.active = false;
|
||||
}
|
||||
// 关闭英雄卡池时同步关闭已打开的英雄信息面板
|
||||
this.hideAllPanels();
|
||||
oops.gui.remove(UIID.HInfo);
|
||||
}
|
||||
|
||||
// ======================== 装备商店面板 ========================
|
||||
|
||||
/**
|
||||
* 装备商店显隐切换按钮回调:
|
||||
* 切换 equipsPanNode 的 active 状态。
|
||||
* 打开前关闭其他面板,确保同时只显示一个。
|
||||
* 装备面板按钮回调:滑动到装备商店面板(index=1)。
|
||||
*/
|
||||
private onShowEquipsClick() {
|
||||
if (!this.equipsPanNode || !this.equipsPanNode.isValid) return;
|
||||
oops.audio.playEffect("music/button");
|
||||
const visible = !this.equipsPanNode.active;
|
||||
if (visible) {
|
||||
this.closeAllPanels();
|
||||
}
|
||||
this.equipsPanNode.active = visible;
|
||||
const activeChild = this.showEquips?.getChildByName("active");
|
||||
if (activeChild) {
|
||||
activeChild.active = visible;
|
||||
}
|
||||
this.slideToPanel(1);
|
||||
}
|
||||
|
||||
/** 关闭装备商店按钮回调 */
|
||||
/** 关闭装备面板按钮回调:隐藏所有面板 */
|
||||
private onCloseEquipsClick() {
|
||||
if (!this.equipsPanNode || !this.equipsPanNode.isValid) return;
|
||||
oops.audio.playEffect("music/button");
|
||||
this.equipsPanNode.active = false;
|
||||
const activeChild = this.showEquips?.getChildByName("active");
|
||||
if (activeChild) {
|
||||
activeChild.active = false;
|
||||
}
|
||||
this.hideAllPanels();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1143,22 +1152,20 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
/**
|
||||
* 进入准备阶段:
|
||||
* - 先将卡牌面板置为收起态,激活 showHeros 按钮可见。
|
||||
* - 再触发一次 onShowHerosClick(等同玩家点击 showHeros 按钮),
|
||||
* 让卡池以"按钮点击"的方式展开,保持与玩家手动点击一致的交互表现。
|
||||
* - 初始化面板轮播布局(如尚未初始化)。
|
||||
* - 滑动到英雄面板(index=0),作为默认展示。
|
||||
* - 激活 showHeros 按钮可见性。
|
||||
*/
|
||||
private enterPreparePhase() {
|
||||
if (!this.cards_node || !this.cards_node.isValid) return;
|
||||
this.initCardsPanelPos();
|
||||
// 先收起面板,确保后续 onShowHerosClick 能正确切换为展开
|
||||
Tween.stopAllByTarget(this.cards_node);
|
||||
this.cards_node.setScale(this.cardsShowScale);
|
||||
this.cards_node.active = false;
|
||||
// 确保面板布局已初始化
|
||||
if (!this.hasInitPanelLayout) {
|
||||
this.initPanelLayout();
|
||||
}
|
||||
// 显式激活「显示英雄卡池」按钮本身,让玩家可见可点
|
||||
if (this.showHeros && this.showHeros.isValid) {
|
||||
this.showHeros.active = true;
|
||||
const showActive = this.showHeros.getChildByName("active");
|
||||
if (showActive) showActive.active = false;
|
||||
}
|
||||
if (this.cards_chou && this.cards_chou.isValid) {
|
||||
const nobg = this.cards_chou.getChildByName("nobg");
|
||||
@@ -1166,8 +1173,8 @@ export class MissionCardComp extends CCComp {
|
||||
nobg.active = !this.canDrawCards();
|
||||
}
|
||||
}
|
||||
// 触发一次 showHeros 按钮点击效果,自动展开卡池
|
||||
this.onShowHerosClick();
|
||||
// 滑动到英雄面板
|
||||
this.slideToPanel(0);
|
||||
}
|
||||
|
||||
private enterBattlePhase() {
|
||||
|
||||
Reference in New Issue
Block a user