加载场景有两种方法,一种是常规的build settings添加scene,一种是通过AssetBundle加载Scene。
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneTest : MonoBehaviour
{
void Start()
{
SceneManager.LoadSceneAsync("xxx1", LoadSceneMode.Additive);
SceneManager.LoadSceneAsync("xxx2", LoadSceneMode.Additive);
SceneManager.LoadSceneAsync("xxx3", LoadSceneMode.Additive);
}
}
上面是标准的异步加载场景,如果不在build settings中添加场景,直接加载scene,就会报下面的错。
因此对于需要热更新的项目,如果场景.unity资源变更了,无法在build settings中重新设置,只能用Assetbundle加载场景。
下面是打ab包的工具(BuildAssetBundleTool放到Scripts/Editor文件夹下)和标准化路径工具。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
/// <summary>
/// 构建工具类:
/// 创建了三种构建方法windows android ios。
/// Build方法:从所有路径查找文件,排除meta后,把每个文件名作为被打包资源名和bundle名(当然一个bundle可以打包多个文件)
/// </summary>
public class BuildAssetBundleTool : Editor
{
//如何使用Build呢,直接添加工具栏
[MenuItem("Tools/Build Windows Bundle")]
static void BundleWindowsBuild()
{
Build(BuildTarget.StandaloneWindows);
}
//如何使用Build呢,直接添加工具栏
[MenuItem("Tools/Build Android Bundle")]
static void BundleAndroidBuild()
{
Build(BuildTarget.Android);
}
//如何使用Build呢,直接添加工具栏
[MenuItem("Tools/Build IOS Bundle")]
static void BundleIOSBuild()
{
Build(BuildTarget.iOS);
}
//为了能够构建多平台,需要把目标平台作为参数传入。
static void Build(BuildTarget target)
{
//主要目的是收集这个build信息,需要打哪些文件,需要给bundle包用一个什么样的名字,BuildAssetBundles函数用到这个Build数组
List<AssetBundleBuild> assetBundleBuilds = new List<AssetBundleBuild>();
//第一步搜索出我们这个所有文件的文件名Directory.GetDirectories和Directory.GetFiles对应两种打包策略一个获取文件夹一个获取文件,GetFiles比较简单
//searchPattern通配符,*是默认
string[] files = Directory.GetFiles(PathUtil.BuildResourcesPath, "*", SearchOption.AllDirectories);
//所有文件都找出来了,需要排除调meta文件
for (int i = 0; i < files.Length; i++)
{
if (files[i].EndsWith(".meta"))
{
continue;
}
//创建一个需要build的Bundle
AssetBundleBuild assetBundle = new AssetBundleBuild();
//处理出来的路径斜杠可能不同。需要规范一下
string fileName = PathUtil.GetStandardPath(files[i]);
string assetName = PathUtil.GetUnityPath(fileName);//获取unity相对路径
//一个assetBundle可以打包多个文件,这里只放一个文件
assetBundle.assetNames = new string[] { assetName };//assetBundle是一个相对路径文件名
//创建包名
string bundleName = fileName.Replace(PathUtil.BuildResourcesPath, "").ToLower();
assetBundle.assetBundleName = bundleName + ".ab";//Bundle需要后缀是.ab,,,,,,,,至此,Bundle的信息收集完了,需要放进list
assetBundleBuilds.Add(assetBundle);
}
//为什么不用另一个重载BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform),是因为需要自己去资源设置bundle名打标签,很麻烦
//第二个参数把list转为array数组
//第三个参数是压缩格式,选择默认
//第四个参数是目标平台,先选择win
if(Directory.Exists(PathUtil.BundleOutPath))
{
//判断是否有路径,如果有这个文件夹,就删掉文件,,递归recursive删掉所有文件和子文件。
Directory.Delete(PathUtil.BundleOutPath, true);
}
Directory.CreateDirectory(PathUtil.BundleOutPath);//删除路径后,创建路径
BuildPipeline.BuildAssetBundles(PathUtil.BundleOutPath, assetBundleBuilds.ToArray(), BuildAssetBundleOptions.None, target);
}
}
/// <summary>
/// 路径工具类:
/// 1定义了所有用到的路径
/// 2返回标准路径或返回unity下的几个文件夹的相对路径
/// </summary>
//因为所有路径都要用到,所以写入一个只读变量中,用来后期访问
public class PathUtil
{
//为什么要把Application定义出来,因为每一次访问都需要GC一次,定义出来就访问一次;
public static readonly string AssetPath = Application.dataPath;
//只读的,需要打Bundle的目录
public static readonly string BuildResourcesPath = AssetPath + "/BuildResources/";
//Bundle输出目录
public static readonly string BundleOutPath = Application.streamingAssetsPath;
/// <summary>
/// 获取Unity的相对路径
/// </summary>
/// <param name="path">绝对路径</param>
/// <returns></returns>
public static string GetUnityPath(string path)
{
if(string.IsNullOrEmpty(path))
{
return string.Empty;
}
//从Assets位置拿到相对目录
return path.Substring(path.IndexOf("Assets"));
}
/// <summary>
/// 获取标准路径
/// </summary>
/// <param name="path">路径</param>
/// <returns></returns>
public static string GetStandardPath(string path)
{
if (string.IsNullOrEmpty(path))
{
return string.Empty;
}
//先处理空格,在处理反斜杠
return path.Trim().Replace("\\", "/");
}
}
这两个工具放到文件夹后,在unity工具栏的位置出现Editor新工具,选择第一个构建Bundle,就可以把创建好的场景打包到StreamingAssets路径下,,,在SceneTest中通过这个路径,加载AssetBundle包,就可以动态加载场景了。
重新编写SceneTest场景加载的脚本,挂到起始场景的任意物体上,就可以动态加载场景了。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class sceneTest : MonoBehaviour
{
// Start is called before the first frame update
IEnumerator Start()
{
yield return LoadSceneAB("xxx1");
yield return LoadSceneAB("xxx2");
yield return LoadSceneAB("xxx3");
}
IEnumerator LoadSceneAB(string sceneName)
{
string assetBundleName = "scenes/" + sceneName +".unity.ab";
string path = Application.dataPath + "/StreamingAssets/" + assetBundleName;
AssetBundleCreateRequest myLoadedAssetBundle = AssetBundle.LoadFromFileAsync(path);
yield return myLoadedAssetBundle;
if (myLoadedAssetBundle != null)
{
SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
}
}
}
运行前后
如果该场景中依赖于其他物体、材质等,也是通过打包才能获取的,那么需要将其他物体也通过AssetBundle获取,获取方法需要编写一个依赖文件,说明此资源依赖于哪些资源,并通过递归的方法,加载这些依赖资源的ab包,下次再写这个地方的实现。