我们公司的一个 MMORPG 项目最近在内存方面碰到了红线,昨天开会讨论了一下。我提出了一个改进方案,写篇 blog 记录一下。

问题是这样的。在当下的手机及平板硬件设备条件下,操作系统留给应用的可用内存并不多,大约只有 500M 左右。

和 PC 环境不同,手机上是交换分区的机制来对应一些临时突发性内存需求的。而手机必须保证一些系统服务(某些高优先级后台业务)的运行,所以在接电话、收取推送等等意外任务发生时,有可能多占用一些内存,导致操作系统杀掉前台任务让出资源。

根据实际测试,游戏想跑在当前主流高端手机上必须把自己的内存占用峰值控制在 400M 内存以下,350 M 会是一个合理的值,而这个值是远远低于 10 年前的 PC 游戏标准的。而我们的项目却是一个写实类型的 拥有大场景的 MMORPG 。

Unity3D 是在智能手机普及以前设计的,远没有料到会被广泛用于手机游戏的制作。它在设计之初并没有为低内存环境考虑。内存使用太粗犷,是在使用 Unity 开发 MMORPG 项目时最被吐槽的一点。

为手机游戏定制游戏引擎,最特别的,和 PC 游戏不同的两点就是内存必须严格控制、能耗必须严格控制。比如在我们开发的 ejoy2d 中,会为资源数据中的项目引用定制短指针,即资源内部的相互引用使用 32bit 偏移量来代替 64bit 指针,每个指针节省出 4 字节内存;变换矩阵使用 6 个 32bit 的定点数,资源中相同矩阵共享一份数据;资源数据尽量连续存放,避免小数据块太多造成内存碎片浪费内存等等。这些显然是 Unity 没花精力去做的。在 PC 上,省下几M 几十M 内存微不足道,但在手机上很可能就是生死之间。

ps. 能耗问题是另一个有趣点。在 PC 上你可以通过多线程并行,压榨出高 fps ,可以不管 CPU 多烫;但是到了手机上,即使 CPU 8 核心已经是标配,还是尽量不要这么做。因为在总任务相同的情况下,单线程能做完的工作只要拆分到多线程上完成,就一定意味着总工作量增加(至少增加了线程间协调的工作)。增加了总能耗。玩家是不想玩一个插着充电线也会玩关机,手机滚烫的游戏的。这个问题有机会我另写一篇 blog 展开,今天是想谈谈内存。


在我们最近的测试中,在较坏情况下,我们的游戏会占到 360M 内存左右,已经接近了内存红线,所以要考虑进一步的优化。其中,较大的一块是游戏场景,占了 120M 内存。

有趣的是,这 120M 内存中只有大约 50M 是用于场景上物件的贴图、模型等等资源数据(注:我们项目没有使用静态批次合并,那样更消耗内存。);也就是说,有 70M 内存用于构建场景本身的结构。所以,并非让美术人员尽量复用同样模型的花花草草就可以省下内存的。换句让美术人员更容易明白的说法,我们的场景中摆的东西太多了,不是减少贴图用量,把同一块石头到处摆可以解决的。这和过去制作 PC 游戏的常识不同。

所以,大部分现有的手机 MMORPG 的画风偏卡通幻想风格不是没有道理。因为那样,可以用有悖现实的物件比例,场景物件个头大,就可以用更少的数量去充斥场景。而写实风讲将就细节丰富,用诸多细节去填满视野。在过去 PC 上,这不是问题,只要少做点独特的模型,少用贴图就能把内存降下来,手机上不行了。


我们并非刚刚意思到这点。一开始,开发人员就针对大场景制定了技术解决方案。

我们在场景上,认为设定了若干包围盒,勾画出一块块小区域。一旦玩家离开包围盒太远,程序就会把包围盒里面的物件卸载出内存。然后在美术设计上,不让玩家有可以从远处观望的角度。我们的美术风格会尽量保证场景细而精致、不追求空旷宏大的场面。

但这还做的不够,我提出了一个改进方案。


  1. 保留包围盒方案。但是包围盒略微扩大,允许包围盒重叠,并可以用多个包围盒来定义一个区域。同一个场景物件只可以属于一个区域,即使它的位置在多个区域内。(区域可以重叠)

  2. 所有物件都标记分类出外观物件和细节物件。比如一个城市的城墙就是外观物件,而城内的所有东西都是细节物件;一片树林的大颗植物是外观物件,地面的花花草草是细节物件。一般情况下,大部分物件都默认是细节物件,只有少数需要远观的才标记成外观。这点,其实原本就做了视距分层,只不过是为了在渲染时做显示剔除用的,并没有用于控制内存。而这次,需要对外观物件和细节物件单独打包分类,便于分开卸载。

  3. 当玩家处于一个区域内部时,必须保证这个区域的外观物件和细节物件都加载到内存。如果之前并不在内存,也需要开启异步加载的流程。当一个玩家距离另一个区域比较近时,只需要确保该区域的外观物件在内存即可,可以卸载任何不在区域的细节物件。


在以上改进方案里,把包围盒扩大以及允许多个包围盒一起构成区域,是为了改进数据加载的时机。单一用距离判断会有瑕疵。比如在城墙外,即使隔的很近也不需要加载城内的细节。而我们完全可以在城门外加一个缓冲的小区域并入城市区域,只要玩家一踩到城门口,城内的细节加载流程就开始启动了。

