Unity中资源打包成Assetsbundle的资料整理

Wesley13
• 阅读 504

最近在研究Unity中关于资源打包的东西,网上看了一堆资料,这里做个整合,说整合,其实也就是Ctrl-C + Ctrl-V,不是原创

首先为了尊重原创,先贴出原创者的文章地址:

http://blog.csdn.net/kenkao/article/details/24290063

http://blog.csdn.net/janeky/article/details/17652021

http://blog.csdn.net/janeky/article/details/17666409

http://blog.csdn.net/janeky/article/details/25923151

本文原创版权归 csdn ken老 所有,转载请详细注明原创作者及出处,以示尊重!

作者:ken老

原文:http://blog.csdn.net/janeky/article/details/17652021

如果这篇文章对你有帮助,敬请关注作者《Unity手游之路》系列教程。

在手游的运营过程中,更新资源是比不可少的。资源管理第一步是资源打包。传统的打包可以将所有物件制成预设Prefab,打包成场景。今天我们来一起学习官方推荐的Assetbundle,它是Unity(Pro)提供的资源打包策略。利用AssetBundle,可以将几乎所有的资源都打包封装,便于客户端更新下载新的资源。

(转载请注明原文出处http://blog.csdn.net/janeky/article/details/17652021)

  • 创建AssetBundle

1.创建一个空的Prefab,命名Cube,然后创建一个Cube,将其拉到刚创建好的Prefab
2.新建一个脚本ExportAssetBundles.cs(代码来自官方文档),保存在Asset/Editor目录下

[csharp]  view plain copy Unity中资源打包成Assetsbundle的资料整理 Unity中资源打包成Assetsbundle的资料整理

  1. //在Unity编辑器中添加菜单

  2. [MenuItem("Assets/Build AssetBundle From Selection")]

  3. static void ExportResourceRGB2()

  4. {  

  5.     // 打开保存面板,获得用户选择的路径

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

  7.     if (path.Length != 0)

  8.     {  

  9.         // 选择的要保存的对象

  10.         Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);

  11.         //打包

  12.         BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, path, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, BuildTarget.StandaloneWindows);  

  13.     }  

  14. }

  

这时我们将看到Asset下面出现Build AssetBundle From Selection和Build Scene
3.选中预设Cube,运行Build AssetBundle From Selection。这时会弹出一个保存框,将其命名为cube.unity3d(这里为了测试方便,放在c盘。实际项目中,我们是需要将他们放在web服务器,供所有客户端下载更新)
4.新建一个场景scene1.unity,上面放置几个模型,然后保存

5.选中该场景,在之前的ExportAssetBundles.cs脚本中添加打包场景的函数,运行Assets->Build Scene,保存为scene1.unity3d(这里为了测试方便,也放在c盘)

[csharp]  view plain copy Unity中资源打包成Assetsbundle的资料整理 Unity中资源打包成Assetsbundle的资料整理

  1. [MenuItem("Assets/Save Scene")]

  2. static void ExportScene()

  3. {  

  4.       // 打开保存面板,获得用户选择的路径

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

  6.     if (path.Length != 0)

  7.     {  

  8.         // 选择的要保存的对象

  9.         Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);

  10.         string[] scenes = {"Assets/scene1.unity"};

  11.         //打包

  12.         BuildPipeline.BuildPlayer(scenes,path,BuildTarget.StandaloneWindows,BuildOptions.BuildAdditionalStreamedScenes);  

  13.     }  

  14. }

  

注意事项
a.AssetBundle的保存后缀名可以是assetbundle或者unity3d
b.BuildAssetBundle要根据不同的平台单独打包,BuildTarget参数指定平台,如果不指定,默认的webplayer

  • 加载AssetBundle

我们通过一个简单的代码来演示如何加载assetbundle,包括加载普通asset和场景。

