开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

开发者专栏

关注:2234

当前位置:游戏蛮牛 技术专区 开发者专栏

__________________________________________________________________________________
开发者干货区版块规则:

  1、文章必须是图文形式。(至少2幅图)
      2、文章字数必须保持在1500字节以上。(编辑器右下角有字数检查)
      3、本版块只支持在游戏蛮牛原创首发,不支持转载。
      4、本版块回复不得无意义,如:顶、呵呵、不错......【真的会扣分的哦】
      5、......
__________________________________________________________________________________
查看: 588|回复: 6

[游戏小猪] 小猪日记,初识Unity中的AssetBundle --->(三)

[复制链接]  [移动端链接]
5熟悉之中
719/1000
排名
3942
昨日变化
24

9

主题

69

帖子

719

积分

Rank: 5Rank: 5

UID
228559
好友
6
蛮牛币
1756
威望
0
注册时间
2017-6-25
在线时间
291 小时
最后登录
2018-6-21

专栏作家

QQ
发表于 2018-3-7 17:25:04 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
       书接上回。前边已经实现了一键打包。我之所以使用AB,是因为我们想要在不重新下载APP的情况下实现资源的更新或者代码的热更新。不管是什么,只要是想要更新的,都会以AB包的形式呈现,想要实现更新,当然需要一个远程的服务器了,但是我并没有,那只好本地模拟;但是自己动手去搭建一个http文件服务器,显然是很费事的,网上有现成的模拟器,那么我就直接拿来用了,这里也分享给大家     链接:https://pan.baidu.com/s/1J5suybtgBQhOgAL6MkG_QA 密码:ogzm
0.jpg