而允许区域重叠,并让物件唯一归属于单一区域则可以解决城门外的细节物体提前加载时机。城外的这块缓冲区上的物件就不必归属到城市板块,它们早在玩家在城外活动时就加载完毕了。

Unity3D 的大场景内存优化的更多相关文章

  1. [原]unity3d ios平台内存优化(一)

    关于内存优化,人云亦云 各有己见.本文将通过设置Strpping Level ,减少内存使用. 先看三幅图: 1.没做任何优化,默认选项 2.设置Stripping level 为 Use micro ...

  2. Unity3D移动端内存优化(NGUI方面)

     Unity3D引擎技术交流QQ群:[21568554] 做3d移动端内存一直是人们头疼的问题,载入的资源释放了,还有其它的须要释放.比方ngui释放,事实上主要是NGUI的Texture和Spr ...

  3. Unity3D内存优化案例讲解

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

  4. Unity3D 游戏开发之内存优化

    项目的性能优化主要围绕CPU.GPU和内存三大方面进行. 无论是游戏还是VR应用,内存管理都是其研发阶段的重中之重. 然而,在我们测评过的大量项目中,90%以上的项目都存在不同程度的内存使用问题.就目 ...

  5. Unity3d代码及效率优化总结

    1.PC平台的话保持场景中显示的顶点数少于200K~3M,移动设备的话少于10W,一切取决于你的目标GPU与CPU. 2.如果你用U3D自带的SHADER,在表现不差的情况下选择Mobile或Unli ...

  6. U3D内存优化

    原创文章如需转载请注明:转载自风宇冲Unity3D教程学院                                                U3D内存优化   读了Hog关于内存管理文章 ...

  7. Unity内存优化技术测试案例

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

  8. Unity堆内存优化

    unity中减少堆内存分配以减少垃圾回收处理:只有局部变量且为值类值的变量是从stack栈中分配内存,其它所有情况都是从heap堆中分配内在.* 缓存获取到的数据.* 频繁被调用的函数中尽量少的分配空 ...

  9. In-Memory:内存优化表的事务处理

    内存优化表(Memory-Optimized Table,简称MOT)使用乐观策略(optimistic approach)实现事务的并发控制,在读取MOT时,使用多行版本化(Multi-Row ve ...

随机推荐

  1. 动态规划(dp)专题

    航线设置   问题描述在美丽的莱茵河畔,每边都分布着N个城市,两边的城市都是唯一对应的友好城市,现需要在友好城市间开通航线以加强往来,但因为莱茵河常年大雾,如果开设的航线发生交叉就有可能出现碰船的现象 ...

  2. 深入分析几种PHP获取客户端IP的情况转

    转 http://developer.51cto.com/art/200912/166495.htm function getip() { $unknown = 'unknown'; if (isse ...

  3. 解决Eclipse启动报错【Failed to create the Java Virtual Machine】

    电脑:2G内存,WIN7 32位. 启动adt-bundle-windows-x86-20140702\eclipse\eclipse.exe时,报错[Failed to create the Jav ...

  4. 【LOJ】#2532. 「CQOI2018」社交网络

    题解 基尔霍夫矩阵,外向树是入度矩阵-邻接矩阵 必须删掉第一行第一列然后再求行列式 代码 #include <bits/stdc++.h> #define fi first #define ...

  5. PHP与MySQL设计模式:代理模式

    一.数据库连接通用类 重要的接口: 接口用来存储MySQL连接数据.实现这个接口的类都可以使用这些数据. 通过接口可以隔离出程序中一个简单而必要的部分,任何程序都可以实现这个接口. 接口通过inter ...

  6. 020 shuffle的重要作用,以及分区的实践

    一:学shuffle原理的必要性 1.说明 学习shuffle的作用是可以对程序进行优化. 在shuffle这个部分有三个部分需要注意: 分区 排序 分组 这个可以进行优化. 二:分区的实践 1.说明 ...

  7. python 函数式编程学习笔记

    函数基础 一个函数就是将一些语句集合在一起的部件,它们能够不止一次地在程序中运行.函数的主要作用: 最大化的代码重用和最小化代码冗余 流程的分解 一般地,函数讲的流程是:告诉你怎样去做某事,而不是让你 ...

  8. 5,EasyNetQ-Send Receive

    而发布/订阅和请求/响应模式是位置透明的,因为您不需要指定消息的消费者所在的位置,发送/接收模式专门用于通过命名队列进行通信. 它也不会对可以通过队列发送的消息的类型做任何假设. 这意味着您可以通过同 ...

  9. Android-多进程初识

    Android-多进程初识 学习自 <Android开发艺术探索> https://baike.baidu.com/item/%E8%BF%9B%E7%A8%8B/382503?fr=al ...

  10. iOS 11开发教程(十六)iOS11应用视图之删除空白视图

    iOS 11开发教程(十六)iOS11应用视图之删除空白视图 当开发者不再需要主视图的某一视图时,可以将该视图删除.实现此功能需要使用到removeFromSuperview()方法,其语法形式如下: ...