[csharp]  view plain copy Unity中资源打包成Assetsbundle的资料整理 Unity中资源打包成Assetsbundle的资料整理

  1. using System;

  2. using UnityEngine;

  3. using System.Collections;

  4. public class Load: MonoBehaviour

  5. {  

  6.     private string BundleURL = "file:///C:/cube.assetbundle";

  7.     private string SceneURL = "file:///C:/scene1.unity3d";

  8.     void Start()

  9.     {  

  10.         //BundleURL = "file//"+Application.dataPath+"/cube.assetbundle";

  11.         Debug.Log(BundleURL);  

  12.         StartCoroutine(DownloadAssetAndScene());  

  13.     }  

  14.     IEnumerator DownloadAssetAndScene()  

  15.     {  

  16.         //下载assetbundle,加载Cube

  17.         using (WWW asset = new WWW(BundleURL))

  18.         {  

  19.             yield return asset;

  20.             AssetBundle bundle = asset.assetBundle;  

  21.             Instantiate(bundle.Load("Cube"));

  22.             bundle.Unload(false);

  23.             yield return new WaitForSeconds(5);

  24.         }  

  25.         //下载场景,加载场景

  26.         using (WWW scene = new WWW(SceneURL))

  27.         {  

  28.             yield return scene;

  29.             AssetBundle bundle = scene.assetBundle;  

  30.             Application.LoadLevel("scene1");

  31.         }  

  32.     }  

  33. }

  

注意事项
a.LoadFromCacheOrDownload 可以指定版本,如果本地版本是新的,将不会从服务器读取
b.如果是多个资源打包在一起,我们要通过bundle.Load(),加载特定的资源
c.挂载在模型上的脚本也可以一起打包,但是保证脚本在原目录也要存在,否则加载出来无法运行。关于如何更新脚本,我将放在以后的章节中阐述。

  • AssetBundle依赖关系

如果一个公共对象被多个对象依赖,我们打包的时候,可以有两种选取。一种是比较省事的,就是将这个公共对象打包到每个对象中。这样会有很多弊端:内存被浪费了;加入公共对象改变了,每个依赖对象都得重新打包。AssetBundle提供了依赖关系打包。我们通过一个简单的例子来学习

[csharp]  view plain copy Unity中资源打包成Assetsbundle的资料整理 Unity中资源打包成Assetsbundle的资料整理

  1. //启用交叉引用,用于所有跟随的资源包文件,直到我们调用PopAssetDependencies

  2.     BuildPipeline.PushAssetDependencies();  

  3.     var options =  

  4.         BuildAssetBundleOptions.CollectDependencies |  

  5.         BuildAssetBundleOptions.CompleteAssets;  

  6.     //所有后续资源将共享这一资源包中的内容,由你来确保共享的资源包是否在其他资源载入之前载入

  7.     BuildPipeline.BuildAssetBundle(  

  8.         AssetDatabase.LoadMainAssetAtPath("assets/artwork/lerpzuv.tif"),

  9.         null, "Shared.unity3d", options);

  10.         //这个文件将共享这些资源,但是后续的资源包将无法继续共享它

  11.     BuildPipeline.PushAssetDependencies();  

  12.     BuildPipeline.BuildAssetBundle(  

  13.         AssetDatabase.LoadMainAssetAtPath("Assets/Artwork/Lerpz.fbx"),

  14.         null, "Lerpz.unity3d", options);

  15.     BuildPipeline.PopAssetDependencies();  

  16.     这个文件将共享这些资源,但是后续的资源包将无法继续共享它  

  17.     BuildPipeline.PushAssetDependencies();  

  18.     BuildPipeline.BuildAssetBundle(  

  19.         AssetDatabase.LoadMainAssetAtPath("Assets/Artwork/explosive guitex.prefab"),

  20.         null, "explosive.unity3d", options);

  21.     BuildPipeline.PopAssetDependencies();  

  22.     BuildPipeline.PopAssetDependencies();

  

我们在程序加载的时候必须保证先加载公共对象。否则,只能是在各个对象加载成功后,再通过程序手动添加进来,比较繁琐。在实际项目中,由于是团队开发,对象间的依赖关系通常会比较凌乱,最好在开发周期就定好相关的规范约束,方便管理。

  • 总结

这一节的内容偏实际操作,官方文档和雨松的blog中已经有更加详细介绍。如果大家还有不明白的地方,可以结合文档再实际操作一下。后面的章节,我将花更多的时间介绍核心的内容:资源的增量更新,和代码程序的更新。

  • 源码

