AssetBundle打包以及游戏更新

游戏热更新前的准备

要用的数据类

public class VersionData
{
    public string downLoadUrl;
    public string version;
    public int versionCode;
    public List<AssetData> assetDatas;
}

public class AssetData
{
    public string abName;
    public int len;
    public string md5;
}

AssetBundle打包工具(ABTools)

using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Unity.Plastic.Newtonsoft.Json;
using UnityEditor;
using UnityEngine;

public class ABTools : Editor
{
    /// <summary>
    /// 生成AB资源文件
    /// </summary>
    [MenuItem("Tools/ABTool")]
    static void Build()
    {
        string outPath = Application.dataPath + "/ABTest/";
        string path = Application.dataPath + "/Res/";
        //获取文件路径数组
        string[] filePaths = Directory.GetFiles(path, ".", SearchOption.AllDirectories);
        foreach (string file in filePaths)
        {
            //过滤unity自动生成的文件
            if (Path.GetExtension(file).Contains("meta")) continue;
            Debug.Log("----------------------------------");
            Debug.Log(file);
            string abName = string.Empty;
            //将文件路径指向Assets
            string fileName = file.Replace(Application.dataPath, "Assets");
            //获取AssetBundle文件对象
            AssetImporter assetImporter = AssetImporter.GetAtPath(fileName);
            //将路径指向Res文件夹下
            abName = fileName.Replace("Assets/Res/", string.Empty);
            //替换斜杠
            abName = abName.Replace("\\", "/");
            Debug.Log("replace before::" + abName);
            //判断文件路径中是否有_Comm,有的话说明要把这些文件打在一个包内
            if (file.Contains("_Comm"))
            {
                //将文件名替换掉  留下文件夹名
                abName = abName.Replace("/" + Path.GetFileName(file), string.Empty);
                Debug.Log("SSS::" + abName);
            }
            else
            {
                //将文件扩展名去掉
                abName = abName.Replace(Path.GetExtension(file), string.Empty);
            }
            assetImporter.assetBundleName = abName;
            Debug.Log(abName);
            //AB包扩展名
            assetImporter.assetBundleVariant = "u3d";
        }
        //打包
        BuildPipeline.BuildAssetBundles(outPath, BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.StandaloneWindows64);

        string[] filePathss = Directory.GetFiles(path, ".", SearchOption.AllDirectories);
        foreach (string file in filePathss)
        {
            //过滤unity自动生成的文件
            if (Path.GetExtension(file).Contains("meta")) continue;
            //将文件路径指向Assets
            string fileName = file.Replace(Application.dataPath, "Assets");
            //获取AssetBundle文件对象
            AssetImporter assetImporter = AssetImporter.GetAtPath(fileName);
            assetImporter.assetBundleName = string.Empty;
        }
        //刷新文件界面
        AssetDatabase.Refresh();
    }

    static VersionData versionData = new VersionData();
    /// <summary>
    /// 生成资源配置文件
    /// </summary>
    [MenuItem("Tools/MakeVersion")]
    static void MakeVersion()
    {
        //下载Url
        versionData.downLoadUrl = "http://127.0.0.1/game/ABTest/";
        //版本号
        versionData.version = "1.0.0";
        //用来判断版本的int值
        versionData.versionCode = 0;

        if (versionData.assetDatas == null)
        {
            versionData.assetDatas = new List<AssetData>();
        }
        else
        {
            versionData.assetDatas.Clear();
        }
        string abPath = Application.dataPath + "/ABTest/";
        //获取文件路径数组
        string[] filePaths = Directory.GetFiles(abPath, ".", SearchOption.AllDirectories);
        foreach (var file in filePaths)
        {
            //判断文件路径是否包含meta或manifest
            if (Path.GetExtension(file).Contains("meta") || Path.GetExtension(file).Contains("manifest")) continue;
            //转换斜杠
            string abName = file.Replace("\\", "/");
            abName = abName.Replace(abPath, string.Empty);
            //读取文件长度
            int len = File.ReadAllBytes(file).Length;
            //计算文件md5值
            string md5 = FileMD5(file);
            AssetData data = new AssetData();
            data.abName = abName;
            data.len = len;
            data.md5 = md5;

            versionData.assetDatas.Add(data);
        }
        //序列化对象
        string version = JsonConvert.SerializeObject(versionData);
        //写出到本地
        File.WriteAllText(abPath + "version.txt", version);
        AssetDatabase.Refresh();
    }

