refactor(game): 重构结算积分与奖励逻辑

1. 移除HeroAttrs中废弃的分数字段
2. 重构积分计算规则,简化为基础分+行为分
3. 调整胜利奖励计算逻辑,改用怪物等级作为成长参数替换原wave计数
4. 同步更新结算配置注释与日志输出
This commit is contained in:
pan
2026-09-11 16:25:28 +08:00
parent 5fe961846f
commit ea15b464ad
5 changed files with 42 additions and 78 deletions

View File

@@ -220,12 +220,6 @@ export class SingletonModuleComp extends ecs.Comp {
passed_wave_20: false, passed_wave_20: false,
highest_dmg: 0, highest_dmg: 0,
score_combat: 0,
score_output: 0,
score_defense: 0,
score_build: 0,
score_efficiency: 0,
achieved_highlights: [], achieved_highlights: [],
} as GameScoreStats, } as GameScoreStats,
@@ -293,11 +287,6 @@ export class SingletonModuleComp extends ecs.Comp {
wave_all_alive_count: 0, wave_all_alive_count: 0,
passed_wave_20: false, passed_wave_20: false,
highest_dmg: 0, highest_dmg: 0,
score_combat: 0,
score_output: 0,
score_defense: 0,
score_build: 0,
score_efficiency: 0,
achieved_highlights: [], achieved_highlights: [],
} as GameScoreStats; } as GameScoreStats;
} }

View File