http://pan.baidu.com/s/1i3BOAPn

  • 参考资料

1.http://www.xuanyusong.com/
2.http://game.ceeger.com/Manual/DownloadingAssetBundles.html

来源:http://www.im286.com/thread-11924518-1-1.html

Assetbundle 是Unity Pro提供提供的功能,它可以把多个游戏对象或者资源二进制文件封装到Assetbundle中,提供了封装与解包的方法使用起来很便利。

1.预设

Assetbundle可以将Prefab封装起来,这是多么方便啊! 而且我也强烈建议大家将Prefab封装成Assetbundle,因为Prefab可以将游戏对象身上带的游戏游戏组件、游戏脚本、材质都封装在一起。当从服务器上将Assetbundle下载以后直接Instantiate就可以放入游戏中。

试想一下,如果只能将原始的二进制资源文件放在服务器上下载,当资源文件下载完毕后,需要动态的创建游戏对象、然后动态的将脚本绑定在游戏对象、动态的将贴图赋予游戏对象等等各种动态的操作。。所以强烈建议使用Prefa,不解释!!!!!

另外,我在举个例子,因为模型有可能会带很多动画文件,那么这样一组模型资源就可能是多个FBX 文件 和 若干png贴图文件 材质文件。这时我只需要把原始模型放入Prefab中,它就会包含这个模型的所有组件、甚至包括它的动画资源、贴图。那么如下图所示,Mode就是模型的Prefab文件,那么我仅仅只需要把Mode这个预设打包成Assetbundle即可。 当我在服务器上下载这个Assetbundle并且载入游戏中就可以直接使用了,切换动画、换贴图都可以。。

2.二进制文件

也并不是Assetbundle中全都要用预设,Assetbundle它也可以将二进制文件直接封装在里面,比如图片、声音、文本信息等等。

3.场景文件

在Unity中可以将一个场景保存在Scene中,Scene就会包含这个场景中的所有,那能不能把Scene也封装成Assetbundle中?答案是能,但是它不能在移动平台上用,因为移动平台上是不能更新脚本的,换句话来说就是即使将脚本绑定在Prefab中,然后下载Assetbundle后,所有脚本是不会执行的,后面说另外一种巧妙用法。

4.移动平台

上面MOMO已经将Assetbundle 的使用原理大致介绍了一下 ,我们在谈谈移动平台。脚本不能更新是移动平台下最大的伤,这就意味着开发者无法绕过App store和 google Play这种在线商店升级应用程序。唯一能做到的就是更新资源、举个例子,游戏中在处理版本升级时,一般会有个大版本号和一个小版本号,大版本号就是 2.0、3.0这种 版本需要在AppStore中更新,大版本主要是升级游戏脚本,然后当小版本号,比如2.0.1 或2.0.2这种只是更新游戏中的资源,通过自己游戏的服务器就可以完成,通过Assetbundle在自己服务器上下载,然后适应在游戏中。如果非要更新脚本,或不得不更新脚本那么只能在Appstore或者google Play去更新大版本。

移动平台上不能更新脚本,那么Prefab上绑定的脚本怎么办?在任何平台上都可以把脚本添加到Prefab上,然后打包成Assetbundle,只有移动平台上有点特殊,比如将Test.cs这条脚本绑定在Prefab中,最后程序通过服务器下载这个Assetbundle ,当载入工程中这条脚本是不会被执行的。

但是如果本地工程有Test.cs这条脚本,那么Unity会自动将这条脚本绑定在下载的Prefab中,并且他们执行的非常好。如果本地工程中没有Test.cs这条脚本,那么Prefab上的脚本是永远都不会执行的。有时我们会在脚本中写一些Public的变量,有可能不同的Prefab上绑定的是相同的脚本,只是Inspector 脚本中的public参数不同。别担心这一点Assetbundle 中的Prefab也是没问题,所以说只要大版本中的脚本没问题,在小版本中只更新游戏资源是一点问题都么有的。

5.移动优化

