某竞技项目 · 技能系统占 39%+ 运行时开销的优化全纪录

性能优化 架构重构 Polaris Python 游戏技能系统

一、为什么要做技能重构?

技能系统是竞技游戏的核心战斗模块,每帧都在高频运转。当它成为性能瓶颈时,影响链路非常长 — 从客户端帧率到服务器承载、从用户体验到运营成本。本次重构的驱动力来自客户端服务器两个方向。

1.1 客户端:机型下探与卡顿分析

项目 M2 阶段的目标是将最低适配机型下探到骁龙 450 级别,长期目标是 430。然而优化初期的实机数据令人担忧:

时间节点设备(骁龙)帧率EarlyTick 开销备注
7.27 周版本45012.6 帧几乎不可玩
8.16 周版本45020 帧22.4msPython 相关为大头
8.16 周版本66030 帧9.9ms基准参考

帧率只是表象。通过 Profiler 抓取,我们进一步分析了帧率波动(卡顿)情况:

骁龙 660 vs 450 帧率波动对比(示意)
660 设备 稳定 450 设备 剧烈 450 设备卡顿幅度远超 660,峰谷差可达 40ms+

结合 Profile 数据,按严重程度排列,卡顿来源如下:

Polaris 技能开销
最大瓶颈
UI 卡顿
资源/动效加载
NPC AOI 进出
重连/Property
Shader 预编译

性能绝对值的组成也很清晰:

  1. Polaris 被动技能(范围检测、图标显示判定等)— 常驻高频 tick,数量最多
  2. Polaris 主动技能(震慑脉冲、近战搏击等)— 单次开销大,节点图复杂

1.2 服务器:成本对比与压力

与同品类成熟产品对比(取 7 月线上数据),服务器资源利用率差距明显:

指标本项目对标产品差距
单核 CPU 服务玩家数12 人50 人4.2x
单核 CPU 战斗局数2 局10 局5x
成本持平最低要求本项目至少需要达到 8 局/核心 才能与对标产品持平
项目利润 = 收入 − (服务器成本 + 人力成本 + 营销成本 + …)
技能系统占了服务器运行开销的大头。优化技能 = 直接降低每局战斗的 CPU 消耗 = 单核承载更多局数 = 服务器成本下降。这不是"锦上添花",而是生存线

1.3 关键结论:技能开销占比 39%+

服务器 CPU 开销分布(战斗场景)
39%+ 技能系统 技能相关 ≥39% 其他游戏逻辑 网络 / IO 其余
结论:无论从客户端帧率还是服务器成本看,技能系统都是必须攻克的性能高地。它占据了至少 39% 的运行时开销,且随着机型下探和玩家规模增长,问题只会更严重。

二、技能重构之最佳实践

优化分为两个维度:优化点 1 — 去掉 Polaris 框架开销(架构层面),优化点 2 — 重构技能逻辑本身(业务层面)。两者叠加才能取得最大收益。

2.1 优化点 1:去 Polaris — 方案对比与架构演进

Polaris 是项目现有的可视化技能编辑框架,策划通过拖拽节点图(.mth 文件)来配置技能逻辑。它对策划非常友好,但运行时存在显著的框架调度开销节点入口调用开销

四种替代方案评估

方案实现方式优化点迁移成本可控性
Polaris 原版 节点图驱动,Python 节点 — 基准
手写 Python 手写代码调用 System 接口 去掉框架 + 节点开销
Polaris 自动展开 工具将节点图编译为 Python 去掉节点调度算法
Polaris C++ 版 框架与节点均用 C++ 实现 语言层面提速

性能对比

Polaris 原版
100%(基准)
手写 Python
~25%
自动展开 Python
~45%
Polaris C++
~35%

最终选择手写 Python 方案:性能提升最显著,代码完全可控,且能渐进式迁移 — 新旧方案可以共存,逐步替换而不影响整体进度。

架构演进:Polaris → 手写 Python 双轨架构
旧架构(Polaris) 策划配置 .mth 节点图 Polaris 运行时 节点调度 + 入口开销 Python 节点逻辑 逐节点解释执行 底层 System 接口 框架调度 + 节点入口 = 额外开销 439 个 .mth 全部走此路径 渐进迁移 新架构(手写 Python 双轨) hybrid_skill_system 主动技能 非常驻型被动/机制 skill_system 常驻型被动/机制 挂载在 Space 上 技能实例(Class) 时间线 + 组件 Space System 事件驱动 / 降频 Tick skill_logic_component(组件式代码复用) 瞬发函数 + 持续性组件 底层 System 接口(共用) 无框架开销 · 直接调用 System · 与 Polaris 共存

2.2 两套手写体系:hybrid_skill_system & skill_system

根据技能的生命周期和运行特征,手写方案分为两套体系:

技能分类决策树
技能/机制类型? 非常驻(有生命周期) 常驻型(全局机制) 主动技能 148 个 .mth 非常驻被动/机制 行为43 + 状态机70 + 被动178 Space System 机制 体力回复 / 呼吸回血 / AFK / ... → hybrid_skill_system(实例化运行) → skill_system(Space 级)

