unity3d 自己主动文件更新系统

         游戏内容变更之后。一般而言不会想让玩家下载整个游戏包又一次安装,由于这样会流失大量玩家。全部游戏更新是必须的。

更新的内容包含 数据、资源、代码。

         基本原理:

1、将须要更新的文件打包成AssetBundle文件,并计算各个文件的crc值。

         以下代码将选择的文件分别导出为AssetBundle文件,并将每一个文件的crc值写入到crc.txt文件里,在Editor文件夹建立一个类,并复制以下代码。

能够在Project文件夹导出选择的文件。

public class ExportAssetBundles {

[MenuItem("Assets/Build AssetBundle From Selection Respective -  Track dependencies")]

         static void ExportResourceRespective()

         {

                   // Bring up save panel

                   string path = EditorUtility.SaveFilePanel ("Save Resource", "", "New Resource", "unity3d");

                   string dirpath = path.Substring(0,path.LastIndexOf("/")+1);

                   string filename = path.Substring(path.LastIndexOf("/")+1);

                   if (path.Length != 0) {

                            #if UNITY_ANDROID

                            string targetDir = "Android/";

                            BuildTarget targetBuild = BuildTarget.Android;

                            #elif UNITY_IPHONE

                            string targetDir = "iPhone/";

                            BuildTarget targetBuild = BuildTarget.iPhone;

                            #elif UNITY_STANDALONE_WIN

                            string targetDir = "StandaloneWindows/";

                            BuildTarget targetBuild = BuildTarget.StandaloneWindows;

                            #endif

                            JSDocument.JSNode node = new JSDocument.JSNode("root");

                            Document.SNode[] nodes = node.putChildren("filehash",Selection.objects.Length);

                            for(int i=0;i<Selection.objects.Length;i++)

                            {

                                     string name = Selection.objects[i].name+".unity3d";

                                     uint crc = 0;

                                     if(!Directory.Exists(dirpath+targetDir))

                                               Directory.CreateDirectory(dirpath+targetDir);

                                     BuildPipeline.BuildAssetBundle(Selection.objects[i],new Object[]{ Selection.objects[i]}, dirpath+targetDir+name,out crc,BuildAssetBundleOptions.CollectDependencies,targetBuild);

                                     nodes[i].put("name",name);

                                     nodes[i].put("crc",crc);

                            }

                            System.IO.File.WriteAllText(dirpath+targetDir+filename+".crc.txt",node.toJSONString());

                   }

         }

}

 

2.须要一个资源更新server,将导出的AssetBundle文件和crc文件上传到资源更新server。能够用一个简单的httpserver。比如nginx。

 

3.client进入游戏之前。首先向更新server请求crc.txt文件。然后从本地的磁盘文件夹中查找crc.txt文件,检查须要更新的文件列表。然后从server下载须要更新的文件。这样,假设server没有更改文件,则仅仅须要下载一次。

4.最新的crc.txt文件到本地,以便下次查询。

以下代码演示 3,4 步骤,当中

Engine.Instance.server_datapath = 服务器下载地址。

Engine.Instance.local_datapath = Application.persistentDataPath+"/;

 

 

