feat: 新增装备商店系统,调整英雄站位与抽卡逻辑

1.  重构抽卡函数,新增排除装备卡参数,装备卡改为商店直购
2.  添加装备商店面板,支持装备浏览与购买,跨波次保留购买记录
3.  调整英雄出场点位坐标,优化战场布局
4.  扩展技能卡槽位,新增两行技能槽位置
5.  重构EquipListComp组件,适配装备商店UI逻辑
6.  删除废弃的melist预制件与相关元文件
7.  调整hnode预制件布局参数
This commit is contained in:
pan
2026-07-22 10:44:01 +08:00
parent d37fadf5cf
commit 401b02be96
9 changed files with 14396 additions and 4153 deletions

View File

@@ -1,149 +1,219 @@
/**
* @file EquipListComp.ts
* @description 单个技能卡效果控制组件UI 视图层 + 逻辑
* @description 装备商店单个装备项组件UI 视图层 + 购买逻辑)
*
* 职责:
* 1. 表示一张已使用的技能卡在战场上的 **可视化实体**
* 2. 按 trigger_type 类型化分发触发逻辑(即时 / 定时 / 驻场 / 事件型)
* 3. 显示技能图标和剩余触发次数。
* 4. 触发结束后自动销毁。
* 1. 渲染单个装备卡的显示信息(名称、等级、属性词条、图标)
* 2. 处理购买点击 → 扣费 → 派发 UseSkillCard 事件触发装备生效
*
* 触发类型CardTriggerType
* - Instant (1)init 时立即触发一次(按 t_times 控制次数,跨波次 NewWave 时再次触发)
* - Interval (2):监听 FightStart → update 帧驱动按 t_inv 间隔重复触发(按 t_times 控制每波次数)
* - Field (3):被动生效,不主动施法(实际由 FieldSkillSet 处理)
* - FightStart (4):监听 FightStart 事件,按 trigger_limit 全局累计上限
* - FightEnd (5):监听 FightEnd 事件(每波结束派发),按 trigger_limit 全局累计上限
* - HeroDead (6):监听 HeroDead 事件(仅英雄阵营派发,怪物死亡不触发)
* - HeroCall (7):监听 MasterCalled主角/技能召唤)+ ReviveSuccess复活
*
* 关键设计:
* - 事件型4-7统一走 onEventTrigger 入口,仅作触发信号,不读取 payload
* - 触发上限Instant/Interval 按 t_times每波内事件型按 trigger_limit全局
* - 跨波次keep_waves 控制存活;事件型 trigger_count 不随波次重置
* - 销毁时通过 GameEvent.RemoveSkillBox 通知 MissSkillsComp 回收槽位
*
* 依赖:
* - CardPoolList / CardTriggerTypeCardSet—— 查询技能卡的触发配置
* - SkillSet —— 技能静态配置icon 字段)
* - GameEvent —— 各类游戏事件
* - smc.mission —— 游戏运行状态
* 装备使用机制
* 与技能卡使用流程一致——购买后派发 GameEvent.UseSkillCard
* 由 MissSkillsComp 接收并创建 SkillBoxComp 实体,
* SkillBoxComp 按 Field 类型被动驻场,属性加成由 FieldSkillHelper 全局聚合。
*/
import { mLogger } from "../common/Logger";
import { _decorator, Node, Prefab, Sprite, Label, Vec3, resources, SpriteAtlas, tween, v3, Tween, NodeEventType } from "cc";
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 { CardPoolList, CardTriggerType } from "../common/config/CardSet";
import { SkillSet, SkillOverrides, FieldSkillSet } from "../common/config/SkillSet";
import { CardConfig, CardPoolList } from "../common/config/CardSet";
import { FieldSkillSet } from "../common/config/SkillSet";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { smc } from "../common/SingletonModuleComp";
import { UIID } from "../common/config/GameUIConfig";
import { MissionEconomy } from "./MissionEconomy";
const { ccclass, property } = _decorator;
/**
* EquipListComp —— 单个技能卡效果视图 + 逻辑组件
* EquipListComp —— 装备商店单个装备项
*
* 由 MissSkillsComp.addSkill() 实例化并初始化
* 在战场上以图标 + 剩余次数的形式呈现。
* 由 MissionCardComp.populateEquipments() 实例化。
* 以图标 + 名称 + 属性词条 + 购买按钮的形式呈现。
*/
@ccclass('EquipListComp')
@ecs.register('EquipListComp', false)
export class EquipListComp extends CCComp {
/** 调试日志开关 */
private debugMode: boolean = true;
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;
// ======================== 技能配置 ========================
/** 技能 UUID */
private s_uuid: number = 0;
/** 卡牌等级 */
private card_lv: number = 1;
/** 是否为即时技能true=使用后立即触发false=战斗中定时触发) */
private is_instant: boolean = true;
/** 总触发次数 */
private trigger_times: number = 1;
/** 触发间隔(秒,仅持续技能有效) */
private trigger_interval: number = 0;
/** 维持的波次数(-1表示直到战斗结束0表示不跨波次>0表示维持的具体波次数 */
private keep_waves: number = 0;
/** 技能覆写参数自定义伤害、Buff等 */
private overrides?: SkillOverrides;
/** 驻场技能 UUID 列表 */
public field: number[] = [];
/** 卡牌自定义图标ID优先级最高未定义则按 trigger_type 自动取) */
private card_icon?: string;
// ======================== 触发类型化扩展 ========================
/** 触发类型(默认即时,保持向后兼容) */
private trigger_type: CardTriggerType = CardTriggerType.Instant;
/** 事件型触发的全局次数上限Infinity 表示无上限) */
private trigger_limit: number = Infinity;
/** 事件型已触发次数 */
private trigger_count: number = 0;
// ======================== 运行时状态 ========================
/** 已触发次数 */
private current_trigger_times: number = 0;
/** 当前计时器(秒) */
private timer: number = 0;
/** 是否处于战斗中(仅战斗中持续技能才计时) */
private in_combat: boolean = false;
/** 是否已初始化 */
private initialized: boolean = false;
/** 当前装备的卡牌配置 */
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);
}
}
// ======================== 数据初始化 ========================
init() {
/**
* 初始化装备卡数据并渲染 UI
*
* @param data 装备卡配置(必须为 trigger_type=Field 的装备卡)
*/
applyCardData(data: CardConfig): void {
if (!data) return;
this.cardData = data;
this.isPurchased = false;
this.updateUI();
}
private registerTrigger(): void {
}
// ======================== UI 渲染 ========================
updateUI() {
if (!this.cardData) return;
const cardConfig = CardPoolList.find(c => c.uuid === this.cardData!.uuid);
// 名称
if (this.name_label) {
this.name_label.string = cardConfig?.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 fields = this.cardData.field || [];
for (let i = 0; i < fieldLabels.length; i++) {
if (!fieldLabels[i]) continue;
if (i < fields.length) {
const fieldConfig = FieldSkillSet[fields[i]];
fieldLabels[i].string = fieldConfig?.info || "";
fieldLabels[i].node.active = true;
} else {
fieldLabels[i].string = "";
fieldLabels[i].node.active = false;
}
}
// 图标(取第一个 field 词条的图标)
if (this.icon_node && fields.length > 0) {
const iconId = FieldSkillSet[fields[0]]?.icon || "";
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}`;
}
}
}
/** 更新图标精灵 */
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. 标记已购买,隐藏购买按钮。
*
* 装备生效链路:
* UseSkillCard → MissSkillsComp.addSkill() → SBox ECS 实体 → SkillBoxComp(Field 类型)
* → FieldSkillHelper.getFieldSkillTotalValue() 全局聚合属性加成
*/
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, "EquipListComp", "purchase failed: not enough coin", {
uuid: this.cardData.uuid,
cost
});
return;
}
// 派发 UseSkillCard装备生效流程与技能卡一致
oops.message.dispatchEvent(GameEvent.UseSkillCard, this.cardData);
// 标记已购买
this.isPurchased = true;
if (this.buy_node) {
this.buy_node.active = false;
}
mLogger.log(this.debugMode, "EquipListComp", "equipment 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) {