目录
随着移动终端的发展,在一些大型游戏中动态加载游戏模型、贴图等资源文件以及实现游戏的更新,对开者来说是非常重要的工作。本文将结合Unity的AssetBundle资源包来做到游戏的更新。
AssetBundle是将资源用Unity提供的一种用于存储资源的压缩格式打包后的集合,它是对资源管理的扩展,可以动态地加载和写在,并大大减少游戏所占地内存,即使是已经发布地游戏也可以用它来增加新的内容。
一般情况下,AssetBundle开发流程地具体步骤如下:
1)创建AssetBundle。开发人员在Unity中通过脚本将所需要地资源打包成AssetBundle文件。
2)上传至服务器。开发者将创建好的AssetBundle文件通过上传工具上传到游戏地服务器中,让游戏客户端可以通过访问服务器来获得当前需要地资源,进而实现游戏的更新。
3)下载AssetBundle。游戏运行时,客户端可以将服务器上的游戏更新所需要的AssetBundle下载到本地设备,再通过加载模块加载到游戏中。Unity提供了相应的API来完成从服务器的下载AssetBundle操作。
4)加载AssetBundle。AssetBundle文件下载完成后,通过Unity提供的API可以加载资源里的模型、贴图、音频等并将它们实例化更新到游戏客户端。
5)卸载AssetBundle,Unity提供了相应的方法来卸载AssetBundle,卸载它可以节约内存。
Unity有自带的打包创建工具,开发者可以一目了然的打包而不需要任何代码。
步骤:
1)选中我们需要打包的资源,在我的示例项目中,有一个名为CubeAsset的预制件
2)在Inspector面板可以看到最底下的AssetBundle选项,现在为None
3)展开选项创建新的AssetBundle。(点击New命名)
注意:只有Asset目录下的资源文件可以被打包到AssetBundle中,AssetBundle的名称固定为小写字母,如果使用了大写,系统会自动转换为小写。每个AssetBundle都可以设置一个Variant,Variant其实是一个后缀,如果有不同分辨率的同名资源,可以添加不同的Variant来加以区分。
创建好的AssetBundle需要导出,这一过程需要编写代码来实现。
Unity简化了开发者遍历资源的过程,自行打包时会将规定的所有资源打包(即之前用打包工具创建并且命名的全部资源),然后将它们置于指定的文件夹中,其声明格式如下:
public static AssetBundleMainfest BuildAseetBundles(string outputPath,BuildAssetBundleOptions assetBundleOptions=BuildAssetBundleOption.None,BuildTarget targetPlatfom=BuildTarget.WebPlayer);
其中,outputPath参数是AssetBundle的输出路径,一般情况下为Assets目录下的某一个文件,如:Application.dataPath+"/AssetBundle"; assetBundleOptions参数为AssetBundle的创建选项; BuildTarget参数为AssetBundle的目标创建平台。?
None | 不使用任何特殊选项构建 assetBundle。 |
UncompressedAssetBundle | Don't compress the data when creating the AssetBundle. |
DisableWriteTypeTree | 不包括 AssetBundle 中的类型信息。 |
DeterministicAssetBundle | 使用存储在资源包中对象的 ID 的哈希构建资源包。 |
ForceRebuildAssetBundle | 强制重新构建 assetBundle。 |
IgnoreTypeTreeChanges | 在执行增量构建检查时忽略类型树更改。 |
AppendHashToAssetBundleName | 向 assetBundle 名称附加哈希。 |
ChunkBasedCompression | 创建 AssetBundle 时使用基于语块的 LZ4 压缩。 |
StrictMode | 如果在此期间报告任何错误,则构建无法成功。 |
DryRunBuild | 进行干运行构建。 |
DisableLoadAssetByFileName | 禁用按照文件名称查找资源包 LoadAsset。 |
DisableLoadAssetByFileNameWithExtension | 禁用按照带扩展名的文件名称查找资源包 LoadAsset。 |
AssetBundleStripUnityVersion | 在构建过程中删除存档文件和序列化文件头中的 Unity 版本号。 |
BuildAssetBundleOptions.None:使用LZMA算法压缩,压缩的包更小,但是加载时间更长。使用之前需要整体解压。当被解压,这个包会使用LZ4重新压缩。使用资源的时候不需要整体解压。在下载的时候可以使用LZMA算法,一旦它被下载了之后,它会使用LZ4算法保存到本地上。
BuildAssetBundleOptions.UncompressedAssetBundle:不压缩,包大,加载快
BuildAssetBundleOptions.ChunkBasedCompression:使用LZ4压缩,压缩率没有LZMA高,但是我们可以加载指定资源而不用解压全部。可以获得可以跟不压缩想媲美的加载速度,而且比不压缩文件要小。
StandaloneOSX | 构建一个 macOS 独立平台(Intel 64 位)。 |
StandaloneWindows | 构建一个 Windows 独立平台。 |
iOS | 构建一个 iOS 播放器。 |
Android | 构建 Android .apk 独立平台应用程序。 |
StandaloneWindows64 | 构建一个 Windows 64 位独立平台。 |
WebGL | WebGL。 |
WSAPlayer | 构建一个 Windows 应用商店应用程序播放器。 |
StandaloneLinux64 | 构建一个 Linux 64 位独立平台。 |
PS4 | 构建一个 PS4 独立平台。 |
XboxOne | 构建一个 Xbox One 独立平台。 |
tvOS | 构建到 Apple 的 tvOS 平台。 |
Switch | 构建一个 Nintendo Switch 播放器。 |
Stadia | 构建一个 Stadia 独立平台。 |
CloudRendering | Build a CloudRendering standalone. |
PS5 | Build to PlayStation 5 platform. |
1)在Assets目录下创建一个Editor文件夹,在Editor文件夹创建一个新的c#脚本
2)输入代码
using UnityEditor;
public class BuildAsset:Editor
{
[MenuItem("Test/BuildAssetBundles")]
static void BuildAssetBundle()
{
BuildPipeline.BuildAssetBundles("./AssetBundle", BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);
}
}
其中我们类继承了Editor使用了MenItem特性,我们可以在菜单栏找到Test下的BuildAssetBundle启用BuildAssetBundle方法。
在编写的输出路径中./表示在当前项目的根目录上,一般输出路径不放在Assets目录下(Assets目录:Application.dataPath)?【注意需要提前创建好输出路径的文件夹】
在打包出来的文件中,如下:
每一个AssetBundle资源都有一个和原文件相关的.mainfest文本类型文件,该文件提供了所有打包资源的CRC(Cyclic Redundancy Check,循环冗余校验)和资源依赖信息。?【如cube.mainifest文件】
除此之外还有一个.mainfest文件,该文件也是文本类型文件,记录了整个 AssetBundle文件夹的信息,包括资源的列表和各个列表之间的依赖关系。
打包好的这些文件需要上传到开发平台供客户端下载,这样就可以达到更新游戏的目的。
public static AssetBundle?LoadFromFile(string?path, uint?crc, ulong?offset);
path | 磁盘上文件的路径。 |
crc | 未压缩内容的可选 CRC-32 校验和。如果该值不为零,则在加载内容之前,将内容与校验和进行比较,如果不匹配,则给出错误。 |
offset | 可选的字节偏移量。此值指定从何处开始读取 AssetBundle。 |
从磁盘上的文件同步加载 AssetBundle。
该函数支持任何压缩类型的捆绑包。 在lzma压缩的情况下,数据将被解压缩到内存中。可以直接从磁盘读取未压缩和块压缩的捆绑包。
与?LoadFromFileAsync?相比,此版本是同步的,在完成 AssetBundle 对象的创建之前不会返回。
这是加载 AssetBundle 的最快方法。
using UnityEngine;
using System.Collections;
using System.IO;
public class LoadFromFileExample : MonoBehaviour
{
void Start()
{
var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine("./AssetBundle","cube"));
if (myLoadedAssetBundle == null)
{
Debug.Log("Failed to load AssetBundle!");
return;
}
var prefab = myLoadedAssetBundle.LoadAsset<GameObject>("CubeAsset");
Instantiate(prefab);
myLoadedAssetBundle.Unload(false);
}
}
从本地存储中加载未压缩的捆绑包时,此 API 非常高效。如果捆绑包未压缩或采用了数据块 (LZ4) 压缩方式,LoadFromFile 将直接从磁盘加载捆绑包。使用此方法加载完全压缩的 (LZMA) 捆绑包将首先解压缩捆绑包,然后再将其加载到内存中。
public static?AssetBundle?LoadFromMemory(byte[]?binary, uint?crc);
binary | 包含 AssetBundle 数据的字节数组。 |
crc | 未压缩内容的可选 CRC-32 校验和。如果该值不为零,则在加载内容之前,将内容与校验和进行比较,如果不匹配,则给出错误。 |
从内存区域同步创建 AssetBundle。
使用此方法从字节数组创建 AssetBundle。当下载了加密数据并需要从未加密的字节创建 AssetBundle 时,这很有用。
与?LoadFromMemoryAsync?相比,此版本是同步的,在完成 AssetBundle 对象的创建之前不会返回。
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class Test : MonoBehaviour
{
void Start()
{
//异步加载的一个变形同步加载,传入具有AssetBundle数据的字节数组
AssetBundle ab = AssetBundle.LoadFromMemory(File.ReadAllBytes(Path.Combine("./AssetBundle", "cube")));
GameObject wallPrefab = ab.LoadAsset<GameObject>("CubeAsset");//获取这个名字的游戏物体
Instantiate(wallPrefab);//实例化这个游戏物体
}
}
public static?AssetBundleCreateRequest?LoadFromMemoryAsync?(byte[]?binary, uint?crc);
binary | 包含 AssetBundle 数据的字节数组。 |
crc | 未压缩内容的 CRC-32 校验和(可选)。如果该参数不为零,则加载前将内容与校验和进行比较,如果不匹配则给出错误。 |
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class Test : MonoBehaviour
{
IEnumerator Start()
{
//打开二进制数组文件,并将打开的数据传进去
AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(Path.Combine("./AssetBundle","cube")));
yield return createRequest;//等待加载时间
AssetBundle ab = createRequest.assetBundle;//获取加载出来的东西
GameObject prefab = ab.LoadAsset<GameObject>("CubeAsset");
Instantiate(prefab);
}
}
从内存区域异步创建 AssetBundle。
使用该方法可从一个字节数组异步创建 AssetBundle。当使用 UnityWebRequest 下载了加密数据并需要从未加密的字节创建 AssetBundle 时,这非常有用。
与?LoadFromMemory?相比,该版本将在后台线程上执行 AssetBundle 解压缩,不会立即创建 AssetBundle 对象。
使用UnityWebRequestAssetBundle.GetAssetBundle(string uri)到服务器下载AB包,现在官方推荐的主流方式,以前使用的WWW方式下载,还有UnityWebRequest.GetAssetBundle(string uri)这两种方式都已经弃用了;使用这个方式不但可以下载服务器上的资源,同时他也可以加载本地的资源,但是强烈建议如果要加载本地资源,还是使用本地同步/异步加载的方式比较妥当
string uriPath = @"file://E:\unity\AB\AssetBundle\cube";
private void Start()
{
StartCoroutine(MyUnityWebRequest());
}
IEnumerator MyUnityWebRequest()
{
//本地加载路径前面要加上file://否则会出错
//string uriPath = @"file://D:\Unity3D_Project\Advanced\Asset Bundle Project\AssetBundles\cubewall.unity3d";
//UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(uriPath);
//服务器上下载
//UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(urlPath);
//本地上下载
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(uriPath);
yield return request.SendWebRequest();
if (string.IsNullOrEmpty(request.error) == false)//判断下载有没有出错,request.error表示错误信息
{
Debug.Log(request.error);//输出错误
yield break;//退出携程
}
//AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request);//获取下载到的资源
//获取资源的另外一种方式,直接获取到UnityWebRequest下载到的东西然后强转成DownloadHandlerAssetBundle,然后再获取到AssetBundle
AssetBundle ab = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle;
GameObject prefab = ab.LoadAsset<GameObject>("CubeAsset");
Instantiate(prefab);
}