2018-01-22 16:44:48 LIQIANGEASTSUN 阅读数 2455
  • unity3D-游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12794 人正在学习 去看看 宋晓波

Unity AssetBundle 热更 资源生成对应 MD5码

游戏版本变更,一般会伴随着 AssetBundle 资源的变动,客户端如何确定哪些资源需要更新,哪些资源是本地没有的。
一般做法就是将整个版本的所有 AssetBundle 文件,每个文件的文件名对应该文件的一个MD5码,写入一个 更新文件如update.txt,当有版本更新时,将update.txt 放到服务器,客户端在启动游戏时,首先会从服务端请求一下当前最新版本号,和本地的版本号比较,如果客户端和服务器版本号一致,则视为客户端为最新版本不需要更新,进入游戏。

如果客户端版本号低于最新版本号,则客户端向服务器请求版本更新文件 update.txt ,将 服务器的 update.txt 下载下来,和本地的 update.txt 文件对比。
找出需要更新的文件:
一、服务端 update.txt 中 的文件名, 客户端 update.txt 中没有的,为新版本添加文件,需要更新
二、服务端 update.txt 中有,客户端 update.txt 也有,但是 MD5 码不同,说明新版本对文件有改动,需要更新

获取文件的MD5码方法

// 获取指定目录下 非 .meta 的文件
// assetPath 为文件目录
string[] files = (string[])Directory.GetFiles(assetPath, "*.*", SearchOption.AllDirectories).Where(s => !s.EndsWith(".meta"));

// filePath 为单个文件路径
using (FileStream fs = File.OpenRead(filePath))
{
    MD5 md5 = MD5.Create();
    byte[] fileMd5Bytes = md5.ComputeHash(fs);  // 计算FileStream 对象的哈希值
    fileMd5 = System.BitConverter.ToString(fileMd5Bytes).Replace("-", "").ToLower();
}

全部代码如下

using UnityEngine;
using System.Text;
using System;
using System.IO;
using UnityEditor;
using System.Linq;
using System.Security.Cryptography;

public static class BuildAssetBundleVersion
{
    public static string assetPath = string.Empty;
    public static void BuildVersion()
    {
        assetPath = Path.Combine(Application.streamingAssetsPath, "AssetBundle");
        Caching.CleanCache();

        CreateUpdateTXT();
        AssetDatabase.Refresh();
    }

    // 创建更新文本
    private static void CreateUpdateTXT()
    {
        string[] files = (string[])Directory.GetFiles(assetPath, "*.*", SearchOption.AllDirectories).Where(s => !s.EndsWith(".meta"));

        StringBuilder stringBuilder = new StringBuilder();
        foreach (string filePath in files)
        {
            string md5 = BuildFileMd5(filePath);

            string fileName = filePath.Substring(filePath.LastIndexOf("AssetBundle\\") + ("AssetBundle\\").Length);
            stringBuilder.AppendLine(string.Format("{0}:{1}", fileName, md5));
        }

        string updatePath = Path.Combine(Application.streamingAssetsPath, "Version/update.txt");
        WriteTXT(updatePath, stringBuilder.ToString());
    }

    private static string BuildFileMd5(string filePath)
    {
        string fileMd5 = string.Empty;
        try
        {
            using (FileStream fs = File.OpenRead(filePath))
            {
                MD5 md5 = MD5.Create();
                byte[] fileMd5Bytes = md5.ComputeHash(fs);  // 计算FileStream 对象的哈希值
                fileMd5 = System.BitConverter.ToString(fileMd5Bytes).Replace("-", "").ToLower();
            }
        }
        catch (System.Exception ex)
        {
            Debug.LogError(ex);
        }

        return fileMd5;
    }

    private static void WriteTXT(string path, string content)
    {
        string directory = Path.GetDirectoryName(path);
        if (!Directory.Exists(directory))
        {
            Directory.CreateDirectory(directory);
        }
        if (File.Exists(path))
        {
            File.Delete(path);
        }

        using (FileStream fs = File.Create(path))
        {
            StreamWriter sw = new StreamWriter(fs, Encoding.ASCII);
            try
            {
                sw.Write(content);

                sw.Close();
                fs.Close();
                fs.Dispose();
            }
            catch (IOException e)
            {
                Debug.Log(e.Message);
            }
        }
    }
}