之前我们说过可以将游戏中的某个游戏对象封装成Assetbundle,也可以将游戏中的整个场景也封装成Assetbundle。但是我认为需要巧妙的使用封装场景,因为场景中肯定有很多公用的模型,如果打包场景的话那么内存与size就是 公用模型的size * N个场景,想想其实挺恐怖的。其实我们可以巧妙的使用,首先把场景中公用的部分和私有的部分统统放入Unity, 然后烘培整个场景。 当场景烘培完毕后把公用的模型部分在拿出去,场景只只保留私有的模型。还可以做一个工具将公用模型在场景中的坐标保存在XML中(每个场景文件会对应一个公用模型的XML信息),最后在将公用的模型分别封装在别的Assetbundle中。

服务器上提供每个场景的Assetbundle ,和公用模型的Assetbundle,一般公用模型的Assetbundle可以放在常驻内存中(可能使用频繁、根据项目的不同而定)场景Assetbundle下载完毕后,现载入场景然后在根据场景对应的XML信息将公用模型部分动态的在添加到场景中,这样就完成了一个场景的构建。

6.总结

对游戏中所有资源进行打包,比如按类型分为五个大部分 界面,模型,特效,声音,场景,脚本。

界面部分:

公用资源包(可复用的资源包)和 每个界面独有得资源包(不可复用的资源包)统一使用Prefab 打包成.assetbundle 二进制格式。

模型部分:

按角色分类,统一使用Prefab 打包成.assetbundle 二进制格式。 模型部分包括模型文件与动画文件,每一个模型文件对应一组动画文件。(如果模型需要换装还需提供对应换装的模型与贴图) ,因为unity4的重定向动画不支持动态加载,所以目前不需要考虑 不同大小 不同规格 不同性别 的模型重定向动画。

特效部分: 统一使用Prefab 打包成.assetbundle 二进制格式。

声音部分: 统一使用Prefab 打包成.assetbundle 二进制格式。

场景部分:场景和前面的有点区别,场景需要导出烘培的光信息并且只能烘培场景之上永远不动的模型,但是这些永远不动的模型有可能会同时在多个场景中使用,所以场景烘培完毕后要把重复使用的对象删除,(运行游戏在动态的加载进来)场景中只保留该场景中永远不会变的模型,以及烘培的光照信息。 打包场景后会生成.unity3D 二进制格式,它和 assetbundle 打包方式是不同的。(另外,也可以考虑 json xml 二进制 来动态组装场景)。

脚本部分:如果Prefab上是带脚本打包Assetbundle的话 脚本是不会被运行的(移动平台), 但是unity有一个技巧,Prefab上的脚本 如果本地有的话它会把本地的同名脚本绑定在Prefab对象上,它会很好的执行。

Prefab打包技巧: Prefab打包时自身是不占多少空间的 <=1KB 但是Prefab上是可以关联 这五大部分 “界面,模型,特效,声音,场景,脚本”以及在Hierarchy视图中 坐标/缩放/旋转。 关联这些信息以后就会很大,所以为了避免资源的浪费尽量避免Prefab重复关联。

一个prefab下面可以同时关联多个游戏对象 ,这里举个例子如果你的 Prefab下面放了一个模型 它的大小可能是500k ,在 Prefab下面放了十个完全相同模型 它的大小可能是501k 。 如果Prefab下面放了两个不同的模型,它的大小可能就会是 500k x 2 的size ,也就是说Prefab与关联的数量是无关的 。

加密部分: assetbundle 是可以转换成 字节数组 ,客户端与服务器约定一组解密 字节数组的算法就可以实现资源加密。

大版本升级:

unity的版本升级其实主要是升级主程序中的脚本。 因为所有的资源都是assetbundle 和 .unity3d 这些资源放在本地或者服务器 解包的方式是完全一样,所以理论上我们的主程序包的大小可以做到很小,可以很好设置把多少资源放在包里 或者把所少资源放在服务器上。在运行的时候服务端应该把所有 assetbundle 和 .unity3d的资源文件的下载地址列表返回给客户端。

小版本升级:

小版本升级也就是更新资源,因为不能更新脚本, 在登陆的时候服务端应该把所有 assetbundle 和 .unity3d的资源文件的下载地址列表返回给客户端。

