• 最近在研究Unity中关于资源打包的东西,网上看了一堆资料,这里做个整合,说整合,其实也就是Ctrl-C + Ctrl-V,不是原创 首先为了尊重原创,先贴出原创者的文章地址: ...

    最近在研究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 janeky 所有,转载请详细注明原创作者及出处,以示尊重!

    作者:janeky

    原文: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目录下
    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.   
    8.     if (path.Length != 0)  
    9.     {  
    10.         // 选择的要保存的对象  
    11.         Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);  
    12.         //打包  
    13.         BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, path, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, BuildTarget.StandaloneWindows);  
    14.     }  
    15. }  
    这时我们将看到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盘)

    1. [MenuItem("Assets/Save Scene")]  
    2. static void ExportScene()  
    3. {  
    4.       // 打开保存面板,获得用户选择的路径  
    5.     string path = EditorUtility.SaveFilePanel("Save Resource""""New Resource""unity3d");  
    6.   
    7.     if (path.Length != 0)  
    8.     {  
    9.         // 选择的要保存的对象  
    10.         Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);  
    11.         string[] scenes = {"Assets/scene1.unity"};  
    12.         //打包  
    13.         BuildPipeline.BuildPlayer(scenes,path,BuildTarget.StandaloneWindows,BuildOptions.BuildAdditionalStreamedScenes);  
    14.     }  
    15. }  
    注意事项
    a.AssetBundle的保存后缀名可以是assetbundle或者unity3d
    b.BuildAssetBundle要根据不同的平台单独打包,BuildTarget参数指定平台,如果不指定,默认的webplayer
    • 加载AssetBundle

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

    1. using System;  
    2. using UnityEngine;  
    3. using System.Collections;  
    4.   
    5. public class Load: MonoBehaviour  
    6. {  
    7.     private string BundleURL = "file:///C:/cube.assetbundle";  
    8.     private string SceneURL = "file:///C:/scene1.unity3d";  
    9.   
    10.     void Start()  
    11.     {  
    12.         //BundleURL = "file//"+Application.dataPath+"/cube.assetbundle";  
    13.         Debug.Log(BundleURL);  
    14.         StartCoroutine(DownloadAssetAndScene());  
    15.     }  
    16.   
    17.     IEnumerator DownloadAssetAndScene()  
    18.     {  
    19.         //下载assetbundle,加载Cube  
    20.         using (WWW asset = new WWW(BundleURL))  
    21.         {  
    22.             yield return asset;  
    23.             AssetBundle bundle = asset.assetBundle;  
    24.             Instantiate(bundle.Load("Cube"));  
    25.             bundle.Unload(false);  
    26.             yield return new WaitForSeconds(5);  
    27.         }  
    28.         //下载场景,加载场景  
    29.         using (WWW scene = new WWW(SceneURL))  
    30.         {  
    31.             yield return scene;  
    32.             AssetBundle bundle = scene.assetBundle;  
    33.             Application.LoadLevel("scene1");  
    34.         }  
    35.          
    36.     }  
    37. }  
    注意事项
    a.LoadFromCacheOrDownload 可以指定版本,如果本地版本是新的,将不会从服务器读取
    b.如果是多个资源打包在一起,我们要通过bundle.Load(),加载特定的资源
    c.挂载在模型上的脚本也可以一起打包,但是保证脚本在原目录也要存在,否则加载出来无法运行。关于如何更新脚本,我将放在以后的章节中阐述。
    • AssetBundle依赖关系
    如果一个公共对象被多个对象依赖,我们打包的时候,可以有两种选取。一种是比较省事的,就是将这个公共对象打包到每个对象中。这样会有很多弊端:内存被浪费了;加入公共对象改变了,每个依赖对象都得重新打包。AssetBundle提供了依赖关系打包。我们通过一个简单的例子来学习
    1. //启用交叉引用,用于所有跟随的资源包文件,直到我们调用PopAssetDependencies  
    2.     BuildPipeline.PushAssetDependencies();  
    3.   
    4.     var options =  
    5.         BuildAssetBundleOptions.CollectDependencies |  
    6.         BuildAssetBundleOptions.CompleteAssets;  
    7.   
    8.   
    9.     //所有后续资源将共享这一资源包中的内容,由你来确保共享的资源包是否在其他资源载入之前载入  
    10.     BuildPipeline.BuildAssetBundle(  
    11.         AssetDatabase.LoadMainAssetAtPath("assets/artwork/lerpzuv.tif"),  
    12.         null"Shared.unity3d", options);  
    13.   
    14.   
    15.         //这个文件将共享这些资源,但是后续的资源包将无法继续共享它  
    16.     BuildPipeline.PushAssetDependencies();  
    17.     BuildPipeline.BuildAssetBundle(  
    18.         AssetDatabase.LoadMainAssetAtPath("Assets/Artwork/Lerpz.fbx"),  
    19.         null"Lerpz.unity3d", options);  
    20.     BuildPipeline.PopAssetDependencies();  
    21.   
    22.   
    23.     这个文件将共享这些资源,但是后续的资源包将无法继续共享它  
    24.     BuildPipeline.PushAssetDependencies();  
    25.     BuildPipeline.BuildAssetBundle(  
    26.         AssetDatabase.LoadMainAssetAtPath("Assets/Artwork/explosive guitex.prefab"),  
    27.         null"explosive.unity3d", options);  
    28.     BuildPipeline.PopAssetDependencies();  
    29.   
    30.   
    31.     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下的版本进行测试。如果要在手机平台上,需要记得更新相关的路径。

    1. using UnityEngine;  
    2. using System.Collections;  
    3. using System.Collections.Generic;  
    4. using System.Text;  
    5. using System.IO;  
    6.   
    7. public class ResUpdate : MonoBehaviour  
    8. {  
    9.     public static readonly string VERSION_FILE = "version.txt";  
    10.     public static readonly string LOCAL_RES_URL = "file://" + Application.dataPath + "/Res/";  
    11.     public static readonly string SERVER_RES_URL = "file:///C:/Res/";  
    12.     public static readonly string LOCAL_RES_PATH = Application.dataPath + "/Res/";  
    13.   
    14.     private Dictionary<stringstring> LocalResVersion;  
    15.     private Dictionary<stringstring> ServerResVersion;  
    16.     private List<string> NeedDownFiles;  
    17.     private bool NeedUpdateLocalVersionFile = false;  
    18.   
    19.     void Start()  
    20.     {  
    21.         //初始化  
    22.         LocalResVersion = new Dictionary<stringstring>();  
    23.         ServerResVersion = new Dictionary<stringstring>();  
    24.         NeedDownFiles = new List<string>();  
    25.   
    26.         //加载本地version配置  
    27.         StartCoroutine(DownLoad(LOCAL_RES_URL + VERSION_FILE, delegate(WWW localVersion)  
    28.         {  
    29.             //保存本地的version  
    30.             ParseVersionFile(localVersion.text, LocalResVersion);  
    31.             //加载服务端version配置  
    32.             StartCoroutine(this.DownLoad(SERVER_RES_URL + VERSION_FILE, delegate(WWW serverVersion)  
    33.             {  
    34.                 //保存服务端version  
    35.                 ParseVersionFile(serverVersion.text, ServerResVersion);  
    36.                 //计算出需要重新加载的资源  
    37.                 CompareVersion();  
    38.                 //加载需要更新的资源  
    39.                 DownLoadRes();  
    40.             }));  
    41.   
    42.         }));  
    43.     }  
    44.   
    45.     //依次加载需要更新的资源  
    46.     private void DownLoadRes()  
    47.     {  
    48.         if (NeedDownFiles.Count == 0)  
    49.         {  
    50.             UpdateLocalVersionFile();  
    51.             return;  
    52.         }  
    53.   
    54.         string file = NeedDownFiles[0];  
    55.         NeedDownFiles.RemoveAt(0);  
    56.   
    57.         StartCoroutine(this.DownLoad(SERVER_RES_URL + file, delegate(WWW w)  
    58.         {  
    59.             //将下载的资源替换本地就的资源  
    60.             ReplaceLocalRes(file, w.bytes);  
    61.             DownLoadRes();  
    62.         }));  
    63.     }  
    64.   
    65.     private void ReplaceLocalRes(string fileName, byte[] data)  
    66.     {  
    67.         string filePath = LOCAL_RES_PATH + fileName;  
    68.         FileStream stream = new FileStream(LOCAL_RES_PATH + fileName, FileMode.Create);  
    69.         stream.Write(data, 0, data.Length);  
    70.         stream.Flush();  
    71.         stream.Close();  
    72.     }  
    73.   
    74.     //显示资源  
    75.     private IEnumerator Show()  
    76.     {  
    77.         WWW asset = new WWW(LOCAL_RES_URL + "cube.assetbundle");  
    78.         yield return asset;  
    79.         AssetBundle bundle = asset.assetBundle;  
    80.         Instantiate(bundle.Load("Cube"));  
    81.         bundle.Unload(false);  
    82.     }  
    83.   
    84.     //更新本地的version配置  
    85.     private void UpdateLocalVersionFile()  
    86.     {  
    87.         if (NeedUpdateLocalVersionFile)  
    88.         {  
    89.             StringBuilder versions = new StringBuilder();  
    90.             foreach (var item in ServerResVersion)  
    91.             {  
    92.                 versions.Append(item.Key).Append(",").Append(item.Value).Append("\n");  
    93.             }  
    94.   
    95.             FileStream stream = new FileStream(LOCAL_RES_PATH + VERSION_FILE, FileMode.Create);  
    96.             byte[] data = Encoding.UTF8.GetBytes(versions.ToString());  
    97.             stream.Write(data, 0, data.Length);  
    98.             stream.Flush();  
    99.             stream.Close();  
    100.         }  
    101.         //加载显示对象  
    102.         StartCoroutine(Show());  
    103.     }  
    104.   
    105.     private void CompareVersion()  
    106.     {  
    107.         foreach (var version in ServerResVersion)  
    108.         {  
    109.             string fileName = version.Key;  
    110.             string serverMd5 = version.Value;  
    111.             //新增的资源  
    112.             if (!LocalResVersion.ContainsKey(fileName))  
    113.             {  
    114.                 NeedDownFiles.Add(fileName);  
    115.             }  
    116.             else  
    117.             {  
    118.                 //需要替换的资源  
    119.                 string localMd5;  
    120.                 LocalResVersion.TryGetValue(fileName, out localMd5);  
    121.                 if (!serverMd5.Equals(localMd5))  
    122.                 {  
    123.                     NeedDownFiles.Add(fileName);  
    124.                 }  
    125.             }  
    126.         }  
    127.         //本次有更新,同时更新本地的version.txt  
    128.         NeedUpdateLocalVersionFile = NeedDownFiles.Count > 0;  
    129.     }  
    130.   
    131.     private void ParseVersionFile(string content, Dictionary<stringstring> dict)  
    132.     {  
    133.         if (content == null || content.Length == 0)  
    134.         {  
    135.             return;  
    136.         }  
    137.         string[] items = content.Split(new char[] { '\n' });  
    138.         foreach (string item in items)  
    139.         {  
    140.             string[] info = item.Split(new char[] { ',' });  
    141.             if (info != null && info.Length == 2)  
    142.             {  
    143.                 dict.Add(info[0], info[1]);  
    144.             }  
    145.         }  
    146.   
    147.     }  
    148.   
    149.     private IEnumerator DownLoad(string url, HandleFinishDownload finishFun)  
    150.     {  
    151.         WWW www = new WWW(url);  
    152.         yield return www;  
    153.         if (finishFun != null)  
    154.         {  
    155.             finishFun(www);  
    156.         }  
    157.         www.Dispose();  
    158.     }  
    159.   
    160.     public delegate void HandleFinishDownload(WWW www);  
    161. }  

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

    1. public class Data  
    2. {  
    3.     private int attr;  
    4.   
    5.     public Data()  
    6.     {  
    7.         attr = 2;  
    8.     }  
    9.   
    10.     public override string ToString()  
    11.     {  
    12.         return attr.ToString();  
    13.     }  
    14. }  

    Script.cs

    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

    1. using UnityEngine;  
    2. using System.Collections;  
    3. using System;  
    4.   
    5. public class CodeUpdate : MonoBehaviour {  
    6.       
    7.     private static readonly string DLL_URL = "file:///c:/test.assetbundle";  
    8.   
    9.     void Start () {  
    10.         StartCoroutine(loadDllScript());  
    11.     }  
    12.       
    13.     private IEnumerator loadDllScript()  
    14.     {  
    15.         WWW www = new WWW(DLL_URL);  
    16.         yield return www;  
    17.         AssetBundle bundle = www.assetBundle;  
    18.         TextAsset asset = bundle.Load("test",typeof(TextAsset)) as TextAsset;  
    19.           
    20.         System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(asset.bytes);  
    21.         Type script1 = assembly.GetType("Script");  
    22.         GameObject obj = new GameObject();  
    23.         obj.AddComponent(script1);  
    24.           
    25.         Type script2 = assembly.GetType("Script2");  
    26.         obj.AddComponent(script2);  
    27.     }  
    28. }  
    • 完整安装包更新

    大部分的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技术点想讨论的,欢迎告知,我今后将会多多写一下大家比较感兴趣的实战内容。

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


    展开全文
  • Unity3d 命令行打包

    2020-05-09 00:15:32
    项目需要进行两次打包,两个exe公用一套数据,前后两次打包还需要对Unity的Main场景中某物体挂载脚本的一个public bool型变量,每次的手残都需要重头开始,有点无语,所以Google大法让我知道了bat,powershell相关,...

    起因

    项目需要进行两次打包,两个exe公用一套数据,前后两次打包还需要对Unity的Main场景中某物体挂载脚本的一个public bool型变量,每次的手残都需要重头开始,有点无语,所以Google大法让我知道了bat,powershell相关,便试着双击bat进行完成两次打包

    过程

    1.首先在Editor文件夹内新建类进行build

    	[MenuItem("Build/Build Editor")]
        public static void BuildEditor()
        {
        	//设置打包选项相关
            BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
            //加入的场景
            buildPlayerOptions.scenes = new[] { "Assets/Scenes/Main.unity", "Assets/Scenes/Editor.unity", "Assets/Scenes/Emulator.unity" };
            //windows中指定某文件夹及其中exe的名字
            buildPlayerOptions.locationPathName = "D:/UnityProject/SandBox/ReleaseEditor/ReleaseEditor.exe";//Environment.GetCommandLineArgs().Last();
            buildPlayerOptions.target = BuildTarget.StandaloneWindows;
            buildPlayerOptions.options = BuildOptions.None;
    
            BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
            BuildSummary summary = report.summary;
    
            if (summary.result == BuildResult.Succeeded)
            {
                Debug.Log("Build succeeded: " + summary.totalSize + " bytes");
            }
    
            if (summary.result == BuildResult.Failed)
            {
                Debug.Log("Build failed");
            }
        }
    

    2.新建txt文件,改其扩展名为.bat,然后双击打开,填入以下代码

    rem 工程路径+执行的方法BuildPlayer.BuildEditor 
    Unity.exe -batchmode -projectPath=D:\UnityProject\SandBox -quit -executeMethod BuildPlayer.BuildEditor
    

    总结

    中间利用bat中for去找到Main.unity特定行时,修改其值,再重定向到新文件,再把新文件复制到原来的Main.unity文件,但是出现了一个恶心的问题,bat打开延迟之后,感叹号之间包裹的字符全都被清除,恰恰.unity文件中存在 !u!,也就是说,替换值之后,所有的!u!将会不见,在unity中的表现就是打不开该Main.unity文件,于是辗转去找到了powershell,哇,用起来是真的舒服。。。
    听说python更舒服,enmmm,看样子逃不过了,23333

    感触

    今天问了问对象为啥要继续写博客,结果自问自答写博客可以整理一遍思路,之后也可以回顾,enmmm,成功被自己说服了,至于为啥之前搭建的博客不用了,不想租服务器了呗 0.0 csdn挺好的,目前来说。

    展开全文
  • 对于Unity,虽然Unity自身会进行压缩加密,但是其解密算法在网上随处可见,如果自己觉得游戏里面的资料具有保密性质,请对其进行自行加密。 本文只是讲述一下过程,采用很简单的打包加密方法,至于需要什么样的加密...

    本文只是讲述一下过程,采用很简单的打包加密方法,至于需要什么样的加密结果,请大家按照需求去修改,字节偏移、前后颠倒加算法都可以,不过一般无需这么复杂,而且太复杂的加密对于极其追求运行效率的游戏来说,也是一重负担。


    对于Unity,虽然Unity自身会进行压缩加密,但是其解密算法在网上随处可见,如果自己觉得游戏里面的资料具有保密性质,请对其进行自行加密。


    打包加密的原理:

    1、大家都知道文件都是由字节组成的。

    2、一张图片之所以看起来很漂亮,是因为其数据按照一定顺序排列。

    漂亮的剑灵妹子


    我们可以用一个文本编辑器将其打开。

    是乱码,不然你还想看到什么呢?


    3、如果我们把图片数据打乱,或者在前面加一些很乱的数据,会怎么样呢?


    嗯,图片不显示了。

    这很容易理解,就像你看片的时候被打了马赛克嘛。。

    仔细想想,为什么打马赛克?

    不就是为了保密嘛。。。


    好的,上面我们就起到了保密-加密功能。


    4、我又找来一张图片……萌萌哒的。。



    同样用一个文本编辑器打开这个图片。

    复制所有的内容到第一个图片文件后面。

    结果会怎么样?

    两个图片会拼在一起吗?



    真不幸……

    不过这是符合我的主题的。加密嘛,就是要让人看不出来。


    原理就讲到这里,下面就是代码了。

    代码不是针对单个文件,而是对多个文件夹进行打包加密。

    加密方法仅仅是多个文件打包。


    -----------------------------------------------------------------------------------

    我是可爱的分割线

    -----------------------------------------------------------------------------------


    用到的知识点:

    1、读写文件

    2、对文件夹、文件的操作,获取所有的文件


    -----------------------------------------------------------------------------------

    我是可爱的分割线

    -----------------------------------------------------------------------------------

    下面是主要的代码:


    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    
    namespace MyPackRes
    {
        class Helper
        {
            public static void Log(string str)
            {
                Console.Write(str+"\n\n");
            }
    
            public static void Pause()
            {
                Console.Write("Press any key to continue . . . ");
                Console.ReadKey(true);
            }
        }
    }
    

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.IO;
    using System.Diagnostics;
    
    namespace MyPackRes
    {
        class Program
        {
            private static int m_id=0;
            private static int m_totalSize = 0;
    
    //         private static List<int> m_idList = new List<int>();
    //         private static List<int> m_startPosList = new List<int>();
    //         private static List<int> m_sizeList = new List<int>();
    //         private static List<string> m_pathList = new List<string>();
    
            private static Dictionary<int,OneFileInfor> m_allFileInfoDic = new Dictionary<int,OneFileInfor>();
    
            private static string m_currentDir = "";
    
            static void Main(string[] args)
            {
                m_currentDir = Environment.CurrentDirectory;
    
                Helper.Log("MyPackRes Start " + m_currentDir);
    
                List<string> folderToPackList = new List<string>();
    
                /**  读取配置文件  **/
                Helper.Log("需要打包的文件夹:");
                StreamReader streamReader = new StreamReader("MyPackRes.ini", Encoding.Default);
                string line;
                while ((line=streamReader.ReadLine())!=null)
                {
                    Helper.Log(line);
                    if (!folderToPackList.Contains(line))
                    {
                        folderToPackList.Add(line);
                    }
                }
                streamReader.Close();
    
                /**  遍历打包文件夹 **/
                for (int index = 0; index < folderToPackList.Count;index++ )
                {
                    PackFolder(folderToPackList[index]);
                }
    
                Helper.Log("打包完毕!");
                Helper.Pause();
            }
    
            /** 遍历文件夹获取所有文件信息 **/
            private static void TraverseFolder(string foldername)
            {
                Helper.Log("遍历文件夹 " + foldername);
    
                /** 读取文件夹下面所有文件的信息 **/
                DirectoryInfo dirInfo = new DirectoryInfo(foldername);
    
                foreach (FileInfo fileinfo in dirInfo.GetFiles("*.*",SearchOption.AllDirectories))
                {
                    string filename = (fileinfo.FullName.Replace(m_currentDir+"\\","")).Replace("\\","/");
                    int filesize = (int)fileinfo.Length;
    
                    Helper.Log(m_id + " : " + filename + " 文件大小: " + filesize);
    
                    OneFileInfor info = new OneFileInfor();
                    info.m_id = m_id;
                    info.m_Size = filesize;
                    info.m_Path = filename;
                    
                    /**  读取这个文件  **/
                    FileStream fileStreamRead = new FileStream(fileinfo.FullName, FileMode.Open, FileAccess.Read);
                    if (fileStreamRead == null)
                    {
                        Helper.Log("读取文件失败 : "+fileinfo.FullName);
                        Helper.Pause();
                        return;
                    }
                    else
                    {
                        byte[] filedata = new byte[filesize];
                        fileStreamRead.Read(filedata, 0, filesize);
                        info.m_data = filedata;
                    }
                    fileStreamRead.Close();
    
    
                    m_allFileInfoDic.Add(m_id,info);
    
                    m_id++;
                    m_totalSize += filesize;
                }
            }
    
    
            /**  打包一个文件夹  **/
            private static void PackFolder(string foldername)
            {
                TraverseFolder(foldername);
    
                Helper.Log("文件数量 : " + m_id);
                Helper.Log("文件总大小 : " + m_totalSize);
    
                /**  更新文件在UPK中的起始点  **/
                int firstfilestartpos = 4 + (4 + 4 + 4 + 256) * m_allFileInfoDic.Count;
                int startpos = 0;
                for (int index = 0; index < m_allFileInfoDic.Count; index++)
                {
                    if (index == 0)
                    {
                        startpos = firstfilestartpos;
                    }
                    else
                    {
                        startpos = m_allFileInfoDic[index - 1].m_StartPos + m_allFileInfoDic[index - 1].m_Size;//上一个文件的开始+文件大小;
                    }
    
                    m_allFileInfoDic[index].m_StartPos = startpos;
                }
    
                /**  写文件  **/
                FileStream fileStream = new FileStream(foldername + ".UPK", FileMode.Create);
    
                /**  文件总数量  **/
                byte[] totaliddata=System.BitConverter.GetBytes(m_id);
                fileStream.Write(totaliddata, 0, totaliddata.Length);
    
                for (int index = 0; index < m_allFileInfoDic.Count;index++ )
                {
                    /** 写入ID **/
                    byte[] iddata = System.BitConverter.GetBytes(m_allFileInfoDic[index].m_id);
                    fileStream.Write(iddata, 0, iddata.Length);
    
                    /**  写入StartPos  **/
                    byte[] startposdata = System.BitConverter.GetBytes(m_allFileInfoDic[index].m_StartPos);
                    fileStream.Write(startposdata, 0, startposdata.Length);
    
                    /**  写入size  **/
                    byte[] sizedata = System.BitConverter.GetBytes(m_allFileInfoDic[index].m_Size);
                    fileStream.Write(sizedata, 0, sizedata.Length);
    
                    /**  写入path  **/
                    byte[] pathdata = new byte[256];
                    byte[] mypathdata = new UTF8Encoding().GetBytes(m_allFileInfoDic[index].m_Path);
    
                    for (int i = 0; i < mypathdata.Length; i++)
                    {
                        pathdata[i] = mypathdata[i];
                    }
                    pathdata[mypathdata.Length] = 0;
    
                    fileStream.Write(pathdata, 0, pathdata.Length);                
                }
    
                /**  写入文件数据  **/
                for (int index = 0; index < m_allFileInfoDic.Count; index++)
                {
                    fileStream.Write(m_allFileInfoDic[index].m_data, 0, m_allFileInfoDic[index].m_Size);
                }
    
                fileStream.Flush();
                fileStream.Close();
    
    
                /** 重置数据 **/
                m_id = 0;
                m_totalSize = 0;
                m_allFileInfoDic.Clear();
    
            }
        }
    
    
        public class OneFileInfor
        {
            public int m_id=0;
            public int m_StartPos=0;
            public int m_Size=0;
            public string m_Path="";
            public byte[] m_data = null;
        };
    }
    

    -----------------------------------------------------------------------------------

    我是可爱的分割线

    -----------------------------------------------------------------------------------

    代码浅显易懂 ( # ▽ # )


    首先新建一个配置文件

    MyPackRes.ini


    里面添加需要打包的文件夹 每行一个:


    看看我们运行结果。

    首先看看我准备的图。从碧之轨迹中TP抽取的哦。


    打包结束后,会生成UPK文件。


    UPK,这个后缀是虚幻引擎的默认资源文件格式哦。


    最后附上工程:

    http://download.csdn.net/detail/cp790621656/8393457


    展开全文
  • unity3D加密

    2017-06-13 10:58:46
    打包资源,加载资源已经是ungity3d很常见的事了,其中为了自己的资源不被别人抄袭,经常会将已经打包好的“xx.unity3d”机密,一种是在外边加一次“保护膜”(比如建个文件夹,把文件夹加密,这种我没研究过),另一...

    打包资源,加载资源已经是ungity3d很常见的事了,其中为了自己的资源不被别人抄袭,经常会将已经打包好的“xx.unity3d”机密,一种是在外边加一次“保护膜”(比如建个文件夹,把文件夹加密,这种我没研究过),另一种就是把“.unity3d”包转换成“.byte”文件,把转换好的“.byte”文件用一定的规律乱掉(规律要记好,解密时还要用),然后再把“.byte”文件打包成“.unity3d”文件,这样打包完的“.unity3d”资源包必须经过两次加载和一次生成才能使用,第一次是加载成“.byte”文件,然后将“.byte”文件生成“.unity3d”虚拟资源包,最后再加载生成的虚拟资源包里的真实资源(中间需要解密)。


    打包资源我就不谈论了,网上有具体的代码,今天主要说说解密时遇到的问题,把".byte"文件加载成“.unity3d”包需要用到AssetBundle.CreateFromMemory(byte[] bytes)方法,这是一个从内存字节生成资源包的方法,返回的是 AssetBundleCreateRequest类型,同是返回的有一个数据流,这就需要用到协同程序(主要是yield return AssetBundleCreateRequest),而第一次加载也需要用到协同程序,因此网上给了一种协同里面套协同的方法。我试着用了一下,发现这个加载脚本需要继承MonoBehaviour类,但是加载资源的脚本里面不需要用到Start()和Update()方法,只是写了一些公开的方法而已,因此不必继承 MonoBehaviour类,因此StartCoroutine方法就不能使用,所以网上给的协同里面套协同就不合适了,于是我就开始了苦逼的一天。


    开始我想在第一次加载的方法(LoadAsset())里面另开个线程,通过线程调用第二次加载(LoadBundle())方法,但是系统报错,说IEnumerator调用必须在主线程中,我郁闷了,所以我就开始查关于AssetBundleCreateRequest的文档,并开始用普通方法调用:因为IEnumerator返回的肯定是IEnumerator类,所以我声明了一个IEnumerator类型的ie变量接受IEnumerator的返回值,然后调用IEnumerator.MoveNext()方法,这样就把AssetBundleCreateRequest类返回给我了,我开始很高兴,因为得到AssetBundleCreateRequest后就可以通过AssetBundleCreateRequest.progress和AssetBundleCreateRequest.isDown查看进度,等到AssetBundleCreateRequest.progress >=1就表明“.unity3d”包创建完成(这是虚拟包,可以直接加载),然后我就可以加载,最后实例化,这就完成了,但是事实没有我想的那么完美,出现了一个意想不到事情,AssetBundleCreateRequest.progress和AssetBundleCreateRequest.isDown竟然不同步,progress显示的是1,按说isDown应该是true,但事实却是false,这很让我费解,然后我就去查这两个到底代表了什么意思,最后发现progress确实是下载进度,但isDown确是是否完成操作,也就是AssetBundleCreateRequest这个异步操作是否完成,这就坑爹了,AssetBundleCreateRequest是继承了AsyncOperation 类,但是这是unity3d自己封装的,不是c#封装的,官网上也没明确给出AsyncOperation 异步操作的终止方法,等于说打了个死结了。就在我以为只能认命时(脚本继承MonoBehaviour)时,突然发现自己脑残了,你妹的别人博客上把二个协同写成一个套一个,我可以把两个协同写在一起啊,一个yield return WWW,一个yield return AssetBundleCreateRequest。完全可以解决不必继承MonoBehaviour的事情,至于为什么这么做,下次再说吧,下面我将代码传上。



    1. public class ResourceManager
    2. {
    3.     bool isloadfromCache = true;//是否从缓存加载
    4.     private static ResourceManager resourceManager;
    5.     int m_CacheVersion = 5;//版本号


    6. //实例化
    7.     public static ResourceManager getInstance()
    8.     {
    9.         if(resourceManager == null)
    10.       {
    11.            resourceManager = new ResourceManager(); 
    12.        }
    13.         return resourceManager; 
    14.     } 

    15. //构造方法
    16.     public ResourceManager()
    17.     {
    18. //我用的是本地加载,网络需要用“http://”
    19.         mResourcePath = "file://" + Application.dataPath;
    20.     }

    21.     //协同程序,需要用StartCoroutine()方法调用,可以外部调用
    22.      public IEnumerator LoadAsset(string fileName)
    23.     {
    24.         string fullurl = mResourcePath + "/" + "Unity3d/"+fileName+".unity3d"; //加载路径
    25.           WWW m_Download;
    26.           if(isloadfromCache){
    27.            m_Download = WWW.LoadFromCacheOrDownload(fullurl,m_CacheVersion); 
    28.           }else{
    29.            m_Download = new WWW(fullurl);
    30.           }
    31.         //第一次协同
    32.           yield return m_Download; 
    33.         if(m_Download.error != null)
    34.           {
    35.             //   Debug.LogError(m_Download.error);
    36.            Debug.Log("Warning errow"+fileName);
    37.            yield break;
    38.              } 
    39.             //第一次加载
    40.             TextAsset txt = m_Download.assetBundle.Load(fileName,typeof(TextAsset)) as TextAsset;
    41.             byte[]  data = txt.bytes;
    42.             byte[] decryptedData = Decryption(data);
    43.             //打成虚拟包
    44.             AssetBundleCreateRequest acr = AssetBundle.CreateFromMemory(decryptedData);
    45.             //原本要第二次协同
    46.              yield return acr;
    47.             AssetBundle bundle = acr.assetBundle;   
    48.             //第二次加载
    49.              bundle .LoadAll();
    50.             Instantiate(bundle.mainAsset); 
    51.     } 
    52.     byte[] Decryption(byte[] data){
    53.         //解密算法,这个要自己写,你加密时是怎样,这里反过来
    54.     }

    55. }
    展开全文
  • Android 打包aar给unity3d使用 项目心得步骤 制作androidproject 修改build.gradle配置 打包aar 1.混淆打包aar 2.jar包导出 3.在其他的android工程引入aar 导入unity项目 运行unity3d输出apk包 新建一个project ...

    步骤

    • 制作androidproject
    • 修改build.gradle配置
    • 打包aar
    • 1.混淆打包aar
    • 2.jar包导出
    • 3.在其他的android工程引入aar
    • 导入unity项目
    • 运行unity3d输出apk包

    新建一个project

    打开AS,新建–>project
    这里写图片描述
    切换到project项目下面
    找到app Moudle
    找到libs,将unity3d 提供的jar包拷贝到libs下面,右键 ->open Moudle setting ,选择Moudle ,点击dependence
    如图
    添加lib
    选择拷贝到libs下面的文件unity_class.jar
    MainAvtivity extends UnityPlayerActivity
    注意要将setContentView()注释掉,不加载界面
    终于到设置build.gradle
    打开app的build.gradle,将apply plugin: ‘com.android.application’修改为apply plugin: ‘com.android.library’,并且删除applicationId “com...*
    如果需要混淆打包配置buildtypes
    buildTypes {
    release {
    // Set minifyEnabled to true if you want to run ProGuard on your project
    minifyEnabled true

            //对齐
            zipAlignEnabled true
    
            //移除无用的resource 文件
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'
    
    
        }
    }
    

    不需要混淆打包就设置minifyenable false;
    修改完gradle 点击Sync Now,完成之后找到 gradle project窗口,找到app(本Moudle的gradle),右键 refresh external project,打开build,找到build命令,运行,生成aar文件,文件一般声称在outputs->aar里面,debug是没有混淆的,release是混淆的。
    但是但是,我们不需要混淆所有的类文件,这就需要找到gradle里面的这句话
    proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-unity.txt’
    ,这里的混淆的规则文件在proguard-unity.txt 里面
    -keep是不混淆
    -keep class *.R
    不混淆R文件
    -keep class com.example.zhcase.unity3d.MainActivity{*;}
    不混淆MainActivity类
    -keep class com.example.zhcase.unity3d.BLEDeviceManager{*;}
    -keep class com.example.zhcase.unity3d.BleDevice{*;}
    -dontwarn com.unity3d.player.**
    -keep class com.unity3d.player.UnityPlayerActivity{*;}
    -keep class com.unity3d.player.* {;}
    不混淆com.unity3d.player包下面所有的类

    大概就是这样子 不明白的可以查下百度
    类似的文章还是蛮多的

    打成jar包

    *在build.gradle 加入
    //task makeJar(type: Copy) {
    // delete ‘build/libs/mysdk.jar’
    // from(‘build/intermediates/bundles/release/’)
    // into(‘build/libs/’)
    // include(‘classes.jar’)
    // rename (‘classes.jar’, ‘mysdk.jar’)
    //}
    //
    //makeJar.dependsOn(build)*
    大概的意思就是新建一个task,删除旧的mysdk.jar包将jar包拷贝到libs下面,mysdk是导出的jar的包名。
    还是Sync Now,在gradle project界面的other的下面找到exportjar,运行输出jar包,要是minifyEnabled还是true,输出的jar包还是混淆的。
    为了测试自己导出的aar包是否可用,我自己新建了一个android project,就做一个简单的界面。
    在As project中引用aar 还是需要将aar拷贝到libs文件夹,在gradle的dependencies 中加入
    compile(name:’app-release’, ext:’aar’)
    name后面的是aar包的包名,
    rebuild project,成功的话在project会在build->intermediates下面看到explored-aar中含有这个包
    然后按照普通的libs包用就可以了

    导入到unity3d之前需要将原来引入的unity3d_classes.jar文件删除,因为unity3d编译时会生成自己的jar包,要是混淆打包的aar就有一个问题,需要-keep class 所有的jar文件不被混淆,然后用解压工具删除classes.jar的包含的unity3d_classes的文件。

    在unity工程中Assets->plugins->android
    这里写图片描述
    Mainfest的文件是从As中项目拷贝过来的,unity的projectname和Mianfest的packagename一致

    Unity调用android的方法

    AndroidJavaClass jc = new AndroidJavaClass(“com.unity3d.player.UnityPlayer”);
    AndroidJavaObject jo = jc.GetStatic(“currentActivity”);
    jo.CallStatic(“StarScane”);

    带参数

    public void connect(string address)
    {
    #if UNITY_ANDROID
    text.text += “连接设备”;
    AndroidJavaClass jc = new AndroidJavaClass(“com.unity3d.player.UnityPlayer”);
    AndroidJavaObject jo = jc.GetStatic(“currentActivity”);
    jo.CallStatic(“connect”,address);
    #endif
    }

    最后build&run,我在这一步还是遇到很多问题,配置SDK路径,显示platforms的Api is 0 ,这类错误基本是SDK路径错误,或者去AS下面的SDKManager更新下SDk版本,也可能会和build tools有关系,反正觉得有问题的你都下载吧 我就是这么解决的,JDK的路径和AS的一样就好
    build&run运行到手机就可以
    [TOC]
    感谢http://www.cnblogs.com/xtqqkss/p/6387271.html的分享,大家可以看看,一步步做,慢慢查问题,还是可以做出来的,虽然我打个包花了我好几天
    再见

    展开全文
  • 本文记录如何通过unity3d进行脚本资源打包加载 1、创建TestDll.cs文件 public class TestDll : MonoBehaviour { void Start () { print("Hi U_tansuo!"); }} 2、生成dll文件 ...
  • 这个工具呢,博主在Unity3D游戏开发之反编译AssetBundle提取游戏资源这篇文章中其实已经提到过了,不过因为有些朋友对如何使用这个工具依然存在问题,所以博主决定特地写一篇文章来讲解如何使用disunity来提取Unity...
  • Unity3D打包到热更,超简单的XLua运用一条龙 目录 1、博客介绍 2、XLua环境配置 3、场景搭建和AB包的准备 (1)场景搭建 (2)Lua脚本准备 (3)AB包的准备 4、基础网络环境的搭建 5、热更新的测试 6、...
  • Unity的资源打包,如果要做完美,其实是很复杂的.那么为什么资源要打包呢,因为我们要做资源更新.没办法啊. 在我看来,完美的资源打包至少有以下几个方面要处理好: 1) 资源分类设计合理.控制包粒度,单个包不能太大. 2) ...
  • 1.资源的创建 注意一下命名规则,一个面板及其相关的东西都放在同一个文件夹中,如果文件夹命名为xxx,则...对lua脚本的打包已经被框架搞好了,不需要我们考虑,我们要考虑的是对前者的打包,详细的见Packager.cs...
  • Unity3d 游戏资源打包加密 图片/XML/TXT等 C 编码 一
  • 打包后的.txt文件是否 能够被unity打包后的.exe文件 读取。using UnityEngine; using System.Collections; using System.Xml.Linq; using System.Xml; using System.IO; public class Test_XML : MonoBehaviour { ...
  • 这里列举出手游开发中用到了所有特殊文件夹。 ...Editor文件夹可以在根目录下,也可以在子目录里,只要名子叫Editor就可以。比如目录:/xxx/xxx/Editor 和 /Editor 是一样的,无论多少个叫Editor的文件夹都可以...
  • 在Slua官方给出的Demo中,加载的lua文件是.txt后缀,而大多数编辑器只能打开.lua文件,所以需要让Unity3D能够加载.lua文件是必要的一步,否则开发过程中就需要频繁的修改后缀名,非常麻烦,S...
  • 最近接入讯飞语音到unity工程里,是需要把.jar接入到unity中 遇到了一些坑 在此分享给大家 希望大家少走弯路 如果有什么问题 欢迎留言讨论qq:1902649397 网上看了很多例子 有点小细节没有注意就会导致 导出apk包不...
  • Unity3d资源解密

    2020-07-13 23:31:45
    解压以后,内有说明文档,按照说明文档步骤,即可解压获取Unity3D游戏资源,游戏资源如果额外使用了加密,将会获取失败。
  • 1.首先,你得理解Unity中各个目录的意思? 我这里说的是移动平台(安卓举例),读,写。所谓读,就是你出大版本的包之后,这个只读的话,就一辈子就这些东西了,不会改变了,不会有其他资源来覆盖或者增加啦。 ...
  • 1.Unity3D是一个3D游戏引擎由于和编辑器集成在一起所以也可以理解为一个制作/开发平台。 2.Unity3D使用javascriptC#作为核心脚本语言 来驱动整个游戏引擎。 3.平台可以发布为Exe执行文件或者打包为供网页调用...
  • 本程序引入了unity3d的程序,那么自然就使用到了jar包unity-class.jar这个jar包。在混淆的时候出现下列问题: 首先第一个问题: java.io.IOException: Can't read [unity-classes.jar] (Can't process class ...
  • 我们公司主要开发AR项目的,所以经常需要做Unity...1.先让unity同事将unity的项目打包Android工程,一定一定要注意打包的时候注意一个关键地方,这个是影响机子适配问题的(这个问题我被深坑了好久): 一定要选上
1 2 3 4 5 ... 20
收藏数 992
精华内容 396
热门标签