Files
pixelheros/assets/script/game/map/SCardComp.ts
panFD ffafefcc72 feat(mission): 新增装备和商店刷新功能,重构抽卡逻辑
1. 新增按权重抽取装备卡的工具函数,支持去重后补齐
2. 重构任务界面卡牌组件:
   - 拆分英雄/装备/商店面板节点绑定,调整属性顺序
   - 新增装备和商店刷新按钮的交互逻辑
   - 替换原固定加载装备/商品列表为按权重随机抽取3个
3. 重构技能卡牌组件,改为技能商店卡项实现,简化原有逻辑
4. 修复prefab中的节点属性偏移和引用关系
2026-07-25 21:44:17 +08:00

263 lines
8.7 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.
/**
* @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();
}
}
}