[Unity3D 游戏开发] 第十一章-资源加载与优化

资源加载与优化:AssetBundle和优化工具。

资源加载与优化

编辑模式:

加载资源:

编辑模式下的资源可分为两类:一类是引擎可识别的资源,例如Prefab、声音、视频、动画和UI等;另一类是引擎无法识别的资源,例如外部导入的资源,这类资源需要通过第三方将它的信息解析出来,最终组织成引擎内可识别资源才可以使用。

可以提供一个相对路径,使用AssetDatabase.LoadAssetAtPath()方法读取任意对象,如果使用GameObject.Instance()只能创建新对象,这会丢失Prefab的引用。

实例化Prefab

在编辑模式下,实例化Prefab需要使用PrefabUtility.InstantiatePrefab()方法。

创建Prefab

使用PrefabUtility.CreatePrefab()方法可以创建Prefab,此时需要提供保存的目录以及当前的游戏对象,其中ReplacePrefabOptions.ConnectToPrefab表示创建Prefab的同时自动关联到创建它的这个游戏对象。

更新Prefab:

卸载资源:

游戏对象和资源的关系

游戏对象与资源是一种引用关系。例如一个模型是由贴图和Mesh组成,将它拖入场景中时,生成的游戏对象就会引用这两种资源。当程序调用GambObject.Destory()或者GameObject.DestoryImmediate()方法时,只会卸载掉它的对象,它身上引用的贴图和Mesh还在内存中。

Unity这样做的原因:很多游戏对象的加载和卸载是很频繁的,如果每次卸载都将引用的资源清理掉,无疑会造成IO的阻塞。但如果长时间不卸载这些资源,那么内存占用必将升高。所以Unity又提供了一个方法来自动卸载无用资源:

  • 调用EditorUtility.UnloadUnusedAssetsImmediate()方法即可卸载编辑器下无用的资源了。
1
2
3
4
5
6
7
8
public class Script_11_06
{
[MenuItem("Assets/My Tools/UnloadUnusedAssetsImmediate",false,3)]
static void UnloadUnusedAssetsImmediate()
{
EditorUtility.UnloadUnusedAssetsImmediate();
}
}

版本管理:

资源在导入Unity的时候,会自动生成很多中间资源,这些资源都不需要上传,只需要将Asset、ProjectSettings文件夹下的所有文件以及.meta文件上传即可。

.meta文件:

.meta文件是Unity自动生成的。每个游戏资源都会有一个对应的.meta文件,它会标记资源在引擎中的一些设置信息,我们可以在资源视图面板中重新设置这些资源的参数。此时.meta文件会保留这些参数,将资源拖入工程时,就会利用这些参数重新压缩资源。换句话说,资源在用户无感知的情况下被Unity优化了。

每个.meta文件都会记录guid这个重要信息。guid就是用来关联资源和游戏对象的引用的。

多工程:

同步文件:

svn外链:

运行模式:

编辑模式下可以放成千上万的资源,这些资源不需要打包在发布的游戏包中。打包时,Unity会自动删除掉没有引用的资源,只会保留Resources目录以及StreamingAssets目录下的资源。

引用资源:

只有被引用到的资源Unity才会打包,那么如何分辨资源是否被引用呢:

  • B贴图被New Material材质引用;
  • New Material材质被Cube引用;
  • Cube被Scene引用;

如果Scene被添加到Scenes In Build中,那么以上这几种资源都会被打入游戏包中。

Resources:

Resources目录下的资源无论是否有引用关系,都会被强制打在游戏包中。Resources文件夹可以是顶层目录,也可以是某个文件夹的子目录,打包后,Unity会自动将它们合并在一起,接着在代码中动态读取这些资源,并且加载它。(Resources目录下的资源尽量不要直接引用在场景中,不然这个资源会被Resources和场景打成两份。)

删除对象:

删除资源:

GC:

AssetBundle:

在网络游戏中,可能需要运行时下载并更新资源,而Unity提供了AssetBundle组件,可以将指定的一部分资源构建成AssetBundle文件,如果需要下载,那么需要将这些AssetBundle文件上传到CDN上。

