From 46cadb3bc227c7b6b78369b723b088035a58e899 Mon Sep 17 00:00:00 2001 From: pan Date: Tue, 9 Jun 2026 10:28:43 +0800 Subject: [PATCH] feat(map): add click preview for skill box on battlefield MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 为战场技能槽节点添加点击事件,点击时打开技能详情弹窗 2. 重构HInfoComp弹窗以支持技能卡预览模式 3. 新增技能卡专属的图标加载和界面渲染逻辑 --- assets/script/game/map/HInfoComp.ts | 134 ++++++++++++++++++------- assets/script/game/map/SkillBoxComp.ts | 15 ++- 2 files changed, 113 insertions(+), 36 deletions(-) diff --git a/assets/script/game/map/HInfoComp.ts b/assets/script/game/map/HInfoComp.ts index cc9218a4..4d5cb639 100644 --- a/assets/script/game/map/HInfoComp.ts +++ b/assets/script/game/map/HInfoComp.ts @@ -19,7 +19,7 @@ * - Hero —— 英雄 ECS 实体类(用于出售删除) * - UIID.IBox —— 英雄详情弹窗 ID */ -import { _decorator, Animation, AnimationClip, Button, Event, Label, Node, NodeEventType, Sprite, resources, CCInteger } from "cc"; +import { _decorator, Animation, AnimationClip, Button, Event, Label, Node, NodeEventType, Sprite, resources, CCInteger, SpriteFrame } 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 { HeroInfo } from "../common/config/heroSet"; @@ -35,7 +35,8 @@ import { mLogger } from "../common/Logger"; import { MissionHeroComp } from "./MissionHeroComp"; import { MoveComp } from "../hero/MoveComp"; import { FacSet, getLvColor } from "../common/config/GameSet"; -import { CKind } from "../common/config/CardSet"; +import { CKind, CardPoolList } from "../common/config/CardSet"; +import { SkillSet } from "../common/config/SkillSet"; import { CardBgComp } from "./CardBgComp"; import { MissionEconomy } from "./MissionEconomy"; @@ -95,6 +96,7 @@ export class HInfoComp extends CCComp { private previewUuid: number = 0; private previewLv: number = 1; private previewPoolLv: number = 1; + private isSkillCard: boolean = false; /** 英雄名字标签缓存引用 */ private nameLabel: Label | null = null; /** 技能信息标签缓存引用 */ @@ -121,9 +123,9 @@ export class HInfoComp extends CCComp { this.bindEvents(); } - onAdded(args: { eid?: number; heroUuid?: number; heroLv?: number; poolLv?: number }) { + onAdded(args: { eid?: number; heroUuid?: number; heroLv?: number; poolLv?: number; isSkillCard?: boolean }) { if (args?.heroUuid) { - this.bindPreviewData(args.heroUuid, args.heroLv ?? 1, args.poolLv ?? 1); + this.bindPreviewData(args.heroUuid, args.heroLv ?? 1, args.poolLv ?? 1, args.isSkillCard ?? false); return; } const eid = args?.eid ?? 0; @@ -149,8 +151,6 @@ export class HInfoComp extends CCComp { */ private refreshPreview() { const heroUuid = this.previewUuid; - const hero = HeroInfo[heroUuid]; - if (!hero) return; const heroLv = Math.max(1, this.previewLv); if (this.lv_node) { @@ -158,7 +158,7 @@ export class HInfoComp extends CCComp { this.lv_node.color = getLvColor(heroLv); } - const kindName = CKind[CKind.Hero]; + const kindName = this.isSkillCard ? CKind[CKind.Skill] : CKind[CKind.Hero]; if (this.BG_node) { this.BG_node.children.forEach(child => { child.active = (child.name === kindName); @@ -167,35 +167,66 @@ export class HInfoComp extends CCComp { }); } - if (heroUuid !== this.iconHeroUuid) { - this.iconHeroUuid = heroUuid; - this.iconVisualToken += 1; - this.updateHeroAnimation(this.icon_node, heroUuid, this.iconVisualToken); - } + if (this.isSkillCard) { + // ================= 技能卡预览 ================= + const config = CardPoolList.find(c => c.uuid === heroUuid); + if (!config) return; - if (this.nameLabel) { - this.nameLabel.string = hero.name ?? ""; - } + if (this.nameLabel) this.nameLabel.string = config.name ?? ""; + if (this.apLabel) { + this.apLabel.string = "-"; + if (this.apPlusLabel) this.apPlusLabel.node.active = false; + } + if (this.hpLabel) { + this.hpLabel.string = "-"; + if (this.hpPlusLabel) this.hpPlusLabel.node.active = false; + } + if (this.infoLabel) { + this.infoLabel.string = config.info ?? ""; + } + if (this.cdLabel) { + this.cdLabel.string = config.t_inv ? `${config.t_inv}s` : "0s"; + } - if (this.apLabel) { - const ap = Math.max(0, Math.floor((hero.ap ?? 0) * heroLv)); - this.apLabel.string = `${ap}`; - if (this.apPlusLabel) this.apPlusLabel.node.active = false; - } - if (this.hpLabel) { - const hp = Math.max(0, Math.floor((hero.hp ?? 0) * heroLv)); - this.hpLabel.string = `${hp}`; - if (this.hpPlusLabel) this.hpPlusLabel.node.active = false; - } + // 更新技能图标 + if (heroUuid !== this.iconHeroUuid) { + this.iconHeroUuid = heroUuid; + this.iconVisualToken += 1; + this.updateSkillAnimation(this.icon_node, config.skill ?? heroUuid, this.iconVisualToken); + } + } else { + // ================= 英雄卡预览 ================= + const hero = HeroInfo[heroUuid]; + if (!hero) return; - if (this.infoLabel) { - this.infoLabel.string = buildSkillDesc(hero); - } + if (heroUuid !== this.iconHeroUuid) { + this.iconHeroUuid = heroUuid; + this.iconVisualToken += 1; + this.updateHeroAnimation(this.icon_node, heroUuid, this.iconVisualToken); + } - if (this.cdLabel) { - const skillKeys = hero.skills ? Object.keys(hero.skills) : []; - const displaySkill = skillKeys.length > 1 ? hero.skills[skillKeys[1]] : (skillKeys.length > 0 ? hero.skills[skillKeys[0]] : null); - this.cdLabel.string = displaySkill?.cd ? `${displaySkill.cd.toFixed(1)}s` : "0s"; + if (this.nameLabel) this.nameLabel.string = hero.name ?? ""; + + if (this.apLabel) { + const ap = Math.max(0, Math.floor((hero.ap ?? 0) * heroLv)); + this.apLabel.string = `${ap}`; + if (this.apPlusLabel) this.apPlusLabel.node.active = false; + } + if (this.hpLabel) { + const hp = Math.max(0, Math.floor((hero.hp ?? 0) * heroLv)); + this.hpLabel.string = `${hp}`; + if (this.hpPlusLabel) this.hpPlusLabel.node.active = false; + } + + if (this.infoLabel) { + this.infoLabel.string = buildSkillDesc(hero); + } + + if (this.cdLabel) { + const skillKeys = hero.skills ? Object.keys(hero.skills) : []; + const displaySkill = skillKeys.length > 1 ? hero.skills[skillKeys[1]] : (skillKeys.length > 0 ? hero.skills[skillKeys[0]] : null); + this.cdLabel.string = displaySkill?.cd ? `${displaySkill.cd.toFixed(1)}s` : "0s"; + } } } @@ -235,15 +266,17 @@ export class HInfoComp extends CCComp { /** * 绑定静态预览数据(卡牌点击查看)。 - * 通过 HeroInfo 配置 × 等级计算初始值,不绑定 ECS 实体。 + * 通过 HeroInfo/CardPoolList 配置 × 等级计算初始值,不绑定 ECS 实体。 */ - bindPreviewData(heroUuid: number, heroLv: number, poolLv: number) { + bindPreviewData(heroUuid: number, heroLv: number, poolLv: number, isSkillCard: boolean = false) { this.isPreview = true; this.previewUuid = heroUuid; this.previewLv = heroLv; this.previewPoolLv = poolLv; + this.isSkillCard = isSkillCard; if (this.sell_node) this.sell_node.active = false; - if (this.close_node) this.close_node.active = false; + // 如果是技能卡预览模式(即点击了战场上的技能),允许关闭弹窗 + if (this.close_node) this.close_node.active = isSkillCard; this.cacheLabels(); this.refresh(); } @@ -427,6 +460,37 @@ export class HInfoComp extends CCComp { }); } + /** + * 为技能图标加载静态图片。 + * @param node 图标节点 + * @param skillUuid 技能 UUID + * @param token 视觉令牌 + */ + private updateSkillAnimation(node: Node, skillUuid: number, token: number) { + if (!node) return; + this.clearIconAnimation(node); // 停止之前的动画 + const sprite = node.getComponent(Sprite) || node.getComponentInChildren(Sprite); + if (!sprite) return; + + const skillData = SkillSet[skillUuid]; + if (!skillData || !skillData.icon) { + sprite.spriteFrame = null; + return; + } + + if (smc.uiconsAtlas) { + const frame = smc.uiconsAtlas.getSpriteFrame(skillData.icon); + if (frame && token === this.iconVisualToken) { + sprite.spriteFrame = frame; + } + } else { + const sf = oops.res.get("game/heros/cards/" + skillData.icon, SpriteFrame) as SpriteFrame; + if (sf && token === this.iconVisualToken) { + sprite.spriteFrame = sf; + } + } + } + /** 停止并清除图标节点上的动画 */ private clearIconAnimation(node: Node) { const anim = node?.getComponent(Animation); diff --git a/assets/script/game/map/SkillBoxComp.ts b/assets/script/game/map/SkillBoxComp.ts index bbadccc7..8f91d4f7 100644 --- a/assets/script/game/map/SkillBoxComp.ts +++ b/assets/script/game/map/SkillBoxComp.ts @@ -26,7 +26,7 @@ * - smc.mission —— 游戏运行状态 */ import { mLogger } from "../common/Logger"; -import { _decorator, Node, Prefab, Sprite, Label, Vec3, resources, SpriteAtlas, tween, v3, Tween } from "cc"; +import { _decorator, Node, Prefab, Sprite, Label, Vec3, resources, SpriteAtlas, tween, v3, Tween, NodeEventType } 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 { CardPoolList } from "../common/config/CardSet"; @@ -34,6 +34,7 @@ import { SkillSet, SkillOverrides } from "../common/config/SkillSet"; import { oops } from "db://oops-framework/core/Oops"; import { GameEvent } from "../common/config/GameEvent"; import { smc } from "../common/SingletonModuleComp"; +import { UIID } from "../common/config/GameUIConfig"; const { ccclass, property } = _decorator; /** @@ -97,6 +98,7 @@ export class SkillBoxComp extends CCComp { oops.message.on(GameEvent.MissionEnd, this.onMissionEnd, this); this.node.on(GameEvent.NewWave, this.onNewWave, this); oops.message.on(GameEvent.NewWave, this.onNewWaveGlobal, this); + this.node.on(NodeEventType.TOUCH_END, this.onNodeClicked, this); } /** 销毁时移除所有事件监听并通知槽位管理器回收 */ @@ -106,12 +108,23 @@ export class SkillBoxComp extends CCComp { oops.message.off(GameEvent.MissionEnd, this.onMissionEnd, this); if (this.node && this.node.isValid) { this.node.off(GameEvent.NewWave, this.onNewWave, this); + this.node.off(NodeEventType.TOUCH_END, this.onNodeClicked, this); } oops.message.off(GameEvent.NewWave, this.onNewWaveGlobal, this); // 通知 MissSkillsComp 回收该节点占用的槽位 oops.message.dispatchEvent(GameEvent.RemoveSkillBox, this.node); } + private onNodeClicked() { + if (!this.initialized) return; + // 点击时弹出 HInfoComp,传入卡牌 UUID 和等级以启用预览模式 + const config = CardPoolList.find(c => c.uuid === this.s_uuid || c.skill === this.s_uuid); + const cardUuid = config ? config.uuid : this.s_uuid; + + oops.gui.remove(UIID.HInfo); + oops.gui.open(UIID.HInfo, { heroUuid: cardUuid, heroLv: this.card_lv, poolLv: config?.pool_lv ?? 1, isSkillCard: true }); + } + /** * 初始化技能卡效果: * 1. 从 CardPoolList 查询技能卡的触发配置。