hybrid_skill_system 需要处理的 .mth 文件总计 439 个

类别数量典型代表
主动技能148传送井盖、替身徽、震慑脉冲
技能行为43change_pose 等
状态机70
被动技能178高处下落踉跄等
关键设计决策:新旧方案可以共存。hybrid_skill_system 与 Polaris 并行运行,可以逐个技能迁移、逐步验证效果,不需要一次性全量替换,大幅降低了迁移风险。

2.3 核心设计原则与代码结构

原则一:一对一映射

一个 .mth 文件对应一个 .py 文件,命名规范清晰:

// Polaris 版本
change_pose.mth

// 手写 Python 版本
hybrid_change_pose.py

原则二:实例化运行

技能从"跑一个节点图"变为"跑一个 Class 实例"。每个技能实例拥有自己的状态和生命周期:

技能实例运行模型
创建 init() 运行(时间线) T=0: 播放动画 T=0.3: 伤害判定 T=0.8: 添加 Buff 组件驱动 component.tick() 销毁 cleanup() 整个过程直接调用 System 接口,无 Polaris 框架参与

原则三:System 接口组合

大部分原 Polaris 节点的逻辑已经封装在各种 System 接口中。手写版本直接调用这些接口,跳过 Polaris 的调度层,实现相同功能但开销更低。

原则四:组件式代码复用

对于跨技能的通用逻辑,使用 skill_logic_component 实现组件式复用,分为两类:

复用类型说明适用场景
持续性组件skill_logic_component 对象,挂载到技能实例上持续运行范围检测、状态维护、周期性效果
瞬发函数可复用的一次性调用函数伤害计算、Buff 添加、动画播放

2.4 优化点 2:逻辑重构方法论

"去 Polaris"解决的是框架层面的开销,但很多技能的逻辑本身也存在不必要的浪费。重构思路分三步:

逻辑重构三步法
01 理解策划需求 反推真正意图 而非照搬实现 02 最小开销实现 降频、事件驱动 消除无效计算 03 与策划协商 允许表现微调 换取数量级提升 核心理念:去 Polaris 消灭框架开销 + 逻辑重构消灭业务开销 = 最大收益

常见的重构手段包括:

  • 降频 — 减少 tick 频率(如 0.4s → 2s),利用数学关系(公约数/公倍数)合并计算
  • 动态降频 — 根据状态自适应调整频率(满血时 30s tick,回血中 1s tick)
  • 事件驱动替代轮询 — 用事件/回调替代周期性检测(如高处下落)
  • 距离分级 — 根据距离动态调整更新频率(近处高频,远处低频)
  • 从玩家级提升到 Space 级 — 将"每人挂一个被动"改为"Space 挂一个 System",消除重复计算

三、案例讲解

以下 6 个案例覆盖了不同的技能类型和优化手段,每个都附有前后对比性能数据

案例 1:体力值回复

5 倍性能提升

需求:部分技能使用需要消耗体力值,不足则不能释放。翻越消耗 25,爬管道消耗 50,体力值上限 100。

原实现(Polaris 被动)

0.4 秒 tick 一次,每次恢复 1 点体力。
每个攻方角色挂一个 attacker_physical_strength.mth 被动。

重构后(skill_system)

体力值回复提升为 Space 级游戏机制。
25 和 50 的 GCD=5,每 2 秒恢复 5 点。

重构分析:
方法 1:GCD(25,50)=5 → 每 2 秒恢复 5 点 → 5 倍性能
方法 2:LCM(25,50)=25 → 每 10 秒恢复 25 点 → 25 倍性能
考虑到 UI 体力条的平滑插值实现成本较大,选择了方法 1 作为平衡。
skill_system 重构 · 降频 Space 级机制 GCD 合并

案例 2:高处下落踉跄

4 倍 + 事件驱动

需求反推:从高处下落后,僵直 1 秒并播放踉跄动画。

原实现

Polaris 被动,每 0.3 秒 tick 一次。
每次做射线检测 + 高度处理。
单次开销 39.89ms

重构后

第一步:hybrid_skill_system 重写 → 9.78ms(4x)
第二步:与策划确认意图,去掉 tick,改为翻越高度检测事件驱动

每 0.3s Tick 射线检测 高度判定 Buff + 动画 翻越事件触发 复用高度检测逻辑 Buff + 动画
hybrid_skill_system 重构 · 事件驱动 消除无效 tick

案例 3:挂机暴露(AFK 机制)

N 个被动 → 1 个 System

需求:攻方 3 分钟内未执行关键行为(开点、占点、猜密码),将被持续标红,直到恢复行为。

原实现(多被动叠加)
  • 每个攻方挂被动 attacker_not_afk.mth,监听行为,触发后给所有守方添加"攻方未挂机" buff
  • 每个守方挂被动 defender_catch_afk.mth,每 1 秒 tick,检查 buff → 给攻方添加 1 秒标红 buff
  • N 攻 + M 守 = N+M 个被动同时运行
