From 9adff47e6a96e35e73591f08ca608433431c5064 Mon Sep 17 00:00:00 2001 From: pan Date: Wed, 3 Jun 2026 10:27:55 +0800 Subject: [PATCH] =?UTF-8?q?refactor(map):=20=E9=87=8D=E6=9E=84=E5=A4=A9?= =?UTF-8?q?=E8=B5=8B=E7=B3=BB=E7=BB=9F=E4=B8=BA=E9=A9=BB=E5=9C=BA=E6=8A=80?= =?UTF-8?q?=E8=83=BD=E5=B1=95=E7=A4=BA=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 将原天赋系统页面重构成驻场技能信息展示页 2. 移除升级、重置等旧功能,仅保留基础数据展示逻辑 3. 新增数值格式化工具函数,兼容百分比与整数加成显示 4. 简化组件依赖,仅保留必要的配置与UI渲染逻辑 5. 统一组件命名与注释,明确职责边界 --- assets/script/game/map/TalentItemComp.ts | 141 ++++-------- assets/script/game/map/TalentsComp.ts | 262 ++++------------------- 2 files changed, 82 insertions(+), 321 deletions(-) diff --git a/assets/script/game/map/TalentItemComp.ts b/assets/script/game/map/TalentItemComp.ts index 8922df12..65240b13 100644 --- a/assets/script/game/map/TalentItemComp.ts +++ b/assets/script/game/map/TalentItemComp.ts @@ -1,125 +1,64 @@ /** * @file TalentItemComp.ts - * @description 单个天赋项组件 + * @description 驻场技能项组件(UI 视图层) + * + * 职责: + * 1. 接收 TalentsComp 下发的 FieldSkillConfig 与当前场上总加成值。 + * 2. 渲染名称 / 基础值 / 当前值 / 描述四段信息。 + * 3. 兼容旧的 `@ecs.register('TalentItem')` 资源引用。 + * + * 依赖: + * - SkillSet.FieldSkillConfig —— 单条驻场技能配置 */ -import { _decorator, Node, Label, Button, resources, SpriteAtlas, Sprite } from "cc"; +import { _decorator, Label } 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 { TalentInfo, TalentType } from "../common/config/TalentSet"; -import { smc } from "../common/SingletonModuleComp"; +import { FieldSkillConfig } from "../common/config/SkillSet"; const { ccclass, property } = _decorator; +/** + * 将驻场配置值统一格式化为可读字符串。 + * 兼容"小数"(0.1 表示 10%)与"整数百分点"(10 表示 10%)两种口径。 + */ +function formatBuffValue(value: number): string { + if (Math.abs(value) < 1) { + return `${(value * 100).toFixed(0)}%`; + } + return value.toString(); +} + +/** TalentItemComp —— 驻场技能项组件 */ @ccclass('TalentItemComp') @ecs.register('TalentItem', false) export class TalentItemComp extends CCComp { - @property({ type: Label, tooltip: "天赋名称" }) + @property({ type: Label, tooltip: "驻场技能名称" }) lbl_name: Label = null!; - @property({ type: Node, tooltip: "图标节点" }) - icon_node: Node = null!; + @property({ type: Label, tooltip: "基础值(来自配置)" }) + lbl_base: Label = null!; - @property({ type: Label, tooltip: "描述" }) - lbl_desc: Label = null!; + @property({ type: Label, tooltip: "当前场上总加成(实时聚合)" }) + lbl_current: Label = null!; - @property({ type: Label, tooltip: "等级进度" }) - lbl_level: Label = null!; - - @property({ type: Label, tooltip: "升级消耗" }) - lbl_cost: Label = null!; - - @property({ type: Button, tooltip: "升级按钮" }) - btn_upgrade: Button = null!; - - @property({ type: Node, tooltip: "背景" }) - item_bg: Node = null!; - - @property({ type: Node, tooltip: "图标背景" }) - icon_bg: Node = null!; - - private _talentId: TalentType = TalentType.Attack; - private _onClickCallback: ((talentId: TalentType, currentLevel: number) => void) | null = null; - private _currentLevel: number = 0; - - private _talentInfo: TalentInfo | null = null; - - protected onLoad(): void { - if (this.btn_upgrade && this.btn_upgrade.node) { - this.btn_upgrade.node.on(Button.EventType.CLICK, this.onUpgradeClicked, this); - } - } + @property({ type: Label, tooltip: "驻场技能描述" }) + lbl_info: Label = null!; /** - * 更新天赋项显示 - * @param talentInfo 天赋配置数据 - * @param currentLevel 当前等级 - * @param onClickCallback 点击升级按钮的回调 + * 刷新单条驻场技能展示 + * @param config FieldSkillSet 中的单条配置 + * @param currentTotal 当前场上同 type 累加值(实时聚合) */ - public updateItem(talentInfo: TalentInfo, currentLevel: number, onClickCallback: (talentId: TalentType, currentLevel: number) => void) { - this._talentInfo = talentInfo; - this._talentId = talentInfo.id; - this._currentLevel = currentLevel; - this._onClickCallback = onClickCallback; - - if (this.lbl_name) { - this.lbl_name.string = talentInfo.name; - } - - // 同步尝试刷新一次图标(如果图集已经缓存过,比如重新打开界面时) - this.refreshIcon(); - - if (this.lbl_desc) { - let currentVal = currentLevel === 0 ? 0 : talentInfo.values[currentLevel - 1]; - this.lbl_desc.string = talentInfo.desc.replace('{value}', currentVal.toString()); - } - - if (this.lbl_level) { - this.lbl_level.string = `${currentLevel}/${talentInfo.maxLevel}`; - } - - let isMax = currentLevel >= talentInfo.maxLevel; - let cost = isMax ? 0 : (talentInfo.costs[currentLevel] ?? 0); - let canUpgrade = !isMax && smc.vmdata.gold >= cost; - - if (this.lbl_cost) { - this.lbl_cost.string = isMax ? "已满级" : `${cost}`; - } - - if (this.btn_upgrade) { - this.btn_upgrade.interactable = canUpgrade; - } + public updateItem(config: FieldSkillConfig, currentTotal: number): void { + if (!config) return; + if (this.lbl_name) this.lbl_name.string = config.name ?? ""; + if (this.lbl_base) this.lbl_base.string = `基础 +${formatBuffValue(config.value)}`; + if (this.lbl_current) this.lbl_current.string = `当前 +${formatBuffValue(currentTotal)}`; + if (this.lbl_info) this.lbl_info.string = config.info ?? ""; } - /** 单独更新图标,供父节点加载完图集后回调或自身更新时调用 */ - public refreshIcon() { - if (!this._talentInfo || !this.icon_node || !this._talentInfo.icon) return; - - if (smc.uiconsAtlas && smc.uiconsAtlas.spriteFrames) { - // 确保 icon 是字符串类型,防止配置写成纯数字导致底层 getSpriteFrame 报错 - const iconStr = String(this._talentInfo.icon); - const frame = smc.uiconsAtlas.getSpriteFrame(iconStr); - if (frame && this.icon_node.isValid) { - const sprite = this.icon_node.getComponent(Sprite) || this.icon_node.addComponent(Sprite); - sprite.spriteFrame = frame; - } - } - } - - private onUpgradeClicked() { - if (this._onClickCallback) { - this._onClickCallback(this._talentId, this._currentLevel); - } - } - - protected onDestroy(): void { - super.onDestroy(); - if (this.btn_upgrade && this.btn_upgrade.node && this.btn_upgrade.node.isValid) { - this.btn_upgrade.node.off(Button.EventType.CLICK, this.onUpgradeClicked, this); - } - } - - /** ECS 组件移除时销毁节点 */ + /** ECS 组件移除时销毁节点(CCComp 抽象方法实现) */ reset() { this.node.destroy(); } diff --git a/assets/script/game/map/TalentsComp.ts b/assets/script/game/map/TalentsComp.ts index b9a395ef..d802b4c2 100644 --- a/assets/script/game/map/TalentsComp.ts +++ b/assets/script/game/map/TalentsComp.ts @@ -1,268 +1,90 @@ /** * @file TalentsComp.ts - * @description 天赋系统页面组件(UI 视图层) + * @description 驻场技能信息展示页组件(UI 视图层) * * 职责: - * 1. 展示玩家等级、当前经验、进度条、金币。 - * 2. 展示天赋列表及每个天赋的当前等级。 - * 3. 处理天赋升级点击事件,扣除金币并保存。 - * 4. 处理重置天赋(看广告)功能。 - * - * 关键设计: - * - 通过 MissionHomeComp 页面切换显示,节点 active 控制显隐。 - * - onAdded(args) 接收参数时刷新界面。 + * 1. 展示当前所有 FieldSkillSet 配置项的名称、基础值、当前场上总加成。 + * 2. 通过 FieldSkillHelper 实时聚合英雄驻场数据并下发给每个 TalentItemComp。 + * 3. 兼容旧的 `@ecs.register('Talents')` 资源引用。 * * 依赖: - * - MissionHomeComp —— 通过节点 active 显隐控制页面切换 - * - smc.collection —— 玩家数据 - * - TalentConfig(TalentSet)—— 天赋配置 + * - SkillSet(FieldSkillSet / FieldSkillConfig)—— 驻场技能配置 + * - FieldSkillHelper —— 场上英雄驻场技能聚合 + * - TalentItemComp —— 单条驻场技能项视图 */ -import { _decorator, Node, Label, Button, ProgressBar, instantiate, Prefab } from "cc"; +import { _decorator, instantiate, Node, Prefab } 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 { mLogger } from "../common/Logger"; -import { smc } from "../common/SingletonModuleComp"; -import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops"; -import { TalentConfig, TalentInfo, TalentType } from "../common/config/TalentSet"; +import { FieldSkillSet, FieldSkillConfig } from "../common/config/SkillSet"; +import { FieldSkillHelper } from "../hero/FieldSkillHelper"; import { TalentItemComp } from "./TalentItemComp"; const { ccclass, property } = _decorator; -/** - * TalentsComp —— 天赋系统界面组件 - * - * 职责: - * 1. 展示玩家等级、当前经验、进度条、金币。 - * 2. 展示天赋列表及每个天赋的当前等级。 - * 3. 处理天赋升级点击事件,扣除金币并保存。 - * 4. 处理重置天赋(看广告)功能。 - */ +/** TalentsComp —— 驻场技能信息页组件 */ @ccclass('TalentsComp') @ecs.register('Talents', false) export class TalentsComp extends CCComp { - @property({ type: Node, tooltip: "标题节点" }) - title_node: Node = null!; - - @property({ type: Label, tooltip: "玩家等级文本,例如 'Lv.12'" }) - lbl_level: Label = null!; - - @property({ type: Label, tooltip: "经验文本,例如 '150/200'" }) - lbl_exp: Label = null!; - - @property({ type: ProgressBar, tooltip: "经验进度条" }) - pb_exp: ProgressBar = null!; - - @property({ type: Label, tooltip: "当前金币文本" }) - lbl_points: Label = null!; - - @property({ type: Node, tooltip: "天赋列表容器,用于动态添加天赋项" }) + @property({ type: Node, tooltip: "驻场技能列表容器" }) talents_content: Node = null!; - @property({ type: Prefab, tooltip: "" }) + @property({ type: Prefab, tooltip: "单条驻场技能项预制" }) prefab_talent_item: Prefab = null!; - @property({ type: Button, tooltip: "看广告重置天赋按钮" }) - btn_reset: Button = null!; - - @property({ type: Button, tooltip: "返回按钮" }) - btn_close: Button = null!; - - - /** 调试日志开关 */ debugMode: boolean = false; - - /** 最大玩家等级 */ - private readonly MAX_PLAYER_LEVEL = 30; - protected onLoad(): void { - if (this.btn_reset && this.btn_reset.node) { - this.btn_reset.node.on(Button.EventType.CLICK, this.onResetClicked, this); - } - if (this.btn_close && this.btn_close.node) { - this.btn_close.node.on(Button.EventType.CLICK, this.onCloseClicked, this); - } - } + /** 首次实例化缓存 */ + private rendered: boolean = false; + + /** 缓存的稳定配置顺序,避免重复渲染时列表抖动 */ + private cachedConfigs: FieldSkillConfig[] = []; protected onEnable(): void { this.refreshUI(); } - /** 刷新整体界面 */ - private refreshUI() { - this.updatePlayerInfo(); - this.updateTalentList(); - } - - /** 更新玩家等级、经验、金币信息 */ - private updatePlayerInfo() { - const collection = smc.collection; - let level = collection.player_level || 1; - let exp = collection.player_exp || 0; - - // 限制最大等级 - if (level > this.MAX_PLAYER_LEVEL) { - level = this.MAX_PLAYER_LEVEL; - } - - if (this.lbl_level) this.lbl_level.string = `Lv.${level}`; - if (this.lbl_points) this.lbl_points.string = `金币: ${smc.vmdata.gold}`; - - // 计算当前等级升级所需经验 - let expRequired = this.getExpRequirement(level); - - if (level >= this.MAX_PLAYER_LEVEL) { - if (this.lbl_exp) this.lbl_exp.string = "已满级"; - if (this.pb_exp) this.pb_exp.progress = 1; - } else { - if (this.lbl_exp) this.lbl_exp.string = `${exp}/${expRequired}`; - if (this.pb_exp) this.pb_exp.progress = exp / expRequired; - } - } - - /** 获取对应等级的升级所需经验 */ - private getExpRequirement(level: number): number { - for (let config of TalentConfig.expRequirements) { - if (level <= config.maxLevel) { - return config.expPerLevel; - } - } - return TalentConfig.expRequirements[TalentConfig.expRequirements.length - 1].expPerLevel; - } - - /** 动态生成或更新天赋列表 */ - private updateTalentList() { + /** 重新拉取最新数据并刷新所有子项 */ + public refreshUI(): void { if (!this.talents_content || !this.prefab_talent_item) return; - const collection = smc.collection; - - // 如果内容为空,则实例化预制体 - if (this.talents_content.children.length === 0) { - TalentConfig.talents.forEach(talentInfo => { - let itemNode = instantiate(this.prefab_talent_item); + // 第一次:实例化所有子节点;之后只更新数据 + if (!this.rendered) { + this.cachedConfigs = Object.values(FieldSkillSet) + .sort((a, b) => a.uuid - b.uuid); + this.cachedConfigs.forEach((cfg) => { + const itemNode = instantiate(this.prefab_talent_item); this.talents_content.addChild(itemNode); - let comp = itemNode.getComponent(TalentItemComp); + const comp = itemNode.getComponent(TalentItemComp); if (comp) { - comp.updateItem(talentInfo, collection.talents[talentInfo.id as TalentType], this.onUpgradeClicked.bind(this)); + comp.updateItem(cfg, 0); } }); - } else { - // 否则直接更新现有节点 - TalentConfig.talents.forEach((talentInfo, index) => { - let itemNode = this.talents_content.children[index]; - if (itemNode) { - let comp = itemNode.getComponent(TalentItemComp); - if (comp) { - comp.updateItem(talentInfo, collection.talents[talentInfo.id as TalentType], this.onUpgradeClicked.bind(this)); - } - } - }); - } - } - - /** 点击升级按钮 */ - private onUpgradeClicked(talentId: TalentType, currentLevel: number) { - const collection = smc.collection; - const talentInfo = TalentConfig.talents.find(t => t.id === talentId); - if (!talentInfo) { - oops.gui.toast("天赋配置不存在"); - return; + this.rendered = true; } - if (currentLevel >= talentInfo.maxLevel) { - oops.gui.toast("该天赋已满级"); - return; - } - - const cost = talentInfo.costs[currentLevel] ?? 0; - - if (smc.vmdata.gold >= cost) { - // 1. 扣除金币消耗 - smc.updateGold(-cost); - // 2. 更新等级 - collection.talents[talentId] = currentLevel + 1; - - // 3. 同步数据(通过 SingletonModuleComp 新增的机制,这里会触发标记脏数据并自动尝试云端同步) - smc.updateCloudData(); - - // 4. 刷新 UI - this.refreshUI(); - - oops.gui.toast("天赋升级成功"); - } else { - oops.gui.toast("金币不足"); - } - } - - /** 点击重置按钮 */ - private onResetClicked() { - // 看广告回调(预留) - this.watch_ad().then(success => { - if (success) { - const collection = smc.collection; - // 计算已消耗金币并返还 - 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++) { - refundedGold += talentInfo.costs[i] ?? 0; - } - } - } - - // 重置天赋等级并返还金币 - for (let k in collection.talents) { - collection.talents[k as any as TalentType] = 0; - } - - if (refundedGold > 0) { - smc.updateGold(refundedGold); - } - - // 同步到云端 - smc.updateCloudData(); - - // 刷新界面 - this.refreshUI(); - oops.gui.toast("天赋已重置,金币已返还"); - } else { - oops.gui.toast("广告观看失败,无法重置"); - } + // 按相同顺序回填最新场上聚合值 + this.cachedConfigs.forEach((cfg, index) => { + const child = this.talents_content.children[index]; + if (!child) return; + const comp = child.getComponent(TalentItemComp); + if (!comp) return; + const total = FieldSkillHelper.getFieldSkillTotalValue(cfg.type); + comp.updateItem(cfg, total); }); } - /** 模拟看广告回调,实际项目中需要替换为真实的广告SDK调用 */ - private watch_ad(): Promise { - return new Promise((resolve) => { - // 模拟广告播放延迟 - setTimeout(() => { - resolve(true); - }, 500); - }); - } - - /** 点击返回按钮 */ - private onCloseClicked() { - this.node.active = false + /** ECS 组件移除时销毁节点 */ + reset() { + this.rendered = false; + this.cachedConfigs = []; + this.node.destroy(); } protected onDestroy(): void { super.onDestroy(); mLogger.log(this.debugMode, 'TalentsComp', "释放界面"); - if (this.btn_reset && this.btn_reset.node && this.btn_reset.node.isValid) { - this.btn_reset.node.off(Button.EventType.CLICK, this.onResetClicked, this); - } - if (this.btn_close && this.btn_close.node && this.btn_close.node.isValid) { - this.btn_close.node.off(Button.EventType.CLICK, this.onCloseClicked, this); - } - } - - /** ECS 组件移除时销毁节点 */ - reset() { - this.node.destroy(); } }