将生成的 AssetBundle 的所有文件生成 MD5码保存到 update.txt 文件

每一行为一条数据,包含
文件名:文件 MD5 码

这里写图片描述

对比时逐行读取,解析文件名, MD5码

伪代码如下

读取服务端 update.txt,逐行解析存入 serverUpdateDic,文件名为 key, MD5 码为 value

读取客户端本地 update.txt, 逐行解析存入 clientUpdateDic,文件名为 key, MD5 码为 value

List<string> updateFileList = new List<string>();
// 遍历服务端数据字典
foreach (var pair in serverUpdateDic)
{
    string fileName = pair.key;
    string md5 = pair.value;

    if (clientUpdateDic 包含 fileName)
    {
        if (clientUpdateDic[fileName] 等于 md5)
        {
            // 客户端本地和服务端的相同不需要更新
            continue;  
        }
    }

    updateFileList.add(fileName);
}
迭代 updateFileList 下载需要更新文件

更新完毕,将客户端本地 update.txt 更新为 最新的 update.txt。
思路,对比文件时将客户端本来就有的不需要更新的文件保存到一个字典中,
对比完成时将这些文件名和MD5码直接写入新的 客户端 update.txt,每下载完成一个文件将文件名和 MD5 码加入到update.txt,所有文件更新完成时,客户端的 update.txt 就同步为何服务器端一致了,好处是下载到一半时断网了,退出游戏了,那么下次进入游戏对比 update.txt 时,就不需要再次下载刚才更新过的文件了。

2016-04-09 23:10:04 linshuhe1 阅读数 5215
  • unity3D-游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12794 人正在学习 去看看 宋晓波

        在之前的文章中,我们提到了用ulua的热更新框架,其中最后提及了通过文件MD5码比对来判断文件是否更新,假如MD5码不一致则可以判定文件发生了变化,所以需要进行更新。因此,我们需要为那些能够进行热更新的文件生成对应的MD5码,每个文件都对应唯一的一个MD5码

        生成步骤:

1.读取文件流

2.读取文件流中的字节数据

3.通过MD5接口生成MD5码(获得的是一个Hash字节数组)

4.将步骤3获得的Hash字节数组转换为字符串


关键代码如下:

        public static string getFileHash(string filePath)
        {           
            try
            {
                FileStream fs = new FileStream(filePath, FileMode.Open);
                int len = (int)fs.Length;
                byte[] data = new byte[len];
                fs.Read(data, 0, len);
                fs.Close();
                MD5 md5 = new MD5CryptoServiceProvider();
                byte[] result = md5.ComputeHash(data);
                string fileMD5 = "";
                foreach (byte b in result)
                {
                    fileMD5 += Convert.ToString(b, 16);
                }
                return fileMD5;   
            }
            catch (FileNotFoundException e)
            {
                Console.WriteLine(e.Message);
                return "";
            }                                 
        }
        调用的时候通过填写制定文件的完整目录,即可获得对应文件的MD5码:

         string md5 = getFileHash("E:\\MyPro\\cubetest.unity3d");

        将所有文件的MD5码都写到一个文本.txt文件中,此即为热更新文件的配置文件,每次上传新版本到服务器时,将这份文件也存放到服务器中。


        游戏检查更新的具体步骤如下:

1.通过请求服务器获取到服务器的MD5码配置文件

2.获取本地的MD5码配置文件

3.逐个比对每个文件的MD5码

4.统计MD5码不一致的文件列表

5.从服务器下载更新文件列表中包含的文件




2018-03-20 10:46:44 ych1995612 阅读数 832
  • unity3D-游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12794 人正在学习 去看看 宋晓波

【参考原文】河乐不为-Unity3D 学习笔记7 —— 获取热更新资源文件的MD5码

概要

