feat: 新增技能装备详情弹窗IBox,替换旧英雄信息弹窗逻辑
1. 重构IBoxComp组件,支持技能和装备详情展示 2. 为EquipBoxComp和SkillBoxComp添加长按弹窗功能 3. 更新UI配置,修正IBox弹窗层级 4. 新增富文本工具函数生成技能装备详情文案
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
* - smc.mission —— 游戏运行状态
|
||||
*/
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Node, Prefab, Sprite, Label, Vec3, resources, SpriteAtlas, tween, v3, Tween, NodeEventType } from "cc";
|
||||
import { _decorator, Node, Prefab, Sprite, Label, Vec3, instantiate, 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 { CardTriggerType } from "../common/config/CardSet";
|
||||
@@ -40,7 +40,7 @@ import { SkillSet, SkillOverrides, FieldSkillSet } from "../common/config/SkillS
|
||||
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";
|
||||
import { IBoxComp } from "./IBoxComp";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
@@ -70,6 +70,13 @@ export class EquipBoxComp extends CCComp {
|
||||
/** cd计时遮罩 */
|
||||
@property({ type: Node })
|
||||
private cd_mask: Node = null;
|
||||
|
||||
/** 装备详情弹窗预制体(长按图标时实例化展示) */
|
||||
@property(Prefab)
|
||||
private iboxPrefab: Prefab = null;
|
||||
|
||||
/** 当前已弹出的 IBox 实例(防止重复弹出) */
|
||||
private iboxNode: Node = null;
|
||||
// ======================== 技能配置 ========================
|
||||
|
||||
/** 技能 UUID */
|
||||
@@ -149,6 +156,7 @@ export class EquipBoxComp extends CCComp {
|
||||
}
|
||||
// 清理长按计时器,防止销毁后回调访问失效节点
|
||||
this.unschedule(this.showInfo);
|
||||
this.closeInfoBox();
|
||||
oops.message.off(GameEvent.NewWave, this.onNewWaveGlobal, this);
|
||||
// 通知 MissEquipComp 回收该节点占用的槽位
|
||||
oops.message.dispatchEvent(GameEvent.RemoveEquipBox, this.node);
|
||||
@@ -156,6 +164,8 @@ export class EquipBoxComp extends CCComp {
|
||||
|
||||
/** 长按时长(秒) */
|
||||
private static readonly LONG_PRESS_TIME: number = 0.5;
|
||||
/** IBox 相对图标的 Y 轴偏移(像素) */
|
||||
private static readonly IBOX_OFFSET_Y: number = 80;
|
||||
/** 信息框是否已弹出(区分长按完成与短按取消) */
|
||||
private info_shown: boolean = false;
|
||||
|
||||
@@ -171,19 +181,53 @@ export class EquipBoxComp extends CCComp {
|
||||
this.unschedule(this.showInfo);
|
||||
if (this.info_shown) {
|
||||
this.info_shown = false;
|
||||
// oops.gui 移除信息框
|
||||
oops.gui.remove(UIID.HInfo);
|
||||
this.closeInfoBox();
|
||||
}
|
||||
}
|
||||
|
||||
/** 长按到点:弹出信息框 */
|
||||
/** 长按到点:实例化 IBox 展示装备详情 */
|
||||
private showInfo() {
|
||||
if (!this.initialized) return;
|
||||
mLogger.log(this.debugMode, "EquipBoxComp", "showInfo", {
|
||||
initialized: this.initialized,
|
||||
iboxPrefab: !!this.iboxPrefab,
|
||||
s_uuid: this.s_uuid,
|
||||
});
|
||||
if (!this.initialized || !this.iboxPrefab) return;
|
||||
this.closeInfoBox();
|
||||
this.info_shown = true;
|
||||
oops.audio.playEffect("music/button");
|
||||
// 弹出 HInfoComp,从 EquipPoolList 查询配置
|
||||
oops.gui.remove(UIID.HInfo);
|
||||
oops.gui.open(UIID.HInfo, { heroUuid: this.s_uuid, heroLv: this.card_lv, poolLv: 1, isSkillCard: true, isEquipCard: true });
|
||||
|
||||
this.iboxNode = instantiate(this.iboxPrefab);
|
||||
// 挂到装备图标的父节点下,使用局部坐标系
|
||||
const parent = this.node.parent;
|
||||
parent?.addChild(this.iboxNode);
|
||||
// 弹窗宽度 720,X 居中为 0,Y 相对图标向上偏移
|
||||
this.iboxNode.setPosition(0, this.node.position.y + EquipBoxComp.IBOX_OFFSET_Y, this.node.position.z);
|
||||
mLogger.log(this.debugMode, "EquipBoxComp", "ibox instantiated", {
|
||||
iboxNodeValid: this.iboxNode?.isValid,
|
||||
iboxNodeActive: this.iboxNode?.active,
|
||||
iboxNodePos: this.iboxNode?.position,
|
||||
parentName: parent?.name,
|
||||
});
|
||||
const ibox = this.iboxNode.getComponent(IBoxComp);
|
||||
mLogger.log(this.debugMode, "EquipBoxComp", "ibox component", {
|
||||
hasIbox: !!ibox,
|
||||
massageNode: ibox?.["massage_node"] ? "bound" : "null",
|
||||
});
|
||||
ibox?.init({
|
||||
equipUuid: this.s_uuid,
|
||||
});
|
||||
mLogger.log(this.debugMode, "EquipBoxComp", "ibox init done", {
|
||||
iboxNodePos: this.iboxNode?.position,
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭并清理已弹出的 IBox */
|
||||
private closeInfoBox() {
|
||||
if (this.iboxNode && this.iboxNode.isValid) {
|
||||
this.iboxNode.destroy();
|
||||
}
|
||||
this.iboxNode = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,75 +1,68 @@
|
||||
/**
|
||||
* @file IBoxComp.ts
|
||||
* @description 英雄信息弹窗组件(IBox,UI 视图层)
|
||||
* @description 技能/装备详情弹窗组件(IBox,UI 视图层)
|
||||
*
|
||||
* 职责:
|
||||
* 1. 作为全局弹窗,展示某个英雄的 **详细技能信息**。
|
||||
* 2. 通过 onAdded(args) 接收英雄 UUID、等级和运行时技能数据。
|
||||
* 3. 自动计算技能行数并动态调整弹窗背景高度和名称位置。
|
||||
* 1. 作为详情弹窗,展示单个技能或装备的 **功能描述详情**。
|
||||
* 2. 通过 init(args) 接收技能 UUID、装备 UUID 或运行时技能数据。
|
||||
* 3. 使用单个 RichText 以 BBCode 展示名称(蓝色)、CD/持续时间(亮绿色)、结构化效果描述。
|
||||
* 4. 点击弹窗任意区域关闭自身。
|
||||
*
|
||||
* 关键设计:
|
||||
* - Line1~Line5 为预设的 5 行技能节点,按需显示/隐藏。
|
||||
* - 每行包含技能名、等级、CD、描述文本和类型图标(近战/远程/辅助)。
|
||||
* - 背景高度 = baseHeight + (行数 - 1) × extraLineHeight。
|
||||
* - 若传入 args.skills(运行时技能),优先使用;否则回退到英雄静态配置。
|
||||
* - 复用 HeroSkillDesc 的结构化模板渲染,数值实时从配置读取,杜绝漂移。
|
||||
* - 技能:展示名称、等级、CD、效果描述(SkillDescSet 模板)。
|
||||
* - 装备:展示名称、全部驻场光环词条(FieldDescSet 模板)。
|
||||
* - 不走 oops.gui,由调用方手动实例化并添加到场景,通过 init() 初始化。
|
||||
*
|
||||
* 依赖:
|
||||
* - HeroInfo(heroSet)—— 英雄静态配置
|
||||
* - SkillSet / IType(SkillSet)—— 技能静态配置
|
||||
* - UIID —— 在 oops.gui 系统中注册的弹窗 ID
|
||||
* - HeroSkillDesc(buildSingleSkillRich / buildEquipRich)—— 描述生成
|
||||
* - SkillSet / FieldSkillSet(SkillSet)—— 技能/光环静态配置
|
||||
* - EquipSet(findEquipByUuid)—— 装备静态配置
|
||||
*/
|
||||
import { _decorator, Label, Node, NodeEventType, UITransform } from "cc";
|
||||
import { _decorator, Label, NodeEventType, RichText } 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";
|
||||
import { IType, SkillSet } from "../common/config/SkillSet";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { SkillSet, SkillOverrides } from "../common/config/SkillSet";
|
||||
import { buildEquipRich, buildSingleSkillRich } from "../common/config/HeroSkillDesc";
|
||||
import { findEquipByUuid } from "../common/config/EquipSet";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/** IBox 初始化参数 */
|
||||
export interface IBoxInitArgs {
|
||||
/** SkillSet 技能 UUID(技能详情模式) */
|
||||
skillUuid?: number;
|
||||
/** 技能等级(默认 1) */
|
||||
skillLv?: number;
|
||||
/** 技能覆写参数(如药水卡自定义档位) */
|
||||
overrides?: SkillOverrides;
|
||||
/** EquipSet 装备 UUID(装备详情模式) */
|
||||
equipUuid?: number;
|
||||
/** 运行时技能数据(运行时技能详情模式,取第一个技能展示) */
|
||||
skills?: Record<number, { uuid: number; lv: number; cd: number; ccd: number }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* IBoxComp —— 英雄信息弹窗视图组件
|
||||
* IBoxComp —— 技能/装备详情弹窗视图组件
|
||||
*
|
||||
* 通过 oops.gui.open(UIID.IBox, { heroUuid, heroLv, skills? }) 打开。
|
||||
* 展示英雄技能列表,支持最多 5 行。
|
||||
* 由调用方通过 instantiate + addChild 手动实例化,然后调用 init(args) 初始化。
|
||||
* 示例:
|
||||
* const node = instantiate(this.iboxPrefab);
|
||||
* this.node.addChild(node);
|
||||
* node.getComponent(IBoxComp).init({ skillUuid: 6301, anchorPos: this.node.worldPosition });
|
||||
*/
|
||||
@ccclass('IBoxComp')
|
||||
@ecs.register('IBoxComp', false)
|
||||
export class IBoxComp extends CCComp {
|
||||
// ======================== 编辑器绑定节点(5 行技能) ========================
|
||||
@property(Node)
|
||||
Line1: Node = null!
|
||||
@property(Node)
|
||||
Line2: Node = null!
|
||||
@property(Node)
|
||||
Line3: Node = null!
|
||||
@property(Node)
|
||||
Line4: Node = null!
|
||||
@property(Node)
|
||||
Line5: Node = null!
|
||||
|
||||
// ======================== 布局常量 ========================
|
||||
/** 弹窗背景基础高度(只有 1 行技能时的高度) */
|
||||
private readonly baseHeight: number = 100;
|
||||
/** 每增加一行技能,背景增加的高度 */
|
||||
private readonly extraLineHeight: number = 50;
|
||||
/** 英雄名称标签的基准 Y 坐标 */
|
||||
private readonly nameBaseY: number = 50;
|
||||
/** 每增加一行技能,名称标签额外上移的 Y 偏移 */
|
||||
private readonly nameExtraLineOffsetY: number = 25;
|
||||
// ======================== 编辑器绑定节点 ========================
|
||||
@property(RichText)
|
||||
massage_node: RichText = null!
|
||||
|
||||
/**
|
||||
* ECS 实体挂载回调:接收外部传入的英雄参数并渲染。
|
||||
* @param args.heroUuid 英雄 UUID
|
||||
* @param args.heroLv 英雄当前等级
|
||||
* @param args.skills (可选)运行时技能数据,若无则使用英雄静态配置
|
||||
* 初始化并渲染弹窗内容。
|
||||
* @param args 见 IBoxInitArgs
|
||||
*/
|
||||
onAdded(args: {
|
||||
heroUuid?: number;
|
||||
heroLv?: number;
|
||||
skills?: Record<number, { uuid: number; lv: number; cd: number; ccd: number }>;
|
||||
}) {
|
||||
this.renderHeroInfo(args);
|
||||
public init(args: IBoxInitArgs) {
|
||||
this.render(args);
|
||||
}
|
||||
|
||||
/** 绑定点击关闭事件 */
|
||||
@@ -93,99 +86,73 @@ export class IBoxComp extends CCComp {
|
||||
// ======================== 渲染逻辑 ========================
|
||||
|
||||
/**
|
||||
* 主渲染方法:解析英雄数据并填充技能行。
|
||||
*
|
||||
* 流程:
|
||||
* 1. 从 args 中取英雄 UUID 和等级。
|
||||
* 2. 优先使用运行时技能数据(args.skills),否则回退英雄静态配置。
|
||||
* 3. 将每个技能映射为 { text, iType },过滤无效项。
|
||||
* 4. 调用 applyLineData 渲染到 Line1~Line5。
|
||||
* 主渲染方法:根据 args 模式分发到技能详情或装备详情。
|
||||
*/
|
||||
private renderHeroInfo(args: {
|
||||
heroUuid?: number;
|
||||
heroLv?: number;
|
||||
skills?: Record<number, { uuid: number; lv: number; cd: number; ccd: number }>;
|
||||
}) {
|
||||
const heroUuid = Math.floor(args?.heroUuid ?? 0);
|
||||
const heroLv = Math.max(1, Math.floor(args?.heroLv ?? 1));
|
||||
const hero = HeroInfo[heroUuid];
|
||||
if (!hero) {
|
||||
this.setHeroName("");
|
||||
this.applyLineData([{ text: "暂无技能信息" }]);
|
||||
private render(args: IBoxInitArgs) {
|
||||
// 装备详情模式
|
||||
if (args?.equipUuid !== undefined) {
|
||||
this.renderEquip(args.equipUuid);
|
||||
return;
|
||||
}
|
||||
this.setHeroName(hero.name);
|
||||
|
||||
// 运行时技能 vs 静态配置
|
||||
const runtimeSkills = args?.skills ? Object.values(args.skills) : [];
|
||||
const sourceSkills = runtimeSkills.length > 0 ? runtimeSkills : Object.values(hero.skills ?? {});
|
||||
|
||||
// 将每个技能转换为显示数据
|
||||
const lineData = sourceSkills.map(skill => {
|
||||
const skillId = Math.floor(skill?.uuid ?? 0);
|
||||
if (!skillId) return null;
|
||||
const config = SkillSet[skillId];
|
||||
if (!config) return null;
|
||||
// 运行时技能直接使用其等级;静态配置的技能等级需叠加英雄等级修正
|
||||
const runtimeLv = runtimeSkills.length > 0 ? Math.max(0, Math.floor(skill.lv ?? 0)) : Math.max(0, Math.floor((skill.lv ?? 1) + heroLv - 2));
|
||||
const cd = Number(skill?.cd ?? 0);
|
||||
return {
|
||||
text: `${config.name} Lv.${runtimeLv} CD:${cd}s ${config.info}`,
|
||||
iType: config.IType
|
||||
};
|
||||
}).filter(item => !!item) as Array<{ text: string; iType: IType }>;
|
||||
|
||||
this.applyLineData(lineData.length > 0 ? lineData : [{ text: "暂无技能信息" }]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将技能数据应用到 Line1~Line5:
|
||||
* 1. 按顺序填充文本和类型图标。
|
||||
* 2. 超出的行隐藏。
|
||||
* 3. 根据显示行数调整弹窗高度和名称位置。
|
||||
*
|
||||
* @param skillLines 技能行数据数组
|
||||
*/
|
||||
private applyLineData(skillLines: Array<{ text: string; iType?: IType }>) {
|
||||
const lines = [this.Line1, this.Line2, this.Line3, this.Line4, this.Line5];
|
||||
const showCount = Math.max(1, Math.min(lines.length, skillLines.length));
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (!line) continue;
|
||||
const active = i < showCount;
|
||||
line.active = active;
|
||||
if (!active) continue;
|
||||
const data = skillLines[i];
|
||||
const text = data?.text ?? "";
|
||||
// 查找技能文本节点并设置内容
|
||||
const noteNode = line.getChildByName("note");
|
||||
const label = noteNode?.getComponent(Label) || noteNode?.getComponentInChildren(Label) || line.getComponentInChildren(Label);
|
||||
if (label) label.string = text;
|
||||
// 更新类型图标
|
||||
this.updateLineTypeIcon(line, data?.iType);
|
||||
// 运行时技能详情模式(取第一个技能)
|
||||
if (args?.skills) {
|
||||
const first = Object.values(args.skills)[0];
|
||||
if (first) {
|
||||
this.renderSkill(first.uuid, first.lv);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 动态调整弹窗背景高度
|
||||
const targetHeight = this.baseHeight + Math.max(0, showCount - 1) * this.extraLineHeight;
|
||||
this.updateIBoxHeight(targetHeight);
|
||||
// 动态调整名称位置
|
||||
this.updateNamePosition(showCount);
|
||||
// 技能详情模式
|
||||
if (args?.skillUuid !== undefined) {
|
||||
this.renderSkill(args.skillUuid, args.skillLv ?? 1, args.overrides);
|
||||
return;
|
||||
}
|
||||
this.setHeroName("");
|
||||
this.setRichText("暂无信息");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置弹窗背景 Bg 节点的高度
|
||||
* @param height 目标高度
|
||||
* 渲染技能详情:名称、等级、CD、结构化效果描述。
|
||||
* @param skillUuid SkillSet 技能 UUID
|
||||
* @param lv 技能等级
|
||||
* @param overrides 可选覆写参数
|
||||
*/
|
||||
private updateIBoxHeight(height: number) {
|
||||
const bgNode = this.node.getChildByName("Bg");
|
||||
const bgTransform = bgNode?.getComponent(UITransform);
|
||||
if (bgTransform) {
|
||||
bgTransform.setContentSize(bgTransform.contentSize.width, height);
|
||||
private renderSkill(skillUuid: number, lv: number, overrides?: SkillOverrides) {
|
||||
const skill = SkillSet[skillUuid];
|
||||
if (!skill) {
|
||||
this.setHeroName("");
|
||||
this.setRichText("未知技能");
|
||||
return;
|
||||
}
|
||||
this.setHeroName(skill.name);
|
||||
this.setRichText(buildSingleSkillRich(skillUuid, overrides, lv));
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染装备详情:名称、全部驻场光环词条。
|
||||
* @param equipUuid EquipSet 装备 UUID
|
||||
*/
|
||||
private renderEquip(equipUuid: number) {
|
||||
const equip = findEquipByUuid(equipUuid);
|
||||
if (!equip) {
|
||||
this.setHeroName("");
|
||||
this.setRichText("未知装备");
|
||||
return;
|
||||
}
|
||||
this.setHeroName(equip.name);
|
||||
this.setRichText(buildEquipRich(equip.field ?? []));
|
||||
}
|
||||
|
||||
/** 将 BBCode 文本写入 RichText */
|
||||
private setRichText(text: string) {
|
||||
if (this.massage_node && this.massage_node.isValid) {
|
||||
this.massage_node.string = text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置英雄名称标签
|
||||
* @param name 英雄名称
|
||||
* 设置标题名称标签
|
||||
* @param name 技能/装备名称
|
||||
*/
|
||||
private setHeroName(name: string) {
|
||||
const bgNode = this.node.getChildByName("Bg");
|
||||
@@ -197,37 +164,10 @@ export class IBoxComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据显示行数调整名称节点的 Y 坐标。
|
||||
* 行数越多,名称越往上移,保持视觉居中。
|
||||
*
|
||||
* @param showCount 当前显示的技能行数
|
||||
*/
|
||||
private updateNamePosition(showCount: number) {
|
||||
const bgNode = this.node.getChildByName("Bg");
|
||||
const nameNode = bgNode?.getChildByName("name");
|
||||
if (!nameNode) return;
|
||||
const targetY = this.nameBaseY + Math.max(0, showCount - 1) * this.nameExtraLineOffsetY;
|
||||
const current = nameNode.position;
|
||||
nameNode.setPosition(current.x, targetY, current.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新技能行的类型图标(互斥显示)。
|
||||
* @param line 技能行节点
|
||||
* @param iType 技能类型(近战 / 远程 / 辅助)
|
||||
*/
|
||||
private updateLineTypeIcon(line: Node, iType?: IType) {
|
||||
const meleeNode = line.getChildByName("Melee");
|
||||
const remoteNode = line.getChildByName("remote");
|
||||
const supportNode = line.getChildByName("support");
|
||||
if (meleeNode) meleeNode.active = iType === IType.Melee;
|
||||
if (remoteNode) remoteNode.active = iType === IType.remote;
|
||||
if (supportNode) supportNode.active = iType === IType.support;
|
||||
}
|
||||
|
||||
/** 点击弹窗任意区域关闭自身 */
|
||||
private onTapClose() {
|
||||
oops.gui.removeByNode(this.node);
|
||||
if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
* - smc.mission —— 游戏运行状态
|
||||
*/
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Node, Sprite, Label, Vec3, tween, v3, Tween, NodeEventType } from "cc";
|
||||
import { _decorator, Node, Prefab, Sprite, Label, Vec3, instantiate, 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 { CardTriggerType, CardConfig } from "../common/config/CardSet";
|
||||
@@ -39,8 +39,8 @@ import { 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";
|
||||
import { getCardIconId, setSpriteFrame } from "../common/CardIconHelper";
|
||||
import { IBoxComp } from "./IBoxComp";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
@@ -70,6 +70,13 @@ export class SkillBoxComp extends CCComp {
|
||||
/** cd计时遮罩 */
|
||||
@property({ type: Node })
|
||||
private cd_mask: Node = null;
|
||||
|
||||
/** 技能详情弹窗预制体(长按图标时实例化展示) */
|
||||
@property(Prefab)
|
||||
private iboxPrefab: Prefab = null;
|
||||
|
||||
/** 当前已弹出的 IBox 实例(防止重复弹出) */
|
||||
private iboxNode: Node = null;
|
||||
// ======================== 技能配置 ========================
|
||||
|
||||
/** 技能 UUID */
|
||||
@@ -149,6 +156,7 @@ export class SkillBoxComp extends CCComp {
|
||||
}
|
||||
// 清理长按计时器,防止销毁后回调访问失效节点
|
||||
this.unschedule(this.showInfo);
|
||||
this.closeInfoBox();
|
||||
oops.message.off(GameEvent.NewWave, this.onNewWaveGlobal, this);
|
||||
// 通知 MissSkillsComp 回收该节点占用的槽位
|
||||
oops.message.dispatchEvent(GameEvent.RemoveSkillBox, this.node);
|
||||
@@ -156,6 +164,8 @@ export class SkillBoxComp extends CCComp {
|
||||
|
||||
/** 长按时长(秒) */
|
||||
private static readonly LONG_PRESS_TIME: number = 0.5;
|
||||
/** IBox 相对图标的 Y 轴偏移(像素) */
|
||||
private static readonly IBOX_OFFSET_Y: number = 80;
|
||||
/** 信息框是否已弹出(区分长按完成与短按取消) */
|
||||
private info_shown: boolean = false;
|
||||
|
||||
@@ -171,22 +181,58 @@ export class SkillBoxComp extends CCComp {
|
||||
this.unschedule(this.showInfo);
|
||||
if (this.info_shown) {
|
||||
this.info_shown = false;
|
||||
// oops.gui 移除信息框
|
||||
oops.gui.remove(UIID.HInfo);
|
||||
this.closeInfoBox();
|
||||
}
|
||||
}
|
||||
|
||||
/** 长按到点:弹出信息框 */
|
||||
/** 长按到点:实例化 IBox 展示技能详情 */
|
||||
private showInfo() {
|
||||
if (!this.initialized) return;
|
||||
mLogger.log(this.debugMode, "SkillBoxComp", "showInfo", {
|
||||
initialized: this.initialized,
|
||||
iboxPrefab: !!this.iboxPrefab,
|
||||
s_uuid: this.s_uuid,
|
||||
});
|
||||
if (!this.initialized || !this.iboxPrefab) return;
|
||||
this.closeInfoBox();
|
||||
this.info_shown = true;
|
||||
oops.audio.playEffect("music/button");
|
||||
// 弹出 HInfoComp,传入卡牌 UUID 和等级以启用预览模式
|
||||
const config = SkillCardList.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?.card_lv ?? 1, isSkillCard: true });
|
||||
const config = SkillCardList.find(c => c.uuid === this.s_uuid || c.skill === this.s_uuid);
|
||||
const skillUuid = config?.skill ?? this.s_uuid;
|
||||
|
||||
this.iboxNode = instantiate(this.iboxPrefab);
|
||||
// 挂到技能图标的父节点下,使用局部坐标系
|
||||
const parent = this.node.parent;
|
||||
parent?.addChild(this.iboxNode);
|
||||
// 弹窗宽度 720,X 居中为 0,Y 相对图标向上偏移
|
||||
this.iboxNode.setPosition(0, this.node.position.y + SkillBoxComp.IBOX_OFFSET_Y, this.node.position.z);
|
||||
mLogger.log(this.debugMode, "SkillBoxComp", "ibox instantiated", {
|
||||
iboxNodeValid: this.iboxNode?.isValid,
|
||||
iboxNodeActive: this.iboxNode?.active,
|
||||
iboxNodePos: this.iboxNode?.position,
|
||||
parentName: parent?.name,
|
||||
});
|
||||
const ibox = this.iboxNode.getComponent(IBoxComp);
|
||||
mLogger.log(this.debugMode, "SkillBoxComp", "ibox component", {
|
||||
hasIbox: !!ibox,
|
||||
massageNode: ibox?.["massage_node"] ? "bound" : "null",
|
||||
});
|
||||
ibox?.init({
|
||||
skillUuid,
|
||||
skillLv: this.card_lv,
|
||||
overrides: this.overrides,
|
||||
});
|
||||
mLogger.log(this.debugMode, "SkillBoxComp", "ibox init done", {
|
||||
iboxNodePos: this.iboxNode?.position,
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭并清理已弹出的 IBox */
|
||||
private closeInfoBox() {
|
||||
if (this.iboxNode && this.iboxNode.isValid) {
|
||||
this.iboxNode.destroy();
|
||||
}
|
||||
this.iboxNode = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user