    static StringBuilder sb = new StringBuilder();
    private static string FileMD5(string filePath)
    {
        FileStream file = new FileStream(filePath, FileMode.Open);
        MD5 md5 = new MD5CryptoServiceProvider();
        byte[] bytes = md5.ComputeHash(file);
        file.Close();
        for (int i = 0; i < bytes.Length; i++)
        {
            sb.Append(bytes[i].ToString("X2"));
        }
        return sb.ToString();
    }
}

游戏AB包管理类(ABManager)

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

public class ABManager : Singleton<ABManager>
{
    AssetBundleManifest assetBundleManifest;
    string AbPath = Application.dataPath + "/ABTest/";
    public ABManager()
    {
        //获取所有资源的信息
        AssetBundle assetBundle = AssetBundle.LoadFromFile(Application.dataPath + "/ABTest/ABTest");
        //加载Manifest
        assetBundleManifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        assetBundle.Unload(false);
    }
    Dictionary<string, MyAssetData> dicAssets = new Dictionary<string, MyAssetData>();

    //加载所有资源
    private T[] LoadAssets<T>(string abName) where T : Object
    {
        //获取所有依赖路径
        string[] dependencies = assetBundleManifest.GetAllDependencies(abName);
        //加载所有依赖的ab包
        foreach (var item in dependencies)
        {
            if(dicAssets.ContainsKey(item))
            {
                dicAssets[item].count++;
            }
            else
            {
                AssetBundle assetBundle1 = AssetBundle.LoadFromFile(AbPath + item);
                MyAssetData assetData = new MyAssetData(assetBundle1);
                dicAssets.Add(item, assetData);
            }
        }
        //加载该AB包
        if(dicAssets.ContainsKey(abName))
        {
            dicAssets[abName].count++;
        }
        else
        {
            AssetBundle assetBundle1 = AssetBundle.LoadFromFile(AbPath + abName);
            MyAssetData assetData = new MyAssetData(assetBundle1);
            dicAssets.Add(abName, assetData);
        }
        return dicAssets[abName].ab.LoadAllAssets<T>();
    }
    /// <summary>
    /// 卸载AB包
    /// </summary>
    /// <param name="abName"></param>
    public void UnLoadAB(string abName)
    {
        //获取所有依赖文件路径
        string[] dependencies = assetBundleManifest.GetAllDependencies(abName);
        //卸载依赖AB包
        foreach (var item in dependencies)
        {
            if (dicAssets.ContainsKey(item))
            {
                dicAssets[item].count--;
                if (dicAssets[item].count <= 0)
                {
                    dicAssets[item].Unload();
                    dicAssets.Remove(item);
                }
            }
        }
        //卸载该AB包
        if (dicAssets.ContainsKey(abName))
        {
            dicAssets[abName].count--;
            if (dicAssets[abName].count <= 0)
            {
                dicAssets[abName].Unload();
                dicAssets.Remove(abName);
            }
        }
    }
    /// <summary>
    /// 加载其余资源
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="abName"></param>
    /// <param name="assetName"></param>
    /// <returns></returns>
    public T LoadOtherAsset<T>(string abName, string assetName)where T : Object
    {
        Object[] objects = LoadAssets<T>(abName);
        Object obj = null;
        //遍历AB包内资源
        foreach (var item in objects)
        {
            if(item.name.Equals(assetName))
            {
                obj = item;
                break;
            }
        }
        return obj as T;
    }