在热更新时通过文件MD5码比对来判断文件是否更新,假如MD5码不一致则可以判定文件发生了变化,所以需要进行更新。因此,需要为那些能够进行热更新的文件生成对应的MD5码,每个文件都对应唯一的一个MD5码。

生成步骤

  1. 读取文件流
  2. 读取文件流中的字节数据
  3. 通过MD5接口生成MD5码(获得的是一个Hash字节数组)
  4. 将步骤3获得的Hash字节数组转换为字符创
    关键代码:
public static string getFileHash(string filePath)  
{             
    try  
    {  
        FileStream fs = new FileStream(filePath, FileMode.Open);  
        int len = (int)fs.Length;  
        byte[] data = new byte[len];  
        fs.Read(data, 0, len);  
        fs.Close();  
        MD5 md5 = new MD5CryptoServiceProvider();  
        byte[] result = md5.ComputeHash(data);  
        string fileMD5 = "";  
        foreach (byte b in result)  
        {  
            fileMD5 += Convert.ToString(b, 16);  
        }  
        return fileMD5;     
    }  
    catch (FileNotFoundException e)  
    {  
        Console.WriteLine(e.Message);  
        return "";  
    }                                   
}

调用的时候通过填写制定文件的完整目录,即可获得对应文件的MD5码:

string md5 = getFileHash("E:\\MyPro\\cubetest.unity3d"); 

游戏检查更新的具体步骤

  1. 通过请求服务器获取到服务器的MD5码配置文件
  2. 获取本地的MD5码配置文件
  3. .逐个比对每个文件的MD5码
  4. 统计MD5码不一致的文件列表
  5. 从服务器下载更新文件列表中包含的文件
2015-05-10 17:40:41 u010200222 阅读数 883
  • unity3D-游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12794 人正在学习 去看看 宋晓波

目前Unity手游的热更新基本采用如下思路:

  1. 首先将资源打包成AssetBundle。如果有两个对象共同依赖于同一个对象,需要采用依赖关系打包。AssetBundle需要根据不同的平台打包,各平台之间不能混用,如IOS和Android。
  2. 为打包后的资源生成MD5值,上传服务器后,通过比对服务器端和客户端文件的MD5值,找出改变的文件,下载到本地。
  3. 通过AssetBundle.CreateFromFile读取本地AssetBundle,因为该方法只能读取未压缩的AssetBundle,所以打包AssetBundle时,需要选择BuildAssetBundleOptions.UncompressedAssetBundle未压缩模式,然后使用LZMA或GZIP压缩后上传服务器。本地下载后需要解压缩保存在Application.persistentDataPath目录下。
2016-03-13 15:46:46 l_jinxiong 阅读数 6495
  • unity3D-游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12794 人正在学习 去看看 宋晓波

Unity5以下的版本,要导出AssetBundle需要自己写一大坨导出的代码(BuildPipeline),想正确处理好资源的依赖关系从而保证资源完整而又不会产生重复资源是一件非常困难的事。Unity5新的AssetBundle系统大大简化了这一操作。Unity打包的时候会自动处理依赖关系,并生成一个.manifest文件,这个文件描述了assetbundle包大小、crc验证、包之间的依赖关系等等,是一个文本文件(但是这个也有坑,有时候prefab更新,重新打包却没有生成新的AssetBundle)。

打包Assetbundle的代码如下

    public static void BuildAssetResource(BuildTarget target)
    {
        string dataPath = Util.DataPath;
        if (Directory.Exists(dataPath))
        {
            Directory.Delete(dataPath, true);
        }
        string resPath = Util.AppDataPath + "/" + AppConst.AssetDirname + "/";
        if (!Directory.Exists(resPath))
        {
            Directory.CreateDirectory(resPath);
        }

        //生成assetbundle
        BuildPipeline.BuildAssetBundles(resPath, BuildAssetBundleOptions.None, target);

        paths.Clear(); 
        files.Clear();
        EditorUtility.ClearProgressBar();

        ///----------------------创建文件列表-----------------------
        string newFilePath = resPath + "/files.txt";
        if (File.Exists(newFilePath)) File.Delete(newFilePath);

        paths.Clear(); 
        files.Clear();
        Recursive(resPath);

        FileStream fs = new FileStream(newFilePath, FileMode.CreateNew);
        StreamWriter sw = new StreamWriter(fs);
        for (int i = 0; i < files.Count; i++)
        {
            string file = files[i];
            if (file.EndsWith(".meta") || file.Contains(".DS_Store"))
            {
                continue;
            }

            string md5 = Util.md5file(file);
            string value = file.Replace(resPath, string.Empty);
            sw.WriteLine(value + "|" + md5);
        }
        sw.Close(); 
        fs.Close();
        AssetDatabase.Refresh();
    }

