Files
pixelheros/assets/script/game/map/MissionHomeComp.ts
panFD 214231d64f refactor(map): 调整英雄列表布局参数与添加页签红点逻辑
1. 新增英雄列表卡片宽度、间距、边距可配置属性,修正列表内容宽度计算
2. 为任务主页添加英雄成长和天赋更新事件监听,实现页签红点自动刷新
3. 补充全英雄可升级状态检测与红点显示逻辑
2026-09-08 22:20:22 +08:00

322 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file MissionHomeComp.ts
* @description 任务主页组件(UI 视图层)
*
* 职责:
* 1. 作为玩家进入游戏后第一个看到的界面(主菜单/大厅)。
* 2. 提供"开始任务"按钮,触发 GameEvent.MissionStart 进入战斗。
* 3. 提供"排行榜"按钮,打开 RanksComp 弹窗。
* 4. 监听 MissionEnd 事件,任务结束后自动切回主页。
*
* 关键设计:
* - start_mission() 分发 MissionStart 事件并隐藏自身节点。
* - mission_end() 响应后重新显示主页。
* - isWxClient() 检测是否运行在微信小游戏环境。
*
* 依赖:
* - GameEvent.MissionStart / MissionEnd —— 游戏生命周期事件
* - UIID.Mission —— 战斗界面 ID
*/
import { _decorator, instantiate, Label, Prefab, resources, Sprite, SpriteAtlas, Tween, tween, UITransform, Vec3, Node } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
import { smc } from "../common/SingletonModuleComp";
import { GameEvent } from "../common/config/GameEvent";
import { Timer } from "db://oops-framework/core/common/timer/Timer";
import { mLogger } from "../common/Logger";
import { UIID } from "../common/config/GameUIConfig";
import { HeroList } from "../common/config/heroSet";
import { canHeroUpgrade } from "../common/config/heroUpSet";
const { ccclass, property } = _decorator;
/**
* MissionHomeComp —— 任务主页视图组件
*
* 游戏大厅界面,提供开始战斗和查看排行的入口。
*/
@ccclass('MissionHomeComp')
@ecs.register('MissionHome', false)
export class MissionHomeComp extends CCComp {
/** 调试日志开关 */
debugMode: boolean = false;
/** 开始游戏按钮节点(预留) */
@property(Node)
start_btn = null!
/** 主页按钮节点(预留) */
@property(Node)
home_btn = null!
/** 英雄图鉴按钮节点(预留) */
@property(Node)
hero_btn = null!
/** 排行榜按钮节点 */
@property(Node)
rank_btn = null!
/** 天赋按钮节点 */
@property(Node)
tals_btn = null!
/** 顶部栏节点(仅主页显示) */
@property(Node)
top_bar_node = null!
@property(Node)
heros_page = null!
@property(Node)
talents_page = null!
@property(Node)
ranks_page = null!
// ======================== 新手指引(开始按钮缩放脉动 + 气泡) ========================
/** 脉动放大倍率 */
private static readonly PULSE_SCALE: number = 1.15;
/** 气泡文案 */
private static readonly TIP_TEXT: string = "点击开始游戏";
/** 气泡弹出/停留/收起节奏(秒) */
private static readonly TIP_STAY: number = 3.4;
/** 指引进行中标记(避免重复启动) */
private guiding: boolean = false;
/** 气泡原文缓存(指引结束后还原) */
private guideTipOriginText: string = "";
/** 注册任务结束事件 */
protected onLoad(): void {
this.on(GameEvent.MissionEnd, this.mission_end, this)
// oops.message 事件总线:成长/天赋数据变化时刷新页签红点
oops.message.on(GameEvent.HERO_GROWTH_UPDATE, this.refreshRedDots, this)
oops.message.on(GameEvent.TALENT_UPDATE, this.refreshRedDots, this)
}
protected onDestroy(): void {
oops.message.off(GameEvent.HERO_GROWTH_UPDATE, this.refreshRedDots, this)
oops.message.off(GameEvent.TALENT_UPDATE, this.refreshRedDots, this)
}
/** 启动时显示主页 */
start() {
this.home_active()
// 新玩家:start_btn 缩放脉动 + smalltip 气泡引导点击开始(仅 1 次)
if (!smc.data.tip_done.start) {
this.playStartGuide();
}
}
onEnable() {
// 节点重新激活时补刷一次红点(事件分发期间本节点可能处于隐藏态)
this.refreshRedDots()
}
update(dt: number) {
}
/**
* 开始任务:
* 1. 打印日志。
* 2. 分发 MissionStart 事件,驱动 MissionComp / MissionCardComp 初始化战斗。
* 3. 隐藏主页节点。
* 4. 隐藏 mapLayer 下的 main 节点。
*/
start_mission() {
mLogger.log(this.debugMode, 'MissionHomeComp', "start_mission")
// 点击开始后完成指引:停止缩放脉动/气泡并记录完成标记
this.stopStartGuide();
smc.data.tip_done.start = true;
oops.gui.open(UIID.Mission)
this.node.active = false;
// 隐藏 mapLayer 下的 main 节点
}
// ======================== 新手指引实现 ========================
/** 启动指引:button 子节点循环缩放脉动 + smalltip 气泡弹出 */
private playStartGuide() {
const btn = this.start_btn;
if (!btn || !btn.isValid) return;
if (this.guiding) return;
this.guiding = true;
// start_btn 下名为 "Button" 的子节点循环缩放脉动(scale 往复,不触碰 position,避免与布局冲突)
const pulseNode = this.getPulseNode();
if (pulseNode) {
Tween.stopAllByTarget(pulseNode);
pulseNode.setScale(1, 1, 1);
tween(pulseNode)
.repeatForever(
tween(pulseNode)
.to(0.3, { scale: new Vec3(MissionHomeComp.PULSE_SCALE, MissionHomeComp.PULSE_SCALE, 1) }, { easing: 'sineInOut' })
.to(0.3, { scale: new Vec3(1, 1, 1) }, { easing: 'sineInOut' })
)
.start();
}
// smalltip 气泡:缓存原文后覆盖文案,弹出后保持显示直至点击
const tip = btn.getChildByName("smalltip");
if (tip) {
const label = tip.getComponentInChildren(Label);
if (label) {
this.guideTipOriginText = label.string;
label.string = MissionHomeComp.TIP_TEXT;
}
tip.active = true;
Tween.stopAllByTarget(tip);
tip.setScale(0, 0, 1);
tween(tip)
.to(0.15, { scale: new Vec3(1.1, 1.1, 1) }, { easing: 'quadOut' })
.to(0.05, { scale: new Vec3(1, 1, 1) })
.start();
}
}
/** 停止指引:脉动复位、气泡收起并还原原文 */
private stopStartGuide() {
if (!this.guiding) return;
this.guiding = false;
const btn = this.start_btn;
if (btn && btn.isValid) {
const pulseNode = this.getPulseNode();
if (pulseNode && pulseNode.isValid) {
Tween.stopAllByTarget(pulseNode);
pulseNode.setScale(1, 1, 1);
}
const tip = btn.getChildByName("smalltip");
if (tip && tip.isValid) {
Tween.stopAllByTarget(tip);
const label = tip.getComponentInChildren(Label);
if (label) label.string = this.guideTipOriginText;
tip.active = false;
}
}
}
/**
* 获取 start_btn 下名为 "Button" 的子节点(即要播放缩放脉动的节点)。
* @returns Button 子节点;未找到返回 null
*/
private getPulseNode(): Node | null {
const btn = this.start_btn;
if (!btn || !btn.isValid) return null;
const node = btn.getChildByName("Button");
return node && node.isValid ? node : null;
}
private showPage(page: Node) {
// 进入分页时隐藏顶部栏,仅主页显示
this.top_bar_node.active = false
this.heros_page.active = (page === this.heros_page)
this.talents_page.active = (page === this.talents_page)
this.ranks_page.active = (page === this.ranks_page)
this.setBarActive(this.hero_btn, page === this.heros_page)
this.setBarActive(this.tals_btn, page === this.talents_page)
this.setBarActive(this.rank_btn, page === this.ranks_page)
this.setBarActive(this.home_btn, false)
}
private hideAllPages() {
// 回到主页时恢复顶部栏显示
this.top_bar_node.active = true
this.heros_page.active = false
this.talents_page.active = false
this.ranks_page.active = false
this.setBarActive(this.home_btn, true)
this.setBarActive(this.hero_btn, false)
this.setBarActive(this.tals_btn, false)
this.setBarActive(this.rank_btn, false)
}
private setBarActive(btn: Node, active: boolean) {
if (!btn) return
const child = btn.getChildByName("active")
if (child) child.active = active
}
/**
* 刷新页签按钮红点:
* - hero_btn/red_dot:任意英雄碎片满足升级条件(canHeroUpgrade)时显示
* - tals_btn/red_dot:存在未分配的天赋点(talent_points > 0)时显示
*/
private refreshRedDots() {
this.setRedDot(this.hero_btn, this.hasUpgradableHero())
this.setRedDot(this.tals_btn, smc.getTalentPoints() > 0)
}
/** 遍历全量英雄,判定是否存在可升级者(未满级且碎片充足) */
private hasUpgradableHero(): boolean {
for (const uuid of HeroList) {
const growth = smc.getHeroGrowth(uuid);
if (canHeroUpgrade(growth.lv, growth.fragments)) return true;
}
return false;
}
/** 切换按钮下 red_dot 子节点的显示 */
private setRedDot(btn: Node, active: boolean) {
if (!btn || !btn.isValid) return
const dot = btn.getChildByName("red_dot")
if (dot) dot.active = active
}
openRanks() {
this.showPage(this.ranks_page)
}
openHero() {
this.showPage(this.heros_page)
}
openTalents() {
this.showPage(this.talents_page)
}
/** 任务结束回调:重新显示主页 */
mission_end() {
mLogger.log(this.debugMode, 'MissionHomeComp', "[MissionHomeComp]=>mission_end")
this.home_active()
}
/** 激活主页显示:刷新数据并显示节点 */
home_active() {
// 任务结束回到主页:确保指引动画与气泡无残留
this.stopStartGuide();
this.uodate_data()
this.hideAllPages()
this.refreshRedDots()
this.node.active = true
// 播放主页背景音乐,恢复默认音量
oops.audio.volumeMusic = 1.0;
oops.audio.playerMusicLoop("music/home")
}
/** 更新主页显示数据(预留) */
uodate_data() {
}
/**
* 判断是否运行在微信小游戏环境。
* @returns true = 微信小游戏环境
*/
isWxClient() {
return typeof wx !== 'undefined' && typeof (wx as any).getSystemInfoSync === 'function';
}
/** ECS 组件移除时销毁节点 */
reset() {
// 销毁前清理指引 tween,防止回调残留
this.stopStartGuide();
this.node.destroy();
}
}