还有个需要考虑的地方,比如现在大版本是2.0.0 ,小版本已经是2.0.5 ,用户的手机上是一个1.5.0的包。 此时用户在打开游戏的时候 应当强制它去appstore中去下载大版本2.0.0 ,当用户下载完毕后登陆游戏,此时服务器告诉客户端现在已经是2.0.5的小版本了,这时候客户端去下载对应小版本的所有 assetbundle 和 .unity3d文件地址列表。

增量更新:理论上增量更新是可行的。因为unity不能更新脚本,所以在处理增量更新的话 需要在代码中做可以兼容增量更新的可能。

因为Assetbundle这块的代码比较多,我还是决定分成两篇文章来写,这篇文章先说原理、下篇文章说代码。欢迎大家来讨论!

前几天我和Unity鑫哥聊天,他告诉我IOS上是无法运行时更新脚本、但是Android上是可以运行时更新脚本,我回家也试了一下但是没能成功,后来我考虑即使成功了项目中我也不打算那么做,因为这样Android和IOS 做起来的差别就太多了, 另外Unity商店中有一个处理运行时更新脚本的插件 unityLua 大家可以去研究研究。

上一次我们学习了如何将资源进行打包。这次就可以用上场了,我们来探讨一下手游资源的增量更新策略。注意哦,只是资源哦。关于代码的更新,我们稍后再来研究。理论上这个方案可以使用各种静态资源的更新,不仅仅是assetbundle打包的。

