初览项目优化

本文探讨了Unity游戏开发中的项目优化,从开发工具、资源、代码、Shader和算法等多个角度进行深入分析。介绍了如何利用Git和JIRA提高团队协作效率,资源优化包括AssetBundle的压缩方式选择、图集打包和内存管理,代码优化关注内存分配和减少无效引用,Shader的合理使用,以及A*算法和AI策略在游戏中的应用。通过具体的案例和技巧,展示了优化游戏性能的实践方法。
摘要由CSDN通过智能技术生成

综述


开发工具的运用

游戏开发是团队合作,美术、程序、策划们都需要使用项目管理的工具。美术提交资源,策划提交文案,程序提交代码。一般主要使用的项目管理工具有 SVNGithub,可以选择其一,或者结合使用。以一个以前的开发为例,使用 SVN 作为项目管理工具,在项目的初期,没有问题,但是到了后期,项目要分版本开发。一旦项目上线后,要接不同渠道的 SDK,会接上很多个,刚开始也使用了 SVN 的 Branch 分支,但是每次更新速度都很慢,而且也不利于多人开发,被折磨之后将其换成了 Github,Git 非常适合程序协同开发以及产品版本管理,这点 SVN 是无法比拟的。当然美术和策划还是使用 SVN,相对来说,Github 操作比 SVN 复杂一些,而美术和策划也不需要像程序一样搞几十个版本,所以他们采用的版本管理工具还是 SVN,比较方便,更有效率。另外,项目对资源和代码逻辑做了分离,逻辑代码都是动态的绑定到资源对象上,这是因为资源经常变动,经常修改,如果上面挂脚本,一旦出现漏挂,会出现一些问题,还需要人去查找这样的问题,浪费时间,所以最好不要直接绑定逻辑脚本。对于程序使用 Git 的主干,我们程序开发把产品框架作为主干,每个模块的逻辑作为分支处理,这样参与逻辑开发的每个程序都可以起一个分支,逻辑写好了,测试完成,最后交给主程去合并,再测试。这样主程的重心就是把控代码质量,通过下面的图可以简单理解一下。
在这里插入图片描述

有了项目管理工具,还需要项目跟踪工具,策划、美术、程序在项目开发协作方面也需要配合,比如策划给美术和程序下达任务以及完成时间节点。作为策划,他要清楚美术和程序的进展情况,如果只是靠口头去传达,容易出现扯皮的事情发生,而且时间和质量不好把控,项目延误是肯定会发生的。这时就需要用到项目跟踪工具,可以使用 JIRA 或者禅道等等,策划会将任务传到 JIRA,主程去分配任务,程序领任务,策划可以定期在上面查看各个任务进度,及时发现任务是否有延期的隐患,及时纠正。这里也配一张图理解一下。
在这里插入图片描述

项目顺利进行后,再接着就是测试了。使用 Unity 去打包,如果工程很大,每次编译都需要耗费不少的时间,而且策划或者测试会不定期的让程序打个包给他看,这样容易打断程序员的开发思路,而且项目在每个节点上也需要去发包测试。总而言之,打包的次数会非常多,如果不将其工具化,程序员会浪费很多时间在这上面,每次打包,资源整合等都会耗费大量时间。鉴于手动打包的各种不利因素,自动打包工具应运而生,我们可以使用一个自动打包工具 Jenkins,它可以采用 Shell 脚本编程,自动更新 SVN 或者 Git 服务器上的内容,只需要点一下按钮,服务器就可以生成当前项目最新的包,很简单的操作,然后将其下载下来安装测试,非常的方便。这样,打包的事情策划和美术都可以操作。

最后,在项目开发时,程序会根据策划需求去写一些依附于 Unity 编辑器的工具,这个也是必须的,比如资源打包、文本文件处理、资源检查、包体大小检测、单元测试等。作为程序开发者,工具当然是越多越好,如果一款游戏只需要操作工具就可以生成,程序员只需要专心维护工具,而策划只需要专心使用工具,美术制作模型,一款产品很快就可以研发出来,类似生产流水线作业。


资源的优化

游戏资源的优化,无非是从模型的面数、材质的数量、骨骼动画的数量、特效的粒子数、图片的压缩、网格合并这些方面去入手,网上这方面的介绍比较多。这些操作可以帮助我们去解决掉部分的问题,但是除了这些还会有一些其他的问题。比如,我们场景中有很多草,或者说有很多透贴的树木,场景中有很多怪物,类似国战这中类型的游戏,两边都需要很多的 NPC 开战等等。这些 NPC 是不可以用网格合并的,因为它们是单个的个体,有自己的属性,是可以被击杀的。另外透贴的树木也是非常耗时的,但是这种问题也是可以解决的,可以利用 GPU 编程实现。

另外资源需要将其打包成 Assetbundle,一部分资源会上传到服务器,进去行资源的更新,这就要涉及AssetBundle 包体的数量、包体的大小、打包的依赖、内存的管理等等,作为开发者都需要认真考虑的。