运行后就是这个界面了,把打好的文件夹拷贝到桌面一分,直接拖进来就可以了
1.jpg
这样就得到了一个HTTP的下载连接。这里贴一个简单的HTTP下载代码,我并没有用WWW去下载
[C#] 纯文本查看 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Net;
using System;

public class DownLoaderEnum
{
    public string url;
    public string path;
    public Action action;
    public DownLoaderEnum(string URL, string PATH,Action ACTION)
    {
        url = URL;
        path = PATH;
        action = ACTION;
    }
}


public class HttpUtil  {

    static HttpUtil instance;

    public static HttpUtil Instance
    {
        get
        {
            if (instance == null)
                instance = new HttpUtil();
            return instance;
        }
    }


    public bool is_DownLoadOver = false;

    /// <summary>
    /// http下载文件
    /// </summary>
    /// <param name="url">下载文件地址</param>
    /// <param name="path">文件存放地址,包含文件名</param>
    /// <returns></returns>
    public  void HttpDownloader(object down)
    {
        is_DownLoadOver = false;
        if (!Directory.Exists((down as DownLoaderEnum).path))
            Directory.CreateDirectory((down as DownLoaderEnum).path);
        string tempPath = System.IO.Path.GetDirectoryName((down as DownLoaderEnum).path) + @"\temp";
        System.IO.Directory.CreateDirectory(tempPath);  //创建临时文件目录
        string tempFile = tempPath + @"\" + System.IO.Path.GetFileName((down as DownLoaderEnum).path) + ".temp"; //临时文件

        if (File.Exists(tempFile))
        {
            File.Delete(tempFile);    //存在则删除
        }
        try
        {
            FileStream fs = new FileStream(tempFile, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
            // 设置参数
            HttpWebRequest request = WebRequest.Create((down as DownLoaderEnum).url) as HttpWebRequest;
            //发送请求并获取相应回应数据
            HttpWebResponse response = request.GetResponse() as HttpWebResponse;
            //直到request.GetResponse()程序才开始向目标网页发送Post请求
            Stream responseStream = response.GetResponseStream();
            //创建本地文件写入流
            //Stream stream = new FileStream(tempFile, FileMode.Create);
            byte[] bArr = new byte[1024];
            int size = responseStream.Read(bArr, 0, bArr.Length);
            while (size > 0)
            {
                //stream.Write(bArr, 0, size);
                fs.Write(bArr, 0, size);
                size = responseStream.Read(bArr, 0, bArr.Length);
            }
            //stream.Close();
            fs.Close();
            responseStream.Close();
            string suffixName = (down as DownLoaderEnum).url;
            int su = suffixName.LastIndexOf('/');
            suffixName = (down as DownLoaderEnum).path + suffixName.Substring(su);
            // Debug.LogError(suffixName);
            if (File.Exists(suffixName))
                File.Delete(suffixName);
            System.IO.File.Move(tempFile, suffixName);
            // return true;
            Debug.LogError("下载完成");
            is_DownLoadOver = true;
        }
        catch (Exception ex)
        {
            Debug.LogError("错误==>>" + ex.Message);
            //return false;
        }
    }
}




这样就能实现一个简单的下载了,然后把它放进一个单独的线程中去执行;
        想要实现更新,我们肯定是要进行一个版本对比的,只有版本有变动的AB包,我们才会去从服务器下载,而没有发生变动的,还从本地加载;如果你已经打过包了,你会发现,每个AB包同级目录中,还多了个文件 XXX.manifest  ,这个文件是为了方便我们肉眼能看到的manifest文件,用小绿本就可以打开,
3.jpg

打开后 AssetFileHash  ,就是这个AB包的hash值。如果资源没有发生过变动,重新打包,这个哈希值是不会变动的;我把cube放大了一点,再重新打包,发现;材质和贴图的哈希值没有变化,而cube的哈希值发生了变化;
       这个哈希值可以在manifest文件中得到 2.jpg ,也可以用C#中自带的方法得到,或者得到他的MD5值。原理都是一样,如果资源没有发生变化,那么这个值不会变,如果发生变化,相应的值也会发生变化;每次游戏启动时,通过对比他们的值,我们确定是否需要去下载新的资源;
       可能有人会想,我直接拉取服务器上的manifest文件和本地的manifest文件,然后将他们两个的信息进行对比,然后在按照哈希值,去进行资源的更新,这样不是很省事么;但是这样是不行的,因为manifest只能唯一,并不能同时加载两个。所以只能是自己手动制作一个配置文件了;配置文件一般我妹做成XML形式或者json形式。但是个人非常讨厌XML,相反,json用起来有一种如丝般的顺滑;unity中为我们自带了json的序列化工具。再要序列化的类上,需要加上序列化标签,就可以实现嵌套和数组;
      
[C#] 纯文本查看 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class JsonUtil
{

    static JsonUtil instance;

    public static JsonUtil Instance
    {
        get
        {
            if (instance == null)
                instance = new JsonUtil();
            return instance;
        }
    }

    public void Init()
    {
  
             
    }
    /// <summary>
    /// 将类转换成json字符串
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="t"></param>
    /// <returns></returns>
    public string ObjectToJson<T>(T t)
    {
        string json = JsonUtility.ToJson(t);
        return json;
    }
    /// <summary>
    /// json字符串,反序列化成对象
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="json"></param>
    /// <returns></returns>
    public T JsonToObject<T>(string json)
    {
        T t = JsonUtility.FromJson<T>(json);
        return t;
    }
}


这里加上标签
[C#] 纯文本查看 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class AllBundleInfo  {

    public List<SingleBundleInfo> BundleInfoList;

}


[System.Serializable]
public class SingleBundleInfo
{
    public string bundleName;
    public string bundleMD5;
}



这样就可以实现嵌套;
   这里我先实现了一个manifest文件的md5值 的获取,因为其他的我们还可以在各自的manifest文件中看到他的哈希值,但是manifest文件却我发看到,至于获取MD5值什么的,网上也是有很多的,我这里直接从网上COPY过来了,这是COPY过来的原文
[C#] 纯文本查看 复制代码
/// <summary>
  /// 提供用于计算指定文件哈希值的方法
  /// <example>例如计算文件的MD5值:
  /// <code>
  ///   String hashMd5=HashHelper.ComputeMD5("MyFile.txt");
  /// </code>
  /// </example>
  /// <example>例如计算文件的CRC32值:
  /// <code>
  ///   String hashCrc32 = HashHelper.ComputeCRC32("MyFile.txt");
  /// </code>
  /// </example>
  /// <example>例如计算文件的SHA1值:
  /// <code>
  ///   String hashSha1 =HashHelper.ComputeSHA1("MyFile.txt");
  /// </code>
  /// </example>
  /// </summary>
  public sealed class HashHelper
  {
      /// <summary>
      ///  计算指定文件的MD5值
      /// </summary>
      /// <param name="fileName">指定文件的完全限定名称</param>
      /// <returns>返回值的字符串形式</returns>
      public static String ComputeMD5(String fileName)
      {
          String hashMD5 = String.Empty;
          //检查文件是否存在,如果文件存在则进行计算,否则返回空值
          if (System.IO.File.Exists(fileName))
          {
              using (System.IO.FileStream fs = new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
              {
                  //计算文件的MD5值
                  System.Security.Cryptography.MD5 calculator=System.Security.Cryptography.MD5.Create();
                  Byte[] buffer = calculator.ComputeHash(fs);
                  calculator.Clear();
                  //将字节数组转换成十六进制的字符串形式
                  StringBuilder stringBuilder = new StringBuilder();
                  for (int i = 0; i < buffer.Length; i++)
                  {
                      stringBuilder.Append(buffer[i].ToString("x2"));
                  }
                 hashMD5= stringBuilder.ToString();
              }//关闭文件流
          }//结束计算
          return hashMD5;
      }//ComputeMD5
      /// <summary>
      ///  计算指定文件的CRC32值
      /// </summary>
      /// <param name="fileName">指定文件的完全限定名称</param>
      /// <returns>返回值的字符串形式</returns>
      public static String ComputeCRC32(String fileName)
      {
          String hashCRC32 = String.Empty;
          //检查文件是否存在,如果文件存在则进行计算,否则返回空值
          if (System.IO.File.Exists(fileName))
          {
              using (System.IO.FileStream fs = new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
              {
              //计算文件的CSC32值
              Crc32 calculator = new Crc32();
              Byte[] buffer = calculator.ComputeHash(fs);
              calculator.Clear();
              //将字节数组转换成十六进制的字符串形式
              StringBuilder stringBuilder = new StringBuilder();
              for (int i = 0; i < buffer.Length; i++)
              {
                  stringBuilder.Append(buffer[i].ToString("x2"));
              }
              hashCRC32 = stringBuilder.ToString();
              }//关闭文件流
          }
          return hashCRC32;
      }//ComputeCRC32
      /// <summary>
      ///  计算指定文件的SHA1值
      /// </summary>
      /// <param name="fileName">指定文件的完全限定名称</param>
      /// <returns>返回值的字符串形式</returns>
      public static String ComputeSHA1(String fileName)
      {
          String hashSHA1 = String.Empty;
          //检查文件是否存在,如果文件存在则进行计算,否则返回空值
          if (System.IO.File.Exists(fileName))
          {
              using (System.IO.FileStream fs = new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
              {
                  //计算文件的SHA1值
                  System.Security.Cryptography.SHA1 calculator = System.Security.Cryptography.SHA1.Create();
                  Byte[] buffer = calculator.ComputeHash(fs);
                  calculator.Clear();
                  //将字节数组转换成十六进制的字符串形式
                  StringBuilder stringBuilder = new StringBuilder();
                  for (int i = 0; i < buffer.Length; i++)
                  {
                      stringBuilder.Append(buffer[i].ToString("x2"));
                  }
                  hashSHA1 = stringBuilder.ToString();
              }//关闭文件流
          }
          return hashSHA1;
      }//ComputeSHA1
  }//end class: HashHelper
   /// <summary>
   /// 提供 CRC32 算法的实现
   /// </summary>
   public class Crc32 : System.Security.Cryptography.HashAlgorithm
   {
       public const UInt32 DefaultPolynomial = 0xedb88320;
       public const UInt32 DefaultSeed = 0xffffffff;
       private UInt32 hash;
       private UInt32 seed;
       private UInt32[] table;
       private static UInt32[] defaultTable;
       public Crc32()
       {
           table = InitializeTable(DefaultPolynomial);
           seed = DefaultSeed;
           Initialize();
       }
       public Crc32(UInt32 polynomial, UInt32 seed)
       {
           table = InitializeTable(polynomial);
           this.seed = seed;
           Initialize();
       }
       public override void Initialize()
       {
           hash = seed;
       }
       protected override void HashCore(byte[] buffer, int start, int length)
       {
           hash = CalculateHash(table, hash, buffer, start, length);
       }
       protected override byte[] HashFinal()
       {
           byte[] hashBuffer = UInt32ToBigEndianBytes(~hash);
           this.HashValue = hashBuffer;
           return hashBuffer;
       }
       public static UInt32 Compute(byte[] buffer)
       {
           return ~CalculateHash(InitializeTable(DefaultPolynomial), DefaultSeed, buffer, 0, buffer.Length);
       }
       public static UInt32 Compute(UInt32 seed, byte[] buffer)
       {
           return ~CalculateHash(InitializeTable(DefaultPolynomial), seed, buffer, 0, buffer.Length);
       }
       public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer)
       {
           return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length);
       }
       private static UInt32[] InitializeTable(UInt32 polynomial)
       {
           if (polynomial == DefaultPolynomial && defaultTable != null)
           {
               return defaultTable;
           }
           UInt32[] createTable = new UInt32[256];
           for (int i = 0; i < 256; i++)
           {
               UInt32 entry = (UInt32)i;
               for (int j = 0; j < 8; j++)
               {
                   if ((entry & 1) == 1)
                       entry = (entry >> 1) ^ polynomial;
                   else
                       entry = entry >> 1;
               }
               createTable[i] = entry;
           }
           if (polynomial == DefaultPolynomial)
           {
               defaultTable = createTable;
           }
           return createTable;
       }
       private static UInt32 CalculateHash(UInt32[] table, UInt32 seed, byte[] buffer, int start, int size)
       {
           UInt32 crc = seed;
           for (int i = start; i < size; i++)
           {
               unchecked
               {
                   crc = (crc >> 8) ^ table[buffer[i] ^ crc & 0xff];
               }
           }
           return crc;
       }
       private byte[] UInt32ToBigEndianBytes(UInt32 x)
       {
           return new byte[] { (byte)((x >> 24) & 0xff), (byte)((x >> 16) & 0xff), (byte)((x >> 8) & 0xff), (byte)(x & 0xff) };
       }
   }//end class: Crc32




这里先只取其中的获取MD5值的一段,并且创建JSON文件在manifest同级目录下
[C#] 纯文本查看 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System;
using System.Text;
using System.Security.Cryptography;

public class BuildeAssets
{

    [MenuItem("Tools/BuildeAsset")]
    static void Build()
    {
        AssetDatabase.RemoveUnusedAssetBundleNames(); //移除没有用的assetbundlename
        SetAssetBundleName();//设置选中物体的assetbundle名字
        BuildPipeline.BuildAssetBundles(GetBundleDirectory(), BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.StandaloneWindows);
        SetManifestVersion();
        AssetDatabase.Refresh();
    }
    static string GetBundleDirectory()
    {
        string path = Application.streamingAssetsPath;
        if (!Directory.Exists(path))
            Directory.CreateDirectory(path);
        return path;
    }

    static void SetAssetBundleName()
    {
        UnityEngine.Object[] objs = Selection.objects;
        string[] path = new string[objs.Length];
        for (int i = 0; i < objs.Length; i++)
        {
            path[i] = AssetDatabase.GetAssetPath(objs[i]);
            SelectionVerifyResult(path[i]);
        }

    }
    static void SelectionVerifyResult(string strpath) //验证选中的东西,是不是包含.
    {
        string[] strs = strpath.Split('.'); //如果包含.的话,分割
        int idex = strs[0].LastIndexOf('/'); //得到这个包含.的选中的文件系统的父级所在的索引
        string parentDir = strs[0].Substring(0, idex);//父级目录
        DirectoryInfo directory = new DirectoryInfo(parentDir);
        FileSystemInfo[] fileSystemInfo = directory.GetFileSystemInfos(); //得到父级目录下的所有文件系统
                                                                          //将unity自动生成的.mete 文件剔除
        foreach (var item in fileSystemInfo)
        {
            if (!item.Name.Contains(".meta"))
            {
                //list.Add(item);
                if ((item as DirectoryInfo) != null) //是文件夹
                {
                    //strpath是从Assets下级目录开始的,需要做一下处理
                    if (item.FullName.Contains(WorkPathName(strpath)))//是刚刚选中的这个文件夹,查询他的子目录
                    {

                        CheckChildFileSystem(item.FullName);
                    }
                }
                else  //是文件
                {
                    Debug.LogError("是文件==>" + item);
                    SetAssetBuildName(item.FullName);
                }
            }
        }

    }

    static string WorkPathName(string strpath)
    {
        string[] strs = strpath.Split('/');

        return strs[strs.Length - 1];
    }

    /// <summary>
    /// 判断是否包含文件夹
    /// </summary>
    /// <param name="suf"></param>
    /// <returns></returns>
    static bool IsContainsDrictory(string suf)  //判断是否包含文件夹
    {
        bool isSuf = false;
        if (suf.Contains("/")) //如果里边还包含文件夹
        {
            isSuf = true;
        }
        else
        {
            isSuf = false;
        }
        return isSuf;
    }

    static void CheckChildFileSystem(string pathname) //检查目录下的子目录
    {
        DirectoryInfo directoryInfo = new DirectoryInfo(pathname);
        FileSystemInfo[] fileSystemInfo = directoryInfo.GetFileSystemInfos();
        foreach (var item in fileSystemInfo)
        {
            if (!item.FullName.Contains(".meta"))
            {
                if ((item as DirectoryInfo) != null)//如果是文件夹
                {
                    CheckChildFileSystem(item.FullName);
                }
                else  //如果是文件
                {
                    SetAssetBuildName(item.FullName);
                }
            }
        }
    }

    static void SetAssetBuildName(string pathname) //设置文件的bundle名字
    {
        //E:\Unity3Dproject\ManniuBlog\Assets\Prefab.s\Cube.prefab

        string strs = Application.dataPath;
        int index = pathname.LastIndexOf(pathname);
        string path = pathname.Substring(strs.Length + 1);
        string name = string.Empty;
        int ind = path.LastIndexOf('.');
        name = path.Substring(0, ind);
        //Debug.LogError(name);
        //这里一定要在Assets开始,全名或者没有都不行
        AssetImporter asset = AssetImporter.GetAtPath("Assets/" + path);
        if (asset != null)
        {
            asset.SetAssetBundleNameAndVariant(name, "bytes");
        }
        else
        {
            Debug.LogError("空");
        }
    }
    static void SetManifestVersion()
    {
        string outPaht = GetBundleDirectory();
        int index= outPaht.LastIndexOf('/');
        string manifest = outPaht.Substring(index + 1);
        string md5 = ComputeMD5(outPaht + "/" + manifest);
        SingleBundleInfo info = new SingleBundleInfo();
        info.bundleName = manifest;
        info.bundleMD5 = md5;
        string json = JsonUtil.Instance.ObjectToJson<SingleBundleInfo>(info);
        WriteManifestJsonConfig(json);
    }

    static string ComputeMD5(string fileName) //计算文件的MD5,网上直接摘得
    {
        string hashMD5 = string.Empty;
        //检查文件是否存在,如果文件存在则进行计算,否则返回空值
        if (File.Exists(fileName))
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                //计算文件的MD5值
                MD5 calculator = MD5.Create();
                Byte[] buffer = calculator.ComputeHash(fs);
                calculator.Clear();
                //将字节数组转换成十六进制的字符串形式
                StringBuilder stringBuilder = new StringBuilder();
                for (int i = 0; i < buffer.Length; i++)
                {
                    stringBuilder.Append(buffer[i].ToString("x2"));
                }
                hashMD5 = stringBuilder.ToString();
            }//关闭文件流
        }//结束计算
        return hashMD5;
    }

    static void WriteManifestJsonConfig(string json)
    {
        string path = GetBundleDirectory() + "/ABconfig.json";
        if (!File.Exists(path))
            File.CreateText(path);
        using (StreamWriter streamWriter = new StreamWriter(path))
        {
            streamWriter.Write(json);
        }
    }
}



4.jpg




回复

使用道具 举报

7日久生情
2129/5000
排名
3175
昨日变化
13

0

主题

1484

帖子

2129

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
185339
好友
0
蛮牛币
2983
威望
0
注册时间
2016-11-20
在线时间
233 小时
最后登录
2018-6-20
发表于 2018-3-12 10:26:10 | 显示全部楼层

回复

使用道具 举报

4四处流浪
327/500
排名
22207
昨日变化
11

0

主题

282

帖子

327

积分

Rank: 4

UID
269036
好友
0
蛮牛币
26
威望
0
注册时间
2018-2-21
在线时间
27 小时
最后登录
2018-4-5
发表于 2018-3-14 09:32:57 | 显示全部楼层
不错谢谢分享

回复

使用道具 举报

4四处流浪
433/500
排名
5213
昨日变化
31

1

主题

33

帖子

433

积分

Rank: 4

UID
226398
好友
0
蛮牛币
465
威望
0
注册时间
2017-6-12
在线时间
159 小时
最后登录
2018-6-20
发表于 2018-3-14 10:49:27 | 显示全部楼层
服务器下载下来报有木马??

回复 支持 反对

使用道具 举报

4四处流浪
492/500
排名
15592
昨日变化
5

0

主题

302

帖子

492

积分

Rank: 4

UID
199204
好友
0
蛮牛币
259
威望
0
注册时间
2017-1-5
在线时间
151 小时
最后登录
2018-6-20
发表于 2018-3-15 09:35:03 | 显示全部楼层
支持一下,谢谢分享

回复 支持 反对

使用道具 举报

7日久生情
1534/5000
排名
1509
昨日变化
5

13

主题

204

帖子

1534

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
191997
好友
2
蛮牛币
40
威望
0
注册时间
2017-2-22
在线时间
561 小时
最后登录
2018-6-20
发表于 2018-4-3 16:57:39 | 显示全部楼层
就我一个人看懵了吗?

回复 支持 反对

使用道具 举报

4四处流浪
327/500
排名
22207
昨日变化
11

0

主题

282

帖子

327

积分

Rank: 4

UID
269036
好友
0
蛮牛币
26
威望
0
注册时间
2018-2-21
在线时间
27 小时
最后登录
2018-4-5
发表于 2018-4-5 15:28:29 | 显示全部楼层
go go go go go go go go go go go go
[发帖际遇]: yiss92 捡了钱没交公 蛮牛币 降了 1 . 幸运榜 / 衰神榜

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

关闭

站长推荐 上一条 /1 下一条

快速回复 返回顶部 返回列表