1. 新增按权重抽取装备卡的工具函数,支持去重后补齐 2. 重构任务界面卡牌组件: - 拆分英雄/装备/商店面板节点绑定,调整属性顺序 - 新增装备和商店刷新按钮的交互逻辑 - 替换原固定加载装备/商品列表为按权重随机抽取3个 3. 重构技能卡牌组件,改为技能商店卡项实现,简化原有逻辑 4. 修复prefab中的节点属性偏移和引用关系
263 lines
8.7 KiB
TypeScript
263 lines
8.7 KiB
TypeScript
/**
|
||
* @file SCardComp.ts
|
||
* @description 技能商店单个技能卡项组件(UI 视图层 + 购买逻辑)
|
||
*
|
||
* 职责:
|
||
* 1. 渲染单个技能卡的显示信息(名称、等级、描述词条、图标)。
|
||
* 2. 处理购买点击 → 扣费 → 派发 UseSkillCard 事件触发技能生效。
|
||
*
|
||
* 技能使用机制:
|
||
* 购买后派发 GameEvent.UseSkillCard,
|
||
* 由 MissSkillsComp 接收并创建 SBox 实体 / SkillBoxComp 执行技能逻辑。
|
||
*/
|
||
import { mLogger } from "../common/Logger";
|
||
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 { CardConfig } from "../common/config/CardSet";
|
||
import { SkillCardList } from "../common/config/SCardSet";
|
||
import { FieldSkillSet, SkillSet, SkillConfig } from "../common/config/SkillSet";
|
||
import { oops } from "db://oops-framework/core/Oops";
|
||
import { GameEvent } from "../common/config/GameEvent";
|
||
import { smc } from "../common/SingletonModuleComp";
|
||
import { MissionEconomy } from "./MissionEconomy";
|
||
|
||
const { ccclass, property } = _decorator;
|
||
|
||
/**
|
||
* SCardComp —— 技能商店单个技能卡项
|
||
*
|
||
* 由 MissionCardComp.dispatchCardsToSkillSlots() 实例化。
|
||
* 以图标 + 名称 + 描述词条 + 购买按钮的形式呈现。
|
||
*/
|
||
@ccclass('SCardComp')
|
||
@ecs.register('SCardComp', false)
|
||
export class SCardComp extends CCComp {
|
||
private debugMode: boolean = false;
|
||
|
||
// ======================== 编辑器绑定节点 ========================
|
||
|
||
/** 技能图标节点 */
|
||
@property({ type: Node })
|
||
private icon_node: Node = null;
|
||
|
||
/** 背景节点 */
|
||
@property({ type: Node })
|
||
private bg_node: Node = null;
|
||
|
||
/** 技能名称 */
|
||
@property(Label)
|
||
private name_label: Label = null;
|
||
|
||
/** 描述词条 1 */
|
||
@property(Label)
|
||
private fied1: Label = null;
|
||
/** 描述词条 2 */
|
||
@property(Label)
|
||
private fied2: Label = null;
|
||
/** 描述词条 3 */
|
||
@property(Label)
|
||
private fied3: Label = null;
|
||
|
||
/** 等级标签 */
|
||
@property(Label)
|
||
private level_label: Label = null;
|
||
|
||
/** 购买按钮节点 */
|
||
@property({ type: Node })
|
||
private buy_node: Node = null;
|
||
|
||
// ======================== 运行时状态 ========================
|
||
|
||
/** 当前技能的卡牌配置 */
|
||
private cardData: CardConfig | null = null;
|
||
/** 是否已购买 */
|
||
private isPurchased: boolean = false;
|
||
|
||
// ======================== 生命周期 ========================
|
||
|
||
onLoad() {
|
||
this.buy_node?.on(NodeEventType.TOUCH_END, this.onBuyClick, this);
|
||
}
|
||
|
||
onDestroy() {
|
||
super.onDestroy();
|
||
if (this.buy_node && this.buy_node.isValid) {
|
||
this.buy_node.off(NodeEventType.TOUCH_END, this.onBuyClick, this);
|
||
}
|
||
}
|
||
|
||
// ======================== 数据初始化 ========================
|
||
|
||
/**
|
||
* 初始化技能卡数据并渲染 UI
|
||
*
|
||
* @param data 技能卡配置,传 null 时清空显示
|
||
*/
|
||
applyCardData(data: CardConfig | null): void {
|
||
this.cardData = data;
|
||
this.isPurchased = false;
|
||
if (data) {
|
||
this.node.active = true;
|
||
this.updateUI();
|
||
} else {
|
||
this.applyEmptyUI();
|
||
}
|
||
}
|
||
|
||
/** 清空 UI 显示 */
|
||
private applyEmptyUI() {
|
||
if (this.name_label) this.name_label.string = "";
|
||
if (this.level_label) this.level_label.string = "";
|
||
const fieldLabels = [this.fied1, this.fied2, this.fied3];
|
||
for (const label of fieldLabels) {
|
||
if (label) {
|
||
label.string = "";
|
||
label.node.active = false;
|
||
}
|
||
}
|
||
if (this.icon_node) {
|
||
const sprite = this.icon_node.getComponent(Sprite) || this.icon_node.getComponentInChildren(Sprite);
|
||
if (sprite) sprite.spriteFrame = null;
|
||
}
|
||
if (this.buy_node) {
|
||
this.buy_node.active = false;
|
||
}
|
||
this.node.active = false;
|
||
}
|
||
|
||
// ======================== UI 渲染 ========================
|
||
|
||
updateUI() {
|
||
if (!this.cardData) return;
|
||
|
||
const skillCard = SkillCardList.find(c => c.uuid === this.cardData!.uuid);
|
||
const skill = this.cardData.skill ? SkillSet[this.cardData.skill] : null;
|
||
|
||
// 名称
|
||
if (this.name_label) {
|
||
this.name_label.string = skillCard?.name || skill?.name || this.cardData.name || "";
|
||
}
|
||
|
||
// 等级标签(★ 表示卡牌等级)
|
||
if (this.level_label) {
|
||
const lv = this.cardData.card_lv ?? 1;
|
||
this.level_label.string = lv >= 2 ? "★".repeat(lv - 1) : "";
|
||
}
|
||
|
||
// 描述词条(最多显示 3 条,对应 fied1/fied2/fied3)
|
||
const fieldLabels = [this.fied1, this.fied2, this.fied3];
|
||
const desc = this.getDescription(skillCard, skill);
|
||
for (let i = 0; i < fieldLabels.length; i++) {
|
||
if (!fieldLabels[i]) continue;
|
||
fieldLabels[i].string = i === 0 ? desc : "";
|
||
fieldLabels[i].node.active = i === 0 && !!desc;
|
||
}
|
||
|
||
// 图标
|
||
if (this.icon_node) {
|
||
const iconId = this.getIconId(skill);
|
||
if (iconId) {
|
||
this.updateIcon(this.icon_node, iconId);
|
||
}
|
||
}
|
||
|
||
// 购买按钮费用显示
|
||
if (this.buy_node) {
|
||
this.buy_node.active = true;
|
||
const costLabel = this.buy_node.getChildByName("num")?.getComponent(Label)
|
||
|| this.buy_node.getComponentInChildren(Label);
|
||
if (costLabel) {
|
||
costLabel.string = `${this.cardData.cost ?? 0}`;
|
||
}
|
||
}
|
||
}
|
||
|
||
/** 获取技能描述:驻场技能取 FieldSkillSet,其余取技能/卡牌配置 */
|
||
private getDescription(skillCard: CardConfig | undefined, skill: SkillConfig | null): string {
|
||
if (!this.cardData) return "";
|
||
if (this.cardData.field && this.cardData.field.length > 0) {
|
||
return FieldSkillSet[this.cardData.field[0]]?.info || "";
|
||
}
|
||
return skillCard?.info || skill?.info || this.cardData.info || "";
|
||
}
|
||
|
||
/** 获取技能图标 id:驻场技能取 FieldSkillSet.icon,其余取 SkillSet.icon */
|
||
private getIconId(skill: SkillConfig | null): string {
|
||
if (!this.cardData) return "";
|
||
if (!this.cardData.skill && this.cardData.field && this.cardData.field.length > 0) {
|
||
const fieldUuid = this.cardData.field[0];
|
||
return FieldSkillSet[fieldUuid]?.icon || `${fieldUuid}`;
|
||
}
|
||
return skill?.icon || `${this.cardData.skill ?? this.cardData.uuid}`;
|
||
}
|
||
|
||
/** 更新图标精灵 */
|
||
private updateIcon(node: Node, iconId: string) {
|
||
if (!node || !iconId) return;
|
||
const sprite = node.getComponent(Sprite) || node.getComponentInChildren(Sprite);
|
||
if (!sprite) return;
|
||
if (smc.uiconsAtlas) {
|
||
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
|
||
sprite.spriteFrame = frame || null;
|
||
}
|
||
}
|
||
|
||
// ======================== 购买逻辑 ========================
|
||
|
||
/**
|
||
* 购买点击处理:
|
||
* 1. 扣除金币(失败则提示并中止)。
|
||
* 2. 派发 UseSkillCard 事件,触发技能生效流程。
|
||
* 3. 标记已购买,隐藏购买按钮。
|
||
*/
|
||
private onBuyClick() {
|
||
if (!this.cardData || this.isPurchased) return;
|
||
|
||
oops.audio.playEffect("music/button");
|
||
|
||
// 扣费
|
||
const cost = this.cardData.cost ?? 0;
|
||
const success = MissionEconomy.spendCoin(cost);
|
||
if (!success) {
|
||
oops.message.dispatchEvent(GameEvent.ShowSmallTip, "buy_coin");
|
||
mLogger.log(this.debugMode, "SCardComp", "purchase failed: not enough coin", {
|
||
uuid: this.cardData.uuid,
|
||
cost
|
||
});
|
||
return;
|
||
}
|
||
|
||
smc.vmdata.scores.refresh_hit_count++;
|
||
|
||
// 派发 UseSkillCard,技能生效流程由 MissSkillsComp 接收处理
|
||
oops.message.dispatchEvent(GameEvent.UseSkillCard, this.cardData);
|
||
|
||
// 标记已购买
|
||
this.isPurchased = true;
|
||
if (this.buy_node) {
|
||
this.buy_node.active = false;
|
||
}
|
||
|
||
mLogger.log(this.debugMode, "SCardComp", "skill card purchased", {
|
||
uuid: this.cardData.uuid,
|
||
cost
|
||
});
|
||
}
|
||
|
||
/** 标记为已购买状态 */
|
||
setPurchased(): void {
|
||
this.isPurchased = true;
|
||
if (this.buy_node) {
|
||
this.buy_node.active = false;
|
||
}
|
||
}
|
||
|
||
/** ECS 组件移除时销毁节点 */
|
||
reset() {
|
||
if (this.node && this.node.isValid) {
|
||
this.node.destroy();
|
||
}
|
||
}
|
||
}
|