Step 1:Bundle构建、加载、更新

(1)搭建Build工具

AB包的意义:如果我们将资源都放在Resources中,我们客户端的大小会越来越大。所以我们希望将资源打包放到服务端,使用的时候客户端到服务端去下载,这样就可以达到缩包的效果。

1)导入xLua

将xlua中Asset目录下的文件保持这个结构拖到我们的项目中。

2)查找BuildResources下的资源文件

进行格式转换和拼接

查找依赖资源

3)使用BuildPipeline构建AB包

BundleBuild策略:


按文件夹打包

按文件打包

优势

Bundle的数量少

更新的补丁小

劣势

后期更新的时候补丁大

首次下载的时候慢

打包的代码参考:

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;

public class BuildTool : Editor
{
[MenuItem("Tools/Build Windows Bundle")]
static void BundleWindowsBuild()
{
Build(BuildTarget.StandaloneWindows);
}

[MenuItem("Tools/Build Android Bundle")]
static void BundleAndroidBuild()
{
Build(BuildTarget.Android);
}

[MenuItem("Tools/Build IOS Bundle")]
static void BundleIOSBuild()
{
Build(BuildTarget.iOS);
}

static void Build(BuildTarget target)
{
List<AssetBundleBuild> assetBundleBuilds = new List<AssetBundleBuild>();
List<string> bundleInfos = new List<string>();
string[] files = Directory.GetFiles(PathUtil.BuildResourcesPath, "*", SearchOption.AllDirectories);
for (int i=0; i<files.Length; i++)
{
if (files[i].EndsWith(".meta")) continue;//meta文件是不用打包的

AssetBundleBuild assetBundle = new AssetBundleBuild();
string fileName = PathUtil.GetStandardPath(files[i]);//文件的格式进行标准化
Debug.Log("FileName : " + fileName);

string assetName = PathUtil.GetUnityPath(fileName);//截取asset之后的部分作为assetname属性
assetBundle.assetNames = new string[] { assetName };
Debug.Log("AssetName: " + assetName );

string bundleName = fileName.Replace(PathUtil.BuildResourcesPath, "").ToLower();//标准化之后将BuildResource以及之前的部分都替换掉,作为Bundle的名字
assetBundle.assetBundleName = bundleName + ".ab";//Bundle的后缀改为ab
Debug.Log("AssetBundleName: " + bundleName + ".ab");
assetBundleBuilds.Add(assetBundle);

//添加依赖信息
List<string> dependenceInfo = GetDependence(assetName);
string bundleInfo = assetName + "|" + bundleName + ".ab";

if (dependenceInfo.Count > 0)
bundleInfo = bundleInfo + "|" + string.Join("|", dependenceInfo);

bundleInfos.Add(bundleInfo);
}

if (Directory.Exists(PathUtil.BundleOutPath)) Directory.Delete(PathUtil.BundleOutPath, true);
//创建输出的目标
Directory.CreateDirectory(PathUtil.BundleOutPath);

//这个就是打包
BuildPipeline.BuildAssetBundles(PathUtil.BundleOutPath, assetBundleBuilds.ToArray(), BuildAssetBundleOptions.None, target);
File.WriteAllLines(PathUtil.BundleOutPath + "/" , bundleInfos);

AssetDatabase.Refresh();
}

//这是获取依赖文件
static List<string> GetDependence(string curFile)
{
List<string> dependence = new List<string>();

string[] files = AssetDatabase.GetDependencies(curFile);//这是一个正向查找依赖的接口,用于查找当前资源所依赖的资源
dependence = files.Where(file => !file.EndsWith(".cs") && !file.Equals(curFile)).ToList();
return dependence;
}
}

点击运行之后创建的文件:

搭建热更新框架_lua

4)加载AB资源:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LoadAB : MonoBehaviour
{
IEnumerator Start()
{
AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/ui/panel.prefab.ab");
yield return request;

AssetBundleRequest bundleRequest = request.assetBundle.LoadAssetAsync("Assets/BuildResources/UI/Panel.prefab");
yield return bundleRequest;

GameObject go = Instantiate(bundleRequest.asset) as GameObject;
go.transform.SetParent(this.transform);
go.SetActive(true);
go.transform.localPosition = Vector3.zero;
}
}

但是这种方式如果依赖资源没有加载,会造成依赖资源的丢失。

(2)建立版本管理系统

版本信息管理:

搭建热更新框架_lua_02

查找依赖文件并且进行保存:

搭建热更新框架_bundle_03

(3)资源管理器

接着我们创建一个资源管理器进行文件的加载。

using Assets.Scripts;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UObject = UnityEngine.Object;

