项目背景

场景

  • 1024*1024
  • 同屏200W面
  • 贴图1024
  • 自由视角

配置表

  • 1000+张配置表
  • C++内存加载350MB

角色

  • 同屏20人
  • 45个职业变种
  • 数万件时装
  • 模型面数3-5W面

业务系统

  • 3000+个界面
  • 200W+行非生成代码

资源

  • 10W+个AB

Unity版本

  • 2019

内存分布

  • Native Heap
  • Code
  • Graphic 图形内存
  • Unknown

Native Heap

Native Heap用来存放 C++层(引擎层) 的各种对象和资源数据。

  • Unity Engine 内部结构体、组件对象
  • 资源(Texture、Mesh、AudioClip 等)的 CPU 端数据
  • NativeContainer(如 NativeArray、NativeList 等)
  • Job System / Burst 编译代码用到的临时缓冲区
  • 底层系统调用(如物理系统、导航系统、动画系统)

Unknown

  • 华佗HybridCLR热更新的元数据

定位工具

Native内存

环境配置

  • Android Studio
  • Profile
  • Record Native Allocation

Code内存

环境配置

  • Android Studio
  • adb shell
  • run-as com.Unity.ClassicSponza cat /proc/11810/maps | grep libil2cpp.so

Lua内存

  • 递归遍历

关键指标

  • Native内存: 200MB
  • C++配置表: 30-40MB
  • Graphic: 100MB
  • Code: 100MB
  • Mono: 100MB
  • UnityNative: 200MB
  • 总计750MB

常见优化手段

纹理贴图

  • 图集
  • ASTC压缩
  • 关闭mipmap
  • 九宫格
  • 对称
  • 单行渐变
  • 单通道贴图
  • 关闭ReadWrite
  • 合理分辨率设置
  • 超采样压缩
  • streamingmipmap

模型

  • LOD
  • 优先级显示
  • 剔除余通道
  • 关闭ReadWrite
  • 名字版替代

场景

  • 公告板
  • LOD
  • 流式加载
  • 分层显示
  • 光照探针优化光照

特效

  • 贴图256
  • 总数限制
  • 粒子最大数量限制
  • LOD加载
  • 去重
  • 模型最大顶点数限制

动画

  • 压缩格式
  • 骨骼剔除
  • 属性剔除
  • 曲线压缩

音频

  • 压缩格式设置
  • 单通道
  • streaming
  • lazy加载

字体

  • 静态图集
  • 动态图集
  • ttf剔除

Shader

  • 变体剔除

优化案例

Mono内存

  • 去除字符串
  • gc替代缓存
  • 数据转移C++
  • 精简数据结构
  • 延迟加载
  • 精准卸载

代码元数据优化(Metadata)

IL2CPP在使用到某个类型时,会初始化这个类型完整的元数据,包括它的所有函数、接口、事件、属性、虚函数表等,并且一旦加载后就不会再释放。所以一个MMO游戏中,充分运行后,代码元数据占用的内存会非常高。

  • 排查泛型Template用的是否较多
  • 排查自动化生产的代码是否较多
  • 排查Lua生产的Wrapper代码是否较多
  • 删除无用插件、代码
  • 排查IL2CPP是否进行裁剪
  • 代码裁减调整至最高等级
  • link.xml

团结引擎通过深入分析,脚本代码实际运行时,通常只会用到很少的一部分元数据(反射、或者虚函数调用时访问)。例如,一个数组类型可能包含155个方法和25个虚函数,实现了6个接口,但实际运行时可能只会用到其中一小部分,导致大量内存被余加载。
因此,可以采用延迟加载策略。这意味着只有当游戏真正需要某个元数据项时,才会初始化和加载该数据。这种方法可以显著减少IL2CPP运行时的内存占用。

Unity后处理

  • 修改Urp的渲染管线,动态开关渲染特性,渲染特性的资源动态分配&释放
  • 重写后处理的框架,内存相关优化代码结构,避免反射进行无效访问
  • 动态加卸载用到的资源

配置表

背景

  • 1000张配置表
  • 内存占用极高
  • C#/Lua频繁访问

可选方案

  • 存储在单侧Lua/C#
  • 混合存储
  • C++存储

方案选择

  • 配置表全部放到C++
  • 生成lua config wrap文件
  • 生成c# config wrap文件
  • 弱表
  • 支持强引用
  • C++内存压缩

优势

  • C#/lua访问C++非常高效
  • 全局只有一份,配置数据零余
  • 内存占用更低
  • 代码结构更优

C++内存压缩

压缩方法

  • 对象池,合并重复单元格
  • 位运算,优化列数据量
  • int8替代指针引用
  • mmap文件映射
  • 数据结构优化
  • 延迟加载

场景流式加载

背景

  • 1024*1024
  • 同屏200W面
  • 贴图1024
  • 自由视角

美术资产分类

  • 微景,装饰物,不影响游戏内容,比如碰撞,导航
  • 近景,小型建筑物
  • 中景,房屋建筑
  • 远景,地形,轮廓,特色标志性建筑

加载策略

  • 格子加载
  • 分层加载
  • 连通图加载
  • 自适应加载
  • 全局LOD加载

总结

  • 精准定位,杜绝主观猜测
  • 深入分析,精通业务
  • 全量优化,不放过任何一个模块
  • 逐个攻克,没有捷径
Logo

这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!

更多推荐