@@ -219,14 +219,14 @@ export function getLvColor(lv: number): Color {
export const RARITY_BG_COLORS: string[] = ["green", "blue", "purple", "yellow", "red"]; export const RARITY_BG_COLORS: string[] = ["green", "blue", "purple", "yellow", "red"];
/** /**
* 战斗结算奖励配置(每局结束后发放:英雄碎片 + 金币 + 天赋经验)。 * 战斗结算奖励配置(每节结束后发放:英雄碎片 + 金币 + 天赋经验)。
* 数值均为"失败基准",胜利时整体乘以 victory_multiplier。 * 胜利奖励随关卡进度(怪物等级 = 大关号)线性增长,整体乘以 victory_multiplier。
*/ */
export const VICTORY_REWARD_SET = { export const VICTORY_REWARD_SET = {
/** 金币:base + wave_win_count × per_wave */ /** 金币:base + monLv × per_lv */
gold: { base: 100, per_wave: 50 }, gold: { base: 100, per_lv: 50 },
/** 天赋经验:base + wave_win_count × per_wave */ /** 天赋经验:base + monLv × per_lv */
talent_exp: { base: 20, per_wave: 10 }, talent_exp: { base: 20, per_lv: 10 },
/** 每个出场英雄保底碎片数 */ /** 每个出场英雄保底碎片数 */
played_fragment: 1, played_fragment: 1,
/** 随机英雄碎片数量(发放给随机抽中的 1 个英雄) */ /** 随机英雄碎片数量(发放给随机抽中的 1 个英雄) */

View File

@@ -100,13 +100,6 @@ export interface GameScoreStats {
passed_wave_20: boolean; // 是否通过第20回合 passed_wave_20: boolean; // 是否通过第20回合
highest_dmg: number; // 单次最高伤害 highest_dmg: number; // 单次最高伤害
// 最终结算分
score_combat: number;
score_output: number;
score_defense: number;
score_build: number;
score_efficiency: number;
// 已达成的亮点成就记录 // 已达成的亮点成就记录
achieved_highlights: any[]; achieved_highlights: any[];
} }

View File

@@ -49,6 +49,7 @@ import { spawningEngine, WAVE_DURATION, BATTLE_TIMEOUT } from "./RogueConfig";
import { MissionMonCompComp } from "./MissionMonComp"; import { MissionMonCompComp } from "./MissionMonComp";
import { import {
getProgressText, getProgressText,
getMonsterLv,
} from "../common/config/StageSet"; } from "../common/config/StageSet";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@@ -638,6 +639,8 @@ export class MissionComp extends CCComp {
// 结算类型:小节胜利显示"下一节",末关通关显示"确认退出" + 通关标题 // 结算类型:小节胜利显示"下一节",末关通关显示"确认退出" + 通关标题
settle_type: this._settleType, settle_type: this._settleType,
settle_title: this._settleTitle, settle_title: this._settleTitle,
// 关卡难度度量(= 大关号,驱动胜利奖励随进度增长)
mon_lv: getMonsterLv(this.currentStage),
// 失败奖励判定数据:本节坚持时间 + 击杀统计(kills=已刷出-存活) // 失败奖励判定数据:本节坚持时间 + 击杀统计(kills=已刷出-存活)
defeat_stats: { defeat_stats: {
clear_time: this.clearTime, clear_time: this.clearTime,

View File

@@ -180,6 +180,9 @@ export class VictoryComp extends CCComp {
/** 失败奖励判定数据(MissionComp 传入:本节坚持时间 + 击杀统计) */ /** 失败奖励判定数据(MissionComp 传入:本节坚持时间 + 击杀统计) */
private defeatStats: { clear_time: number; kills: number; total: number } | null = null; private defeatStats: { clear_time: number; kills: number; total: number } | null = null;
/** 本节怪物等级(= 大关号,驱动胜利奖励随关卡进度增长) */
private monLv: number = 1;
/** 加载时隐藏 loading 遮罩并绑定按钮事件 */ /** 加载时隐藏 loading 遮罩并绑定按钮事件 */
protected onLoad(): void { protected onLoad(): void {
this.node.getChildByName("loading").active = false this.node.getChildByName("loading").active = false
@@ -225,6 +228,7 @@ export class VictoryComp extends CCComp {
this.settleType = args.settle_type ?? "defeat"; this.settleType = args.settle_type ?? "defeat";
this.isVictory = !!args.victory; this.isVictory = !!args.victory;
this.defeatStats = args.defeat_stats ?? null; this.defeatStats = args.defeat_stats ?? null;
this.monLv = Math.max(1, Number(args.mon_lv) || 1);
// 根据是否可复活决定按钮显示 // 根据是否可复活决定按钮显示
this.node.getChildByName("btns").getChildByName("next").active = !args.can_revive this.node.getChildByName("btns").getChildByName("next").active = !args.can_revive
@@ -312,58 +316,38 @@ export class VictoryComp extends CCComp {
* 计算单局总分并更新到 smc.vmdata.scores.score。 * 计算单局总分并更新到 smc.vmdata.scores.score。
* 同时刷新 smc.data.score 历史最高分。 * 同时刷新 smc.data.score 历史最高分。
* *
* @param isVictory 是否胜利(胜利 +10 分,失败不加分) * 积分规则:
* @param allAlive 是否全英雄存活获胜(额外 +5 分) * - 胜利 +10 分,全队存活 +5 分;失败 -10 分
* - 暴击/闪避/击晕/冰冻每次 +1 分,单项封顶 5 分(不分胜负均计入)
*
* @param isVictory 是否胜利
* @param allAlive 是否全英雄存活
*/ */
private calculateTotalScore(isVictory: boolean, allAlive: boolean) { private calculateTotalScore(isVictory: boolean, allAlive: boolean) {
const s = smc.vmdata.scores; const s = smc.vmdata.scores;
// 0. 胜负分:胜利 +10,全英雄存活获胜额外 +5,失败不加分 // 0. 胜负分:胜利 +10(全队存活再 +5),失败 -10
let score_result = 0; let base = 0;
if (isVictory) { if (isVictory) {
score_result = 10; base = 10;
if (allAlive) { if (allAlive) {
score_result += 5; base += 5;
} }
} else {
base = -10;
} }
// 1. 战绩分:衡量生存能力——活几回合、赢几场。 // 1. 战斗行为分:暴击/闪避/击晕/冰冻每次 +1,单项封顶 5
s.score_combat = (s.wave_win_count * 100) const critPts = Math.min(5, s.crt_count);
- (s.wave_remain_monsters * 15) const dodgePts = Math.min(5, s.dod_count);
+ (s.wave_all_alive_count * 50) const stunPts = Math.min(5, s.stun_count);
+ (s.passed_wave_20 ? 500 : 0); const freezePts = Math.min(5, s.freeze_count);
// 2. 输出分:衡量伤害能力——团队火力如何。 // 存储当前局分数
s.score_output = Math.floor(s.total_dmg / 100) * 10 s.score = Math.floor(base + critPts + dodgePts + stunPts + freezePts);
+ (s.crt_count * 5)
// 风怒机制已停用: 风怒次数不再计入输出分
// + (s.wf_count * 5)
+ (s.highest_dmg * 2);
// 3. 防御分:衡量生存能力——团队多能扛。 // 亮点成就判定:仅作结算页亮点标签展示,不计入总分
s.score_defense = (s.shield_block_count * 5) s.achieved_highlights = this.getAchievedHighlights(s);
+ Math.floor(s.heal_total / 50) * 10
+ (s.dead_trigger_count * 8);
// 4. 构建分:衡量阵容构建质量——团队配合程度。
s.score_build = 0; // 待完善
// 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);
// 6. 亮点成就额外加分 (按等级叠加)
const achieved = this.getAchievedHighlights(s);
s.achieved_highlights = achieved; // 记录已达成的亮点信息
let highlightBonus = 0;
for (const item of achieved) {
highlightBonus += item.config.scoreBonus;
}
// 取整并存储当前局分数
s.score = Math.floor(score_result + s.score_combat + s.score_output + s.score_defense + s.score_build + s.score_efficiency + highlightBonus);
// 记录滚动前的历史最高分(动画起始值) // 记录滚动前的历史最高分(动画起始值)
this._prevBestScore = smc.data.score; this._prevBestScore = smc.data.score;
@@ -384,13 +368,8 @@ export class VictoryComp extends CCComp {
(s as any).isNewRecord = isNewRecord; (s as any).isNewRecord = isNewRecord;
mLogger.log(this.debugMode, 'VictoryComp', `[VictoryComp] 结算总分: ${s.score}`, { mLogger.log(this.debugMode, 'VictoryComp', `[VictoryComp] 结算总分: ${s.score}`, {
combat: s.score_combat, base,
output: s.score_output, critPts, dodgePts, stunPts, freezePts,
defense: s.score_defense,
build: s.score_build,
efficiency: s.score_efficiency,
highlightBonus: highlightBonus,
achievedHighlights: achieved,
isNewRecord: isNewRecord, isNewRecord: isNewRecord,
prevBest: this._prevBestScore prevBest: this._prevBestScore
}); });
@@ -608,7 +587,9 @@ export class VictoryComp extends CCComp {
/** /**
* 计算并发放结算奖励,结果存入 _rewardResult。 * 计算并发放结算奖励,结果存入 _rewardResult。
* 胜利:原公式不变(base + wave × per_wave,整体 ×1.2)。 * 胜利:随关卡进度增长(base + monLv × per_lv,整体 ×1.2)。
* Why: 无回合概念后 wave_win_count 每节被 resetScores 清零恒为 1,
* 旧的 per_wave 增长失效,改挂怪物等级(= 大关号)随关卡推进线性增长。
* 失败:受"坚持时间 + 击杀比例"限制—— * 失败:受"坚持时间 + 击杀比例"限制——
* - 坚持 < 15s 或本节一只怪未杀 → 无任何奖励; * - 坚持 < 15s 或本节一只怪未杀 → 无任何奖励;
* - 达标 → 坚持奖励(固定)+ 击杀比例奖励(基数 × 本节击杀比例), * - 达标 → 坚持奖励(固定)+ 击杀比例奖励(基数 × 本节击杀比例),
@@ -616,9 +597,7 @@ export class VictoryComp extends CCComp {
* @param isVictory 是否胜利 * @param isVictory 是否胜利
*/ */
private grantRewards(isVictory: boolean) { private grantRewards(isVictory: boolean) {
const s = smc.vmdata.scores;
const cfg = VICTORY_REWARD_SET; const cfg = VICTORY_REWARD_SET;
const wave = Math.max(0, s.wave_win_count || 0);
// 失败奖励资格判定:坚持时间与击杀数不达标则整场无奖励 // 失败奖励资格判定:坚持时间与击杀数不达标则整场无奖励
let defeatQualified = false; let defeatQualified = false;
@@ -637,8 +616,8 @@ export class VictoryComp extends CCComp {
let talentExp: number; let talentExp: number;
if (isVictory) { if (isVictory) {
const mult = cfg.victory_multiplier; const mult = cfg.victory_multiplier;
gold = Math.floor((cfg.gold.base + wave * cfg.gold.per_wave) * mult); gold = Math.floor((cfg.gold.base + this.monLv * cfg.gold.per_lv) * mult);
talentExp = Math.floor((cfg.talent_exp.base + wave * cfg.talent_exp.per_wave) * mult); talentExp = Math.floor((cfg.talent_exp.base + this.monLv * cfg.talent_exp.per_lv) * mult);
} else if (defeatQualified) { } else if (defeatQualified) {
// 失败达标 = 坚持奖励(固定)+ 击杀比例奖励 // 失败达标 = 坚持奖励(固定)+ 击杀比例奖励
gold = Math.floor(cfg.defeat.survive.gold + cfg.defeat.kill.gold * killRatio); gold = Math.floor(cfg.defeat.survive.gold + cfg.defeat.kill.gold * killRatio);
@@ -690,7 +669,7 @@ export class VictoryComp extends CCComp {
this._rewardResult = { gold, talentExp, silver, diamond, fragments }; this._rewardResult = { gold, talentExp, silver, diamond, fragments };
mLogger.log(this.debugMode, 'VictoryComp', '[VictoryComp] 结算奖励发放', { mLogger.log(this.debugMode, 'VictoryComp', '[VictoryComp] 结算奖励发放', {
isVictory, wave, gold, talentExp, silver, diamond, fragments, isVictory, monLv: this.monLv, gold, talentExp, silver, diamond, fragments,
defeatStats: this.defeatStats, defeatQualified, killRatio, defeatStats: this.defeatStats, defeatQualified, killRatio,
}); });
} }