public static IEnumerator  UpdateDataFromServer(UpdateProgress up)

    {

 

        string server_datapath = Engine.Instance.server_datapath;

        string local_datapath = Engine.Instance.local_datapath;

 

        byte[] server_crc_data  = null;

 

        Dictionary<string,long> filehash_server=new Dictionary<string, long>();

        Dictionary<string,long>  filehash_local=new Dictionary<string, long>();

        List<string> needUpdateFile = new List<string>();

 

        Debug.Log("Load Server FileHash");

 

        using(WWW www = new WWW(server_datapath+"crc.txt"))

        {

            yield return www;

            if (!String.IsNullOrEmpty(www.error))

            {

                Debug.Log("Load Filehash Failed");

                up(1.0f);

                yield break;

            }

            JSDocument.JSNode node = new JSDocument.JSNode("filehash",www.text);

            Document.SNode[] data = node.getChildren("filehash");  

 

            for(int i=0;i<data.Length;i++)

            {

                filehash_server[data[i].get("name","")] = data[i].get("crc",(long)0);

            }

 

            server_crc_data = www.bytes;

        }

        Debug.Log("Load Local FileHash");

        //从本地载入文件MD5表。可能没有

 

        try

        {

 

            JSDocument.JSNode node = new JSDocument.JSNode("filehash",System.IO.File.ReadAllText(local_datapath+"crc.txt"));

            Document.SNode[] data = node.getChildren("filehash");  

            for(int i=0;i<data.Length;i++)

            {

                filehash_local[data[i].get("name","")] = data[i].get("crc",(long)0);

            }

        }

        catch(Exception e)

        {

            Debug.Log(e.Message);

        }

        Debug.Log("Check FileHash");

 

        //计算须要更新的文件

        foreach(KeyValuePair<string,long> data in filehash_server)

        {

            //更新须要的文件

            if(!filehash_local.ContainsKey(data.Key) || filehash_local[data.Key] != data.Value)

            {

                needUpdateFile.Add(data.Key);

            }

        }

 

        Debug.Log("Update File");

 

        //下载并存储

        for(int i=0;i<needUpdateFile.Count;i++)

        {

            using(WWW www = new WWW(server_datapath+needUpdateFile[i]))

            {

                yield return www;

                byte[] bytes = null;

 

                if (!String.IsNullOrEmpty(www.error))

                {

                    Debug.Log(www.error);

                    yield break;

                }

                else

                {

                    bytes = www.bytes;

                }

 

                up((float)i/needUpdateFile.Count);

 

                string path = local_datapath+ needUpdateFile[i];

                Debug.Log(path);

                FileStream fs = new FileStream(path,FileMode.Create);

                fs.Write(bytes,0,bytes.Length);

                fs.Flush();

                fs.Close();

 

//              //保存

//              BinaryWriter writer;

//              FileInfo t =new FileInfo(local_datapath+ needUpdateFile[i]);

//                if(!t.Exists)

//              {

//                  writer = new BinaryWriter(t.Open(FileMode.OpenOrCreate));

//              }

//              else

//              {

//                  t.Delete();

//                  writer = new BinaryWriter(t.Open(FileMode.Create));

//              }

//              writer.Write(bytes);

//              writer.Close();

               

            }

        }

 

        Debug.Log("Save FileHash");

 

        if(needUpdateFile.Count>0)

        {

            //保存最新的文件MD5值表

//          FileStream fs = new FileStream(local_datapath+"crc.txt",FileMode.Create);

//          fs.Write(server_crc_data,0,server_crc_data.Length);

//          fs.Flush();

//            fs.Close();

 

            BinaryWriter writer;

            FileInfo t =new FileInfo(local_datapath+"crc.txt");

            if(!t.Exists)

            {

                writer = new BinaryWriter(t.Open(FileMode.Create));

            }

            else

            {

                t.Delete();

                writer = new BinaryWriter(t.Open(FileMode.Create));

            }

            writer.Write(server_crc_data);

            writer.Close();

 

            Debug.Log(local_datapath+"crc.txt");

        }

    }

 

 

 

5.载入已经更新完毕的存储在本地的AssetBundle,须要注意的是。Unity3d同样的文件同一时候仅仅能有一个AssetBundle在内存中,所以我们对于同样文件的载入做了同步。

(loadRefCount)。

 

static HashSet<string> loadRefCount = new HashSet<string>();

public delegate void delegateLoadFinish(GameObject go);

public static IEnumerator LoadModel(string res,delegateLoadFinish onLoadFinish)

    {

        GameObject resObject = Resources.Load<GameObject>("cards/"+res);

        if(resObject !=null)

        {

            onLoadFinish((GameObject)GameObject.Instantiate(resObject));

            yield break;

        }

 

        //假设包括资源。返回

        while(loadRefCount.Contains(res))

        {

            yield return true;

        }

 

 

        loadRefCount.Add(res);

        Debug.Log(Engine.Instance.local_datapath+res+".unity3d");

 

        AssetBundleCreateRequest crcLocalBundle = AssetBundle.CreateFromMemory(

            System.IO.File.ReadAllBytes(Engine.Instance.local_datapath+res+".unity3d"));

       

       yield return crcLocalBundle;

 

        {

            GameObject cardObject = GameObject.Instantiate(crcLocalBundle.assetBundle.mainAsset)as GameObject;

            crcLocalBundle.assetBundle.Unload(false);

            onLoadFinish(cardObject);

        }

 

        loadRefCount.Remove(res);

    }

 

posted @ 2017-06-08 17:25  yfceshi  阅读(1729)  评论(1编辑  收藏  举报