在资源属性窗口底部有一个选项,设置AssetBundle的名字,设置好AssetBundle的名字调用上面的函数就可以把全部的Assetbundle生成出来。AssetBundle的名字固定为小写。另外,每个AssetBundle都可以设置一个Variant,其实就是一个后缀,实际AssetBundle的名字会添加这个后缀。如果有不同分辨率的同名资源,可以使用这个来做区分。



加载Assetbundle的代码

using UnityEngine;
using System.Collections;

namespace AssetBundles
{
    public abstract class AssetBundleLoadOperation : IEnumerator
    {
        public object Current
        {
            get
            {
                return null;
            }
        }
        public bool MoveNext()
        {
            return !IsDone();
        }

        public void Reset()
        {
        }

        abstract public bool Update();

        abstract public bool IsDone();
    }

    public class AssetBundleLoadLevelOperation : AssetBundleLoadOperation
    {
        protected string m_AssetBundleName;
        protected string m_LevelName;
        protected bool m_IsAdditive;
        protected string m_DownloadingError;
        protected AsyncOperation m_Request;

        public AssetBundleLoadLevelOperation(string assetbundleName, string levelName, bool isAdditive)
        {
            m_AssetBundleName = assetbundleName;
            m_LevelName = levelName;
            m_IsAdditive = isAdditive;
        }

        public override bool Update()
        {
            if (m_Request != null)
                return false;

            LoadedAssetBundle bundle = AssetBundleManager.GetLoadedAssetBundle(m_AssetBundleName, out m_DownloadingError);
            if (bundle != null)
            {
                if (m_IsAdditive)
                    m_Request = Application.LoadLevelAdditiveAsync(m_LevelName);
                else
                    m_Request = Application.LoadLevelAsync(m_LevelName);
                return false;
            }
            else
                return true;
        }

        public override bool IsDone()
        {
            if (m_Request == null && m_DownloadingError != null)
            {
                Debug.LogError(m_DownloadingError);
                return true;
            }

            return m_Request != null && m_Request.isDone;
        }
    }

    public abstract class AssetBundleLoadAssetOperation : AssetBundleLoadOperation
    {
        public abstract T GetAsset<T>() where T : UnityEngine.Object;
    }

    public class AssetBundleLoadAssetOperationSimulation : AssetBundleLoadAssetOperation
    {
        Object m_SimulatedObject;

        public AssetBundleLoadAssetOperationSimulation(Object simulatedObject)
        {
            m_SimulatedObject = simulatedObject;
        }

        public override T GetAsset<T>()
        {
            return m_SimulatedObject as T;
        }

        public override bool Update()
        {
            return false;
        }

        public override bool IsDone()
        {
            return true;
        }
    }

    public class AssetBundleLoadAssetOperationFull : AssetBundleLoadAssetOperation
    {
        protected string m_AssetBundleName;
        protected string m_AssetName;
        protected string m_DownloadingError;
        protected System.Type m_Type;
        protected AssetBundleRequest m_Request = null;

        public AssetBundleLoadAssetOperationFull(string bundleName, string assetName, System.Type type)
        {
            m_AssetBundleName = bundleName;
            m_AssetName = assetName;
            m_Type = type;
        }

        public override T GetAsset<T>()
        {
            if (m_Request != null && m_Request.isDone)
            {
                return m_Request.asset as T;
            }
            else
            {
                return null;
            }
        }