设置AssetBundle:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using UnityEditor;
using UnityEngine;
using System.IO;
using UnityEngine.Events;
public class Script_11_12
{
[MenuItem("Tools/BuildAssetbundle")]
static void BuildAssetbundle()
{
string outPath = Path.Combine(Application.dataPath,"StreamingAssets");

// 如果目录已经存在,则删除它
if(Directory.Exists(outPath))
{
Directory.Delete(outPath,true);
}
Directory.CreateDirectory(outPath);

// 构建AssetBundle
BuildPipeline.BuildAssetBundles(outPath,BuildAssetBundleOptions.ChunkBasedCompression,
BuildTarget.StandaloneOSX);

// 刷新
AssetDatabase.Refresh();
}
}

依赖关系:

如果两个Prefab都依赖了同一份材质和贴图文件,那么按照上面的打包方式,材质和贴图会生成两份。可以在有可能出现冗余的资源上输入它的AssetBundle名称,然后再构建AssetBundle,这样两个Prefab就会自动依赖材质和贴图了。构建AssetBundle后,会生成资源依赖关系文本(似乎是.manifest格式的)

通过脚本设置依赖关系:

压缩格式:

AssetBundle提供了如下三种可选的压缩格式:

  • LZMA压缩:如果不做特殊指定,AssetBundle默认会以这种方式压缩,它的优点是Bundle会被压缩的特别小,缺点是每次使用都需解压,可能会带来卡顿。(不建议在项目中使用。)
  • 不压缩BuildAssetBundleOptions.UnCompressedAssetBundle它的缺点是构建出来的AssetBundle比较大,优点是加载的非常快。可以将不压缩的AssetBundle构建出来,用第三方压缩算法压缩它,再将它上传到CDN上,这样下载的时候还是压缩过的AssetBundle,所以不影响下载流量。接着,使用第三方解压算法将它写在硬盘上,这样用户在读取的时候就会非常快了。
  • LZ4压缩方式BulidAssetBundleOptions.ChunkBasedCompression它是LZMA与不压缩之间的折中方案,构建出来的Bundle会比不压缩的小一点,加载速度会比LZMA压缩的快一点。(建议在项目中使用它。)

加载包体内的AssetBundle:

包体内的AssetBundle只能放在StreamingAsset目录下,别的目录是无法读取的。可以使用AssetBundle.LoadFromFile()或者AssetBundle.LoadFromFileAsync()方法同步或者异步加载。这里需要注意的是,加载AssetBundle之前,需要使用AssetBundleManifest提取每个AssetBundle的相互依赖关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class Script_11_14 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
AssetBundle assetbundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath,"StreamingAssets"));
AssetBundleManifest manifest = assetbundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

// 加载AssetBundle前,需要加载依赖的Bundle
foreach(var item in manifest.GetAllDependencies("cube.unity3d"))
{
AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath,item));
}

// 读取Bundle
assetbundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath,"cube.unity3d"));

// 从Bundle中读取资源
GameObject prefab = assetbundle.LoadAsset<GameObject>("Cube");
// 实例化资源
GameObject.Instantiate<GameObject>(prefab);
}
}

下载AssetBundle:

Unity提供了WWW类来进行下载。它可以下载包括AssetBundle在内的任意资源,此时只需要提供一个URL下载地址即可。如果下载本地任意资源,则在前面加上file://即可。

1
// 此处应有代码:

使用WWW来下载AssetBundle,接着通过File.WritteAllBytes()将AssetBundle写在本地中,最后使用AssetBundle.LoadFromFile()从硬盘中加载它。

加载场景:

卸载AssetBundle:

在同一个AssetBundle文件中,可以同时构建多个资源文件。正确的从AssetBundle中实例化一个Prefab的步骤如下

  1. 从硬盘中加载AssetBundle对象
  2. 从AssetBundle对象中加载需要的资源对象
  3. 从资源读取对象并且将其实例化到Hierarchy视图中,变成真正的游戏对象

由此可见,一次加载需要产生3种对象:AssetBundle对象、资源对象和游戏对象,接着开始卸载对象。

  • GambObject.Destroy()方法只能卸载游戏对象,资源对象还静静的在内存中。
  • Resources.UnloadUnusedAssets()方法也无法卸载资源对象,因为它被AssetBundle对象引用着。
  • AssetBundle.UnloadAllAssetBundles(true);方法可以全部卸载掉资源对象,其中参数true表示同时卸载AssetBundle对象以及资源对象。资源对象一旦卸载掉,下次再加载时又需要耗时处理,所以有时候只希望卸载AssetBundle对象而不卸载资源对象,这时候参数就可以填false了。

游戏对象:

优化工具:

资源管理实例:


参考:

  1. 《Unity3D游戏开发(第二版)》宣雨松 著