public class ResourceManager : MonoBehaviour
{
internal class BundleInfo
{
public string AssetName;
public string BundleName;
public List<string> Dependences;
}

//存放bundle信息
private Dictionary<string, BundleInfo> m_BundleInfos = new Dictionary<string, BundleInfo>();


public void ParseVersionFile()
{
//拿到版本文件路径
string url = Path.Combine(PathUtil.BundleResourcePath, AppConst.FileListName);//框架层
//读取
string[] data = File.ReadAllLines(url);//一行一行的读

//解析文件信息
for (int i = 0; i < data.Length; i++)
{
BundleInfo bundleInfo = new BundleInfo();
string[] info = data[i].Split('|');//分割
bundleInfo.AssetName = info[0];//这个是filelist里面的名字
bundleInfo.BundleName = info[1];//这个是打好Bundle包之后的资产地址

//数组,可以动态扩容
bundleInfo.Dependences = new List<string>(info.Length - 2);
for (int j = 2; j < info.Length; j++)//索引从2开始才是依赖资源
{
bundleInfo.Dependences.Add(info[j]);
}
m_BundleInfos.Add(bundleInfo.AssetName, bundleInfo);

//if (info[0].IndexOf("LuaScripts") > 0)
// Manager.Lua.LuaNames.Add(info[0]);
}
}


//internal void LoadLua(string name, Action<UObject> p)
//{
// throw new NotImplementedException();
//}

//异步加载资源
IEnumerator LoadBundleAsync(string assetName, Action<UObject> action = null)
{
//获取文件信息
string bundleName = m_BundleInfos[assetName].BundleName;
string bundlePath = Path.Combine(PathUtil.BundleResourcePath, bundleName);

List<string> dependences = m_BundleInfos[assetName].Dependences;

if (dependences != null && dependences.Count > 0)
{
//递归加载依赖资源
for (int i = 0; i < dependences.Count; i++)
{
yield return LoadBundleAsync(dependences[i]);
}
}

//加载Bundle资源
AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(bundlePath);
yield return request;

AssetBundleRequest bundleRequest = request.assetBundle.LoadAssetAsync(assetName);
yield return bundleRequest;
Debug.Log("This is LoadBundleAsync");
action?.Invoke(bundleRequest?.asset);
}

#if UNITY_EDITOR
void EditorLoadAsset(string assetName, Action<UObject> action = null)
{
Debug.Log("This is EditorMode");
UObject obj = UnityEditor.AssetDatabase.LoadAssetAtPath(assetName, typeof(UObject));

if (obj == null)
Debug.LogError("Assets name does not exists: " + assetName);
action?.Invoke(obj);
}
#endif

public void LoadAsset(string assetName, Action<UObject> action)
{
#if UNITY_EDITOR
if (AppConst.gameMode == GameMode.EditorMode)
EditorLoadAsset(assetName, action);
else
#endif
StartCoroutine(LoadBundleAsync(assetName, action));
}

public void LoadUI(string assetName, Action<UObject> action = null)
{
LoadAsset(PathUtil.GetUIPath(assetName), action);
}

public void LoadMusic(string assetName, Action<UObject> action = null)
{
LoadAsset(PathUtil.GetMusicPath(assetName), action);
}

public void LoadSound(string assetName, Action<UObject> action = null)
{
LoadAsset(PathUtil.GetSoundPath(assetName), action);
}

public void LoadEffect(string assetName, Action<UObject> action = null)
{
LoadAsset(PathUtil.GetEffectPath(assetName), action);
}

public void LoadScene(string assetName, Action<UObject> action = null)
{
LoadAsset(PathUtil.GetScenePath(assetName), action);
}

public void LoadLua(string assetName, Action<UnityEngine.Object> action = null)
{
LoadAsset(assetName, action);
}

public void LoadPrefab(string assetName, Action<UnityEngine.Object> action = null)
{
LoadAsset(assetName, action);
}
}

(4)热更新部分

热更新的方案选择:


整包

分包

含义

完整资源放在包内


优势

首次更新时间少

安装包下载快

劣势

安装包下载时间长,首次安装事件久

首次更新时间久

热更新的步骤:

搭建热更新框架_bundle_04

整包策略:streamingasset是只读的,persistentDataPath是可读写的。初次安装的时候将只读目录的内容拷贝到可读写目录,后续检查更新也都是从可读写目录进行更新下载的。

分包策略:直接和可读写目录进行交互。

热更新代码部分:

搭建热更新框架_lua_05

检测初次安装:只读目录有热更新资源、可读写目录没有热更资源、判断filelist文件是否存在。需要注意filelist需要最后写入

检查更新:下载资源服务器上的filelist,对比文件信息和本地是否一致

搭建热更新框架_bundle_06

热更新代码步骤

1)下载文件并完成文件释放

需要提前在创建只读目录和可读写目录

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class HotUpdate : MonoBehaviour
{
internal class DownFileInfo
{
public string url;

public string fileName;
public DownloadHandler fileData;
}
//下载文件
IEnumerator DownLoadFile(DownFileInfo info,Action<DownloadHandler> Complete)
{
UnityWebRequest webRequest = UnityWebRequest.Get(info.url);
yield return webRequest.SendWebRequest();
//检测
if(webRequest.isHttpError || webRequest.isNetworkError)
{
Debug.Log("下载文件出错"+ info.url);
yield break;
}
info.fileData = webRequest.downloadHandler;
Complete?.Invoke(info);
//下载完成之后需要释放掉
webRequest.Dispose();
}

//下载多个文件
IEnumerator DownLoadFile(List<DownFileInfo> infos, Action<DownloadHandler> Complete, Action DownLoadAllComplete)
{
foreach(DownFileInfo info in infos)
{
yield return DownLoadFile(info, Complete);
}
DownLoadAllComplete?.Invoke();
}
}


Step 2: 热更新管理器

(1)Lua加载和管理


(2)Lua绑定和执行


Step 3:业务逻辑管理器