Unity资源热更
Posted GamePal
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity资源热更相关的知识,希望对你有一定的参考价值。
Unity2017
一、创建Bundle打包工具ExportAssetBundles
using UnityEditor; using UnityEngine; public class ExportAssetBundles : EditorWindow { [MenuItem("Assets/Build AssetBundle From Selection")] static void ExportResourceRespective() { // 打开保存面板,获得用户选择的路径 string path = EditorUtility.SaveFilePanel("Save Resource", "", "New Resource", "assetbundle"); if (path.Length != 0) { // 选择的要保存的对象 Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets); //打包android // BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, path, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, BuildTarget.Android); //打包iPhone //BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, path, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, BuildTarget.ios); //打包Window BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, path, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, BuildTarget.StandaloneWindows); /*BuildAssetBundle有5个参数 * 1 第一个是主资源 * 2 第二个是资源数组 这两个参数必须有一个不为null 如果主资源存在于资源数组中是没有任何关系的,如果设置了主资源,可以通过Bundle.mainAsset来直接使用它 * 3 第三个参数是路径 一般我们设置为 Application.streamingAssetsPath + Bundle的目标路径和Bundle名称 * 4 第四个参数有四个选项 .CollectDependencies会去查找依赖 .CompleteAssets会强制包含整个资源 DeterministicAssetBundle会确保生成唯一ID,在打包依赖时会有用到 * 5 第五个参数是目标平台 */ } } [MenuItem("Assets/Save Scene")] static void ExportScene() { // 打开保存面板,获得用户选择的路径 string path = EditorUtility.SaveFilePanel("Save Resource", "", "New Resource", "unity3d"); if (path.Length != 0) { // 选择的要保存的对象 Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets); string[] scenes = { "Assets/Scenes/scene1.unity" }; //打包场景Windows //BuildPipeline.BuildPlayer(scenes, path, BuildTarget.StandaloneWindows, BuildOptions.BuildAdditionalStreamedScenes | BuildOptions.UncompressedAssetBundle); //打包场景Iphone BuildPipeline.BuildPlayer(scenes, path, BuildTarget.iOS, BuildOptions.BuildAdditionalStreamedScenes | BuildOptions.UncompressedAssetBundle); } } }
二、资源更新及读取
using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Text; using System.IO; using UnityEngine.SceneManagement; public class ResUpdate : MonoBehaviour { public static readonly string VERSION_FILE = "version.txt"; public static readonly string LOCAL_RES_URL = "file:///D:/Output/Temp/Res/Local/"; //www加载路径 public static readonly string SERVER_RES_URL = "file:///D:/Output/Temp/Res/Server/"; public static readonly string LOCAL_RES_PATH = "D:\\\\Output\\\\Temp\\\\Res\\\\Local\\\\"; //本地文件夹路径 public static readonly string SERVER_RES_PATH = "D:\\\\Output\\\\Temp\\\\Res\\\\Server\\\\"; //服务器文件夹路径 private Dictionary<string, string> LocalResVersion; //fileName -> Md5 private Dictionary<string, string> ServerResVersion; private List<string> NeedDownFiles; private bool NeedUpdateLocalVersionFile = false; public delegate void HandleFinishDownload(WWW www); void Start() { //初始化 CreatConfiga(SERVER_RES_PATH); //创建服务端配置文件 version.txt CreatConfiga(LOCAL_RES_PATH); //创建客户端配置文件 version.txt 此时文件无内容 ServerResVersion = new Dictionary<string, string>(); //fileName -> Md5 LocalResVersion = new Dictionary<string, string>(); NeedDownFiles = new List<string>(); ////加载本地version配置 StartCoroutine(DownLoad(LOCAL_RES_URL + VERSION_FILE, delegate (WWW localVersion) { //把本地version.txt内容localVersion.text 保存到LocalResVersion字典 ;version.txt此时文件无内容 ParseVersionFile(localVersion.text, LocalResVersion); //加载服务端version配置 StartCoroutine(this.DownLoad(SERVER_RES_URL + VERSION_FILE, delegate (WWW serverVersion) { //保存服务端version ParseVersionFile(serverVersion.text, ServerResVersion); //计算出需要重新加载的资源 CompareVersion(); //加载需要更新的资源 DownLoadRes(); })); })); } /// <summary> /// 依次加载需要更新的资源到SERVER_RES_PATH /// </summary> private void DownLoadRes() { if (NeedDownFiles.Count == 0) { UpdateLocalVersionFile(); return; } string file = NeedDownFiles[0]; NeedDownFiles.RemoveAt(0); StartCoroutine(this.DownLoad(SERVER_RES_URL + file, delegate (WWW w) { //将下载的资源替换本地就的资源 ReplaceLocalRes(file, w.bytes); DownLoadRes(); })); } /// <summary> /// 把需要添加或更新文件写入客户端文件夹 /// </summary> /// <param name="fileName"></param> /// <param name="data"></param> private void ReplaceLocalRes(string fileName, byte[] data) { string filePath = LOCAL_RES_PATH + fileName; FileStream stream = new FileStream(LOCAL_RES_PATH + fileName, FileMode.Create); stream.Write(data, 0, data.Length); stream.Flush(); stream.Close(); } //显示资源 private IEnumerator Show() { string BundleURL = "file:///D:/Output/Temp/Res/Local/Cube.assetbundle"; using (WWW asset = new WWW(BundleURL)) { yield return asset; AssetBundle bundle = asset.assetBundle; Instantiate(bundle.LoadAsset("Cylinder")); bundle.Unload(false); yield return new WaitForSeconds(5); } } private IEnumerator LoadScene() { string path = LOCAL_RES_PATH + "test.unity3d"; Debug.Log(path); AssetBundle bundle = AssetBundle.LoadFromFile(path); string[] scenes = bundle.GetAllScenePaths(); string scene = Path.GetFileNameWithoutExtension(scenes[0]); SceneManager.LoadScene(scene); yield return new WaitForSeconds(5); } /// <summary> /// NeedDownFiles.Count > 0 更新本地的version配置 ; 于场景中加载资源 /// </summary> private void UpdateLocalVersionFile() { if (NeedUpdateLocalVersionFile) { StringBuilder versions = new StringBuilder(); foreach (var item in ServerResVersion) { versions.Append(item.Key).Append(",").Append(item.Value).Append("\\n"); } FileStream stream = new FileStream(LOCAL_RES_PATH + "//" + VERSION_FILE, FileMode.Create); byte[] data = Encoding.UTF8.GetBytes(versions.ToString()); stream.Write(data, 0, data.Length); stream.Flush(); stream.Close(); } //加载显示对象 StartCoroutine(Show()); //载入场景 //StartCoroutine(LoadScene()); } /// <summary> /// 对比 LocalResVersion和ServerResVersion 字典,更新NeedDownFiles列表 /// </summary> private void CompareVersion() { //遍历ServerResVersion字典 foreach (var version in ServerResVersion) { string fileName = version.Key; string serverMd5 = version.Value; //新增的资源 if (!LocalResVersion.ContainsKey(fileName)) { NeedDownFiles.Add(fileName); } else { //需要替换的资源 依据Md5码校验是否替换 string localMd5; LocalResVersion.TryGetValue(fileName, out localMd5); if (!serverMd5.Equals(localMd5)) { NeedDownFiles.Add(fileName); } } } //本次有更新,同时更新本地的version.txt NeedUpdateLocalVersionFile = NeedDownFiles.Count > 0; } /// <summary> /// 把version.txt中版本信息写入字典 /// </summary> /// <param name="content"></param> /// <param name="dict"></param> private void ParseVersionFile(string content, Dictionary<string, string> dict) { if (content == null || content.Length == 0) { return; } string[] items = content.Split(new char[] { \'\\n\' }); foreach (string item in items) //test.unity3d,dab1894eb803a45b8e8a07b3daac2b17 { string[] info = item.Split(new char[] { \',\' }); if (info != null && info.Length == 2) { dict.Add(info[0], info[1]); } } } /// <summary> /// 根据url连接 返回WWW 给委托 /// </summary> /// <param name="url"></param> /// <param name="finishFun"></param> /// <returns></returns> private IEnumerator DownLoad(string url, HandleFinishDownload finishFun) { WWW www = new WWW(url); yield return www; if (finishFun != null) { finishFun(www); } www.Dispose(); } /// <summary> /// 获取resPath下打包资源,创建配置文件version.txt /// </summary> /// <param name="resPath"></param> public void CreatConfiga(string resPath) { string[] files = Directory.GetFiles(resPath, "*", SearchOption.AllDirectories); System.Text.StringBuilder versions = new System.Text.StringBuilder(); for (int i = 0; i < files.Length; i++) { string filePath = files[i]; string extension = filePath.Substring(files[i].LastIndexOf(".")); if (extension == ".assetbundle" || extension == ".unity3d") { string relativePath = filePath.Replace(resPath, "").Replace("\\\\", "/"); string md5 = MD5File(filePath); versions.Append(relativePath).Append(",").Append(md5).Append("\\n"); } } FileStream stream = new FileStream(resPath + "\\\\" + "version.txt", FileMode.Create); byte[] data = Encoding.UTF8.GetBytes(versions.ToString()); stream.Write(data, 0, data.Length); stream.Flush(); stream.Close(); } /// <summary> /// 根据文件名生成Md5码 /// </summary> /// <param name="file"></param> /// <returns></returns> public static string MD5File(string file) { try { FileStream fs = new FileStream(file, FileMode.Open); System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); byte[] retVal = md5.ComputeHash(fs); fs.Close(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < retVal.Length; i++) { sb.Append(retVal[i].ToString("x2")); } return sb.ToString(); } catch (System.Exception ex) { throw new System.Exception("md5file() fail,error:" + ex.Message); } } }
三、这里创建了两个文件夹Server&Local模拟服务器和本地
四、网络测试
using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using UnityEngine; using UnityEngine.SceneManagement; public class RuziniuDriver : MonoBehaviour { public static string LOCAL_RES_PATH; //local exchange path public static readonly string VERSION_FILE = "version.txt"; public static readonly string SERVER_RES_URL = "http://54.193.6.32:8080/U3dFileToServer/upLoad/WFJ/AssetBundle/"; string LOCAL_RES_URL = null; public delegate void HandleFinishDownload(WWW www); private Dictionary<string, string> LocalResVersion; //fileName -> Md5 private Dictionary<string, string> ServerResVersion; private List<string> NeedDownFiles; private bool NeedUpdateLocalVersionFile = false; // Use this for initialization void Start() { LOCAL_RES_PATH = Application.persistentDataPath + "/StreamingAssets/"; if(!Directory.Exists(LOCAL_RES_PATH)) { Directory.CreateDirectory(LOCAL_RES_PATH); } CreatConfig(LOCAL_RES_PATH); //创建客户端 ServerResVersion = new Dictionary<string, string>(); //fileName -> Md5 LocalResVersion = new Dictionary<string, string>(); NeedDownFiles = new List<string>(); StartCoroutine(DownLoad(LOCAL_RES_PATH + VERSION_FILE, delegate (WWW localVersion) { //把本地version.txt内容localVersion.text 保存到LocalResVersion字典 ;version.txt此时文件无内容 ParseVersionFile(localVersion.text, LocalResVersion); //加载服务端version配置 StartCoroutine(this.DownLoad(SERVER_RES_URL + VERSION_FILE, delegate (WWW serverVersion) { //保存服务端version ParseVersionFile(serverVersion.text, ServerResVersion); //计算出需要重新加载的资源 CompareVersion(); //加载需要更新的资源 DownLoadRes(); })); })); } /// <summary> /// 对比 LocalResVersion和ServerResVersion 字典,更新NeedDownFiles列表 /// </summary> private void CompareVersion() { //遍历ServerResVersion字典 foreach (var version in ServerResVersion) { string fileName = version.Key; string serverMd5 = version.Value; //新增的资源 if (!LocalResVersion.ContainsKey(fileName)) { NeedDownFiles.Add(fileName); } else { //需要替换的资源 依据Md5码校验是否替换 string localMd5; LocalResVersion.TryGetValue(fileName, out localMd5); if (!serverMd5.Equals(localMd5)) { NeedDownFiles.Add(fileName); } } } //本次有更新,同时更新本地的version.txt NeedUpdateLocalVersionFile = NeedDownFiles.Count > 0; } /// <summary> /// 根据VERSION_FILE 依次加载需要更新的资源到LOCAL_RES_PATH /// </summary> private void DownLoadRes() { if (NeedDownFiles.Count == 0) { UpdateLocalVersionFile(); return; } string file = NeedDownFiles[0]; NeedDownFiles.RemoveAt(0); StartCoroutine(this.DownLoad(SERVER_RES_URL + file, delegate (WWW w) { //将下载的资源替换本地旧的资源 ReplaceLocalRes(file, w.bytes); DownLoadRes(); }) ); } /// <summary> /// 根据url连接 返回WWW 给委托 /// </summary> /// <param name="url"></param> /// <param name="finishFun"></param> /// <returns></returns> private IEnumerator DownLoad(string url, HandleFinishDownload finishFun) { WWW www = new WWW(url); yield return www; if (finishFun != null) { finishFun(www); } www.Dispose(); } /// <summary> /// 把需要添加或更新文件写入客户端文件夹 /// </summary> /// <param name="fileName"></param> /// <param name="data"></param> private void ReplaceLocalRes(string fileName, byte[] data) { string filePath = LOCAL_RES_PATH + fileName; FileStream stream = new FileStream(LOCAL_RES_PATH + fileName, FileMode.Create); stream.Write(data, 0, data.Length); stream.Flush(); stream.Close(); } /// <summary> /// 更新 VERSION_FILE:version.txt /// </summary> private void UpdateLocalVersionFile() { if (NeedUpdateLocalVersionFile) { StringBuilder versions = new StringBuilder(); foreach (var item in ServerResVersion) { versions.Append(item.Key).Append(",").Append(item.Value).Append("\\n"); } FileStream stream = new FileStream(LOCAL_RES_PATH + "//" + VERSION_FILE, FileMode.Create); byte[] data = Encoding.UTF8.GetBytes(versions.ToString()); stream.Write(data, 0, data.Length); stream.Flush(); stream.Close(); } //加载显示对象 //StartCoroutine(Show()); //载入场景 //StartCoroutine(LoadScene()); } /// <summary> /// 把version.txt中版本信息写入字典 /// </summary> /// <param name="content">资源名称</param> /// <param name="dict">版本字典</param> private void ParseVersionFile(string content, Dictionary<string, string> dict) { if (content == null || content.Length == 0) { return; } string[] items = content.Split(new char[] { \'\\n\' }); foreach (string item in items) //test.unity3d,dab1894eb803a45b8e8a07b3daac2b17 { string[] info = item.Split(new char[] { \',\' }); if (info != null && info.Length == 2) { dict.Add(info[0], info[1]); } } } /// <summary> /// 获取resPath下打包资源,创建配置文件version.txt /// </summary> /// <param name="resPath"></param> void CreatConfig(string resPath) { string[] files = Directory.GetFiles(resPath, "*", SearchOption.AllDirectories); System.Text.StringBuilder versions = new System.Text.StringBuilder(); for (int i = 0; i < files.Length; i++) { string filePath = files[i]; string extension = filePath.Substring(files[i].LastIndexOf(".")); if (extension == ".assetbundle" || extension == ".unity3d") { string assetName = filePath.Replace(resPath, "").Replace("\\\\", ""); string md5 = MD5File(filePath); versions.Append(assetName).Append(",").Append(md5).Append("\\n"); } } FileStream stream = new FileStream(resPath + "\\\\" + "version.txt", FileMode.Create); byte[] data = Encoding.UTF8.GetBytes(versions.ToString()); stream.Write(data, 0, data.Length); stream.Flush(); stream.Close(); } /// <summary> /// 根据文件名生成Md5码 /// </summary> /// <param name="file"></param> /// <returns></returns> string MD5File(string file) { try { FileStream fs = new FileStream(file, FileMode.Open); System.Security.Cryptography.MD5 md5 = newUnity资源热更-Addressables总结Unity 之 Addressable可寻址系统 -- 资源热更新