Files
pixelheros/assets/script/game/map/SSkillBoxComp.ts
pan db26865e03 feat(map,hero): 实现英雄技能整组合并展示功能
重构英雄技能展示逻辑,支持将同一触发槽位的多个技能/光环词条合并为一个技能展示:
1. 新增触发组整组分档渲染接口,合并同槽位多技能档位数据
2. 新增组名获取函数,统一使用槽位首个自定义名称作为展示名
3. 调整图鉴面板技能列表,每个触发槽位仅展示一个合并后的图标
4. 升级技能弹窗渲染逻辑,支持整组模式下的合并说明展示
5. 优化注释与代码结构,统一技能展示口径
2026-09-04 14:15:45 +08:00

269 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, getTriggerGroupName, getFieldGroupName,
} 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;
/** 技能等级标签 */
@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;
/** 整组模式(英雄图鉴):true 时该图标代表整个触发槽位(atking/dead/fstart/atked/call/field),
* 组内多技能/词条合并为一个名称与一套说明展示,弹窗按整组取档 */
private hero_group: boolean = false;
/** 卡牌等级 */
private rarity: 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.rarity,
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,
// 整组模式:弹窗按整个触发槽位合并渲染(组内多技能/词条统一说明)
hero_group: this.hero_uuid ? this.hero_group : 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;
* 整组模式下取组内首个 s_uuid/词条,仅用于图标与兜底名)
* @param s_lv 当前英雄等级下生效的技能等级(显示在等级标签)
* @param locked 当前等级未激活时显示锁定遮罩
* @param isField true 时图标取 FieldSkillSet(驻场光环词条)
* @param heroUuid 所属英雄 UUID(点击弹窗按英雄技能分档描述渲染)
* @param slot 技能所属槽位(普攻/大招传 undefined;触发/光环传对应类型,复活已纳入 dead)
* @param isGroup 整组模式:该图标代表整个触发槽位,组名取组内首个自定义名,
* 弹窗按整组合并渲染(组内多技能/词条统一说明)
*/
initHeroSkill(s_uuid: number, s_lv: number, locked: boolean, isField: boolean = false, heroUuid: number = 0, slot?: SkillTriggerType, isGroup: boolean = false) {
this.s_uuid = s_uuid;
this.hero_uuid = heroUuid;
this.hero_slot = slot;
this.hero_group = isGroup;
this.rarity = 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;
}
}
/**
* 解析技能名称:
* 1. 显式设置的卡牌名(最高优先级);
* 2. 英雄图鉴模式(hero_uuid>0):从 heroSet 英雄配置解析英雄语境名
* (普攻/大招 HSkillInfo.name、trigger 组名、光环 FieldEntry.name、复活走 dead 组名);
* 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);复活已纳入 dead 触发组。
* 指定槽位时仅在对应槽位查找(同一底座可同时是普攻与触发技能,需精确区分);
* 未命中返回空串,由调用方回退底座名。
*/
private resolveHeroSkillName(): string {
const hero = HeroInfo[this.hero_uuid];
if (!hero) return "";
// 整组模式:整个触发槽位一个技能,取组名(组内首个自定义名)
if (this.hero_group && this.hero_slot !== undefined) {
return this.hero_slot === SkillTriggerType.Field
? getFieldGroupName(hero)
: getTriggerGroupName(hero, this.hero_slot);
}
// 槽位指定:光环/触发组各自取值,未命中直接回退(复活已纳入 dead)
if (this.hero_slot !== undefined) {
if (this.hero_slot === SkillTriggerType.Field) {
return getFieldSkillName(hero, this.s_uuid);
}
// 复活走 dead 槽位
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);
}
// 复活已纳入 dead 触发组,上方 dead 循环已覆盖
return "";
}
/**
* 更新 UI:
* - 图标:从 uicons 图集获取
* - 名称标签:显示技能名称
* - 等级标签:显示技能等级(Lv.x 样式)
*/
updateUI() {
// 加载技能图标
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.rarity}`;
}
}
/** ECS 组件移除时销毁节点 */
reset() {
if (this.node && this.node.isValid) {
this.node.destroy();
}
}
}