合理的调配游戏资源,掌握方法非常重要。
Unity 提供了两种资源分类。一种是 Unity 内部加载资源使用的是实例化,实例化的资源要存放到 Unity 引擎的 Resources 文件夹中,程序通过 Resources.Load 函数加载;另一种是程序外部资源加载,使用的是打包文件 AssetBundle,大部分游戏上线产品都是通过加载外部资源实现的。

优化加载 AssetBundle
游戏包体的大小,单独程序是控制不了的,它同样需要美术协同配合。

Unity 支持三种 AssetBundle 打包的压缩方式,即 LZMA、LZ4,以及不压缩。

1.LZMA 压缩方式
这是一种默认的压缩形式,这种标准压缩格式是一个单一 LZMA 流序列化数据文件,并且在使用前需要解压缩整个包体。LZMA 压缩是比较流行的压缩格式,能使压缩后文件达到最小,但是解压相对缓慢,导致加载时需要较长的解压时间。

2.LZ4 压缩方式
Unity 支持 LZ4 压缩,能使得压缩量更大,而且在使用资源包前不需要解压整个包体。LZ4 压缩是一种“Chunk-based”算法,因此当对象从 LZ4 压缩包中加载时,只有这个对象的对应模块被解压即可,这速度更快,意味着不需要等待解压整个包体。LZ4 压缩格式是在 Unity5.3 版本中开始引入的,之前的版本不可用。

3.不压缩的方式
不压缩的方式打包后包体会很大,很占用空间,但是一旦 Assetbundle 下载下来,访问会非常快。不推荐这种方式打包,因为现在的加载功能做的很友好了,完全可以用加载界面来进行后台加载资源,不会消耗太长的时间。

资源打包 AssetBundle 使用 LZ4 压缩的话(BuildPipeline.BuildAssetBundles,第二个参数传递 BuildAssetBundleOptions.ChunkBasedCompression),打包默认的是 LZMA 压缩,具有最高的压缩比。而替换为 LZ4 压缩,压缩比没有 LZMA 高,但是加载速度大幅提高,这需要根据项目均衡考虑。

图集打包的优化
打包图集,需要注意它和 AB 的匹配关系。假设,如果三张图片指定了同一个图集,而又分别指定了不同的 AB 名,则三个 AB 里面都包含了此图集(三张图片),这样就会造成严重的资源浪费。在此再说一下 Unity 的内存管理,先看一下这张图:
在这里插入图片描述
官方说法是,Untiy 支持 LZ4 压缩,该压缩方式支持将 AB 中的个体资源分开压缩,这样就可以在 AB 加载资源时分别对资源进行解压而非整体解压。也就是说 AssetBundle.LoadFromFile 这个高效的 API 会从本地硬盘中加载未压缩或者采用 LZ4 压缩的 AB 包。在移动端,API 只会加载 AB 的 header,而不会把整个 AB 加载进内存,在进行诸如 AssetBundle.Load 这样的方法时才会根据头文件的信息,将硬盘中 AB 对应的数据加载出来,现在将多个资源打在一个 AB 包里将成为一个新的选择,将不用再担心加载 AB 且不立即释放 AB 所造成的额外内存开销,因为在移动端, AB 在内存中的镜像只是 AB 的 header 部分。

对于项目来讲,若是项目计划使用 UGUI 的自动图集功能,而自动图集功能需要将多张图片同时打包到一个 AB 中才能正常生成图集,这就需要项目有加载涵盖多个资源的 AB 且不即刻释放。

虽然 LoadFromFile 加载的 AB 内存开销已经很小了,但还是要注意同时加载大量 AB 的情况。

卸载 AssetBundle
知道何时加载或卸载一个 AssetBundle 是很重要的。如果一个 AssetBundle 被不恰当地卸载,它会导致内存中的对象复制。在某些情况下,不恰当地卸载 AssetBundle 也会导致不良行为,比如导致纹理丢失。

再说一下 UnLoad 卸载 AssetBundle 资源的 API 函数,这个 API 卸载被调用的是 AssetBundle 的头信息,它里面的参数指示是否要卸载从这个 AssetBundle 实例化的所有对象,如果选择 True,那么所有来自于这个 AssetBundle 的对象也将立即被卸载,即使它们目前正在活动场景中使用,这是个很危险的操作。

举个例子,假设一个材质 M 是从一个 AssetBundle 中加载的,并且假设 M 材质当前处于活动场景中。

如果调用了 AB.Unload(true),那么 M 将从场景中删除,销毁并卸载。但是,如果调用了 AB.Unload(false),那么 AB 的头信息将会被卸载,但是 M 材质将保留在场景中,并且仍然具有功能。调用 AssetBundle.Unload(false) 断开 M 和 AB 之间的链接,如果 AB 稍后再加载,那么 AB 中包含的对象的新副本将被加载到内存中。

如果稍后再次加载 AB,则将重新加载 AssetBundle 头信息的新副本。但是,材质 M 并没有从这个新的 AB 副本中加载,Unity 没有在 AB 和 M 的新副本之间建立任何链接。

