feat(map): add click preview for skill box on battlefield

1. 为战场技能槽节点添加点击事件,点击时打开技能详情弹窗
2. 重构HInfoComp弹窗以支持技能卡预览模式
3. 新增技能卡专属的图标加载和界面渲染逻辑
This commit is contained in:
pan
2026-06-09 10:28:43 +08:00
parent f62b5ecc2b
commit 46cadb3bc2
2 changed files with 113 additions and 36 deletions

View File

@@ -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);

View File

@@ -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 查询技能卡的触发配置。