diff options
Diffstat (limited to 'Assets/Scripts')
-rw-r--r-- | Assets/Scripts/Editor.meta | 8 | ||||
-rw-r--r-- | Assets/Scripts/Editor/BuildCustomPipeline.cs | 16 | ||||
-rw-r--r-- | Assets/Scripts/Editor/BuildCustomPipeline.cs.meta | 11 | ||||
-rw-r--r-- | Assets/Scripts/Editor/BundleHelper.cs | 82 | ||||
-rw-r--r-- | Assets/Scripts/Editor/BundleHelper.cs.meta | 11 | ||||
-rw-r--r-- | Assets/Scripts/FileUtil.cs | 57 | ||||
-rw-r--r-- | Assets/Scripts/FileUtil.cs.meta | 11 | ||||
-rw-r--r-- | Assets/Scripts/PathHelper.cs | 41 | ||||
-rw-r--r-- | Assets/Scripts/PathHelper.cs.meta | 11 | ||||
-rw-r--r-- | Assets/Scripts/ResManager.cs | 273 | ||||
-rw-r--r-- | Assets/Scripts/ResManager.cs.meta | 11 | ||||
-rw-r--r-- | Assets/Scripts/Test.cs | 56 | ||||
-rw-r--r-- | Assets/Scripts/Test.cs.meta | 11 |
13 files changed, 599 insertions, 0 deletions
diff --git a/Assets/Scripts/Editor.meta b/Assets/Scripts/Editor.meta new file mode 100644 index 0000000..b1cf477 --- /dev/null +++ b/Assets/Scripts/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 22e71af268e34ac4f86477c2ec704ddf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/BuildCustomPipeline.cs b/Assets/Scripts/Editor/BuildCustomPipeline.cs new file mode 100644 index 0000000..3c91409 --- /dev/null +++ b/Assets/Scripts/Editor/BuildCustomPipeline.cs @@ -0,0 +1,16 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; + +public class BuildCustomPipeline : IPreprocessBuildWithReport +{ + public int callbackOrder { get { return 0; } } + public void OnPreprocessBuild(BuildReport report) + { + // BundleHelper.BuildBundles(); + } + +} diff --git a/Assets/Scripts/Editor/BuildCustomPipeline.cs.meta b/Assets/Scripts/Editor/BuildCustomPipeline.cs.meta new file mode 100644 index 0000000..5230b90 --- /dev/null +++ b/Assets/Scripts/Editor/BuildCustomPipeline.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 900a82c1f64da4b488ea055108308070 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/BundleHelper.cs b/Assets/Scripts/Editor/BundleHelper.cs new file mode 100644 index 0000000..631d542 --- /dev/null +++ b/Assets/Scripts/Editor/BundleHelper.cs @@ -0,0 +1,82 @@ +using System.Collections; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Linq; +using System.IO; +using UnityEngine; +using UnityEditor; + +public class BundleHelper +{ + + static string TempPath = Application.temporaryCachePath + "/PackData"; + static string TargetPath = Application.dataPath + "/StreamingAssets/PackData"; // or Application.streamingAssetsPath + static string MainBundleName = "PackData"; + static string MainBundle = TargetPath + "/PackData"; + static string ManifestFile = Application.dataPath + "/Resources/bundles.manifest"; + + [MenuItem("Build/Build bundles")] + public static void BuildBundles() + { + if(!Directory.Exists(TempPath)) + Directory.CreateDirectory(TempPath); + BuildAssetBundleOptions options = BuildAssetBundleOptions.None; + options |= BuildAssetBundleOptions.ChunkBasedCompression; + options |= BuildAssetBundleOptions.DeterministicAssetBundle; + options |= BuildAssetBundleOptions.ForceRebuildAssetBundle; + options |= BuildAssetBundleOptions.StrictMode; +#if UNITY_IOS + BuildPipeline.BuildAssetBundles(TempPath, options, BuildTarget.iOS); +#elif UNITY_ANDROID + BuildPipeline.BuildAssetBundles(TempPath, options, BuildTarget.Android); +#else + BuildPipeline.BuildAssetBundles(TempPath, options, BuildTarget.StandaloneWindows); +#endif + Debug.Log("Bundle生成结束"); + // Move to StreamingAssets + if (!Directory.Exists(TargetPath)) + Directory.CreateDirectory(TargetPath); + string[] files = Directory.GetFiles(TempPath, "*.*"); + string dirPath = null; + foreach(string file in files) + { + if (!file.EndsWith(".manifest") && !file.EndsWith(".meta")) + { + dirPath = file.Replace('\\', '/'); + dirPath = dirPath.Replace(TempPath, TargetPath); + if (File.Exists(dirPath)) + File.Delete(dirPath); + File.Move(file, dirPath); + } + } + // 将bundle的md5写入bundles.manifest文件 + AssetBundle mainBundle = AssetBundle.LoadFromFile(MainBundle); + if (mainBundle != null) + { + AssetBundleManifest manifest = mainBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); + if (manifest != null) + { + if (File.Exists(ManifestFile)) + File.Delete(ManifestFile); + using (StreamWriter file = new System.IO.StreamWriter(ManifestFile, true)) + { + string[] bundles = manifest.GetAllAssetBundles(); + List<string> _bundles = bundles.ToList<string>(); + _bundles.Add(MainBundleName); + foreach (string bundle in _bundles) + { + string path = TargetPath + "/" + bundle ; + if (!File.Exists(path)) + continue; + long size = new FileInfo(path).Length; + byte[] data = File.ReadAllBytes(path); + string hash = FileUtil.GetMD5Hash(data); + file.WriteLine(bundle + "," + size + "," + hash); + } + }; + } + mainBundle.Unload(true); + } + } + +} diff --git a/Assets/Scripts/Editor/BundleHelper.cs.meta b/Assets/Scripts/Editor/BundleHelper.cs.meta new file mode 100644 index 0000000..673f75d --- /dev/null +++ b/Assets/Scripts/Editor/BundleHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9eb05f681b7bd80448b2f488711dad41 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/FileUtil.cs b/Assets/Scripts/FileUtil.cs new file mode 100644 index 0000000..b0e9b8c --- /dev/null +++ b/Assets/Scripts/FileUtil.cs @@ -0,0 +1,57 @@ +using System.Collections; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.IO; +using System; +using UnityEngine; + +public class FileUtil { + + public static string GetMD5Hash(byte[] data) + { + string strHash = ""; + using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider()) + { + byte[] bytesHash = md5.ComputeHash(data); + strHash = BitConverter.ToString(bytesHash); + strHash = strHash.Replace("-", ""); + } + return strHash.ToLower(); + } + + public static string GetMD5Hash(string pathName) + { + if (!File.Exists(pathName)) + { + Debug.LogError("GetMD5Hash Error, file not exist: " + pathName); + return ""; + } + string strResult = ""; + string strHash = ""; + + byte[] bytesHash; + + FileStream fs = null; + using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider()) + { + try + { + fs = new FileStream(pathName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + bytesHash = md5.ComputeHash(fs); + fs.Close(); + strHash = BitConverter.ToString(bytesHash); + strHash = strHash.Replace("-", ""); + + strResult = strHash; + } + catch (System.Exception ex) + { + Debug.LogError("read md5 file error :" + pathName + " e: " + ex.ToString()); + } + } + + return strResult.ToLower(); + } + +} diff --git a/Assets/Scripts/FileUtil.cs.meta b/Assets/Scripts/FileUtil.cs.meta new file mode 100644 index 0000000..aeb1552 --- /dev/null +++ b/Assets/Scripts/FileUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0481cdb792bfaac469dd9cd32f24af3a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/PathHelper.cs b/Assets/Scripts/PathHelper.cs new file mode 100644 index 0000000..7c23dd6 --- /dev/null +++ b/Assets/Scripts/PathHelper.cs @@ -0,0 +1,41 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class PathHelper{ + // 用于bundle读取的streaming路径 + // 不需要加file://协议 + public static string GetStreamingFullFilePath(string filePath) + { +#if UNITY_ANDROID + return Application.dataPath + "!assets/" + filePath; +#else + return Application.streamingAssetsPath + "/" + filePath; +#endif + } + + // 用于www读取的streaming路径 + // 需要加file协议 + public static string GetStreamingWWWFullFilePath(string filePath) + { +#if UNITY_ANDROID + return Application.streamingAssetsPath + "/" + filePath; +#else + return "file://" + Application.streamingAssetsPath + "/" + filePath; +#endif + } + + public static string GetPersistentFullFilePath(string filePath) + { + return Application.persistentDataPath + "/" + filePath; + } + +#if UNITY_IOS +// 将文件从自动上传iCloud中排除 +public static void ExcludeFileFromiCloud(string filePath) +{ + string full = GetPersistentFullFilePath(filePath); + UnityEngine.iOS.Device.SetNoBackupFlag(full); +} +#endif +} diff --git a/Assets/Scripts/PathHelper.cs.meta b/Assets/Scripts/PathHelper.cs.meta new file mode 100644 index 0000000..f13fefb --- /dev/null +++ b/Assets/Scripts/PathHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f866276720b96643a5cec178754bb40 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/ResManager.cs b/Assets/Scripts/ResManager.cs new file mode 100644 index 0000000..ba47f8e --- /dev/null +++ b/Assets/Scripts/ResManager.cs @@ -0,0 +1,273 @@ +using System.Collections; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.IO; +using UnityEngine; +using UnityEngine.UI; +using MEC; + +public class ResManager { + + private struct BundleInfo{ + public long size; + public string hash; + } + + public static ResManager Instance + { + get + { + if (_instance == null) + _instance = new ResManager(); + return _instance; + } + } + static ResManager _instance; + + Test test; + string Content; + + static string BundleRoot = "PackData"; + static string ManifestBundleName = "PackData"; + static string ManifestFileResName = "bundles"; // under Resources/bundles.manifest + static string ManifesteRemotePath = "bundles.manifest"; +#if UNITY_STANDALONE_WIN + static string cdnAddress = "http://localhost/bundles/"; // PC +#elif UNITY_ANDROID + static string cdnAddress = "http://10.0.2.2/bundles/"; // AndroidEmu +#endif + + Dictionary<string, AssetBundle> CachedBundles = new Dictionary<string, AssetBundle>(); + Dictionary<string, string> FileMapping = new Dictionary<string, string>(); + AssetBundleManifest Manifest; + +#region 更新相关 + // 本地streamingAssetsPath下的bundles数据,从固化的bundles.manifest中读取 + Dictionary<string, BundleInfo> localBundles = new Dictionary<string, BundleInfo>(); + // CDN上的bundles数据 + Dictionary<string, BundleInfo> remoteBundles = new Dictionary<string, BundleInfo>(); +#endregion + + public void Init(Test test) + { + this.test = test; + LoadLocalBundlesInfo(); + Timing.Instance.RunCoroutineOnInstance(DownloadManifest()); + } + + void Print(string content , bool alert = false) + { + test.Print(content, alert); + } + + public string ResContent(string path) + { + TextAsset text = Resources.Load(path) as TextAsset; + if (text == null) + return null; + return text.text; + } + + public void ReadFileMapping() + { + AssetBundle bundle = LoadBundle("bundle00.ab"); // 不可以省略后缀 + if (bundle == null) + { + string path = PathHelper.GetStreamingFullFilePath(BundleRoot + "/bundle00.ab"); + Print("Bundle 为空, path = " + path, true); + return; + } + TextAsset text = bundle.LoadAsset<TextAsset>("fileMapping"); + if (text == null) + return; + string content = text.text; + content = content.Replace("\r", string.Empty); + string[] lines = content.Split('\n'); + Print("Load FileMapping:"); + foreach (string line in lines) + { + string[] part = line.Split(','); + FileMapping.Add(part[0], part[1]); + Print(part[0] + " " + part[1]); + } + } + + /// <summary> + /// 刚开始进入游戏的时候要写入.manifest,用来后续对比更新 + /// </summary> + public void LoadManifest() + { + AssetBundle bundle = LoadBundle(ManifestBundleName); + if (bundle == null) + { + Print("没有Manifest", true); + return; + } + Manifest = bundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); + if (Manifest == null) + { + Print("AssetBundleManifest", true); + return; + } + Print("All bundles:"); + string[] bundleNames = Manifest.GetAllAssetBundles(); + foreach (var bundleName in bundleNames) + { + Print(bundleName); + } + } + + public AssetBundle LoadBundle(string name) + { + AssetBundle bundle; + if (CachedBundles.TryGetValue(name, out bundle)) + { + return bundle; + } + string path = PathHelper.GetPersistentFullFilePath(BundleRoot + "/" + name); + Print(path); + if (!File.Exists(path)) + { + Print("不在persistent里"); + path = PathHelper.GetStreamingFullFilePath(BundleRoot + "/" + name); + } + bundle = AssetBundle.LoadFromFile(path); + if(bundle == null) + Print("是空的"); + CachedBundles.Add(name, bundle); + return bundle; + } + + public string GetBundleName(string assetName) + { + string bundleName; + FileMapping.TryGetValue(assetName, out bundleName); + return bundleName; + } + + public string BundleContent(string resName) + { + string bundleName = GetBundleName(resName); + if (bundleName == null) + return null; + AssetBundle bundle = LoadBundle(bundleName); + if (bundle == null) + return null; + TextAsset text = bundle.LoadAsset<TextAsset>(resName); + if (text == null) + return null; + return text.text; + } + + /// <summary> + /// 根据Resources/bundles.manifest读取本地bundle数据 + /// </summary> + private void LoadLocalBundlesInfo() + { + TextAsset manifest = Resources.Load<TextAsset>(ManifestFileResName); + if(manifest == null) + { + Debug.LogError("本地Resources目录下没有bundles.manifest"); + return; + } + string[] bundles = manifest.text.Replace("\r", string.Empty).Split('\n'); + string[] info; + foreach(string bundle in bundles) + { + info = bundle.Split(','); + if (info.Length != 3) + continue; + BundleInfo bi = new BundleInfo(); + bi.size = long.Parse(info[1]); + bi.hash = info[2]; + localBundles.Add(info[0], bi); + } + } + + IEnumerator<float> DownloadManifest() + { + WWW www = new WWW(cdnAddress + ManifesteRemotePath); + while(!www.isDone) + { + yield return Timing.WaitForSeconds(0.01f); + } + string content = www.text; + string[] bundles = content.Replace("\r", string.Empty).Split('\n'); + string[] info; + foreach (string bundle in bundles) + { + info = bundle.Split(','); + if (info.Length != 3) + continue; + BundleInfo bi = new BundleInfo(); + bi.size = long.Parse(info[1]); + bi.hash = info[2]; + remoteBundles.Add(info[0], bi); + } + // Download bundles + Timing.Instance.RunCoroutineOnInstance(DownloadBundles()); + } + + IEnumerator<float> DownloadBundles() + { + List<string> downloadBundles = new List<string>(); + foreach(var b in remoteBundles) + { + string bundleName = b.Key; + long size = b.Value.size; + string hash = b.Value.hash; + BundleInfo localInfo; + //1. 剔除首包中就有的 + if (localBundles.TryGetValue(bundleName, out localInfo) && localInfo.hash == hash) + continue; + //2. 剔除上次小版本更新下载下来的 + string persistentPath = PathHelper.GetPersistentFullFilePath(BundleRoot + "/" + bundleName); + if(File.Exists(persistentPath)) + { + long lastsize = new FileInfo(persistentPath).Length; + if(lastsize == size) + { + string lasthash = FileUtil.GetMD5Hash(persistentPath); + if (lasthash == "") + { + Debug.LogError("计算md5出错"); + } + else if (lasthash == hash) + continue; + } + } + downloadBundles.Add(bundleName); + } + //3. 下载 + foreach (string bundle in downloadBundles) + { + WWW www = new WWW(cdnAddress + bundle); + while(!www.isDone) + { + yield return Timing.WaitForSeconds(0.01f); + } + // save to persistentDataPath + Print("->下载" + bundle + "完毕"); + byte[] data = www.bytes; + string folder = PathHelper.GetPersistentFullFilePath(BundleRoot); + if (!Directory.Exists(folder)) + Directory.CreateDirectory(folder); + File.WriteAllBytes(folder + "/" + bundle, data); + } + + Print("Bundle 下载完毕,共下载" + downloadBundles.Count + "个"); + Print("------------------------------------------------------------------------"); + // test test + ResManager.Instance.ReadFileMapping(); + ResManager.Instance.LoadManifest(); + // Done + OnDownloaded(); + } + + void OnDownloaded() + { + if (test != null) + test.Testing(); + } + +} diff --git a/Assets/Scripts/ResManager.cs.meta b/Assets/Scripts/ResManager.cs.meta new file mode 100644 index 0000000..0e58ce6 --- /dev/null +++ b/Assets/Scripts/ResManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4014a0d4dcc96c744acda2162e08e6cd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Test.cs b/Assets/Scripts/Test.cs new file mode 100644 index 0000000..da32140 --- /dev/null +++ b/Assets/Scripts/Test.cs @@ -0,0 +1,56 @@ +using System.Collections; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.IO; +using UnityEngine; +using UnityEngine.UI; + +public class Test : MonoBehaviour { + + public Text text; + string Content; + + public void Print(string content, bool alert = false) + { + if (content == null) + content = "空"; + if(alert) + Content += "<Color=#ff0000ff>"; + Content += content + "\n"; + if(alert) + Content += "</Color>"; + text.text = Content; + } + + private void Awake() + { + ResManager.Instance.Init(this); + } + + public void Testing() + { + Print("----------------------------------------------------------------------------------------"); + Print("Application.dataPath=" + Application.dataPath); + Print("Application.streamingAssetsPath=" + Application.streamingAssetsPath); + Print("Application.persistentDataPath=" + Application.persistentDataPath); + Print("Application.temporaryCachePath=" + Application.temporaryCachePath); + Print("----------------------------------------------------------------------------------------"); + Print("读取Assets/Resources/res1.txt , 内容是"); + Print(ResManager.Instance.ResContent("res1")); + Print("读取Assets/Resources/SubFolder/res1.txt , 内容是"); + Print(ResManager.Instance.ResContent("SubFolder/res1")); + Print("读取Assets/Art/Resources/res5.txt , 内容是"); + Print(ResManager.Instance.ResContent("res5")); + Print("----------------------------------------------------------------------------------------"); + // AssetBundle.LoadAsset()可以省略后缀 + Print("读取Assets/Files/file1.txt , 内容是"); + Print(ResManager.Instance.BundleContent("file1.txt")); + Print(ResManager.Instance.BundleContent("file1")); + Print("读取Assets/Files/file1.abc, 内容是"); + Print(ResManager.Instance.BundleContent("file1.abc")); + Print("读取Assets/Files/file1.bcd, 内容是"); + Print(ResManager.Instance.BundleContent("file1.bcd")); + Print("说明进Bundle的资源不含后缀的资源名字不能冲突", true); + } + +} diff --git a/Assets/Scripts/Test.cs.meta b/Assets/Scripts/Test.cs.meta new file mode 100644 index 0000000..9c5d14a --- /dev/null +++ b/Assets/Scripts/Test.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 97aee000e53f1464bbd45e96b6ea0f9a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: |