重构后(单 Space System)
  • AFK 作为游戏机制挂在 Space 上,1 个 System 管理全部玩家
  • 添加 3 分钟定时器,触发关键行为则重置
  • 触发 AFK 后:添加 1 小时标红 buff,恢复行为后清除
AFK 机制时序(重构后)
T=0 T=3min 触发行为 T=3min (新) 3 分钟定时器 添加标红 buff 清除 buff 重置定时器 新 3 分钟定时器 重复…
skill_system 重构 · 架构升级 N+M 被动 → 1 System 定时器驱动

案例 4:呼吸回血

动态降频 · 满血30x

需求:不满血且未被眩晕时触发回血。30 秒内受击则暂停回血。

原实现

Polaris 被动,每 1 秒 tick。
无论满血与否均以固定频率检查。

重构后

skill_system Space 级。
正在回血 → 1s tick
满血 → 30s 后再 tick

动态 Tick 频率示意
旧: 新: 回血中 1s/次 满血 30s/次
skill_system 重构 · 动态降频 状态感知调度

案例 5:占点人描边

11 倍 · 单次 3ms→0.26ms

需求:

  • 守方:所有占点人始终描边(开点红色 / 未开点绿色)
  • 攻方:40 米内显示描边(颜色规则同上),40 米外不显示
原实现

1 秒 tick 检查所有 6 个占点人状态。
单次开销 3ms
不区分距离,统一频率。

重构后

Space System,单次开销 0.26ms
守方:property 变更触发。
攻方:6 人独立定时器 + 距离分级降频。

距离分级策略

攻方视角 — 占点人距离分级 Tick 策略
攻方玩家 <40m: [1,7]s 线性插值 40~60m 1.5s/次(边界高频) >60m 10s/次 40m 60m 传送类技能:立刻 tick 一次
距离区间Tick 频率设计意图
< 40 米按距离 [1, 7] 秒线性插值可见区域,越近越需要精确
40 ~ 60 米1.5 秒/次边界区域,准备进入/退出可见
> 60 米10 秒/次远处,低优先级
传送技能触发立刻 tick位置突变,需要即时响应

总体期望频率从 每秒 1 次 → 平均每 6 秒 1 次,结合单次开销 11 倍降低,综合性能提升巨大。

skill_system 重构 · 距离分级降频 独立定时器 Property 事件驱动

案例 6:主动技能统一迁移

去 Polaris 框架开销

所有主动技能统一采用 hybrid_skill_system 实现,从节点图驱动转为 Class 实例化运行。典型案例:

技能名称原文件迁移方式
传送井盖portal_.mthhybrid_skill_system 重写
替身徽substitute_badge.mthhybrid_skill_system 重写
门锁干扰器lock_door.mthhybrid_skill_system 重写
震慑脉冲charge_hit_.mthhybrid_skill_system 重写

主动技能的优化主要来自去除 Polaris 框架层的调度开销,逻辑本身变化不大,属于批量机械迁移,但收益稳定且确定性高。

hybrid_skill_system 批量迁移 框架开销消除

案例总览

案例优化手段核心技巧性能收益
体力值回复skill_system + 重构GCD 合并降频5x
高处下落踉跄hybrid + 重构Tick → 事件驱动4x+
AFK 机制skill_system + 重构N 被动 → 1 System架构级优化
呼吸回血skill_system + 重构动态调频满血 30x
占点人描边skill_system + 重构距离分级降频11x
主动技能hybrid_skill_system去 Polaris 框架~4x

四、总结与展望

优化方法矩阵

hybrid_skill_system

适用:主动技能、非常驻型被动/机制

思路:Class 实例化运行 + 时间线驱动 + skill_logic_component 组件复用

特点:一个 .mth 对应一个 .py,与 Polaris 共存,逐步迁移

skill_system

适用:常驻型被动 / 全局游戏机制

思路:Space 级 System + 事件驱动 / 动态降频 Tick

特点:将"每人挂被动"提升为"Space 统一管理",消除重复计算

重构三原则

  1. 理解策划需求与当前实现 反推真正意图,而非照搬已有实现方案。很多性能问题源于"用高成本手段实现了一个低要求的需求"。
  2. 最小开销实现需求 降频、事件驱动、距离分级 — 每一步都在减少无效计算。能用定时器就不 tick,能用事件就不轮询。
  3. 部分实现可与策划协商 允许表现层的微小调整(如体力恢复粒度),换取数量级的性能提升。这不是偷工减料,而是工程权衡。
Polaris 的重新定位
策划预研 · 白模验证 · 快速原型

Polaris 并非被废弃,而是回归核心价值。策划用 Polaris 验证玩法,程序用手写方案上线运行 — 两者各司其职,不再让可视化框架承担线上运行时的性能压力。

可参考案例编号

#31778 体力值回复重构 · #31746 呼吸回血重构 · #31745 AFK 机制重构 · #32213 门锁干扰器 · #32092 鹰眼主动 · #32201 替身徽主动 · #31867 change_pose