(转载请注明原文地址http://blog.csdn.net/janeky/article/details/17666409

  • 原理

现在的手游安装有几种方式。一种是安装的时候就把程序和资源安装到本地。另外一种是只安装程序和少量的必要资源,然后在启动的时候再把缺少的资源下载完整。手游一般不建议和传统页游一样,在运行过程中加载资源,那样做会导致用户体验会比较差些。上述的两种安装模式,在更新资源上本质都是相同的。都是比较服务器资源的版本和本地资源的版本,以确定哪些资源要下载(包括需要更新的和新增的)。

  • 实践

        1.资源打包。
资源打包之前,要先规划好资源之间的相互依赖关系。把一些共性的东西抽取出来,尽量减少不必要的耦合。一些比较好的做法有,所有物件尽可能做成Prefab,场景上的东西越少越好,“一切都是动态加载”。
        2.生成文件MD5
关于文件的MD5,这里就不详细描述了。大家可以简单理解它为一个文件的状态标记。如果文件有更改,那么它的md5一定是改变的,单纯的移动文件是不会更改的。md5验证还可以起到安全验证的作用,保证本地文件不被篡改。举个例子,我们经常从网上上下载软件时,一般都会给出一个md5值,你下载后,对比一下已下载文件的md5值,就可以知道文件有没有被篡改。在版本发布时,我们需要对所有打包好的文件计算md5值,然后保存在一个配置文件中。关于这部分的工作,我之前写过一个可视化小工具(https://github.com/kenro/File_Md5_Generator),现在分享给大家。如果大家觉得有用,记得打星哦:)
        3.版本比较
先加载本地的version.txt,将结果缓存起来。下载服务器的version.txt,与本地的version进行比较,筛选出需要更新和新增的资源
        4.下载资源
依次下载更新的资源,如果本地已经有旧资源,则替换之,否则就新建保存起来

        5.更新本地版本配置文件version.txt

用服务器的version.txt替换掉本地的version.txt。这样做是为了确保下次启动的时候,不会再重复更新了。

        6.从本地加载assetbundle进行测试显示。

这里将一个模型制成Prefab,打包成assetbundle。程序从本地加载后,显示在场景中

        7.更新服务器的assetbundle,重新生成版本号文件。

        8.重复6的步骤

我们可以验证,我们的程序不用任何改动,资源已经实现了更新。场景中显示的已经是最新的模型了。

关于上述的流程,我写了一个小的演示demo。我这里没有用到web服务器,而是将本地的另外一个文件夹作为资源服务器目录。这里的目录只针对windows下的版本进行测试。如果要在手机平台上,需要记得更新相关的路径。

[csharp]  view plain copy Unity中资源打包成Assetsbundle的资料整理 Unity中资源打包成Assetsbundle的资料整理

  1. using UnityEngine;

  2. using System.Collections;

  3. using System.Collections.Generic;

  4. using System.Text;

  5. using System.IO;

  6. public class ResUpdate : MonoBehaviour

  7. {  

  8.     public static readonly string VERSION_FILE = "version.txt";

  9.     public static readonly string LOCAL_RES_URL = "file://" + Application.dataPath + "/Res/";

  10.     public static readonly string SERVER_RES_URL = "file:///C:/Res/";

  11.     public static readonly string LOCAL_RES_PATH = Application.dataPath + "/Res/";

  12.     private Dictionary<string, string> LocalResVersion;

  13.     private Dictionary<string, string> ServerResVersion;

  14.     private List NeedDownFiles;

  15.     private bool NeedUpdateLocalVersionFile = false;

  16.     void Start()

  17.     {  

  18.         //初始化

  19.         LocalResVersion = new Dictionary<string, string>();

  20.         ServerResVersion = new Dictionary<string, string>();

  21.         NeedDownFiles = new List();

  22.         //加载本地version配置

  23.         StartCoroutine(DownLoad(LOCAL_RES_URL + VERSION_FILE, delegate(WWW localVersion)

  24.         {  

  25.             //保存本地的version

  26.             ParseVersionFile(localVersion.text, LocalResVersion);  

  27.             //加载服务端version配置

  28.             StartCoroutine(this.DownLoad(SERVER_RES_URL + VERSION_FILE, delegate(WWW serverVersion)

  29.             {  

  30.                 //保存服务端version

  31.                 ParseVersionFile(serverVersion.text, ServerResVersion);  

  32.                 //计算出需要重新加载的资源

  33.                 CompareVersion();  

  34.                 //加载需要更新的资源

  35.                 DownLoadRes();  

  36.             }));  

  37.         }));  

  38.     }  

  39.     //依次加载需要更新的资源

  40.     private void DownLoadRes()

  41.     {  

  42.         if (NeedDownFiles.Count == 0)

  43.         {  

  44.             UpdateLocalVersionFile();  

  45.             return;

  46.         }  

  47.         string file = NeedDownFiles[0];

  48.         NeedDownFiles.RemoveAt(0);  

  49.         StartCoroutine(this.DownLoad(SERVER_RES_URL + file, delegate(WWW w)

  50.         {  

  51.             //将下载的资源替换本地就的资源

  52.             ReplaceLocalRes(file, w.bytes);  

  53.             DownLoadRes();  

  54.         }));  

  55.     }  

  56.     private void ReplaceLocalRes(string fileName, byte[] data)

  57.     {  

  58.         string filePath = LOCAL_RES_PATH + fileName;

  59.         FileStream stream = new FileStream(LOCAL_RES_PATH + fileName, FileMode.Create);

  60.         stream.Write(data, 0, data.Length);  

  61.         stream.Flush();  

  62.         stream.Close();  

  63.     }  

  64.     //显示资源

  65.     private IEnumerator Show()

  66.     {  

  67.         WWW asset = new WWW(LOCAL_RES_URL + "cube.assetbundle");

  68.         yield return asset;

  69.         AssetBundle bundle = asset.assetBundle;  

  70.         Instantiate(bundle.Load("Cube"));

  71.         bundle.Unload(false);

  72.     }  

  73.     //更新本地的version配置

  74.     private void UpdateLocalVersionFile()

  75.     {  

  76.         if (NeedUpdateLocalVersionFile)

  77.         {  

  78.             StringBuilder versions = new StringBuilder();

  79.             foreach (var item in ServerResVersion)

  80.             {  

  81.                 versions.Append(item.Key).Append(",").Append(item.Value).Append("\n");

  82.             }  

  83.             FileStream stream = new FileStream(LOCAL_RES_PATH + VERSION_FILE, FileMode.Create);

  84.             byte[] data = Encoding.UTF8.GetBytes(versions.ToString());

  85.             stream.Write(data, 0, data.Length);  

  86.             stream.Flush();  

  87.             stream.Close();  

  88.         }  

  89.         //加载显示对象

  90.         StartCoroutine(Show());  

  91.     }  

  92.     private void CompareVersion()

  93.     {  

  94.         foreach (var version in ServerResVersion)

  95.         {  

  96.             string fileName = version.Key;

  97.             string serverMd5 = version.Value;

  98.             //新增的资源

  99.             if (!LocalResVersion.ContainsKey(fileName))

  100.             {  

  101.                 NeedDownFiles.Add(fileName);  

  102.             }  

  103.             else

  104.             {  

  105.                 //需要替换的资源

  106.                 string localMd5;

  107.                 LocalResVersion.TryGetValue(fileName, out localMd5);

  108.                 if (!serverMd5.Equals(localMd5))

  109.                 {  

  110.                     NeedDownFiles.Add(fileName);  

  111.                 }  

  112.             }  

  113.         }  

  114.         //本次有更新,同时更新本地的version.txt

  115.         NeedUpdateLocalVersionFile = NeedDownFiles.Count > 0;  

  116.     }  

  117.     private void ParseVersionFile(string content, Dictionary<string, string> dict)

  118.     {  

  119.         if (content == null || content.Length == 0)

  120.         {  

  121.             return;

  122.         }  

  123.         string[] items = content.Split(new char[] { '\n' });

  124.         foreach (string item in items)

  125.         {  

  126.             string[] info = item.Split(new char[] { ',' });

  127.             if (info != null && info.Length == 2)

  128.             {  

  129.                 dict.Add(info[0], info[1]);  

  130.             }  

  131.         }  

  132.     }  

  133.     private IEnumerator DownLoad(string url, HandleFinishDownload finishFun)

  134.     {  

  135.         WWW www = new WWW(url);

  136.         yield return www;

  137.         if (finishFun != null)

  138.         {  

  139.             finishFun(www);  

  140.         }  

  141.         www.Dispose();  

  142.     }  

  143.     public delegate void HandleFinishDownload(WWW www);

  144. }

  

  • 总结

资源更新的原理,本质上都是相似的。我之前也从事过页游的开发,资源更新流程也类似。所以技术的本质是掌握思维方式,平台和语言都是永远在变的。我们最后归纳一下流程:比较服务端的资源版本和本地的资源版本,找出需要更新的资源,然后依次下载。如果大家有更好的策略,欢迎分享探讨 ken@iamcoding.com

  • 源码

http://pan.baidu.com/s/1mgNnR8O

  • 参考资料

Unity3d官网文档

之前我们已经学过手机游戏的资源热更新策略了。在实际手游的开发运营中,我们需要经常修复bug,增加新玩法。这些通常都涉及到代码的更新。unity游戏代码的更新比较复杂,也存在不同的更新策略,各有优缺点,在不同的平台上做法也不尽相同。这里主要谈一些比较常用的策略和各大手机平台上的策略。大家有更好的思路,欢迎探讨。

(转载请注明出处 http://blog.csdn.net/janeky/article/details/25923151

  • 反射

大部分编程语言都是支持反射的,利用反射,可以动态去加载所需的程序。C#也是同样可以用反射来实现。要实现代码的更新,我们在项目初期就要做好规划,将一些容易变更的业务逻辑代码独立划分。每次更新时,将代码打包成dll,再打包成资源文件。程序启动时,检查更新到客户端,客户端通过反射重新加载代码运行。下面通过一个简单的demo来演示。

1.在vs中新建一个代码库工程,命名为test
2.添加几个类Scirpt,Scirpt2,Data
3.将这个项目生成DLL,test.dll
4.新建一个unity项目,将DLL倒入到Asset,改名为test.bytes,不然可能会报错
5.利用我们之前实现过的打包脚本,将test.bytes打包成test.assetbundle。
6.创建CodeUpdate.cs脚本,用于加载代码资源,反射调用。
7.为了验证代码更新后,可以直接加载使用,我们可以更改一下Data.cs的代码,重复以上过程,可以看到,更新了代码打包后,我们重新运行游戏,就可以看到效果

Data.cs

[csharp]  view plain copy Unity中资源打包成Assetsbundle的资料整理 Unity中资源打包成Assetsbundle的资料整理

  1. public class Data

  2. {  

  3.     private int attr;

  4.     public Data()

  5.     {  

  6.         attr = 2;  

  7.     }  

  8.     public override string ToString()

  9.     {  

  10.         return attr.ToString();

  11.     }  

  12. }

  

Script.cs

[csharp]  view plain copy Unity中资源打包成Assetsbundle的资料整理 Unity中资源打包成Assetsbundle的资料整理

  1. public class Script: MonoBehaviour
  2. {  
  3.     void Start()
  4.     {  
  5.         Debug.Log("------------------I am script 1");
  6.         Data data = new Data();
  7.         Debug.Log("-------------" + data.ToString());
  8.     }  
  9. }

  

CodeUpdate.cs

[csharp]  view plain copy Unity中资源打包成Assetsbundle的资料整理 Unity中资源打包成Assetsbundle的资料整理

  1. using UnityEngine;

  2. using System.Collections;

  3. using System;

  4. public class CodeUpdate : MonoBehaviour {

  5.     private static readonly string DLL_URL = "file:///c:/test.assetbundle";

  6.     void Start () {

  7.         StartCoroutine(loadDllScript());  

  8.     }  

  9.     private IEnumerator loadDllScript()

  10.     {  

  11.         WWW www = new WWW(DLL_URL);

  12.         yield return www;

  13.         AssetBundle bundle = www.assetBundle;  

  14.         TextAsset asset = bundle.Load("test",typeof(TextAsset)) as TextAsset;

  15.         System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(asset.bytes);  

  16.         Type script1 = assembly.GetType("Script");

  17.         GameObject obj = new GameObject();

  18.         obj.AddComponent(script1);  

  19.         Type script2 = assembly.GetType("Script2");

  20.         obj.AddComponent(script2);  

  21.     }  

  22. }

  

  • 完整安装包更新

大部分的app更新都是采用完整包更新。在程序启动的时候,检查服务器的最新版本,如果比本地的版本要新,就下载服务器的版本,重新安装替换本地的程序。在IOS平台上,是由App Store来统一管理的。客户端程序只需检查版本,跳转到app store页面即可。android 平台的更新更灵活,略微复杂。在判断版本号,确定要更新后,直接就可以下载服务器的最新的apk文件,安装替换本地的。这里就不演示代码了。大家先理清楚思路,流程,就容易实现了。

  • LUA脚本更新

LUA一直是一种很神奇的脚本语言,无处不在,服务端,客户端,大型机,嵌入式设备都能看到它的踪影。虽然Unity3d官方不支持Lua脚本,但是已经有人写了c#版本的lua解析器了。我们可以将业务代码用Lua来实现。每次要更新代码的时候,只要将lua当做资源文件更新到客户端,运行即可。

C#版 Lua,有很多个版本,这里选择云风他们公司开源的UniLua,大家可以去Githunb下载

https://github.com/xebecnan/UniLua/wiki

  • IOS平台

比较遗憾,IOS是一个封闭的平台,所以它对app程序监管比较严格,一般情况下不运行热更新,每次版本更新都需要提交审核。所以涉及到手游代码的更新,都是采用完整包更新。LUA脚本更新的方式,有朋友试过说可以(他们一般是在程序上线一段时间后才使用Lua更新)。但是也存在风险的,如果被苹果发现,是属于违规的。这里不建议使用。

  • Android平台

目前比较通用的方式是用代码dll反射更新机制。我们在实际过程中,将稳定不变的底层代码单独规划,用作游戏的主程序。全部业务逻辑代码发布时候,打包成dll,制成资源文件。客户端下载后,反射加载。只有当底层主程序要更新是,才单独下载主程序的apk文件,重新安装替换。平时的代码更新,可以随意更新代码dll

  • 总结

上面说的几种方式,各有优缺点。在不同的平台上策略也不尽相同。说一下我的经验:一般是优先发布android版本,有问题随时热更新代码调试。待版本稳定后,发布ios越狱版本。全部稳定后,最后才发布app store。众所周知,app store的审查周期比较长,有可能他们员工去休个假,几个星期才审核通过:)。每次审核不通过,又得重新修改提交审查,又是漫长的等待。在游戏界,时间就是生命。我们尽量在android平台上调试版本。

ps.大家有什么好的Unity3d技术点想讨论的,欢迎告知,我今后将会多多写一下大家比较感兴趣的实战内容。

最后祝大家工作顺利,项目大卖~。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
3年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这