refactor(talent): 天赋升级系统重构为金币消耗模式

- 移除碎片相关配置与数据结构,统一使用 costs 数组配置每级金币消耗
- 优化天赋配置文案,去除名称中的冗余字样,精简属性描述文本
- 清理 SingletonModuleComp 存档及云端同步中的碎片字段
- 修改 TalentsComp 界面逻辑,升级校验、扣除及重置返还全面切换为金币体系
- 调整界面显示细节:等级格式变更为 current/max,消耗仅显示纯数值
This commit is contained in:
walkpan
2026-05-10 23:59:39 +08:00
parent 5e5a152fec
commit 78e325e8e5
3 changed files with 48 additions and 155 deletions

View File

@@ -24,7 +24,7 @@ import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/modu
import { mLogger } from "../common/Logger";
import { smc } from "../common/SingletonModuleComp";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
import { TalentConfig, TalentFragmentInfo, TalentFragmentType, TalentInfo, TalentType } from "../common/config/TalentSet";
import { TalentConfig, TalentInfo, TalentType } from "../common/config/TalentSet";
const { ccclass, property } = _decorator;
@@ -32,9 +32,9 @@ const { ccclass, property } = _decorator;
* TalentsComp —— 天赋系统界面组件
*
* 职责:
* 1. 展示玩家等级、当前经验、进度条、碎片库存
* 1. 展示玩家等级、当前经验、进度条、金币
* 2. 展示天赋列表及每个天赋的当前等级。
* 3. 处理天赋升级点击事件,扣除碎片并保存。
* 3. 处理天赋升级点击事件,扣除金币并保存。
* 4. 处理重置天赋(看广告)功能。
*/
@ccclass('TalentsComp')
@@ -53,7 +53,7 @@ export class TalentsComp extends CCComp {
@property({ type: ProgressBar, tooltip: "经验进度条" })
pb_exp: ProgressBar = null!;
@property({ type: Label, tooltip: "当前碎片库存摘要文本" })
@property({ type: Label, tooltip: "当前金币文本" })
lbl_points: Label = null!;
@property({ type: Node, tooltip: "天赋列表容器,用于动态添加天赋项" })
@@ -96,12 +96,11 @@ export class TalentsComp extends CCComp {
this.updateTalentList();
}
/** 更新玩家等级、经验、碎片信息 */
/** 更新玩家等级、经验、金币信息 */
private updatePlayerInfo() {
const collection = smc.collection;
let level = collection.player_level || 1;
let exp = collection.player_exp || 0;
const fragmentSummary = this.getFragmentSummaryText();
// 限制最大等级
if (level > this.MAX_PLAYER_LEVEL) {
@@ -109,7 +108,7 @@ export class TalentsComp extends CCComp {
}
if (this.lbl_level) this.lbl_level.string = `Lv.${level}`;
if (this.lbl_points) this.lbl_points.string = fragmentSummary;
if (this.lbl_points) this.lbl_points.string = `金币: ${smc.vmdata.gold}`;
// 计算当前等级升级所需经验
let expRequired = this.getExpRequirement(level);
@@ -175,18 +174,18 @@ export class TalentsComp extends CCComp {
lblDesc.string = talentInfo.desc.replace('{value}', currentVal.toString());
}
if (lblLevel) lblLevel.string = `Lv.${currentLevel}`;
if (lblLevel) lblLevel.string = `${currentLevel}/${talentInfo.maxLevel}`;
let isMax = currentLevel >= talentInfo.maxLevel;
const levelCost = isMax ? null : this.getLevelFragmentCost(talentInfo, currentLevel);
const canUpgrade = !!levelCost && this.hasEnoughFragments(levelCost);
let cost = isMax ? 0 : (talentInfo.costs[currentLevel] ?? 0);
let canUpgrade = !isMax && smc.vmdata.gold >= cost;
if (lblCost) {
lblCost.string = isMax ? "已满级" : `消耗: ${this.buildFragmentCostText(levelCost!)}`;
lblCost.string = isMax ? "已满级" : `${cost}`;
}
if (btnUpgrade) {
btnUpgrade.interactable = !isMax && canUpgrade;
btnUpgrade.interactable = canUpgrade;
// 清除旧的监听,避免重复绑定
btnUpgradeNode?.off(Button.EventType.CLICK);
btnUpgradeNode?.on(Button.EventType.CLICK, () => {
@@ -209,15 +208,11 @@ export class TalentsComp extends CCComp {
return;
}
const levelCost = this.getLevelFragmentCost(talentInfo, currentLevel);
if (!levelCost) {
oops.gui.toast("天赋消耗配置异常");
return;
}
const cost = talentInfo.costs[currentLevel] ?? 0;
if (this.hasEnoughFragments(levelCost)) {
// 1. 扣除单碎片消耗
this.consumeFragments(levelCost);
if (smc.vmdata.gold >= cost) {
// 1. 扣除金币消耗
smc.updateGold(-cost);
// 2. 更新等级
collection.talents[talentId] = currentLevel + 1;
@@ -229,7 +224,7 @@ export class TalentsComp extends CCComp {
oops.gui.toast("天赋升级成功");
} else {
oops.gui.toast("碎片不足或已满级");
oops.gui.toast("金币不足");
}
}
@@ -239,31 +234,26 @@ export class TalentsComp extends CCComp {
this.watch_ad().then(success => {
if (success) {
const collection = smc.collection;
// 计算已消耗碎片并返还
const refundedFragments: Partial<Record<TalentFragmentType, number>> = {};
// 计算已消耗金币并返还
let refundedGold = 0;
for (let id in collection.talents) {
let talentId = Number(id) as TalentType;
let level = collection.talents[talentId];
let talentInfo = TalentConfig.talents.find(t => t.id === talentId);
if (talentInfo) {
for (let i = 0; i < level; i++) {
const levelCost = this.getLevelFragmentCost(talentInfo, i);
if (!levelCost) continue;
refundedFragments[levelCost.type] = (refundedFragments[levelCost.type] || 0) + levelCost.amount;
refundedGold += talentInfo.costs[i] ?? 0;
}
}
}
// 重置天赋等级并返还碎片
// 重置天赋等级并返还金币
for (let k in collection.talents) {
collection.talents[k as any as TalentType] = 0;
}
for (const fragmentType in refundedFragments) {
const key = Number(fragmentType) as TalentFragmentType;
const amount = refundedFragments[key] || 0;
if (amount > 0) {
collection.talent_fragments[key] += amount;
}
if (refundedGold > 0) {
smc.updateGold(refundedGold);
}
// 同步到云端
@@ -271,59 +261,13 @@ export class TalentsComp extends CCComp {
// 刷新界面
this.refreshUI();
oops.gui.toast("天赋已重置,碎片已返还");
oops.gui.toast("天赋已重置,金币已返还");
} else {
oops.gui.toast("广告观看失败,无法重置");
}
});
}
/** 获取升级到下一等级所需的单碎片消耗 */
private getLevelFragmentCost(talentInfo: TalentInfo, currentLevel: number): { type: TalentFragmentType; amount: number } | null {
if (talentInfo.fragmentType === undefined) {
return null;
}
return {
type: talentInfo.fragmentType,
amount: talentInfo.costs[currentLevel] ?? 0
};
}
/** 判断当前库存是否满足一次升级 */
private hasEnoughFragments(cost: { type: TalentFragmentType; amount: number }): boolean {
const bag = smc.collection.talent_fragments;
return bag[cost.type] >= cost.amount;
}
/** 执行碎片扣除 */
private consumeFragments(cost: { type: TalentFragmentType; amount: number }) {
const bag = smc.collection.talent_fragments;
bag[cost.type] = Math.max(0, bag[cost.type] - cost.amount);
}
/** 生成单次升级消耗文案 */
private buildFragmentCostText(cost: { type: TalentFragmentType; amount: number }): string {
const info = this.getFragmentInfo(cost.type);
const own = smc.collection.talent_fragments[cost.type];
const fragmentName = info ? info.name : `碎片${cost.type}`;
return `${fragmentName} ${own}/${cost.amount}`;
}
/** 顶部碎片库存摘要 */
private getFragmentSummaryText(): string {
const bag = smc.collection.talent_fragments;
const summary = TalentConfig.fragments
.filter(fragment => bag[fragment.id] > 0)
.map(fragment => `${fragment.name}:${bag[fragment.id]}`)
.join(" ");
return summary ? `碎片: ${summary}` : "碎片: 暂无";
}
/** 按碎片类型查找碎片配置 */
private getFragmentInfo(type: TalentFragmentType): TalentFragmentInfo | undefined {
return TalentConfig.fragments.find(fragment => fragment.id === type);
}
/** 模拟看广告回调实际项目中需要替换为真实的广告SDK调用 */
private watch_ad(): Promise<boolean> {
return new Promise((resolve) => {