        public override bool Update()
        {
            if (m_Request != null)
                return false;

            LoadedAssetBundle bundle = AssetBundleManager.GetLoadedAssetBundle(m_AssetBundleName, out m_DownloadingError);
            if (bundle != null)
            {
                m_Request = bundle.m_AssetBundle.LoadAssetAsync(m_AssetName, m_Type);
                return false;
            }
            else
            {
                return true;
            }
        }

        public override bool IsDone()
        {
            if (m_Request == null && m_DownloadingError != null)
            {
                Debug.LogError(m_DownloadingError);
                return true;
            }

            return m_Request != null && m_Request.isDone;
        }
    }

    public class AssetBundleLoadManifestOperation : AssetBundleLoadAssetOperationFull
    {
        public AssetBundleLoadManifestOperation(string bundleName, string assetName, System.Type type)
            : base(bundleName, assetName, type)
        {
        }

        public override bool Update()
        {
            base.Update();

            if (m_Request != null && m_Request.isDone)
            {
                AssetBundleManager.AssetBundleManifestObject = GetAsset<AssetBundleManifest>();
                return false;
            }
            else
            {
                return true;
            }
        }
    }
}

using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using System.Collections;
using System.Collections.Generic;
namespace AssetBundles
{
    public class LoadedAssetBundle
    {
        public AssetBundle m_AssetBundle;
        public int m_ReferencedCount;

        public LoadedAssetBundle(AssetBundle assetBundle)
        {
            m_AssetBundle = assetBundle;
            m_ReferencedCount = 1;
        }
    }

    public class AssetBundleManager : MonoBehaviour
    {
        static string m_BaseDownloadingURL = "";
        static string[] m_ActiveVariants = { };
        static AssetBundleManifest m_AssetBundleManifest = null;
        static Dictionary<string, LoadedAssetBundle> m_LoadedAssetBundles = new Dictionary<string, LoadedAssetBundle>();
        static Dictionary<string, WWW> m_DownloadingWWWs = new Dictionary<string, WWW>();
        static Dictionary<string, string> m_DownloadingErrors = new Dictionary<string, string>();
        static List<AssetBundleLoadOperation> m_InProgressOperations = new List<AssetBundleLoadOperation>();
        static Dictionary<string, string[]> m_Dependencies = new Dictionary<string, string[]>();

        // The base downloading url which is used to generate the full downloading url with the assetBundle names.
        public static string BaseDownloadingURL
        {
            get { return m_BaseDownloadingURL; }
            set { m_BaseDownloadingURL = value; }
        }

        // Variants which is used to define the active variants.
        public static string[] ActiveVariants
        {
            get { return m_ActiveVariants; }
            set { m_ActiveVariants = value; }
        }

        // AssetBundleManifest object which can be used to load the dependecies and check suitable assetBundle variants.
        public static AssetBundleManifest AssetBundleManifestObject
        {
            set { m_AssetBundleManifest = value; }
        }

        public static void SetSourceAssetBundleDirectory(string relativePath)
        {
            BaseDownloadingURL = Util.GetStreamingAssetsPath() + relativePath;
        }

        public static void SetSourceAssetBundleURL(string absolutePath)
        {
            BaseDownloadingURL = absolutePath + Utility.GetPlatformName() + "/";
        }

        public static void SetDevelopmentAssetBundleServer()
        {
            TextAsset urlFile = Resources.Load("AssetBundleServerURL") as TextAsset;
            string url = (urlFile != null) ? urlFile.text.Trim() : null;
            if (url == null || url.Length == 0)
            {
                Debug.LogError("Development Server URL could not be found.");
            }
            else
            {
                AssetBundleManager.SetSourceAssetBundleURL(url);
            }
        }

        // Get loaded AssetBundle, only return vaild object when all the dependencies are downloaded successfully.
        static public LoadedAssetBundle GetLoadedAssetBundle(string assetBundleName, out string error)
        {
            if (m_DownloadingErrors.TryGetValue(assetBundleName, out error))
            {
                return null;
            }

            LoadedAssetBundle bundle = null;
            m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
            if (bundle == null)
            {
                return null;
            }

            // No dependencies are recorded, only the bundle itself is required.
            string[] dependencies = null;
            if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies))
            {
                return bundle;
            }

