2 Commits

Author SHA1 Message Date
walkpan
78e325e8e5 refactor(talent): 天赋升级系统重构为金币消耗模式
- 移除碎片相关配置与数据结构,统一使用 costs 数组配置每级金币消耗
- 优化天赋配置文案,去除名称中的冗余字样,精简属性描述文本
- 清理 SingletonModuleComp 存档及云端同步中的碎片字段
- 修改 TalentsComp 界面逻辑,升级校验、扣除及重置返还全面切换为金币体系
- 调整界面显示细节:等级格式变更为 current/max,消耗仅显示纯数值
2026-05-10 23:59:39 +08:00
walkpan
5e5a152fec fix(ui): 调整天赋界面布局和精灵边框
- 更新 aui.plist 中精灵的边框值以正确适配九宫格缩放
- 修改 talents.prefab 中布局组件的参数,优化天赋图标的排列间距和约束
2026-05-10 23:55:30 +08:00
8 changed files with 4095 additions and 3894 deletions

View File

@@ -70,10 +70,10 @@
"height": 216,
"rawWidth": 200,
"rawHeight": 216,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"borderTop": 57,
"borderBottom": 45,
"borderLeft": 56,
"borderRight": 49,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1152,23 +1152,23 @@
"__id__": 50
},
"_resizeMode": 0,
"_layoutType": 2,
"_layoutType": 3,
"_cellSize": {
"__type__": "cc.Size",
"width": 40,
"height": 40
},
"_startAxis": 0,
"_paddingLeft": 0,
"_paddingRight": 0,
"_paddingTop": 20,
"_paddingLeft": 39.9,
"_paddingRight": 40,
"_paddingTop": 50,
"_paddingBottom": 0,
"_spacingX": 0,
"_spacingY": 5,
"_spacingX": 30,
"_spacingY": 30,
"_verticalDirection": 1,
"_horizontalDirection": 0,
"_constraint": 0,
"_constraintNum": 2,
"_constraint": 2,
"_constraintNum": 3,
"_affectedByScale": false,
"_isAlign": false,
"_id": ""

View File

@@ -8,7 +8,7 @@ import { WxCloudApi } from "../wx_clound_client_api/WxCloudApi";
import { GameEvent } from "./config/GameEvent";
import { GameScoreStats } from "./config/HeroAttrs";
import { mLogger } from "./Logger";
import { TalentFragmentType, TalentType } from "./config/TalentSet";
import { TalentType } from "./config/TalentSet";
import { gameDataSync } from "./GameDataSync";
import { FightSet } from "./config/GameSet";
@@ -23,7 +23,6 @@ export interface GameDate{
talents: Record<TalentType, number>,
player_level: number,
player_exp: number,
talent_fragments: Record<TalentFragmentType, number>,
talent_points?: number,
}
}
@@ -67,7 +66,6 @@ export class SingletonModuleComp extends ecs.Comp {
talents: Record<TalentType, number>;
player_level: number;
player_exp: number;
talent_fragments: Record<TalentFragmentType, number>;
talent_points?: number;
} = {
talents: {
@@ -85,16 +83,6 @@ export class SingletonModuleComp extends ecs.Comp {
}, // 存储各个天赋的等级: { talent_id: level }
player_level: 1, // 玩家等级
player_exp: 0, // 玩家当前经验
talent_fragments: {
[TalentFragmentType.Power]: 0,
[TalentFragmentType.Tempest]: 0,
[TalentFragmentType.Vitality]: 0,
[TalentFragmentType.Frost]: 0,
[TalentFragmentType.Tactics]: 0,
[TalentFragmentType.Spirit]: 0,
[TalentFragmentType.Trade]: 0,
[TalentFragmentType.Fate]: 0
}, // 当前拥有的天赋碎片库存
talent_points: 0, // 兼容旧存档的历史字段
};
@@ -266,9 +254,6 @@ export class SingletonModuleComp extends ecs.Comp {
}
if (typeof remoteCol.player_level === 'number') this.collection.player_level = remoteCol.player_level;
if (typeof remoteCol.player_exp === 'number') this.collection.player_exp = remoteCol.player_exp;
if (remoteCol.talent_fragments) {
Object.assign(this.collection.talent_fragments, remoteCol.talent_fragments);
}
if (typeof remoteCol.talent_points === 'number') this.collection.talent_points = remoteCol.talent_points;
}
}

