feat: 新增刷新石机制并优化抽卡消耗逻辑

1.  新增refresh_stone玩家数据字段与对应变更事件
2.  实现刷新石的获取、消耗和刷新可用性校验逻辑
3.  抽卡优先消耗刷新石,不足时再扣除金币
4.  为购买按钮添加可配置的消耗标签,优化UI查找逻辑
5.  更新对应预制件绑定新的消耗标签组件
This commit is contained in:
panFD
2026-07-30 13:46:21 +08:00
parent 14fdb0efa9
commit 42c63e6482
12 changed files with 3294 additions and 74 deletions

View File

@@ -77,6 +77,7 @@ export class SingletonModuleComp extends ecs.Comp {
level: 1,//关卡等级
max_mission: 4,//最大关卡
coin: 0,
refresh_stone: 0,//刷新石(刷新卡池时优先消耗,不足时消耗金币)
time: 15 * 60,//游戏时间
},
scores: {

View File

@@ -78,6 +78,7 @@ export enum GameEvent {
UpdateMissionGet = "UpdateMissionGet",
GlobalAttrChange = "GlobalAttrChange",
CoinAdd = "CoinAdd",
RefreshStone = "RefreshStone", // 刷新石数量变更事件
ShowSmallTip = "ShowSmallTip",
CardPoolUpgrade = "CardPoolUpgrade",
TriggerSkill = "TriggerSkill", // 瞬间触发施法事件

View File

@@ -75,6 +75,10 @@ export class CHeroComp extends CCComp {
@property({ type: Node })
private buy_node: Node = null;
/** 消费金币标签(未绑定时回退查找 buy_node 下的 num/Label 子节点) */
@property(Label)
private cost_label: Label = null;
// ======================== 运行时状态 ========================
/** 当前槽位承载的卡牌配置null 表示空槽 */
@@ -313,7 +317,8 @@ export class CHeroComp extends CCComp {
// 购买按钮费用显示
if (this.buy_node) {
this.buy_node.active = true;
const costLabel = this.buy_node.getChildByName("num")?.getComponent(Label)
const costLabel = this.cost_label
|| this.buy_node.getChildByName("num")?.getComponent(Label)
|| this.buy_node.getComponentInChildren(Label);
if (costLabel) {
costLabel.string = `${this.cardData.cost ?? 0}`;

View File

@@ -69,6 +69,10 @@ export class EquipListComp extends CCComp {
@property({ type: Node })
private buy_node: Node = null;
/** 消费金币标签(未绑定时回退查找 buy_node 下的 num/Label 子节点) */
@property(Label)
private cost_label: Label = null;
// ======================== 运行时状态 ========================
/** 当前装备的卡牌配置 */
@@ -195,7 +199,8 @@ export class EquipListComp extends CCComp {
// 购买按钮费用显示
if (this.buy_node) {
this.buy_node.active = true;
const costLabel = this.buy_node.getChildByName("num")?.getComponent(Label)
const costLabel = this.cost_label
|| this.buy_node.getChildByName("num")?.getComponent(Label)
|| this.buy_node.getComponentInChildren(Label);
if (costLabel) {
costLabel.string = `${this.cardData.cost ?? 0}`;

View File

@@ -72,6 +72,10 @@ export class ItemListComp extends CCComp {
@property({ type: Node })
private buy_node: Node = null;
/** 消费金币标签(未绑定时回退查找 buy_node 下的 num/Label 子节点) */
@property(Label)
private cost_label: Label = null;
// ======================== 运行时状态 ========================
/** 当前商品的卡牌配置 */
@@ -232,7 +236,8 @@ export class ItemListComp extends CCComp {
// 购买按钮费用显示
if (this.buy_node) {
this.buy_node.active = true;
const costLabel = this.buy_node.getChildByName("num")?.getComponent(Label)
const costLabel = this.cost_label
|| this.buy_node.getChildByName("num")?.getComponent(Label)
|| this.buy_node.getComponentInChildren(Label);
if (costLabel) {
costLabel.string = `${this.cardData.cost ?? 0}`;

View File

@@ -376,6 +376,7 @@ export class MissionCardComp extends CCComp {
this.on("PhasePrepareStart", this.onPhasePrepareStart, this);
oops.message.on(GameEvent.CoinAdd, this.onCoinAdd, this);
oops.message.on(GameEvent.RefreshStone, this.onRefreshStone, this);
oops.message.on(GameEvent.MasterCalled, this.onMasterCalled, this);
oops.message.on(GameEvent.HeroDead, this.onHeroDead, this);
oops.message.on(GameEvent.HeroSell, this.onHeroSell, this);
@@ -442,6 +443,11 @@ export class MissionCardComp extends CCComp {
}
}
/** 刷新石数量变更:刷新刷新按钮的可用状态与费用显示 */
private onRefreshStone() {
this.updateDrawCostUI();
}
/** 战斗开始:保留卡牌面板,允许玩家在战斗阶段继续抽卡和召唤英雄 */
private onFightStart() {
this.enterBattlePhase();
@@ -620,6 +626,7 @@ export class MissionCardComp extends CCComp {
/** 解除按钮监听,避免节点销毁后回调泄漏 */
private unbindEvents() {
oops.message.off(GameEvent.CoinAdd, this.onCoinAdd, this);
oops.message.off(GameEvent.RefreshStone, this.onRefreshStone, this);
oops.message.off(GameEvent.MasterCalled, this.onMasterCalled, this);
oops.message.off(GameEvent.HeroDead, this.onHeroDead, this);
oops.message.off(GameEvent.HeroSell, this.onHeroSell, this);
@@ -1700,7 +1707,8 @@ export class MissionCardComp extends CCComp {
}
private canDrawCards() {
return MissionEconomy.getCoin() >= MissionEconomy.getRefreshCost(this.refreshCost);
// 有刷新石时可直接刷新,否则校验金币是否足够
return MissionEconomy.canRefresh(this.refreshCost);
}
private updateDrawCostUI() {

View File

@@ -52,10 +52,48 @@ export class MissionEconomy {
return Math.floor(cost);
}
/** 获取当前刷新石数量 */
static getRefreshStone(): number {
return Math.max(0, Math.floor(smc.vmdata.mission_data.refresh_stone ?? 0));
}
/**
* 增加刷新石
* @param amount 增加数量,<=0 时忽略
*/
static addRefreshStone(amount: number) {
if (amount <= 0) return;
amount = Math.floor(amount);
smc.vmdata.mission_data.refresh_stone = this.getRefreshStone() + amount;
// 发送 UI 更新事件(复用 RefreshStone 事件通知刷新按钮状态刷新)
oops.message.dispatchEvent(GameEvent.RefreshStone, { delta: amount });
}
/**
* 判断当前是否可以执行一次刷新(有刷新石或金币足够)
*/
static canRefresh(baseCost: number = 1): boolean {
if (this.getRefreshStone() > 0) return true;
return this.getCoin() >= this.getRefreshCost(baseCost);
}
/**
* 执行刷新卡牌并扣除金币
* 执行刷新卡牌
* 1. 刷新石 > 0 时优先扣除 1 颗刷新石(不消耗金币)。
* 2. 刷新石不足时扣除金币(含刷新优惠折扣)。
*/
static executeRefresh(baseCost: number = 1): boolean {
// 优先消耗刷新石
const stones = this.getRefreshStone();
if (stones > 0) {
smc.vmdata.mission_data.refresh_stone = stones - 1;
smc.vmdata.scores.refresh_count++;
oops.message.dispatchEvent(GameEvent.RefreshStone, { delta: -1 });
return true;
}
// 刷新石不足 → 扣除金币
const cost = this.getRefreshCost(baseCost);
const success = this.spendCoin(cost);
if (success) {

View File

@@ -69,6 +69,10 @@ export class SCardComp extends CCComp {
@property({ type: Node })
private buy_node: Node = null;
/** 消费金币标签(未绑定时回退查找 buy_node 下的 num/Label 子节点) */
@property(Label)
private cost_label: Label = null;
// ======================== 运行时状态 ========================
/** 当前技能的卡牌配置 */
@@ -232,7 +236,8 @@ export class SCardComp extends CCComp {
// 购买按钮费用显示
if (this.buy_node) {
this.buy_node.active = true;
const costLabel = this.buy_node.getChildByName("num")?.getComponent(Label)
const costLabel = this.cost_label
|| this.buy_node.getChildByName("num")?.getComponent(Label)
|| this.buy_node.getComponentInChildren(Label);
if (costLabel) {
costLabel.string = `${this.cardData.cost ?? 0}`;