            // Make sure all dependencies are loaded
            foreach (var dependency in dependencies)
            {
                if (m_DownloadingErrors.TryGetValue(assetBundleName, out error))
                {
                    return bundle;
                }

                // Wait all the dependent assetBundles being loaded.
                LoadedAssetBundle dependentBundle;
                m_LoadedAssetBundles.TryGetValue(dependency, out dependentBundle);
                if (dependentBundle == null)
                {
                    return null;
                }
            }

            return bundle;
        }

        public static AssetBundleLoadManifestOperation Initialize()
        {
            BaseDownloadingURL = Util.GetRelativePath();
//            return Initialize(Utility.GetPlatformName());
            return Initialize(AppConst.AssetDirname);
        }

        // Load AssetBundleManifest.
        public static AssetBundleLoadManifestOperation Initialize(string manifestAssetBundleName)
        {
            var go = new GameObject("AssetBundleManager", typeof(AssetBundleManager));
            DontDestroyOnLoad(go);
            LoadAssetBundle(manifestAssetBundleName, true);
            var operation = new AssetBundleLoadManifestOperation(manifestAssetBundleName, "AssetBundleManifest", typeof(AssetBundleManifest));
            m_InProgressOperations.Add(operation);
            return operation;
        }

        // Load AssetBundle and its dependencies.
        protected static void LoadAssetBundle(string assetBundleName, bool isLoadingAssetBundleManifest = false)
        {
            if (!isLoadingAssetBundleManifest)
            {
                if (m_AssetBundleManifest == null)
                {
                    Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()");
                    return;
                }
            }

            // Check if the assetBundle has already been processed.
            bool isAlreadyProcessed = LoadAssetBundleInternal(assetBundleName, isLoadingAssetBundleManifest);

            // Load dependencies.
            if (!isAlreadyProcessed && !isLoadingAssetBundleManifest)
            {
                LoadDependencies(assetBundleName);
            }
        }

        // Remaps the asset bundle name to the best fitting asset bundle variant.
        protected static string RemapVariantName(string assetBundleName)
        {
            string[] bundlesWithVariant = m_AssetBundleManifest.GetAllAssetBundlesWithVariant();

            string[] split = assetBundleName.Split('.');

            int bestFit = int.MaxValue;
            int bestFitIndex = -1;
            // Loop all the assetBundles with variant to find the best fit variant assetBundle.
            for (int i = 0; i < bundlesWithVariant.Length; i++)
            {
                string[] curSplit = bundlesWithVariant[i].Split('.');
                if (curSplit[0] != split[0])
                    continue;

                int found = System.Array.IndexOf(m_ActiveVariants, curSplit[1]);

                // If there is no active variant found. We still want to use the first 
                if (found == -1)
                    found = int.MaxValue - 1;

                if (found < bestFit)
                {
                    bestFit = found;
                    bestFitIndex = i;
                }
            }

            if (bestFit == int.MaxValue - 1)
            {
                Debug.LogWarning("Ambigious asset bundle variant chosen because there was no matching active variant: " + bundlesWithVariant[bestFitIndex]);
            }

            if (bestFitIndex != -1)
            {
                return bundlesWithVariant[bestFitIndex];
            }
            else
            {
                return assetBundleName;
            }
        }

        // Where we actuall call WWW to download the assetBundle.
        protected static bool LoadAssetBundleInternal(string assetBundleName, bool isLoadingAssetBundleManifest)
        {
            // Already loaded.
            LoadedAssetBundle bundle = null;
            m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
            if (bundle != null)
            {
                bundle.m_ReferencedCount++;
                return true;
            }
            if (m_DownloadingWWWs.ContainsKey(assetBundleName))
            {
                return true;
            }

            WWW download = null;
            string url = m_BaseDownloadingURL + assetBundleName;

            // For manifest assetbundle, always download it as we don't have hash for it.
            if (isLoadingAssetBundleManifest)
            {
                download = new WWW(url);
            }
            else
            {
                download = WWW.LoadFromCacheOrDownload(url, m_AssetBundleManifest.GetAssetBundleHash(assetBundleName), 0);
            }

            m_DownloadingWWWs.Add(assetBundleName, download);

            return false;
        }

        // Where we get all the dependencies and load them all.
        static protected void LoadDependencies(string assetBundleName)
        {
            if (m_AssetBundleManifest == null)
            {
                Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()");
                return;
            }

            // Get dependecies from the AssetBundleManifest object..
            string[] dependencies = m_AssetBundleManifest.GetAllDependencies(assetBundleName);
            if (dependencies.Length == 0)
                return;

            for (int i = 0; i < dependencies.Length; i++)
            {
                dependencies[i] = RemapVariantName(dependencies[i]);
            }

            // Record and load all dependencies.
            m_Dependencies.Add(assetBundleName, dependencies);
            for (int i = 0; i < dependencies.Length; i++)
            {
                LoadAssetBundleInternal(dependencies[i], false);
            }
        }

        // Unload assetbundle and its dependencies.
        static public void UnloadAssetBundle(string assetBundleName)
        {
            UnloadAssetBundleInternal(assetBundleName);
            UnloadDependencies(assetBundleName);
        }

        static protected void UnloadDependencies(string assetBundleName)
        {
            string[] dependencies = null;
            if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies))
            {
                return;
            }

            // Loop dependencies.
            foreach (var dependency in dependencies)
            {
                UnloadAssetBundleInternal(dependency);
            }

            m_Dependencies.Remove(assetBundleName);
        }

        static protected void UnloadAssetBundleInternal(string assetBundleName)
        {
            string error;
            LoadedAssetBundle bundle = GetLoadedAssetBundle(assetBundleName, out error);
            if (bundle == null)
            {
                return;
            }

            if (--bundle.m_ReferencedCount == 0)
            {
                bundle.m_AssetBundle.Unload(false);
                m_LoadedAssetBundles.Remove(assetBundleName);
            }
        }

        void Update()
        {
            // Collect all the finished WWWs.
            var keysToRemove = new List<string>();
            foreach (var keyValue in m_DownloadingWWWs)
            {
                WWW download = keyValue.Value;

                // If downloading fails.
                if (download.error != null)
                {
                    m_DownloadingErrors.Add(keyValue.Key, string.Format("Failed downloading bundle {0} from {1}: {2}", keyValue.Key, download.url, download.error));
                    keysToRemove.Add(keyValue.Key);
                    continue;
                }

                // If downloading succeeds.
                if (download.isDone)
                {
                    AssetBundle bundle = download.assetBundle;
                    if (bundle == null)
                    {
                        m_DownloadingErrors.Add(keyValue.Key, string.Format("{0} is not a valid asset bundle.", keyValue.Key));
                        keysToRemove.Add(keyValue.Key);
                        continue;
                    }
                    m_LoadedAssetBundles.Add(keyValue.Key, new LoadedAssetBundle(download.assetBundle));
                    keysToRemove.Add(keyValue.Key);
                }
            }

            // Remove the finished WWWs.
            foreach (var key in keysToRemove)
            {
                WWW download = m_DownloadingWWWs[key];
                m_DownloadingWWWs.Remove(key);
                download.Dispose();
            }

            // Update all in progress operations
            for (int i = 0; i < m_InProgressOperations.Count; )
            {
                if (!m_InProgressOperations[i].Update())
                {
                    m_InProgressOperations.RemoveAt(i);
                }
                else
                {
                    i++;
                }
            }
        }

        // Load asset from the given assetBundle.
        public static AssetBundleLoadAssetOperation LoadAssetAsync(string assetBundleName, string assetName, System.Type type)
        {
            AssetBundleLoadAssetOperation operation = null;
            assetBundleName = RemapVariantName(assetBundleName);
            LoadAssetBundle(assetBundleName);
            operation = new AssetBundleLoadAssetOperationFull(assetBundleName, assetName, type);
            m_InProgressOperations.Add(operation);           
            return operation;
        }

        // Load level from the given assetBundle.
        public static AssetBundleLoadOperation LoadLevelAsync(string assetBundleName, string levelName, bool isAdditive)
        {
            AssetBundleLoadOperation operation = null;
            assetBundleName = RemapVariantName(assetBundleName);
            LoadAssetBundle(assetBundleName);
            operation = new AssetBundleLoadLevelOperation(assetBundleName, levelName, isAdditive);
            m_InProgressOperations.Add(operation);
            return operation;
        }
    } 
}

