燃值
正正考核期 0 做的世界进度玩法:燃值积累条 + 燃值点(FirePoint)。 积累条是团队协作数值——三层加成(团队人数/战斗人数/安全区人数)全走配置表; 燃值点消耗完全在战斗服(Buff 体系),客户端零扣减逻辑。 页面虽小,但把「数值配置化 + 服务器权威边界」两件事做得很干净。
0 到 1
三层加成全配置化
双通道刷新
客户端零扣减
1. 工作清单
| 内容 | 性质 |
|---|---|
| 燃值系统 0 到 1:积累协议对接、三层加成计算、进度 UI(主界面火焰条)、燃值点属性通知 | 0 建 |
2. 理论:三层加成模型与职责边界
2.1 积累速率:三层加成的叠加结构
| 层 | 驱动 | 公式 | 上限 |
|---|---|---|---|
| ① 团队人数层 | isAllTeamMemberInSameLevel 且 playerCount > 1 | min(playerCount, param0) | max_fire_layer_limit_of_team_member |
| ② 战斗成员层 | 队友 battleState 为 BATTLE 的数量 | battleCount * param1 | (随总钳制) |
| ③ 安全区成员层 | 队友在安全区的数量 | min(safeCount * param2, param7) | max_fire_layer_limit_of_safe_zone_member |
| 总钳制 | 三层相加后 | AddLayer = min(AddLayer, param3) | fire_point_gather_max_layer |
加成层数 → 每跳增加量:查 fire_point_gather_layer_value 数组表(层号即下标)。 客户端每 3 秒一跳把增加量累进 fireGatherValue,到 fire_point_max_gather_value 封顶。
数值全部配置化的收益
七张 commonParam 配置(param0-param7)没有一个魔法数字写死在代码里:
策划调「安全区每人加几层」「团队最多算几人」全部改表;
数值策划调整节奏不用发版。「层数表」还顺带把非线性留了口子——
第 3 层加 15、第 4 层加 30,数值曲线自由。
2.2 职责切分:客户端算速率,服务器管一切权威值
1
服务器推送权威值
WorldFirePointGatherValueSCNotify:fireGatherValue / banFirePointCount / isAllTeamMemberInSameLevel
2
客户端平滑推进
面板 3 秒定时器跑 UpdateFirePointGatherValue,按三层加成逐跳累进(表现插值)
3
UI 双通道刷新
定时器跳一拍刷一次 + 服务器通知到达立刻刷(Emit FirePointGatherValueChange)
「客户端零扣减」的边界设计
客户端敢自己加 fireGatherValue(积累条可以客户端先走),
但燃值点的消耗(banFirePointCount、扣点)一行逻辑都没有——
消耗发生在战斗服 Buff 体系(燃值点挂 GameplayTag 的 Buff 上),
客户端只收 ATTRIBUTE_TYPE_FIRE_POINT 属性通知打日志。
判断标准:积累是表现(客户端可乐观推进),消耗是裁定(必须服务器)。
混了这个边界的模块,回放/断线重连时数值必对不上。
3. 代码片段
3.1 三层加成计算:每层独立钳制 + 总钳制
Src\Game\Module\FirePoint\FirePointModel.ts:95-131(节选)
public UpdateFirePointGatherValue(): void {
if (this.FirePointInfo === undefined) { return; }
// 人数加成:团队人数越多,加成层数越高(最多4层)
const playerCount = ModelManager.TeamModel?.GetTeamMemberNum();
if (playerCount === undefined) { return; }
this.AddLayer = 0; // 重置加成层数
if (this.FirePointInfo.isAllTeamMemberInSameLevel) {
if (playerCount > 1) {
const param0 = configCommonParamById.GetIntConfig('max_fire_layer_limit_of_team_member')!;
this.AddLayer += Math.min(playerCount, param0);
}
// 战斗中玩家加成:每个战斗中的玩家提供额外加成
const battleMemberCount = this.CountOfBattleMember();
const param1 = configCommonParamById.GetIntConfig('fire_point_battle_member_add_layer')!;
this.AddLayer += battleMemberCount * param1;
// 安全区中玩家加成:每个安全区中的玩家提供额外加成(限制最大4层)
const safeZoneMemberCount = this.CountOfSafeZoneMember();
const param2 = configCommonParamById.GetIntConfig('fire_point_safe_zone_member_add_layer')!;
const param7 = configCommonParamById.GetIntConfig('max_fire_layer_limit_of_safe_zone_member')!;
const safeZoneAddition = safeZoneMemberCount * param2;
this.AddLayer += Math.min(safeZoneAddition, param7);
}
// 最终加成层数不能超过配置上限
const param3 = configCommonParamById.GetIntConfig('fire_point_gather_max_layer')!;
this.AddLayer = Math.min(this.AddLayer, param3);
...
}
钳制的层次感
团队层 min(人数封顶)、安全区层 min(乘积封顶)、最后总 min(全层封顶)——
每层先各自钳,总和再钳一次。
这样任何一层配置改到离谱,AddLayer 都不会穿透 fire_point_gather_max_layer,
查表下标永远安全(见下一段的越界防御)。
3.2 层数查表 + 上限封顶
Src\Game\Module\FirePoint\FirePointModel.ts:133-167
// 根据加成层数增加燃值积累
const param4 = configCommonParamById.GetIntArrayConfig('fire_point_gather_layer_value')!;
if (this.FirePointInfo && this.AddLayer < param4.length) {
const add = param4[this.AddLayer];
this.FirePointInfo.fireGatherValue += add; //速率
}
// 检查是否达到燃值上限,达到则重置并标记需要分配燃值点
const param5 = configCommonParamById.GetIntConfig('fire_point_max_gather_value')!;
if (this.FirePointInfo && this.FirePointInfo.fireGatherValue >= param5) {
this.FirePointInfo.fireGatherValue = param5; // 重置燃值积累到上限
}
}
public GetFirePointGatherValue(): number {
if (!this.FirePointInfo) { return 0; }
const param5 = configCommonParamById.GetIntConfig('fire_point_max_gather_value')!;
if (this.FirePointInfo.fireGatherValue >= param5) {
return 1; // 返回上限值
}
return this.FirePointInfo.fireGatherValue / param5;
}
UI 读的是归一化进度
AddLayer < param4.length 的越界检查保证查表安全(配置表删行不会越界读 undefined);
GetFirePointGatherValue 返回 0-1 归一化进度而不是裸值——
UI 层 SetFillAmount 直接吃,表现和数值上限解耦(改上限不用动 UI)。
3.3 通知正发:Controller 薄得只有正发
Src\Game\Module\FirePoint\FirePointController.ts:31-48
// 燃值累计值更新通知
private readonly OnFirePointGatherValueNotify = (notify: Game.Protocol.WorldFirePointGatherValueSCNotify): void => {
const firePointInfo = ModelManager.FirePointModel!.GetInfo();
if (firePointInfo) {
firePointInfo.fireGatherValue = notify.fireGatherValue;
firePointInfo.banFirePointCount = notify.banFirePointCount;
firePointInfo.isAllTeamMemberInSameLevel = notify.isAllTeamMemberInSameLevel;
EventSystem.Emit(EEventName.FirePointGatherValueChange);
}
};
private readonly OnFirePointTeamMemberInfoNotify = (
notify: Game.Protocol.WorldFirePointTeamMemberInfoSCNotify,
): void => {
ModelManager.FirePointModel?.SetMemberInfo(notify.fireTeamMemberInfo);
};
MVC 分层的教科书样例
61 行的 Controller 只做三件事:注册两条通知、把权威值灌进 Model、Emit 事件。
没有一行数值计算。
服务器通知一到直接覆盖 fireGatherValue——
客户端 3 秒一跳的乐观推进随时被权威值校准,
两条通道(客户端速率 + 服务器校准)最终收敛。
3.4 UI 面板:3 秒定时器 + 事件双驱动
Src\Game\Module\MainUi\Views\ChildView\BattleUiFirePanel.ts:47-60, 94-100(节选)
protected override OnStart(): void {
super.OnStart();
this.Refresh();
this.RefreshTimer = this.OwnedTimers.Forever(this.OnTimerRefresh, 3000, 'FirePanelView.OnStart');
}
private readonly OnTimerRefresh = (): void => {
if (ModelManager.FirePointModel === undefined) { return; }
ModelManager.FirePointModel.UpdateFirePointGatherValue(); // 通道1:定时器推进积累
this.Refresh();
};
private readonly FirePointGatherValueChange = (): void => {
this.Refresh(); // 通道2:服务器通知立刻刷
};
private Refresh(): void {
if (ModelManager.FirePointModel === undefined) { return; }
const firePointValue = ModelManager.FirePointModel.GetFirePointGatherValue();
this.BarFlame?.SetFillAmount(firePointValue);
}
双通道刷新的取舍
只有服务器通知:积累条 3 秒一跳的服务器节奏推送太浪费;
只有客户端定时器:服务器改值(比如 ban 掉燃值点)UI 不同步。
两条并存:定时器管平滑推进,通知管权威校准。
定时器挂在 OwnedTimers 上(面板销毁自动回收),不会泄漏。
4. 面试怎么讲
燃值系统的加成模型是怎么设计的设计
三层加成叠加:团队人数层(全员同关卡才生效,min 人数封顶)、
战斗成员层(每个战斗中的队友加固定层)、安全区成员层(乘积后 min 封顶)。
三层各自钳制后再做一次总钳制。
所有数字全部走 commonParam 配置表,含一张「层数 → 每跳增量」数组表—— 数值曲线非线性也支持,策划调参零代码改动。 总钳制同时保证了查表下标永远不越界。
所有数字全部走 commonParam 配置表,含一张「层数 → 每跳增量」数组表—— 数值曲线非线性也支持,策划调参零代码改动。 总钳制同时保证了查表下标永远不越界。
客户端和服务端的数值边界怎么划关联
一句话:积累是表现,消耗是裁定。
积累条客户端每 3 秒按加成速率乐观推进(UI 平滑),
服务器随时推权威值直接覆盖校准(两条通道收敛);
燃值点的消耗在战斗服 Buff 体系(ATTRIBUTE_TYPE_FIRE_POINT 属性通知),
客户端一行扣减逻辑都没有,banFirePointCount 只读不写。
这个边界混了的代价很具体:断线重连/回放时数值必对不上, 多人场景里每个人的燃值点会各算各的。
这个边界混了的代价很具体:断线重连/回放时数值必对不上, 多人场景里每个人的燃值点会各算各的。