feat(map): add click preview for skill box on battlefield
1. 为战场技能槽节点添加点击事件,点击时打开技能详情弹窗 2. 重构HInfoComp弹窗以支持技能卡预览模式 3. 新增技能卡专属的图标加载和界面渲染逻辑
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 查询技能卡的触发配置。
|
||||
|
||||
Reference in New Issue
Block a user