Files
pixelheros/assets/script/game/map/SSkillBoxComp.ts
panFD e4530d6d1c feat(hero skill): add slot support for hero skill detail panel
1. 新增技能槽位参数,解决同底座技能(如普攻+触发技能)的分档定位问题
2. 新增2级/3级面板节点配置,自动隐藏无对应档位的面板
3. 重构英雄技能档位生成逻辑,支持按槽位精确查找技能配置
4. 优化技能弹窗渲染逻辑,传递槽位参数避免匹配错误分组
2026-08-30 14:44:27 +08:00

287 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.
import { _decorator, Node, Sprite, Label, 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 { oops } from "db://oops-framework/core/Oops";
import { CardTriggerType } from "../common/config/CardSet";
import { SkillSet, FieldSkillSet } from "../common/config/SkillSet";
import {
FieldEntry, HeroInfo, TriggerGrouped, SkillTriggerType,
getGroupedTriggerName, getFieldSkillName, getReviveSkillName,
} from "../common/config/heroSet";
import { UIID } from "../common/config/GameUIConfig";
import { smc } from "../common/SingletonModuleComp";
const { ccclass, property } = _decorator;
/**
* 技能小图标组件
*
* 由英雄图鉴面板(HerosListComp.addSkillIcon)实例化,
* 以图标 + 技能等级 + 锁定遮罩的形式展示英雄技能,点击弹出技能信息框。
*/
@ccclass('SSkillBoxComp')
@ecs.register('SSkillBoxComp', false)
export class SSkillBoxComp extends CCComp {
/** 技能图标节点 */
@property({ type: Node })
private icon_node: Node = null;
/** 技能图标节点:下面有 green blue purple red yellow 5个不同颜色节点*/
@property({ type: Node })
private bg_node: Node = null;
/** 技能等级标签 */
@property(Label)
private lv_label: Label = null;
/** 技能名称标签 */
@property(Label)
private name_label: Label = null;
/** 锁定遮罩节点(active 时表示技能未激活) */
@property({ type: Node })
private locked: Node = null;
/** 技能 UUID */
private s_uuid: number = 0;
/** 所属英雄 UUID(英雄图鉴模式,>0 时点击弹窗走英雄技能分档描述) */
private hero_uuid: number = 0;
/** 技能所属槽位(英雄图鉴模式):同一底座可同时为普攻与触发技能(如 5002 的 6004),
* 弹窗按槽位精确取档,避免自动定位命中错误分组 */
private hero_slot?: SkillTriggerType;
/** 卡牌等级 */
private card_lv: number = 1;
/** 卡牌自定义图标ID(优先级最高,未定义则按 trigger_type 自动取) */
private card_icon?: string;
/** 技能名称(装备卡取卡牌名,英雄图鉴取技能名) */
private skill_name: string = "";
/** 触发类型(默认即时,保持向后兼容) */
private trigger_type: CardTriggerType = CardTriggerType.Instant;
/** 驻场技能词条列表(uuid 指向 FieldSkillSet 底座,value 为生效数值) */
public field: FieldEntry[] = [];
/** 英雄图鉴模式下的图标来源:skill=SkillSet,field=FieldSkillSet;undefined 表示装备卡模式 */
private hero_icon_source?: "skill" | "field";
/** 点击事件是否已注册(防止重复绑定) */
private _clickRegistered: boolean = false;
/**
* 注册点击事件(幂等,可安全重复调用)
*/
private registerClick() {
if (this._clickRegistered) return;
this._clickRegistered = true;
this.node.on(NodeEventType.TOUCH_END, this.onClick, this);
}
/** 点击:弹出技能信息框 */
private onClick() {
oops.audio.playEffect("music/button");
oops.gui.open(UIID.SInfo, {
uuid: this.s_uuid,
skill_lv: this.card_lv,
card_icon: this.card_icon,
skill_name: this.resolveSkillName(),
field: this.field,
trigger_type: this.trigger_type,
// 英雄图鉴模式:携带所属英雄,弹窗按英雄技能分档描述渲染
hero_uuid: this.hero_uuid || undefined,
// 槽位:同一底座不同槽位视为不同技能(激活等级/档位各自独立)
hero_slot: this.hero_uuid ? this.hero_slot : undefined,
});
}
onLoad() {
this.registerClick();
}
onDestroy() {
super.onDestroy();
if (this.node && this.node.isValid) {
this.node.off(NodeEventType.TOUCH_END, this.onClick, this);
}
}
/**
* 初始化英雄技能图标(英雄图鉴面板专用):
* 图标按 s_uuid 直接取 SkillSet / FieldSkillSet 配置,不走 EquipPoolList。
* @param s_uuid 技能 UUID(大招/触发技能指向 SkillSet,光环词条指向 FieldSkillSet)
* @param s_lv 当前英雄等级下生效的技能等级(显示在等级标签)
* @param locked 当前等级未激活时显示锁定遮罩
* @param isField true 时图标取 FieldSkillSet(驻场光环词条)
* @param heroUuid 所属英雄 UUID(点击弹窗按英雄技能分档描述渲染)
* @param slot 技能所属槽位(普攻/大招传 undefined;触发/光环/复活传对应类型)
*/
initHeroSkill(s_uuid: number, s_lv: number, locked: boolean, isField: boolean = false, heroUuid: number = 0, slot?: SkillTriggerType) {
this.s_uuid = s_uuid;
this.hero_uuid = heroUuid;
this.hero_slot = slot;
this.card_lv = s_lv;
this.hero_icon_source = isField ? "field" : "skill";
this.updateUI();
this.setLocked(locked);
}
/**
* 设置技能激活状态:
* - locked = true → 显示锁定遮罩,技能未激活(隐藏等级标签)
* - locked = false → 隐藏锁定遮罩,技能已激活(恢复等级标签)
*/
setLocked(locked: boolean) {
if (this.locked) {
this.locked.active = locked;
}
if (this.lv_label) {
this.lv_label.node.active = !locked;
}
}
/**
* 根据技能等级切换 bg_node 下的颜色子节点显示。
* 等级与颜色对应关系:
* 技能卡档位:card_lv 1 (wave 1) → green,2 (wave 5) → blue,3 (wave 8) → purple
* 英雄技能等级:s_lv 1~5 → green / blue / purple / red / yellow
*/
private updateBgNode() {
if (!this.bg_node) return;
const lvToColor: Record<number, string> = {
1: "green",
2: "blue",
3: "purple",
4: "red",
5: "yellow",
};
const targetColor = lvToColor[this.card_lv];
this.bg_node.children.forEach(child => {
child.active = (child.name === targetColor);
});
}
/**
* 解析技能名称:
* 1. 显式设置的卡牌名(最高优先级);
* 2. 英雄图鉴模式(hero_uuid>0):从 heroSet 英雄配置解析英雄语境名
* (普攻/大招 HSkillInfo.name、trigger 组名、光环 FieldEntry.name、复活 LvReviveEntry.name);
* 3. 回退 SkillSet / FieldSkillSet 底座名。
*/
private resolveSkillName(): string {
if (this.skill_name) return this.skill_name;
if (this.hero_uuid > 0) {
const heroName = this.resolveHeroSkillName();
if (heroName) return heroName;
}
if (this.hero_icon_source === "field") {
return this.s_uuid ? (FieldSkillSet[this.s_uuid]?.name ?? "") : "";
}
if (this.hero_icon_source === "skill") {
return this.s_uuid ? (SkillSet[this.s_uuid]?.name ?? "") : "";
}
if (this.trigger_type === CardTriggerType.Interval) {
return this.s_uuid ? (SkillSet[this.s_uuid]?.name ?? "") : "";
}
if (this.trigger_type === CardTriggerType.Field) {
const fieldUuid = this.field?.[0]?.uuid;
return fieldUuid ? (FieldSkillSet[fieldUuid]?.name ?? "") : "";
}
return "";
}
/**
* 英雄图鉴模式:按技能 uuid 在英雄配置中定位,取英雄语境展示名。
* 覆盖普攻/大招(skills)、5 类触发技能、驻场光环(field)、复活(revive)。
* 指定槽位时仅在对应槽位查找(同一底座可同时是普攻与触发技能,需精确区分);
* 未命中返回空串,由调用方回退底座名。
*/
private resolveHeroSkillName(): string {
const hero = HeroInfo[this.hero_uuid];
if (!hero) return "";
// 槽位指定:光环/复活/触发组各自取值,未命中直接回退
if (this.hero_slot !== undefined) {
if (this.hero_slot === SkillTriggerType.Field) {
return getFieldSkillName(hero, this.s_uuid);
}
if (this.hero_slot === SkillTriggerType.Revive) {
return getReviveSkillName(hero, this.s_uuid);
}
const grouped = hero[this.hero_slot] as TriggerGrouped | undefined;
if (grouped?.[this.s_uuid]?.length) return getGroupedTriggerName(grouped, this.s_uuid);
return "";
}
// 普攻/大招
const hSkill = hero.skills?.[this.s_uuid];
if (hSkill?.name) return hSkill.name;
// 触发技能组(call/dead/fstart/atking/atked)
for (const type of [SkillTriggerType.Call, SkillTriggerType.Dead, SkillTriggerType.FStart, SkillTriggerType.Atking, SkillTriggerType.Atked]) {
const grouped = hero[type] as TriggerGrouped | undefined;
if (grouped?.[this.s_uuid]?.length) return getGroupedTriggerName(grouped, this.s_uuid);
}
// 驻场光环词条
if (hero.field?.some(e => e.uuids.some(u => u.uuid === this.s_uuid))) {
return getFieldSkillName(hero, this.s_uuid);
}
// 复活技能
if (hero.revive?.some(e => e.s_uuid === this.s_uuid)) {
return getReviveSkillName(hero, this.s_uuid);
}
return "";
}
/**
* 更新 UI:
* - 背景:根据技能等级切换对应颜色子节点显示
* - 图标:从 uicons 图集获取
* - 名称标签:显示技能名称
* - 等级标签:显示技能等级(Lv.x 样式)
*/
updateUI() {
// 按技能等级切换背景颜色节点(与 getLvColor 等级配色一致)
this.updateBgNode();
// 加载技能图标
if (this.icon_node) {
// 优先使用卡牌自定义 icon;未定义则按图鉴模式 / 装备卡 trigger_type 自动取
let iconId: string | undefined = this.card_icon;
if (!iconId) {
if (this.hero_icon_source === "field") {
// 英雄图鉴:驻场光环词条图标取 FieldSkillSet
iconId = this.s_uuid ? FieldSkillSet[this.s_uuid]?.icon : undefined;
} else if (this.hero_icon_source === "skill") {
// 英雄图鉴:大招/触发技能图标取 SkillSet
iconId = this.s_uuid ? SkillSet[this.s_uuid]?.icon : undefined;
} else if (this.trigger_type === CardTriggerType.Interval) {
iconId = this.s_uuid ? SkillSet[this.s_uuid]?.icon : undefined;
} else if (this.trigger_type === CardTriggerType.Field) {
const fieldUuid = this.field?.[0]?.uuid;
iconId = fieldUuid ? FieldSkillSet[fieldUuid]?.icon : undefined;
}
}
if (this.icon_node && this.icon_node.isValid) {
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;
}
}
}
// 显示技能名称
if (this.name_label) {
this.name_label.string = this.resolveSkillName();
}
// 显示技能等级(Lv.x 样式)
if (this.lv_label) {
this.lv_label.string = `Lv.${this.card_lv}`;
}
}
/** ECS 组件移除时销毁节点 */
reset() {
if (this.node && this.node.isValid) {
this.node.destroy();
}
}
}