客户端更新Assetbundle的时候并不是把全部生成的Assetbundle都下载更新,前面打包Assetbundle的时候生成一个一个files.txt的文件,这个文件里记录了每个Assetbundle的md5值,只有md5值变了的才会下载

    IEnumerator OnUpdateResource()
    {
        string dataPath = Util.DataPath;  //数据目录
        string url = AppConst.WebUrl + AppConst.AppName + "/";
        string listUrl = url + "files.txt";
        WWW www = new WWW(listUrl); yield return www;
        if (www.error != null)
        {
            yield break;
        }
        if (!Directory.Exists(dataPath))
        {
            Directory.CreateDirectory(dataPath);
        }
        File.WriteAllBytes(dataPath + "files.txt", www.bytes);
        string filesText = www.text;
        string[] files = filesText.Split('\n');

        for (int i = 0; i < files.Length; i++)
        {
            if (string.IsNullOrEmpty(files[i]))
            {
                continue;
            }
            string[] keyValue = files[i].Split('|');
            string f = keyValue[0];
            string localfile = (dataPath + f).Trim();
            string path = Path.GetDirectoryName(localfile);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            string fileUrl = url + f;
            bool canUpdate = !File.Exists(localfile);
            if (!canUpdate)
            {
                string remoteMd5 = keyValue[1].Trim();
                string localMd5 = Util.md5file(localfile);
                canUpdate = !remoteMd5.Equals(localMd5);
                if (canUpdate)
                {
                    File.Delete(localfile);
                }
            }
            if (canUpdate)
            {   
                //这里都是资源文件,用线程下载
                BeginDownload(fileUrl, localfile);
            }
        }

        while (!updateWorker.IsDownFinish())
        {
            yield return new WaitForEndOfFrame();
        }
        yield return new WaitForEndOfFrame();
        StartGame();
    }