    Dictionary<int, string> dicGameObject = new Dictionary<int, string>();
    /// <summary>
    /// 加载GameObject
    /// </summary>
    /// <param name="abName"></param>
    /// <param name="assetName"></param>
    /// <returns></returns>
    public GameObject LoadGameObject(string abName, string assetName)
    {
        Object obj = LoadOtherAsset<GameObject>(abName, assetName);
        GameObject go = GameObject.Instantiate(obj) as GameObject;
        dicGameObject.Add(go.GetInstanceID(), abName);
        return go;
    }
    /// <summary>
    /// 加载图集
    /// </summary>
    /// <param name="abName"></param>
    /// <param name="assetName"></param>
    /// <returns></returns>
    public Sprite[] LoadSprites(string abName, string assetName)
    {
        SpriteAtlas spriteAtlas = LoadOtherAsset<SpriteAtlas>(abName, assetName);
        Sprite[] sprites = new Sprite[spriteAtlas.spriteCount];
        spriteAtlas.GetSprites(sprites);
        return sprites;
    }
    /// <summary>
    /// 加载精灵
    /// </summary>
    /// <param name="abName"></param>
    /// <param name="assetName"></param>
    /// <param name="spriteName"></param>
    /// <returns></returns>
    public Sprite LoadSprite(string abName, string assetName, string spriteName)
    {
        SpriteAtlas spriteAtlas = LoadOtherAsset<SpriteAtlas>(abName, assetName);
        return spriteAtlas.GetSprite(spriteName);
    }
    /// <summary>
    /// 销毁GameObject
    /// </summary>
    /// <param name="go"></param>
    public void DestroyGameObject(GameObject go)
    {
        int id = go.GetInstanceID();
        string abName = dicGameObject[id];
        Object.Destroy(go);
        dicGameObject.Remove(id);
        UnLoadAB(abName);
    }
}
/// <summary>
/// 资源数据类
/// </summary>
public class MyAssetData
{
    public AssetBundle ab;
    public int count;

    public MyAssetData(AssetBundle a)
    {
        ab = a;
        count = 1;
    }

    public void Unload()
    {
        ab.Unload(true);
    }
}

StreamingAssets路径到PersistentData路径以及检查更新

using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using XLua;

public class UpdateLoad : MonoBehaviour
{
    Action update;
    LuaEnv luaEnv;
    Action<string> sceneLoadFinish;
    /// <summary>
    /// P路径
    /// </summary>
    public static string PPath
    {
        get
        {
            return Application.persistentDataPath + "/ABTest/";
        }
    }
    /// <summary>
    /// S路径
    /// </summary>
    public static string SPath
    {
        get
        {
#if UNITY_ANDROID
            return "jar:file://" + Application.dataPath + "!/assets/ABTest";
#else
            return Application.streamingAssetsPath + "/ABTest/";
#endif
        }
    }
    // Start is called before the first frame update
    void Start()
    {
        //判断是否是第一次打开游戏
        if(!Directory.Exists(PPath))
        {
            //创建P路径
            Directory.CreateDirectory(PPath);
            //将S文件夹中的文件复制到P路径下
            StartCoroutine(Copy());
        }
        else
        {
            //检查是否有更新
            StartCoroutine(CheckUpdate());
        }
    }

    IEnumerator Copy()
    {
        //定义版本文件路径
        string streamingAssetPathVersion = SPath + "version.txt";
        Debug.Log(streamingAssetPathVersion);
        string versionContent = "";
#if UNITY_ANDROID
        //获取本地版本文件
        UnityWebRequest unityWebRequest = UnityWebRequest.Get(streamingAssetPathVersion);
        yield return unityWebRequest.SendWebRequest();
        if(unityWebRequest.result == UnityWebRequest.Result.ConnectionError)
        {
            Debug.Log(unityWebRequest.error);
        }
        else
        {
            versionContent = unityWebRequest.downloadHandler.text;
        }
        unityWebRequest.Dispose();
#else
        versionContent = File.ReadAllText(streamingAssetPathVersion);

#endif
        //反序列化版本信息
        VersionData versionData = JsonConvert.DeserializeObject<VersionData>(versionContent);
        //遍历资源包资源
        for (int i = 0; i < versionData.assetDatas.Count; i++)
        {
            AssetData assetData = versionData.assetDatas[i];
            string spath = SPath + assetData.abName;
            string p = PPath + assetData.abName;
            string fileName = Path.GetFileName(spath);
            string dir = Path.GetDirectoryName(p);
            if(!Directory.Exists (dir))
            {
                Directory.CreateDirectory (dir);
            }
#if UNITY_ANDROID
            //从S路径获取文件
            UnityWebRequest unityWebRequest1 = UnityWebRequest.Get("file://" + spath);
            yield return unityWebRequest1.SendWebRequest();
            if(unityWebRequest1.result == UnityWebRequest.Result.ConnectionError)
            {
                Debug.Log(unityWebRequest1.error);
            }
            else
            {
                //将文件写入到P路径下
                File.WriteAllBytes(p, unityWebRequest1.downloadHandler.data);
            }
            unityWebRequest1.Dispose();
#else
            File.Copy(spath, dir + fileName);
#endif
        }
        //将版本信息也复制到P路径下
        File.WriteAllText(PPath + "/version.txt", versionContent);
        yield return null;

        //检查是否有更新
        StartCoroutine(CheckUpdate());
    }

