diff --git a/assets/script/game/common/SingletonModuleComp.ts b/assets/script/game/common/SingletonModuleComp.ts index fdce20a8..ebaa27a8 100644 --- a/assets/script/game/common/SingletonModuleComp.ts +++ b/assets/script/game/common/SingletonModuleComp.ts @@ -92,16 +92,35 @@ export class SingletonModuleComp extends ecs.Comp { // 生存统计 heal_total: 0, // 治疗总量 lifesteal_total: 0, // 吸血总量 + shield_block_count: 0, + dead_trigger_count: 0, // 资源统计 exp_total: 0, // 经验总数 gold_total: 0, // 金币总数 + gold_earned: 0, + gold_spent: 0, + refresh_count: 0, + refresh_hit_count: 0, // 击杀统计 melee_kill_count: 0, // 近战怪击杀数量 remote_kill_count: 0, // 远程怪击杀数量 elite_kill_count: 0, // 精英怪击杀数量 boss_kill_count: 0, // Boss击杀数 + + // 战绩统计 + wave_win_count: 0, + wave_remain_monsters: 0, + wave_all_alive_count: 0, + passed_wave_20: false, + highest_dmg: 0, + + score_combat: 0, + score_output: 0, + score_defense: 0, + score_build: 0, + score_efficiency: 0, } as GameScoreStats, gold: 0, // 金币数据(MVVM绑定字段) @@ -120,6 +139,51 @@ export class SingletonModuleComp extends ecs.Comp { } } + /** + * 重置单局评分数据 + * 在每次新战斗开始时调用,确保上一局的得分不会带入新一局 + */ + resetScores() { + this.vmdata.scores = { + score: 0, + crt_count: 0, + wf_count: 0, + dod_count: 0, + back_count: 0, + stun_count: 0, + freeze_count: 0, + total_dmg: 0, + atk_count: 0, + avg_dmg: 0, + thorns_dmg: 0, + crit_dmg_total: 0, + heal_total: 0, + lifesteal_total: 0, + shield_block_count: 0, + dead_trigger_count: 0, + exp_total: 0, + gold_total: 0, + gold_earned: 0, + gold_spent: 0, + refresh_count: 0, + refresh_hit_count: 0, + melee_kill_count: 0, + remote_kill_count: 0, + elite_kill_count: 0, + boss_kill_count: 0, + wave_win_count: 0, + wave_remain_monsters: 0, + wave_all_alive_count: 0, + passed_wave_20: false, + highest_dmg: 0, + score_combat: 0, + score_output: 0, + score_defense: 0, + score_build: 0, + score_efficiency: 0, + } as GameScoreStats; + } + // ==================== 数据管理方法 ==================== /** diff --git a/assets/script/game/common/config/HeroAttrs.ts b/assets/script/game/common/config/HeroAttrs.ts index 08a743fe..75bcd55c 100644 --- a/assets/script/game/common/config/HeroAttrs.ts +++ b/assets/script/game/common/config/HeroAttrs.ts @@ -54,16 +54,36 @@ export interface GameScoreStats { // 生存统计 heal_total: number; // 治疗总量 lifesteal_total: number;// 吸血总量 + shield_block_count: number; // 格挡次数 + dead_trigger_count: number; // 死亡触发次数 // 资源统计 exp_total: number; // 经验总数 gold_total: number; // 金币总数 + gold_earned: number; // 总收入金币 + gold_spent: number; // 消耗金币 + refresh_count: number; // 刷新次数 + refresh_hit_count: number; // 刷新命中次数(刷新后选中卡) // 击杀统计 melee_kill_count: number; // 近战怪击杀数量 remote_kill_count: number; // 远程怪击杀数量 elite_kill_count: number; // 精英怪击杀数量 boss_kill_count: number; // Boss击杀数 + + // 战绩统计 + wave_win_count: number; // 回合胜利次数 + wave_remain_monsters: number; // 累计每回合留存敌人数量 + wave_all_alive_count: number; // 全员存活胜利次数 + passed_wave_20: boolean; // 是否通过第20回合 + highest_dmg: number; // 单次最高伤害 + + // 最终结算分 + score_combat: number; + score_output: number; + score_defense: number; + score_build: number; + score_efficiency: number; } diff --git a/assets/script/game/common/config/scoring-system.md.meta b/assets/script/game/common/config/scoring-system.md.meta new file mode 100644 index 00000000..ffe6cd8b --- /dev/null +++ b/assets/script/game/common/config/scoring-system.md.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.0.1", + "importer": "text", + "imported": true, + "uuid": "8127fa12-7890-4bad-9dff-eeb9e1e4d6c9", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/hero/HeroAtkSystem.ts b/assets/script/game/hero/HeroAtkSystem.ts index f3982f3f..a13489ac 100644 --- a/assets/script/game/hero/HeroAtkSystem.ts +++ b/assets/script/game/hero/HeroAtkSystem.ts @@ -144,12 +144,20 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd if (isCrit) { damage = Math.floor(damage * (1 + FightSet.CRIT_DAMAGE / 100)); reDate.isCrit=true; - + if (damageEvent.Attrs.fac === FacSet.HERO) { + // 【评分系统 - 输出分】统计暴击次数与暴击造成的总伤害 + smc.vmdata.scores.crt_count++; + smc.vmdata.scores.crit_dmg_total += damage; + } } mLogger.log(this.debugMode, 'HeroAtkSystem', " after crit",damage) // 护盾吸收 const shieldResult = this.absorbShield(TAttrsComp, damage); damage = shieldResult.remainingDamage; + if (shieldResult.absorbedDamage > 0) { + // 【评分系统 - 防御分】统计护盾成功抵挡伤害的次数 + smc.vmdata.scores.shield_block_count += shieldResult.absorbedDamage; + } mLogger.log(this.debugMode, 'HeroAtkSystem', " after shield",damage) // 显示护盾吸收飘字 if (shieldResult.absorbedDamage > 0 && targetView) { @@ -160,6 +168,14 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd // TAttrsComp.hp -= damage; // 应用伤害到数据层 TAttrsComp.add_hp(-damage); // 使用 add_hp 以触发 dirty_hp 和 UI 更新 + if (damageEvent.Attrs.fac === FacSet.HERO) { + // 【评分系统 - 输出分】统计团队造成的总伤害以及单次最高伤害记录 + smc.vmdata.scores.total_dmg += damage; + if (damage > smc.vmdata.scores.highest_dmg) { + smc.vmdata.scores.highest_dmg = damage; + } + } + // 增加受击计数并触发 atked 技能 TAttrsComp.atked_count++; this.checkAndTriggerAtkedSkills(TAttrsComp, targetView); @@ -301,6 +317,10 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd for (let i = 0; i < triggerCount; i++) { TAttrsComp.dead.forEach((uuid: number) => { + if (TAttrsComp.fac === FacSet.HERO) { + // 【评分系统 - 防御分】统计死亡触发技能的生效次数 + smc.vmdata.scores.dead_trigger_count++; + } oops.message.dispatchEvent(GameEvent.TriggerSkill, { s_uuid: uuid, heroAttrs: TAttrsComp, diff --git a/assets/script/game/hero/SCastSystem.ts b/assets/script/game/hero/SCastSystem.ts index 23db7ae7..11ea3d10 100644 --- a/assets/script/game/hero/SCastSystem.ts +++ b/assets/script/game/hero/SCastSystem.ts @@ -429,6 +429,10 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate const addHp = Math.floor(sAp*_cAttrsComp.ap/100); model.add_hp(addHp); target.health(addHp); + if (_cAttrsComp.fac === FacSet.HERO) { + // 【评分系统 - 防御分】统计团队造成的总治疗量 + smc.vmdata.scores.heal_total += addHp; + } } else if (kind === SkillKind.Shield && sAp !== 0) { const addShield = Math.max(0, Math.floor(sAp)); model.add_shield(addShield); diff --git a/assets/script/game/map/CardComp.ts b/assets/script/game/map/CardComp.ts index cc3ad939..a23607ff 100644 --- a/assets/script/game/map/CardComp.ts +++ b/assets/script/game/map/CardComp.ts @@ -331,6 +331,10 @@ export class CardComp extends CCComp { } // 扣除金币 this.setMissionCoin(currentCoin - cardCost); + // 【评分系统 - 效率分】记录购卡消耗的金币,以及刷新后的选中卡次数(命中率分子) + smc.vmdata.scores.gold_spent += cardCost; + smc.vmdata.scores.refresh_hit_count++; + oops.message.dispatchEvent(GameEvent.CoinAdd, { syncOnly: true, delta: -cardCost diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index 2c8fabd3..d40cc6e8 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -298,6 +298,9 @@ export class MissionCardComp extends CCComp { const v = typeof payload === 'number' ? payload : (payload?.delta ?? payload?.value ?? 0); if (v === 0) return; this.setMissionCoin(this.getMissionCoin() + v); + // 【评分系统 - 效率分】精确统计整局游戏的总收入与额外支出(如卖卡、技能扣费等) + if (v > 0) smc.vmdata.scores.gold_earned += v; + else smc.vmdata.scores.gold_spent -= v; this.updateCoinAndCostUI(); this.playCoinChangeAnim(v > 0); } @@ -554,6 +557,9 @@ export class MissionCardComp extends CCComp { return; } this.setMissionCoin(currentCoin - cost); + // 【评分系统 - 效率分】记录因刷新卡池消耗的金币,以及刷新次数 + smc.vmdata.scores.gold_spent += cost; + smc.vmdata.scores.refresh_count++; this.playCoinChangeAnim(false); this.updateCoinAndCostUI(); mLogger.log(this.debugMode, "MissionCardComp", "click draw", { diff --git a/assets/script/game/map/MissionComp.ts b/assets/script/game/map/MissionComp.ts index 7399964a..47fca7c0 100644 --- a/assets/script/game/map/MissionComp.ts +++ b/assets/script/game/map/MissionComp.ts @@ -445,6 +445,33 @@ export class MissionComp extends CCComp { smc.mission.in_fight = false; smc.vmdata.mission_data.in_fight = false; smc.mission.stop_spawn_mon = true; + + if (smc.mission.play && !smc.mission.pause) { + // 【评分系统 - 战绩分】每回合胜利加分 + smc.vmdata.scores.wave_win_count++; + // 【评分系统 - 战绩分】记录每回合结束时场上留存的敌人数量(扣分项) + smc.vmdata.scores.wave_remain_monsters += smc.vmdata.mission_data.mon_num; + + let allAlive = true; + let hasHero = false; + ecs.query(this.heroAttrsMatcher).forEach(entity => { + const attrs = entity.get(HeroAttrsComp); + if (attrs && attrs.fac === FacSet.HERO) { + hasHero = true; + if (attrs.is_dead) allAlive = false; + } + }); + // 【评分系统 - 战绩分】记录全员存活的胜利回合数(额外加分) + if (hasHero && allAlive) { + smc.vmdata.scores.wave_all_alive_count++; + } + + // 【评分系统 - 战绩分】判断是否通过最后一关(第20回合) + if (this.currentWave === 20) { + smc.vmdata.scores.passed_wave_20 = true; + } + } + // 触发战斗结束技能(fend) this.triggerHeroBattleSkills(false); @@ -682,7 +709,13 @@ export class MissionComp extends CCComp { this.heapTrendBaseMB = -1; this.monsterCountSyncTimer = 0; this.lastPrepareCoinWave = 0; + + // 重置所有的战局得分数据,防止上一局的数据污染 + smc.resetScores(); + smc.vmdata.mission_data.coin = Math.max(0, Math.floor(CardInitCoins)); + // 【评分系统 - 效率分】记录初始获得的金币收入 + smc.vmdata.scores.gold_earned += smc.vmdata.mission_data.coin; } // ======================== 波次管理 ======================== @@ -761,6 +794,8 @@ export class MissionComp extends CCComp { return; } smc.vmdata.mission_data.coin = Math.max(0, Math.floor((smc.vmdata.mission_data.coin ?? 0) + reward)); + // 【评分系统 - 效率分】记录每波次发放的金币奖励收入 + smc.vmdata.scores.gold_earned += reward; this.lastPrepareCoinWave = wave; oops.message.dispatchEvent(GameEvent.CoinAdd, { delta: reward, syncOnly: true }); diff --git a/assets/script/game/map/VictoryComp.ts b/assets/script/game/map/VictoryComp.ts index 94c3d6d4..19c9af16 100644 --- a/assets/script/game/map/VictoryComp.ts +++ b/assets/script/game/map/VictoryComp.ts @@ -48,6 +48,32 @@ export class VictoryComp extends CCComp { @property(Node) mvp_node=null! + + // ======================== 结算 UI 绑定 ======================== + @property({ type: Label, tooltip: "总分文本" }) + total_score_label: Label = null!; + + @property({ type: Node, tooltip: "战绩分节点 (需包含 score_label 和 progress_bar 子节点)" }) + combat_node: Node = null!; + + @property({ type: Node, tooltip: "输出分节点 (需包含 score_label 和 progress_bar 子节点)" }) + output_node: Node = null!; + + @property({ type: Node, tooltip: "防御分节点 (需包含 score_label 和 progress_bar 子节点)" }) + defense_node: Node = null!; + + @property({ type: Node, tooltip: "构建分节点 (需包含 score_label 和 progress_bar 子节点)" }) + build_node: Node = null!; + + @property({ type: Node, tooltip: "效率分节点 (需包含 score_label 和 progress_bar 子节点)" }) + efficiency_node: Node = null!; + + @property({ type: Node, tooltip: "亮点成就标签的容器" }) + highlights_container: Node = null!; + + @property({ type: Prefab, tooltip: "亮点成就标签预制体" }) + highlight_prefab: Prefab = null!; + /** 调试日志开关 */ debugMode: boolean = false; /** 奖励等级(预留) */ @@ -334,39 +360,41 @@ export class VictoryComp extends CCComp { */ private calculateTotalScore() { const s = smc.vmdata.scores; - let totalScore = 0; + + // 1. 战绩分:衡量生存能力——活几回合、赢几场。 + s.score_combat = (s.wave_win_count * 100) + - (s.wave_remain_monsters * 15) + + (s.wave_all_alive_count * 50) + + (s.passed_wave_20 ? 500 : 0); - // 1. 战斗行为分 - totalScore += s.crt_count * ScoreWeights.CRT_KILL; - totalScore += s.wf_count * ScoreWeights.WF_TRIGGER; - totalScore += s.dod_count * ScoreWeights.DODGE_SUCCESS; - totalScore += s.back_count * ScoreWeights.BACK_SUCCESS; - totalScore += s.stun_count * ScoreWeights.STUN_SUCCESS; - totalScore += s.freeze_count * ScoreWeights.FREEZE_SUCCESS; + // 2. 输出分:衡量伤害能力——团队火力如何。 + s.score_output = Math.floor(s.total_dmg / 100) * 10 + + (s.crt_count * 5) + + (s.wf_count * 5) + + (s.highest_dmg * 2); - // 2. 伤害转化分 - totalScore += s.total_dmg * ScoreWeights.DMG_FACTOR; - totalScore += s.avg_dmg * ScoreWeights.AVG_DMG_FACTOR; - totalScore += s.thorns_dmg * ScoreWeights.THORNS_DMG_FACTOR; - totalScore += s.crit_dmg_total * ScoreWeights.CRIT_DMG_FACTOR; + // 3. 防御分:衡量生存能力——团队多能扛。 + s.score_defense = (s.shield_block_count * 5) + + Math.floor(s.heal_total / 50) * 10 + + (s.dead_trigger_count * 8); - // 3. 击杀得分 - totalScore += s.melee_kill_count * ScoreWeights.KILL_MELEE; - totalScore += s.remote_kill_count * ScoreWeights.KILL_REMOTE; - totalScore += s.elite_kill_count * ScoreWeights.KILL_ELITE; - totalScore += s.boss_kill_count * ScoreWeights.KILL_BOSS; + // 4. 构建分:衡量阵容构建质量——团队配合程度。 + s.score_build = 0; // 待完善 - // 4. 生存得分 - totalScore += s.heal_total * ScoreWeights.HEAL_FACTOR; - totalScore += s.lifesteal_total * ScoreWeights.LIFESTEAL_FACTOR; - - // 5. 资源得分 - totalScore += s.exp_total * ScoreWeights.EXP_FACTOR; - totalScore += s.gold_total * ScoreWeights.GOLD_FACTOR; + // 5. 效率分:衡量资源利用——金币花得值不值。 + const goldRatio = s.gold_earned > 0 ? (s.gold_spent / s.gold_earned) : 0; + const refreshRatio = s.refresh_count > 0 ? (s.refresh_hit_count / s.refresh_count) : 0; + s.score_efficiency = Math.floor(goldRatio * 100) + Math.floor(refreshRatio * 50); // 取整并存储 - s.score = Math.floor(totalScore); - mLogger.log(this.debugMode, 'VictoryComp', `[VictoryComp] 结算总分: ${s.score}`); + s.score = Math.floor(s.score_combat + s.score_output + s.score_defense + s.score_build + s.score_efficiency); + mLogger.log(this.debugMode, 'VictoryComp', `[VictoryComp] 结算总分: ${s.score}`, { + combat: s.score_combat, + output: s.score_output, + defense: s.score_defense, + build: s.score_build, + efficiency: s.score_efficiency + }); } // ======================== 操作入口 ========================