如果调用 AB.LoadAsset() 来重载 M 材质,Unity 不会将 M 的旧副本解释为 AB 中数据的实例,所以 Unity 将加载 M 的新副本,在场景中会有两个相同的 M 副本。

AseetBundle 资源更新
最基本的资源更新流程,先加载本地的资源文件列表,然后获取服务器上的资源列表,根据 MD5 或者版本号比较二者,如果服务器上的资源是最新的,则下载服务器上的 AssetBundle 资源替换本地的资源,同时更新资源列表。在资源更新的时候,我们就要考虑是否压缩 AssetBundle ,需要考虑的问题有以下几个。

A。AssetBundle 的加载时间,当从本地存储或本地缓存加载时,未压缩的 AssetBundle 比压缩的 AssetBundle 要快得多。从远程服务器下载压缩的 AssetBundle 通常比下载未压缩的 AssetBundle更快。

B。AssetBundle 的构建时间,当压缩文件时, LZMA 和 LZ4 非常慢,而 Unity 编辑器在串行中处理 AssetBundle。所以具有大量 AssetBundle 的项目将花费大量的时间来压缩它们。

C。应用程序的大小,如果在应用程序中装载了 AssetBundle,压缩它们将减少应用程序的总大小,或者 AssetBundle 可以在安装后下载。

D。内存使用量,在 Unity 5.3 之前,所有 Unity 的解压机制都需要在解压之前将整个压缩的 AssetBundle 加载到内存中。如果内存使用很重要,可以使用未压缩的或 LZ4 压缩的 Assetbundle。

E。下载时间是,如果 AssetBundle 是大型的,或者用户处于带宽受限的环境中,比如通过在移动设备上下载,或者在低速或有度量的连接上下载,那么压缩可能是必需的。如果只有几十兆字节的数据被传送到高速连接的个人电脑上,就有可能忽略压缩。

Mono 内存回收
Mono 通过垃圾回收机制(Garbage Collect,简称GC)对内存进行管理。Mono 内存分为两部分,已用内存(used)和堆内存(heap),已用内存指的是 Mono 实际需要使用的内存,堆内存指的是 Mono 向操作系统申请的内存,两者的差值就是 Mono 的空闲内存。当Mono 需要分配内存时,会先查看空闲内存是否足够,如果足够的话,直接在空闲内存中分配,否则 Mono 会进行一次 GC 以释放更多的空闲内存,如果 GC 之后仍然没有足够的空闲内存,则 Mono 会向操作系统申请内存,并扩充堆内存,具体如下图所示。
在这里插入图片描述
GC 的主要作用在于从已用内存中找出那些不再需要使用的内存,并进行释放。Mono 中的 GC 主要有以下几个步骤:
1.停止所有需要 Mono 内存分配的线程。
2.遍历所有已用内存,找到那些不再需要使用的内存,并进行标记。
3.释放被标记的内存到空闲内存。
4.重新开始被停止的线程。

除了空闲内存不足时 Mono 会自动调用 GC 外,也可以在代码中调用 GC.Collect() 手动进行 GC。但是,GC 本身是比较耗时的操作,而且由于 GC 会暂停那些需要 Mono 内存分配的线程(C#代码创建的线程和主线程),因此无论是否在主线程中调用,GC 都会导致游戏一定程度上的卡顿,需要谨慎处理。另外,GC 释放的内存只会留给 Mono 使用,并不会交还给操作系统,因此 Mono 堆内存是只增不减的。这也就是为啥我们在项目中调用 GC 手动释放,但是查看我们的 Mono 内存还是只增不减的原因。

那么,Mono 是如何判断已用内存中哪些是不再需要使用的呢?是通过引用关系的方式来进行的。Mono 会跟踪每次内存分配的动作,并维护一个分配对象表,当 GC 的时候,以全局数据区和当前寄存器中的对象为根节点,按照引用关系进行遍历,对于遍历到的每一个对象,将其标记为活的(alive)。

由于 GC 以全局数据区和当前寄存器中的对象为根节点进行遍历,所以对象的被标记意味着该对象可以通过全局对象或者当前上下文访问到,而没有被标记的对象则意味着该对象无法通过任何途径访问到,即该对象“失联”了,GC 最终会将所有“失联”的对象内存进行回收。

Mono 已经有了完善的 GC 机制,但还会存在内存泄漏,只是此处的内存泄漏需要重新定义一下,我们把对象已经不再需要使用却没有被 GC 回收的情况称为 Mono 内存泄漏。Mono 内存泄漏会使空闲内存减少,GC 频繁,Mono 堆不断扩充,最终导致游戏内存占用的升高。

游戏中大部分 Mono 内存泄漏的情况都是由于静态对象的引用引起的,因此对于静态对象的使用需要特别注意,尽量少用静态对象,对于不再需要的对象将其引用设置为 null,使其可以被 GC 及时回收,但是由于游戏代码过于复杂,对象间的引用关系层层嵌套,真正操作起来难度很大。可以首先使用 腾讯的 Cube 工具进行分析,根据 Mono 内存趋势找出泄漏的具体场景,然后再使用快照对比功能进行详细分析。

<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值