这些都准备好之后,我们需要一个web服务器,到http://nginx.org/下载一个nginx,并把生成的Assetbundle拷到其目录下的


启动nginx,导出一个apk包,运行就可以看到效果了。

题外话,android 下热更dll不能用这种方式,5x的打包方去加载dll的Assetbundle会是空的,要用4.x打包方式,如果有人知道5.x怎么弄,求告知

BuildPipeline.BuildAssetBundle(mainAsset, null, Application.dataPath + "mydll.assetbundle",
                               BuildAssetBundleOptions.None,BuildTarget.Android);

        WWW www = new WWW(url);

        yield return www;

        if(www.error != null)
        {
            Debug.Log("加载 出错");
        }

        if(www.isDone)
        {
            Debug.Log("加载完毕");
            AssetBundle ab = www.assetBundle;

            try
            {
                Assembly aly = System.Reflection.Assembly.Load(((TextAsset)www.assetBundle.mainAsset).bytes);

                foreach (var i in aly.GetTypes())
                {
                    //调试代码
                    Debug.Log(i.Name);
                    Component c = this.gameObject.AddComponent(i);
                }
            }
            catch (Exception e) 
            {
                Debug.Log("加载DLL出错");
                Debug.Log(e.Message);
            }
        }

最后附上工程项目 https://github.com/caolaoyao/AutoUpdateAssetBundle

主要参考:http://unity3d.com/cn/node/17559

Unity3d热更新一

阅读数 121

UnityC# MD5验证

阅读数 2506

没有更多推荐了,返回首页