    IEnumerator CheckUpdate()
    {
        string localVersion = PPath + "version.txt";
        Debug.Log(localVersion);
        //读取本地版本文件
        string localVersionContent = File.ReadAllText(localVersion);
        VersionData localVersionData = JsonConvert.DeserializeObject<VersionData>(localVersionContent);
        Dictionary<string, AssetData> versionDic = new Dictionary<string, AssetData>();
        //将本地资源添加到字典中
        for(int i = 0; i < localVersionData.assetDatas.Count; i++)
        {
            AssetData assetData = localVersionData.assetDatas[i];
            versionDic.Add(assetData.abName, assetData);
        }
        string remoteVersion = localVersionData.downLoadUrl + "version.txt";
        string remoteVersioonContent = "";
        //获取服务器的版本文件
        UnityWebRequest unityWebRequest = UnityWebRequest.Get(remoteVersion);
        yield return unityWebRequest.SendWebRequest();
        if(unityWebRequest.result == UnityWebRequest.Result.ConnectionError)
        {
            Debug.Log("出错");
        }
        else
        {
            remoteVersioonContent = unityWebRequest.downloadHandler.text;
        }
        //反序列化从服务器中获取的版本文件
        VersionData remoteVersionData = JsonConvert.DeserializeObject<VersionData>(remoteVersioonContent);
        List<AssetData> updateList = new List<AssetData>();
        //先判断本地版本是否小于服务器的版本
        if(localVersionData.versionCode < remoteVersionData.versionCode)
        {
            //循环从服务器获取的资源列表
            for (int i = 0; i < remoteVersionData.assetDatas.Count; i++)
            {
                AssetData assetData = remoteVersionData.assetDatas[i];
                //对比本地资源的字典中是否有该资源
                if(versionDic.ContainsKey(assetData.abName))
                {
                    //如果有再判断文件md5值是否有变化
                    if(versionDic[assetData.abName].md5 != assetData.md5)
                    {
                        updateList.Add(assetData);
                    }
                }
                //本地没有该资源直接添加到要更新的资源列表中
                else
                {
                    updateList.Add(assetData);
                }
            }
        }
        else
        {
            EnterGame();
            yield break;
        }
        //最后将需要更新的文件从服务器下载到本地
        for (int i = 0; i < updateList.Count; i++)
        {
            string abName = updateList[i].abName;
            UnityWebRequest updateAsset = UnityWebRequest.Get(remoteVersionData.downLoadUrl + abName);
            yield return updateAsset.SendWebRequest();

            if(updateAsset.result == UnityWebRequest.Result.ConnectionError)
            {
                Debug.Log(updateAsset.result);
            }
            else
            {
                string perPath = PPath + abName;
                string filename = Path.GetFileName(perPath);
                string dir = Path.GetDirectoryName(perPath).Replace("\\", "/") + "/";
                //判断本地是否存在该路径的文件夹
                if(!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }
                //将下载的资源写入到本地
                File.WriteAllBytes(dir + filename, updateAsset.downloadHandler.data);
            }
        }
        //最后更新版本文件
        File.WriteAllText(PPath + "version.txt", remoteVersioonContent);
        yield return null;
        EnterGame();
    }

    private void EnterGame()
    {
        PlayerConfig.Ins.Init();
        luaEnv = new LuaEnv();
        luaEnv.AddBuildin("rapidjson", XLua.LuaDLL.Lua.LoadRapidJson);
        luaEnv.AddLoader(CustomLoader);
        luaEnv.DoString("require 'GameMain'");
        Action action = luaEnv.Global.Get<Action>("Login");
        action();
        sceneLoadFinish = luaEnv.Global.Get<Action<string>>("SceneLoadFinish");
        SceneManager.sceneLoaded += sceneLoad;
    }

    private void sceneLoad(Scene arg0, LoadSceneMode arg1)
    {
        sceneLoadFinish(arg0.name);
    }

    private byte[] CustomLoader(ref string filepath)
    {
        string path = Application.dataPath + "/Lua/" + filepath + ".lua";
        return File.ReadAllBytes(path);
    }

    public void Select()
    {
        Action action = luaEnv.Global.Get<Action>("Select");
        action();
    }

    // Update is called once per frame
    public void Update()
    {
        update?.Invoke();
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值