1. 新增unlock_lv字段存储技能解锁所需英雄等级,与技能等级解耦 2. 优化锁定状态下的UI展示:优先显示等级解锁提示,回退使用技能等级 3. 重构技能图标生成逻辑,传递解锁等级参数 4. 更新天赋道具预制体,适配新的解锁提示UI展示逻辑
324 lines
15 KiB
TypeScript
324 lines
15 KiB
TypeScript
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, getHeroSkillIcon,
|
||
LvBonusAttrsEntry,
|
||
} 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;
|
||
/** 普攻 lv2 专属模式(英雄图鉴):true 时该图标代表普攻 lv2 强化档,
|
||
* 名称/图标取 lv1,弹窗只展示 lv2 档位说明(裁剪 lv1 档) */
|
||
private hero_normal_lv2: 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";
|
||
/** bonus 档位模式:该图标代表英雄额外属性加成的某一档(图鉴展示,无技能底座 uuid) */
|
||
private bonus_entry?: LvBonusAttrsEntry;
|
||
/** 未激活时的解锁所需英雄等级(用于"x级解锁"展示,与技能等级 rarity 解耦) */
|
||
private unlock_lv?: number;
|
||
|
||
/** 点击事件是否已注册(防止重复绑定) */
|
||
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");
|
||
// bonus 档位模式:专属弹窗参数(无技能底座 uuid,直接透传档位数据)
|
||
if (this.bonus_entry) {
|
||
oops.gui.open(UIID.SInfo, {
|
||
uuid: 0,
|
||
skill_lv: 1,
|
||
hero_uuid: this.hero_uuid || undefined,
|
||
bonus_entry: this.bonus_entry,
|
||
});
|
||
return;
|
||
}
|
||
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,
|
||
// 普攻 lv2 专属模式:弹窗只展示 lv2 档位说明(名称/图标取 lv1)
|
||
hero_normal_lv2: this.hero_uuid ? this.hero_normal_lv2 : undefined,
|
||
});
|
||
}
|
||
|
||
onLoad() {
|
||
this.registerClick();
|
||
}
|
||
|
||
onDestroy() {
|
||
super.onDestroy();
|
||
if (this.node && this.node.isValid) {
|
||
this.node.off(NodeEventType.TOUCH_END, this.onClick, this);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化 bonus 档位图标(英雄图鉴面板专用):
|
||
* 图标/名称直接取档位配置的 icon/name,无技能底座 uuid;点击弹窗走 bonus 专属渲染。
|
||
* @param heroUuid 所属英雄 UUID(激活判定用局外等级)
|
||
* @param entry bonus 档位配置(含 lv/attrs/name/icon/info)
|
||
* @param locked 当前英雄等级未达档位解锁等级时显示锁定遮罩
|
||
*/
|
||
initBonus(heroUuid: number, entry: LvBonusAttrsEntry, locked: boolean) {
|
||
this.hero_uuid = heroUuid;
|
||
this.bonus_entry = entry;
|
||
// 等级标签展示解锁所需英雄等级(Lv.n),便于玩家直观看到解锁条件
|
||
this.rarity = entry.lv;
|
||
this.card_icon = entry.icon;
|
||
this.skill_name = entry.name ?? "";
|
||
this.updateUI();
|
||
this.setLocked(locked);
|
||
}
|
||
|
||
/**
|
||
* 初始化英雄技能图标(英雄图鉴面板专用):
|
||
* 图标按 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 整组模式:该图标代表整个触发槽位,组名取组内首个自定义名,
|
||
* 弹窗按整组合并渲染(组内多技能/词条统一说明)
|
||
* @param normalLv2 普攻 lv2+ 强化档专属模式:名称/图标取 lv1,弹窗只展示 lv2+ 档位说明;
|
||
* 等级显示偏移一档(配置 Lv2 → 显示 Lv1,Lv3 → 显示 Lv2),强化档从 1 起计
|
||
* @param unlockLv 未激活时的解锁所需英雄等级(触发/光环/强化档未达解锁条件时展示"x级解锁",
|
||
* 与技能等级 s_lv 解耦;未传时回退按 rarity 显示)
|
||
*/
|
||
initHeroSkill(s_uuid: number, s_lv: number, locked: boolean, isField: boolean = false, heroUuid: number = 0, slot?: SkillTriggerType, isGroup: boolean = false, normalLv2: boolean = false, unlockLv?: number) {
|
||
this.s_uuid = s_uuid;
|
||
this.hero_uuid = heroUuid;
|
||
this.hero_slot = slot;
|
||
this.hero_group = isGroup;
|
||
this.hero_normal_lv2 = normalLv2;
|
||
this.unlock_lv = unlockLv;
|
||
// 普攻 lv2+ 强化档:等级显示偏移一档(配置 Lv2 显示 Lv1,Lv3 显示 Lv2),
|
||
// 强化档作为独立于普攻的技能存在,等级从 1 起计
|
||
this.rarity = normalLv2 ? Math.max(1, s_lv - 1) : s_lv;
|
||
this.hero_icon_source = isField ? "field" : "skill";
|
||
this.updateUI();
|
||
this.setLocked(locked);
|
||
}
|
||
|
||
/**
|
||
* 设置技能激活状态:
|
||
* - locked = true → 显示锁定遮罩,技能未激活(隐藏等级标签,名称标签显示"x级解锁")
|
||
* - locked = false → 隐藏锁定遮罩,技能已激活(恢复等级标签与技能名)
|
||
*/
|
||
setLocked(locked: boolean) {
|
||
if (this.locked) {
|
||
this.locked.active = locked;
|
||
}
|
||
if (this.lv_label) {
|
||
this.lv_label.node.active = !locked;
|
||
}
|
||
if (this.name_label) {
|
||
// 未激活时不展示技能名,改为展示解锁条件(优先取英雄等级阈值 unlock_lv,回退技能等级 rarity)
|
||
this.name_label.string = locked ? `${this.unlock_lv ?? this.rarity}级解锁` : this.resolveSkillName();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解析技能名称:
|
||
* 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_uuid > 0) {
|
||
// 英雄图鉴:英雄语境图标优先(overrides.icon 覆写),底座 icon 兜底
|
||
iconId = getHeroSkillIcon(HeroInfo[this.hero_uuid], this.s_uuid, this.hero_slot, this.hero_group);
|
||
}
|
||
if (!iconId && this.hero_icon_source === "field") {
|
||
// 光环词条图标取 FieldSkillSet
|
||
iconId = this.s_uuid ? FieldSkillSet[this.s_uuid]?.icon : undefined;
|
||
} else if (!iconId && this.hero_icon_source === "skill") {
|
||
// 大招/触发技能图标取 SkillSet
|
||
iconId = this.s_uuid ? SkillSet[this.s_uuid]?.icon : undefined;
|
||
} else if (!iconId && this.trigger_type === CardTriggerType.Interval) {
|
||
iconId = this.s_uuid ? SkillSet[this.s_uuid]?.icon : undefined;
|
||
} else if (!iconId && 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);
|
||
// 技能图标统一走 gui/ui7 图集缓存
|
||
if (smc.skillIconsAtlas && iconId) {
|
||
const frame = smc.skillIconsAtlas.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();
|
||
}
|
||
}
|
||
}
|