Files
pixelheros/assets/script/game/map/SinfoComp.ts
pan b2f474d73b feat(UI): 添加技能信息弹窗功能
1. 新增UIID.SInfo枚举项与弹窗配置
2. 为技能图标组件添加点击弹窗逻辑
3. 实现完整的技能信息弹窗UI组件,支持展示技能详情、等级提升说明
4. 支持点击外部区域关闭弹窗,防止重复绑定点击事件
2026-08-27 11:21:30 +08:00

344 lines
12 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 SinfoComp.ts
* @description 技能信息弹窗组件(UI 视图层)
*
* 职责:
* 1. 展示技能小图标(SSkillBoxComp)点击后的详细信息弹窗。
* 2. 展示技能名称、当前等级、图标、主功能说明。
* 3. 展示 2 级 / 3 级 激活状态(已激活显示"已激活",未激活显示"x级激活")
* 及相对上一档的能力提升说明。
* 4. 点击弹窗外部区域关闭。
*
* 使用(oops.gui 模块):
* oops.gui.open(UIID.SInfo, {
* uuid: 8701, // 卡牌/技能 UUID
* card_lv: 1, // 当前等级
* card_icon: "xxx", // 图标ID(可选,缺省自动解析)
* skill_name: "战后恢复", // 名称(可选)
* field: [...], // 驻场词条(装备卡)
* trigger_type: 3, // 触发类型
* });
*
* 依赖:
* - oops.gui(UI 生命周期)—— open / removeByNode
* - SkillSet / FieldSkillSet(技能/光环静态配置)
* - SkillCardList / EquipPoolList(卡牌静态配置)
* - HeroSkillDesc(buildCardDescRich 富文本描述生成)
*/
import { _decorator, Node, Label, RichText, NodeEventType, Sprite, Color, input, Input, EventTouch } 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 "db://oops-framework/core/Oops";
import { CardTriggerType, CardConfig } from "../common/config/CardSet";
import { SkillSet, FieldSkillSet } from "../common/config/SkillSet";
import { SkillCardList } from "../common/config/SCardSet";
import { EquipPoolList } from "../common/config/EquipSet";
import { FieldEntry } from "../common/config/heroSet";
import { buildCardDescRich } from "../common/config/HeroSkillDesc";
import { smc } from "../common/SingletonModuleComp";
const { ccclass, property } = _decorator;
/** SInfo 弹窗初始化参数 */
export interface SInfoParams {
/** 卡牌/技能 UUID */
uuid: number;
/** 当前等级 */
card_lv: number;
/** 图标ID(可选,优先级最高) */
card_icon?: string;
/** 技能名称(可选) */
skill_name?: string;
/** 驻场词条(装备卡专用) */
field?: FieldEntry[];
/** 触发类型 */
trigger_type?: CardTriggerType;
}
/** 等级提升信息(用于 2 级 / 3 级面板) */
interface LvUpgradeInfo {
/** 是否已激活 */
unlocked: boolean;
/** 等级标签文本(如 "Lv.2") */
lvText: string;
/** 激活状态文本("已激活" 或 "2级激活") */
statusText: string;
/** 能力提升富文本描述 */
desc: string;
}
@ccclass('SinfoComp')
@ecs.register('SinfoComp', false)
export class SinfoComp extends CCComp {
// ======================== 编辑器绑定节点 ========================
/** 技能图标节点 */
@property({ type: Node, displayName: "技能图标" })
private icon_node: Node = null;
/** 技能名称标签 */
@property({ type: Label, displayName: "技能名称" })
private name_label: Label = null;
/** 当前等级标签 */
@property({ type: Label, displayName: "当前等级" })
private lv_label: Label = null;
/** 主功能说明(富文本) */
@property({ type: RichText, displayName: "主功能说明" })
private main_info: RichText = null;
/** 2级激活状态标签 */
@property({ type: Label, displayName: "2级激活状态" })
private lv2_status: Label = null;
/** 2级提升说明(富文本) */
@property({ type: RichText, displayName: "2级提升说明" })
private lv2_desc: RichText = null;
/** 3级激活状态标签 */
@property({ type: Label, displayName: "3级激活状态" })
private lv3_status: Label = null;
/** 3级提升说明(富文本) */
@property({ type: RichText, displayName: "3级提升说明" })
private lv3_desc: RichText = null;
// ======================== 运行时状态 ========================
/** 弹窗参数 */
private params: SInfoParams | null = null;
// ======================== 生命周期 ========================
onLoad() {
// 点击弹窗自身(BlockInputEvents 拦截)不关闭,仅点击外部遮罩关闭
this.node.on(NodeEventType.TOUCH_END, this.onSelfTouch, this);
}
onDestroy() {
super.onDestroy();
if (this.node && this.node.isValid) {
this.node.off(NodeEventType.TOUCH_END, this.onSelfTouch, this);
}
// 注销全局触摸监听
input.off(Input.EventType.TOUCH_END, this.onGlobalTouch, this);
}
/**
* oops.gui 添加到舞台回调(框架约定入口)
* @param params 见 SInfoParams
*/
onAdded(params: SInfoParams) {
this.params = params;
this.render();
// 延迟一帧注册全局触摸,避免本次点击立即触发关闭
this.scheduleOnce(() => {
input.on(Input.EventType.TOUCH_END, this.onGlobalTouch, this);
}, 0);
}
/** 点击弹窗自身:阻止冒泡,不关闭 */
private onSelfTouch(event: EventTouch) {
event.propagationStopped = true;
}
/** 全局触摸结束:点击弹窗外部关闭 */
private onGlobalTouch() {
// 若点击落在弹窗内部(被 BlockInputEvents 拦截)则不会走到这里
this.close();
}
/** 关闭弹窗 */
private close() {
oops.gui.removeByNode(this.node);
}
// ======================== 渲染逻辑 ========================
/** 主渲染入口 */
private render() {
if (!this.params) return;
const p = this.params;
// ---- 图标 ----
this.renderIcon(p);
// ---- 名称 ----
const name = this.resolveName(p);
this.setLabel(this.name_label, name);
// ---- 当前等级 ----
this.setLabel(this.lv_label, `Lv.${p.card_lv}`);
// ---- 主功能说明 ----
this.renderMainInfo(p);
// ---- 2 级 / 3 级 激活信息 ----
const lv2Info = this.buildLvUpgradeInfo(p, 2);
const lv3Info = this.buildLvUpgradeInfo(p, 3);
this.renderLvPanel(lv2Info, this.lv2_status, this.lv2_desc);
this.renderLvPanel(lv3Info, this.lv3_status, this.lv3_desc);
}
/** 渲染技能图标 */
private renderIcon(p: SInfoParams) {
if (!this.icon_node || !this.icon_node.isValid) return;
const iconId = p.card_icon ?? this.resolveIconId(p);
let sprite = this.icon_node.getComponent(Sprite) || this.icon_node.addComponent(Sprite);
if (smc.uiconsAtlas && iconId) {
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
sprite.spriteFrame = frame || null;
} else {
sprite.spriteFrame = null;
}
}
/** 解析图标ID */
private resolveIconId(p: SInfoParams): string | undefined {
if (p.card_icon) return p.card_icon;
// 装备卡:从 EquipPoolList 查 icon
const equip = EquipPoolList.find(c => c.uuid === p.uuid);
if (equip?.icon) return equip.icon;
// 技能卡:从 SkillCardList 查 icon
const skillCard = SkillCardList.find(c => c.uuid === p.uuid || c.skill === p.uuid);
if (skillCard?.icon) return skillCard.icon;
// 驻场词条:从 FieldSkillSet 取
if (p.trigger_type === CardTriggerType.Field && p.field?.length) {
const fieldUuid = p.field[0].uuid;
return FieldSkillSet[fieldUuid]?.icon;
}
// 技能底座:从 SkillSet 取
const skillUuid = skillCard?.skill ?? p.uuid;
return SkillSet[skillUuid]?.icon;
}
/** 解析技能名称 */
private resolveName(p: SInfoParams): string {
if (p.skill_name) return p.skill_name;
const equip = EquipPoolList.find(c => c.uuid === p.uuid);
if (equip?.name) return equip.name;
const skillCard = SkillCardList.find(c => c.uuid === p.uuid || c.skill === p.uuid);
if (skillCard?.name) return skillCard.name;
if (p.trigger_type === CardTriggerType.Field && p.field?.length) {
const fieldUuid = p.field[0].uuid;
return FieldSkillSet[fieldUuid]?.name ?? "";
}
const skillUuid = skillCard?.skill ?? p.uuid;
return SkillSet[skillUuid]?.name ?? "";
}
/** 渲染主功能说明 */
private renderMainInfo(p: SInfoParams) {
if (!this.main_info || !this.main_info.isValid) return;
// 构建 CardConfig 用于描述生成
const card = this.buildCardConfig(p);
if (card) {
this.main_info.string = buildCardDescRich(card);
} else {
// 兜底:直接展示配置中的 info 字段
const info = this.resolveInfoText(p);
this.main_info.string = info || "暂无描述";
}
}
/** 构建 CardConfig 供描述生成器使用 */
private buildCardConfig(p: SInfoParams): CardConfig | null {
// 优先从装备卡池查
const equip = EquipPoolList.find(c => c.uuid === p.uuid);
if (equip) return equip;
// 其次从技能卡池查
const skillCard = SkillCardList.find(c => c.uuid === p.uuid || c.skill === p.uuid);
if (skillCard) return skillCard;
// 手动构建
return {
uuid: p.uuid,
type: 2, // CardType.Skill
cost: 0,
weight: 0,
kind: 2, // CKind.Skill
card_lv: p.card_lv,
icon: p.card_icon,
name: p.skill_name,
field: p.field,
trigger_type: p.trigger_type,
} as CardConfig;
}
/** 解析主功能说明纯文本(兜底) */
private resolveInfoText(p: SInfoParams): string {
const equip = EquipPoolList.find(c => c.uuid === p.uuid);
if (equip?.info) return equip.info;
const skillCard = SkillCardList.find(c => c.uuid === p.uuid || c.skill === p.uuid);
if (skillCard?.info) return skillCard.info;
const skillUuid = skillCard?.skill ?? p.uuid;
return SkillSet[skillUuid]?.info ?? "";
}
/** 构建指定等级的提升信息 */
private buildLvUpgradeInfo(p: SInfoParams, targetLv: number): LvUpgradeInfo {
const unlocked = p.card_lv >= targetLv;
const lvText = `Lv.${targetLv}`;
const statusText = unlocked ? "已激活" : `${targetLv}级激活`;
// 能力提升描述:从对应等级的装备卡配置中提取
const desc = this.buildLvDesc(p, targetLv);
return { unlocked, lvText, statusText, desc };
}
/** 构建指定等级的提升描述 */
private buildLvDesc(p: SInfoParams, targetLv: number): string {
// 从装备卡池中查找同名更高档位配置
const currentEquip = EquipPoolList.find(c => c.uuid === p.uuid);
if (currentEquip) {
const higher = EquipPoolList.find(c =>
c.name === currentEquip.name && c.card_lv === targetLv
);
if (higher) {
return buildCardDescRich(higher);
}
return "";
}
// 技能卡:从技能卡池查找同技能更高档位
const currentCard = SkillCardList.find(c => c.uuid === p.uuid || c.skill === p.uuid);
if (currentCard) {
const skillUuid = currentCard.skill ?? currentCard.uuid;
const higher = SkillCardList.find(c =>
(c.skill ?? c.uuid) === skillUuid && c.card_lv === targetLv
);
if (higher) {
return buildCardDescRich(higher);
}
}
return "";
}
/** 渲染单个等级面板 */
private renderLvPanel(info: LvUpgradeInfo, statusLabel: Label | null, descRich: RichText | null) {
if (statusLabel && statusLabel.isValid) {
statusLabel.string = info.statusText;
// 已激活显示绿色,未激活显示灰色
statusLabel.color = info.unlocked
? new Color(0, 255, 10, 255)
: new Color(150, 150, 150, 255);
}
if (descRich && descRich.isValid) {
descRich.string = info.desc || (info.unlocked ? "" : "未解锁");
}
}
/** 设置 Label 文本 */
private setLabel(label: Label | null, text: string) {
if (label && label.isValid) {
label.string = text;
}
}
/** ECS 组件移除时销毁节点 */
reset() {
if (this.node && this.node.isValid) {
this.node.destroy();
}
}
}