View File

@@ -17,28 +17,6 @@ export enum TalentType {
SellBonus = 11 // 出售补贴
}
export enum TalentFragmentType {
Power = 1, // 力量碎片
Tempest = 2, // 风暴碎片
Vitality = 3, // 生机碎片
Frost = 4, // 寒霜碎片
Tactics = 5, // 谋略碎片
Spirit = 6, // 灵契碎片
Trade = 7, // 商贸碎片
Fate = 8 // 命运碎片
}
export interface TalentFragmentInfo {
/** 碎片 ID */
id: TalentFragmentType;
/** 碎片名称 */
name: string;
/** 碎片图标或标识(可选) */
icon?: string;
/** 该碎片可用于哪些天赋,单个碎片最多映射 3 个天赋 */
talentIds: TalentType[];
}
export interface TalentInfo {
/** 天赋 ID */
id: number;
@@ -52,10 +30,8 @@ export interface TalentInfo {
maxLevel: number;
/** 每一级的加成数值从第1级到最大级 */
values: number[];
/** 每一级的消耗数量,下标 0 表示升到 1 级 */
/** 每一级的金币消耗数量,下标 0 表示升到 1 级 */
costs: number[];
/** 升级所需的单碎片类型 */
fragmentType: TalentFragmentType;
}
export const TalentConfig = {
@@ -66,41 +42,29 @@ export const TalentConfig = {
{ maxLevel: 30, expPerLevel: 200 }
],
// 天赋碎片配置:不同天赋可共用同一种碎片,单个碎片最多映射 3 个天赋
fragments: [
{ id: TalentFragmentType.Power, name: "力量碎片", icon: "◆", talentIds: [TalentType.Attack, TalentType.Critical, TalentType.Puncture] },
{ id: TalentFragmentType.Tempest, name: "风暴碎片", icon: "◇", talentIds: [TalentType.WindFury, TalentType.RefreshDiscount] },
{ id: TalentFragmentType.Vitality, name: "生机碎片", icon: "●", talentIds: [TalentType.Hp, TalentType.DeadTrigger, TalentType.Summon] },
{ id: TalentFragmentType.Frost, name: "寒霜碎片", icon: "○", talentIds: [TalentType.Freeze] },
{ id: TalentFragmentType.Tactics, name: "谋略碎片", icon: "■", talentIds: [TalentType.BuyDiscount] },
{ id: TalentFragmentType.Spirit, name: "灵契碎片", icon: "□", talentIds: [TalentType.SellBonus] },
{ id: TalentFragmentType.Trade, name: "商贸碎片", icon: "▲", talentIds: [] },
{ id: TalentFragmentType.Fate, name: "命运碎片", icon: "△", talentIds: [] }
] as TalentFragmentInfo[],
// 所有天赋定义(使用数组维护)
talents: [
{ id: TalentType.Attack, name: "攻击强化", icon: "⚔️", desc: "所有英雄 ATK +{value}%",
maxLevel: 5, values: [3, 6, 9, 12, 15], costs: [1, 1, 2, 2, 3], fragmentType: TalentFragmentType.Power },
{ id: TalentType.Hp, name: "生命强化", icon: "❤️", desc: "所有英雄 HP +{value}%",
maxLevel: 5, values: [5, 10, 15, 20, 25], costs: [1, 1, 2, 2, 3], fragmentType: TalentFragmentType.Vitality },
{ id: TalentType.Critical, name: "暴击强化", icon: "🔥", desc: "所有英雄暴击率 +{value}%",
maxLevel: 5, values: [2, 4, 6, 8, 10], costs: [1, 1, 2, 2, 3], fragmentType: TalentFragmentType.Power },
{ id: TalentType.WindFury, name: "风怒强化", icon: "⚡", desc: "所有英雄风怒率 +{value}%",
maxLevel: 5, values: [2, 4, 6, 8, 10], costs: [1, 1, 2, 2, 3], fragmentType: TalentFragmentType.Tempest },
{ id: TalentType.Freeze, name: "冰冻强化", icon: "❄️", desc: "所有英雄冰冻率 +{value}%",
maxLevel: 5, values: [2, 4, 6, 8, 10], costs: [1, 1, 2, 2, 3], fragmentType: TalentFragmentType.Frost },
{ id: TalentType.Puncture, name: "穿刺强化", icon: "🗡️", desc: "所有英雄穿刺 +{value}",
maxLevel: 5, values: [0.2, 0.4, 0.6, 0.8, 1.0], costs: [1, 1, 2, 2, 3], fragmentType: TalentFragmentType.Power },
{ id: TalentType.DeadTrigger, name: "亡语强化", icon: "🛡️", desc: "死亡触发技能额外触发次数+{value}次",
maxLevel: 1, values: [1], costs: [25], fragmentType: TalentFragmentType.Vitality },
{ id: TalentType.Summon, name: "召唤强化", icon: "🛡️", desc: "召唤触发技能额外触发次数+{value}次",
maxLevel: 1, values: [1], costs: [25], fragmentType: TalentFragmentType.Vitality },
{ id: TalentType.BuyDiscount, name: "购优惠", icon: "🛒", desc: "购买英雄 -{value}金",
maxLevel: 1, values: [1], costs: [10], fragmentType: TalentFragmentType.Tactics },
{ id: TalentType.RefreshDiscount, name: "刷新优惠", icon: "🔄", desc: "刷新重抽 -{value}金",
maxLevel: 1, values: [1], costs: [10], fragmentType: TalentFragmentType.Tempest },
{ id: TalentType.SellBonus, name: "出售补贴", icon: "💰", desc: "出售英雄返还 +{value}金币",
maxLevel: 1, values: [1], costs: [10], fragmentType: TalentFragmentType.Spirit }
{ id: TalentType.Attack, name: "攻击", icon: "⚔️", desc: "+{value}%",
maxLevel: 5, values: [3, 6, 9, 12, 15], costs: [1, 1, 2, 2, 3] },
{ id: TalentType.Hp, name: "生命", icon: "❤️", desc: "+{value}%",
maxLevel: 5, values: [5, 10, 15, 20, 25], costs: [1, 1, 2, 2, 3] },
{ id: TalentType.Critical, name: "暴击", icon: "🔥", desc: "+{value}%",
maxLevel: 5, values: [2, 4, 6, 8, 10], costs: [1, 1, 2, 2, 3] },
{ id: TalentType.WindFury, name: "风怒", icon: "⚡", desc: "+{value}%",
maxLevel: 5, values: [2, 4, 6, 8, 10], costs: [1, 1, 2, 2, 3] },
{ id: TalentType.Freeze, name: "冰冻", icon: "❄️", desc: "+{value}%",
maxLevel: 5, values: [2, 4, 6, 8, 10], costs: [1, 1, 2, 2, 3] },
{ id: TalentType.Puncture, name: "穿刺", icon: "🗡️", desc: "+{value}",
maxLevel: 5, values: [0.2, 0.4, 0.6, 0.8, 1.0], costs: [1, 1, 2, 2, 3] },
{ id: TalentType.DeadTrigger, name: "亡语额外触发", icon: "🛡️", desc: "+{value}次",
maxLevel: 1, values: [1], costs: [25] },
{ id: TalentType.Summon, name: "召唤额外触发", icon: "🛡️", desc: "+{value}次",
maxLevel: 1, values: [1], costs: [25] },
{ id: TalentType.BuyDiscount, name: "购优惠", icon: "🛒", desc: "-{value}金",
maxLevel: 1, values: [1], costs: [10] },
{ id: TalentType.RefreshDiscount, name: "刷新优惠", icon: "🔄", desc: "-{value}金",
maxLevel: 1, values: [1], costs: [10] },
{ id: TalentType.SellBonus, name: "出售返还", icon: "💰", desc: "+{value}金币",
maxLevel: 1, values: [1], costs: [10] }
] as TalentInfo[]
};

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) => {