最近在想怎样的网络地址可以打开就开始下载游戏资源,并能够被 unity WWW 类加载,找到这篇不错的文章 , 转载过来 , 希望对有同样问题的朋友有所帮助.

原文地址:点击打开链接

我们的游戏制作完发布出去提供给玩家,为了给玩家带来更好的游戏体验,要做各种的优化以及设计,首先,游戏资源的加载就是一个非常重要的方面(尤其是网页游戏)。由于我们的游戏资源比较大,不能一下全部加载出来,如果是这样,可能会造成玩家长时间的等待。所以我们应该采取动态加载的方式,让玩家在玩游戏的过程中来一点一点从服务器加载游戏资源。要实现这样的效果,首先就必须要制作用于一点点加载的游戏资源。

(注:本文只是谈及这些游戏资源的制作和下载,关于游戏运行中的动态加载不做讨论)

(再注:本文涉及到的代码都是以C#语言来编写的)

开发环境:

Windows 7

Unity3D 3.5.1f2

本文中将会涉及到以下的内容:

1、  UnityEditor命名空间

2、  Editor模式下窗口制作

3、 导出功能的具体实现

4、 资源的下载

5、 下载后使用

1、 UnityEditor命名空间

这个命名空间下的类是在Unity的编辑模式下使用的,我们可以用它来制作各种小工具来辅助开发,提高开发效率。这里的所有的类都不能在Unity的运行时里使用。只能在编辑器下使用,并且在使用他们的时候还必须要放到项目Project视图下的Editor文件夹中。需要注意一点的就是,我们的项目代码里如果有使用到UnityEditor命名空间时,在项目的最后编译是不能通过的,必须要移除他们。

我们来看一个我们即将使用到的一个Attribute:

MenuItem是UnityEditor命名空间下的一个属性标志,它可以定义出一个菜单条目,并添加在Unity编辑器的菜单栏中,语法:

[csharp]  view plain copy
  1. [MenuItem(“Tools/Export”)]

我们来新建一个工程看一下效果(具体创建步骤这里真的不说了)

(注:我的项目中加了很多装饰性的东西,这里就不一一说明怎么实现了哈)

完成之后,先在Project下创建Editor文件夹并创建一个脚本文件,输入以下内容:

[csharp]  view plain copy
  1. using UnityEditor;
  2. using UnityEngine;
  3. using System.Collections;
  4. /// <summary>
  5. /// author : qyxls
  6. /// </summary>
  7. public class ExportTools : MonoBehaviour
  8. {
  9. [MenuItem("Tools/Export")]
  10. static void Execute ()
  11. {
  12. Debug.Log("Menu is selected !!");
  13. }
  14. }

当我们点击菜单栏上的对应菜单选项:ToolsàExport时,

菜单项会调用静态的Execute()方法,即可在Console面板中打印出”Menu is selected”。

这里要注意两点:

1、 引入UnityEditor命名空间。

2、 MenuItem要调用的方法需要是static的。

关于UnityEditor的更多详细内容,请参照官方文档,这里不做重点讲解。

2、Editor模式下窗口制作

要制作一个小工具,提供出一个友好界面是很有必要的。UnityEditor下的类可以很方便的完成这一需求。我们通过这些类,可以实现各种不同的控件:

怎么样,还算丰富吧?这些控件的具体实现我不想说,请自行查看API吧。

这里我还是遵循本文的主旨,围绕本文的中心思想(本文我们是要导出资源到服务器,并在游戏中下载这个资源过来使用)实现一个界面。

用例描述:

导出场景中的一个模型,并带着默认材质,如果该模型有多个可替换的贴图,也把这些贴图作为该模型的资源一并导出到一个资源包中。

按照这个需求,我猜想界面应该是这样的:

一个导出模型的口,一个提供可选贴图数量的口,根据用户输入的可选数量,给提供出对应的贴图导出口,最后填写完毕之后有一个按钮用于导出交互。

,不好意思,这哪里是猜想,我其实早就写好了。其实也没骗你了,我在写之前是猜想的!

要实现上面这个窗口,我该怎么做呢?

首先,定义一个继承EditorWindow的类,然后,重写OnGUI方法即可。我们这里在之前的代码基础上做修改添加:

[csharp]  view plain copy
  1. using UnityEditor;
  2. using UnityEngine;
  3. /// <summary>
  4. /// author : qyxls
  5. /// </summary>
  6. public class ExportTools :<strong> <span style="color:#ff0000;">EditorWindow</span> </strong>
  7. {
  8. [MenuItem("Tools/Export")]
  9. static void Execute ()
  10. {
  11. // 实例化一个Window窗口 //
  12. ExportTools windows = EditorWindow.GetWindow<ExportTools>(true, "Export Tools");
  13. }
  14. void OnGUI()
  15. {
  16. }
  17. }

这里要注意的就是将原来的脚本有继承自MonoBehaviour 修改为继承自EditorWindow。并在Execute ()方法中对当前的Window实例化。这时我们就可以得到一个Window窗口了:

其次,就是向我们生成的窗口中添加不同的控件,这些控件的生成都是在OnGUI()方法中实现的。和MonoBehaviour的OnGUI方法一样,EditorWindow的OnGUI()方法也主要是处理UI的,我们关于UI控件的生成处理都要写在这个方法里。OnGUI()这个方法每帧调用好几次(每个事件一次),所以一些逻辑处理要避免在这里调用。

[csharp]  view plain copy
  1. private string savePath;
  2. private GameObject exportObject;
  3. private int optionalCount = 0;
  4. private Texture2D[] optionalTexture = new Texture2D[0];
  5. void OnGUI()
  6. {
  7. /*
  8. * ObjectField:
  9. * 是这里的第一个控件,它可以允许用户拖拽将一个Object的对象赋给它。
  10. * 如果要限制可接收的对象类型,可以通过第三个参数来限制类型这里表示直接收GameObject类型
  11. * 第四个bool型的参数标志能否接受当前scene里的对象,true表示接受
  12. * 这个方法返回的是一个Object类型的值,最后要将它转化为需要的类型
  13. */
  14. exportObject = EditorGUILayout.ObjectField("Export Object", exportObject,
  15. typeof(GameObject), true)
  16. as GameObject;
  17. // 就相当于提供一个换行,用于格式化控件的 //
  18. EditorGUILayout.Space();
  19. // IntField:该控件只能输入 int 类型的值//
  20. optionalCount = EditorGUILayout.IntField("Optional Count", optionalCount);
  21. for(int i=0; i<optionalCount; i++)
  22. {
  23. if(optionalTexture.Length != optionalCount)
  24. {
  25. optionalTexture = new Texture2D[optionalCount];
  26. }
  27. EditorGUILayout.Space();
  28. // 这里将 ObjectField 限制只接受Texture2D类型的值 //
  29. optionalTexture[i] = EditorGUILayout.ObjectField("Optional Textures " + i, optionalTexture[i],
  30. typeof(Texture2D), false)
  31. as Texture2D;
  32. }
  33. EditorGUILayout.Space();
  34. EditorGUILayout.Space();
  35. EditorGUILayout.BeginHorizontal();
  36. EditorGUILayout.Space();
  37. // 导出按钮 //
  38. if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))
  39. {
  40. }
  41. EditorGUILayout.EndHorizontal();
  42. }

这里一些必要的东西我都添加都注释理了,就不重复了。

到这里这个窗口就基本算是完成了。

3、导出功能的具体实现

以上只是实现出了这样一个窗口,具体响应功能,以及必要的逻辑实现还都不具备,这里我们将为这个窗口添加具体的功能实现代码。

[csharp]  view plain copy
  1. <span style="white-space:pre">  </span>private void ExportAndSave(GameObject go)
  2. {
  3. //该方法将打开保存对话框,选择导出文件的保存位置//
  4. savePath = EditorUtility.SaveFilePanel("Save", @"E:\", go.name, "unity3d");
  5. Export(go, savePath);
  6. }
  7. private void Export(GameObject go, string filePath)
  8. {
  9. // IsPersistent 判断传入的对象是磁盘文件还是场景文件(即是否是Project视图下的文件,是返回true)//
  10. if(!EditorUtility.IsPersistent(go))
  11. {
  12. GameObject tmp = GameObject.Instantiate(go) as GameObject;
  13. go = GetPrefab(tmp, go.name) as GameObject;
  14. }
  15. Object[] asset = optionalTexture;
  16. if(File.Exists(filePath)) File.Delete(filePath);
  17. /*
  18. BuildPipeline.BuildAssetBundle():该方法是将提供的对象导出成Unity能识别的二进制文件
  19. 第一个参数是提供一个要导出的对象,第二个参数是一个Object[]类型,它可以将数据附加到第一个
  20. 参数定义的主数据一起整体导出.但是这两个参数要求必须是磁盘文件的格式,所以上面的if语句判断
  21. 是否是磁盘文件类型,如果不是,先将其转化为prefab,在Assets下临时保存一下。这个转化就是要
  22. 用到 PrefabUtility 类里的方法。
  23. */
  24. BuildPipeline.BuildAssetBundle(go, asset, filePath, BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);
  25. // 将暂时生成的prefab文件使用完后删除 //
  26. AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(go));
  27. }
  28. /// <summary>
  29. /// 该方法来产生临时prefab文件
  30. /// </param>
  31. private Object GetPrefab(GameObject go, string name)
  32. {
  33. Object result = PrefabUtility.CreateEmptyPrefab("Assets/" + name + ".prefab");
  34. result = PrefabUtility.ReplacePrefab(go, result);
  35. Object.DestroyImmediate(go);
  36. return result;
  37. }

这里我又新添加了三个方法来具体实现导出并保存的逻辑:

  • private voidExportAndSave(GameObject go):

在这个方法里只要关注一下怎么打开一个保存对话框就可以了

[csharp]  view plain copy
  1. //该方法将打开保存对话框,选择导出文件的保存位置。第二和第三个参数表示默认保存位置和默认文件名//
  2. savePath =EditorUtility.SaveFilePanel("Save", @"E:\", go.name,"unity3d");
  • private void Export(GameObjectgo, string filePath)

这个方法具体实现了导出二进制文件的功能。这里需要说明的是 BuildPipeline.BuildAssetBundle(): 该方法是将提供的对象导出成Unity能识别的二进制文件第一个参数是提供一个要导出的对象,第二个参数是一个Object[]类型,它可以将数据附加到第一个参数定义的主数据一起整体导出.但是这两个参数要求必须是磁盘文件的格式,所以上面的if语句判断是否是磁盘文件类型,如果不是,先将其转化为prefab,在Assets下临时保存一下。这个转化就是要用到 PrefabUtility 类里的方法。具体判断是否是磁盘文件,是通过 if(!EditorUtility.IsPersistent(go))这一句来判断的:如果go不是磁盘文件,是场景对象,则执行该语句里的代码来生成磁盘文件,具体的是下面这个方法来实现的。

  • private ObjectGetPrefab(GameObject go, string name)

我们在导出前,如果导出信息设置的不正确,可能会致使导出的文件有问题或者不可用,所以在导出之前对信息有效性的验证也是必要的:

[csharp]  view plain copy
  1. <span style="white-space:pre">  </span>/// <summary>
  2. /// 数据验证,如果导出信息填写有误,将给用户错误提示
  3. /// </summary>
  4. private bool Validate()
  5. {
  6. bool b1 = (exportObject == null);
  7. bool b2 = false;
  8. foreach(Texture2D t in optionalTexture)
  9. {
  10. b2 = b2 || (t == null);
  11. }
  12. return !(b1 || b2);
  13. }

如果用户全部信息都填写完整了,该方法会返回true,导出时可以根据返回值状态来做相应的响应。

[csharp]  view plain copy
  1. <span style="white-space:pre">      </span>// 导出按钮 //
  2. if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))
  3. {
  4. if(Validate())
  5. {
  6. ExportAndSave(exportObject);
  7. Clear();//成功导出数据后,清除导出信息//
  8. }
  9. else
  10. {
  11. //导出信息填写有误时,给出提示//
  12. EditorUtility.DisplayDialog("错误提示", "导出信息设置有误,请返回检查!", "确定");
  13. }
  14. }

这里可以看到我还添加了一个Clear()方法,该方法在用户导出完毕时,将导出工具面板的信息清除掉,以便开始导出其它资源:

[csharp]  view plain copy
  1. <span style="white-space:pre">  </span>/// <summary>
  2. /// 所有数据正确导出后,清除填写的导出信息,以便导出下一条数据
  3. /// </summary>
  4. private void Clear()
  5. {
  6. exportObject = null;
  7. optionalCount = 0;
  8. }

到这里,我们导出的所有逻辑就完成了,这样子的一个导出工具也基本完成了。此时,我们的完整代码应该是这个样子的:

[csharp]  view plain copy
  1. using System.IO;
  2. using UnityEditor;
  3. using UnityEngine;
  4. /// <summary>
  5. /// author : qyxls
  6. /// </summary>
  7. public class ExportTools : EditorWindow
  8. {
  9. [MenuItem("Tools/Export")]
  10. static void Execute ()
  11. {
  12. // 实例化一个Window窗口 //
  13. EditorWindow.GetWindow<ExportTools>(true, "Export Tools");
  14. }
  15. private string savePath;
  16. private GameObject exportObject;
  17. private int optionalCount = 0;
  18. private Texture2D[] optionalTexture = new Texture2D[0];
  19. void OnGUI()
  20. {
  21. /*
  22. * ObjectField:
  23. * 是这里的第一个控件,它可以允许用户拖拽将一个Object的对象赋给它。
  24. * 如果要限制可接收的对象类型,可以通过第三个参数来限制类型这里表示直接收GameObject类型
  25. * 第四个bool型的参数标志能否接受当前scene里的对象,true表示接受
  26. * 这个方法返回的是一个Object类型的值,最后要将它转化为需要的类型
  27. */
  28. exportObject = EditorGUILayout.ObjectField("Export Object", exportObject,
  29. typeof(GameObject), true)
  30. as GameObject;
  31. // 就相当于提供一个换行,用于格式化控件的 //
  32. EditorGUILayout.Space();
  33. // IntField:该控件只能输入 int 类型的值//
  34. optionalCount = EditorGUILayout.IntField("Optional Count", optionalCount);
  35. for(int i=0; i<optionalCount; i++)
  36. {
  37. if(optionalTexture.Length != optionalCount)
  38. {
  39. optionalTexture = new Texture2D[optionalCount];
  40. }
  41. EditorGUILayout.Space();
  42. // 这里将 ObjectField 限制只接受Texture2D类型的值 //
  43. optionalTexture[i] = EditorGUILayout.ObjectField("Optional Textures " + i, optionalTexture[i],
  44. typeof(Texture2D), false)
  45. as Texture2D;
  46. }
  47. EditorGUILayout.Space();
  48. EditorGUILayout.Space();
  49. EditorGUILayout.BeginHorizontal();
  50. EditorGUILayout.Space();
  51. // 导出按钮 //
  52. if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))
  53. {
  54. if(Validate())
  55. {
  56. ExportAndSave(exportObject);
  57. Clear();//成功导出数据后,清除导出信息//
  58. }
  59. else
  60. {
  61. //导出信息填写有误时,给出提示//
  62. EditorUtility.DisplayDialog("错误提示", "导出信息设置有误,请返回检查!", "确定");
  63. }
  64. }
  65. EditorGUILayout.EndHorizontal();
  66. }
  67. private void ExportAndSave(GameObject go)
  68. {
  69. //该方法将打开保存对话框,选择导出文件的保存位置。第二和第三个参数表示默认保存位置和默认文件名//
  70. savePath = EditorUtility.SaveFilePanel("Save", @"E:\", go.name, "unity3d");
  71. Export(go, savePath);
  72. }
  73. private void Export(GameObject go, string filePath)
  74. {
  75. // IsPersistent 判断传入的对象是磁盘文件还是场景文件(即是否是Project视图下的文件,是返回true)//
  76. if(!EditorUtility.IsPersistent(go))
  77. {
  78. GameObject tmp = GameObject.Instantiate(go) as GameObject;
  79. go = GetPrefab(tmp, go.name) as GameObject;
  80. }
  81. //Texture2D本身就是磁盘文件了,这里就没必要再转化了//
  82. Object[] asset = optionalTexture;
  83. if(File.Exists(filePath)) File.Delete(filePath);
  84. /*
  85. BuildPipeline.BuildAssetBundle():该方法是将提供的对象导出成Unity能识别的二进制文件
  86. 第一个参数是提供一个要导出的对象,第二个参数是一个Object[]类型,它可以将数据附加到第一个
  87. 参数定义的主数据一起整体导出.但是这两个参数要求必须是磁盘文件的格式,所以上面的if语句判断
  88. 是否是磁盘文件类型,如果不是,先将其转化为prefab,在Assets下临时保存一下。这个转化就是要
  89. 用到 PrefabUtility 类里的方法。
  90. */
  91. BuildPipeline.BuildAssetBundle(go, asset, filePath, BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);
  92. // 将暂时生成的prefab文件使用完后删除 //
  93. AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(go));
  94. }
  95. /// <summary>
  96. /// 该方法来产生临时prefab文件
  97. /// </param>
  98. private Object GetPrefab(GameObject go, string name)
  99. {
  100. Object result = PrefabUtility.CreateEmptyPrefab("Assets/" + name + ".prefab");
  101. result = PrefabUtility.ReplacePrefab(go, result);
  102. Object.DestroyImmediate(go);
  103. return result;
  104. }
  105. /// <summary>
  106. /// 数据验证,如果导出信息填写有误,将给用户错误提示
  107. /// </summary>
  108. private bool Validate()
  109. {
  110. bool b1 = (exportObject == null);
  111. bool b2 = false;
  112. foreach(Texture2D t in optionalTexture)
  113. {
  114. b2 = b2 || (t == null);
  115. }
  116. return !(b1 || b2);
  117. }
  118. /// <summary>
  119. /// 所有数据正确导出后,清除填写的导出信息,以便导出下一条数据
  120. /// </summary>
  121. private void Clear()
  122. {
  123. exportObject = null;
  124. optionalCount = 0;
  125. }
  126. }

工具界面应该是这样子:

到这里我们通过这个小小的导出工具就可以制作出要需要的资源文件了,这些资源文件是存放在服务器上的,接下来我们一起看看关于这些资源文件的下载。

4、获取资源文件

这些文件是可以就从本地磁盘加载进游戏里使用的,但这里为了模拟从远程服务器下载这样一个模式,我还是将刚刚制作好的文件上传到远程主机来给大家展示一下这种的从远端获取的做法(其实从本地磁盘加载几乎是一样的)。

第一步:将文件上传到服务器。

我真的没有服务器,但是我感觉度娘很热情,估计能帮上我们什么忙。

(此处省略好几个字。。。。。。其实就是怎么将刚刚导出的文件上传到“百度云”)

上传不说了,这里看看怎么获取刚刚上传资源的完整地址。

用Google浏览器(码农用这个没有什么大问题吧?)登上“百度云”,找到刚刚上传的文件,点击下载,然后按Ctrl+J打开下载列表,右击正在下载的文件,选择“复制链接地址”就可以取到该文件的完整地址了。

这个是我的:

http://bj.baidupcs.com/file/8752f8cf08e92dded7127aa4dc0489f7?xcode=28baeb9afc859429429dd4c38dda1979442b8d6833d75b4f&fid=604160625-250528-2314552676&time=1377828806&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-cULgvhDQRRDEe32IavH35RKmn1Y%3D&to=bb&fm=N,B,U&expires=8h&rt=pr&r=392549107&logid=3993021536&fn=dice.unity3d

这里我们暂且先这样用着,在真正的项目开发中,资源的地址肯定会直接或间接的给出来的,这里不必纠结。

我们来具体看看下载,这里下载要使用到的类是WWW。在实例化WWW的时候,我们只需将资源的url地址给它,即可开始下载,实例化完WWW后我们只需判断这个实例是否下载完成,如果完成了,即可以取下载来的资源来用了。代码是这样的:(这个类不是UnityEditor里的类,新建一个C#类并继承自MonoBehaviour)

[csharp]  view plain copy
  1. using UnityEngine;
  2. using System.Collections;
  3. /// <summary>
  4. /// author : qyxls
  5. /// </summary>
  6. public class Downloader : MonoBehaviour
  7. {
  8. private string url = " http://qd.baidupcs.com/file/5d06bd73f17afc5a5eb3e5497d0b6007?xcode=71a918e1fad4242470466f5cc4a869c8ec18245ec1f0d579&fid=604160625-250528-2002528139&time=1377831362&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-FsMzIz4cENbozwsto38a47bDc64%3D&to=qb&fm=Q,B,U&expires=8h&rt=pr&r=709102273&logid=1896966947&fn=dice.unity3d ";
  9. private WWW www;
  10. void Start ()
  11. {
  12. this.www = new WWW(this.url);
  13. }
  14. void Update ()
  15. {
  16. if(www == null) return;
  17. if(www.isDone)
  18. {
  19. print ("Download completed");
  20. }
  21. }
  22. }

当启动了Unity之后,会发现很快就会在Console视图中打印出来了“Download completed”,而且还孜孜不倦的一直不肯停歇,这里我们下载完了,只要对下载完的资源处理一次就够了,没必要没完没了的处理,多浪费感情啊,所以我们该定义一个标志,来标记下载完成这么一个状态:

[csharp]  view plain copy
  1. private bool isCompleted = false;
  2. void Update ()
  3. {
  4. if(www == null) return;
  5. if(!isCompleted && www.isDone)
  6. {
  7. print ("Download completed");
  8. isCompleted = true;
  9. }
  10. }

现在是不是只有这么一条打印信息了?

这段代码是非常简单的,这里也没有什么要多说的,就是提这么一点,这里我们是直接根据资源的URL去访问下载的该资源,但在实际项目中,我们经常要处理的是根据不同的条件访问同一地址而返回不同的数据来使用,这里要使用的是WWW的另一个构造方法,可以带除URL外的其它请求参数:

[csharp]  view plain copy
  1. private void WWWWithParameter(string url, string parameter)
  2. {
  3. WWWForm form = new WWWForm();
  4. form.AddField("Content", parameter);
  5. WWW www = new WWW(url, form);
  6. }

可以看到,只需将参数封装在WWWForm中再去用WWW访问服务器就可以了。

(本例中我们没有采用带参数的访问是因为这样的话,我们还要加一个后台处理程序,要根据请求参数来返回数据,这样我们就必须要在本机上安装服务器,书写服务器代码等等等等,这样就得多做很多其它与我们这个话题相去深远的工作了。。。。。。。。(好吧,我承认我不会配置服务器))

到此本节的全部代码是这样子的:

[csharp]  view plain copy
  1. using UnityEngine;
  2. using System.Collections;
  3. /// <summary>
  4. /// author : qyxls
  5. /// </summary>
  6. public class Downloader : MonoBehaviour
  7. {
  8. private string url = " http://qd.baidupcs.com/file/5d06bd73f17afc5a5eb3e5497d0b6007?xcode=71a918e1fad4242470466f5cc4a869c8ec18245ec1f0d579&fid=604160625-250528-2002528139&time=1377831362&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-FsMzIz4cENbozwsto38a47bDc64%3D&to=qb&fm=Q,B,U&expires=8h&rt=pr&r=709102273&logid=1896966947&fn=dice.unity3d ";
  9. private WWW www;
  10. private bool isCompleted = false;
  11. void Start ()
  12. {
  13. this.www = new WWW(this.url);
  14. }
  15. void Update ()
  16. {
  17. if(www == null) return;
  18. if(!isCompleted && www.isDone)
  19. {
  20. print ("Download completed");
  21. isCompleted = true;
  22. }
  23. }
  24. /*
  25. private void WWWWithParameter(string url, string parameter)
  26. {
  27. WWWForm form = new WWWForm();
  28. form.AddField("Content", parameter);
  29. WWW www = new WWW(url, form);
  30. }
  31. */
  32. }

5、下载回来资源的使用

通过上面的操作,我们已经将资源下载到了本机,但是,大家也都看到了,我通过上面的方法的操作,说是下载完了资源,但我们场景中还是什么都没有啊,这个怎么解释?我用迅雷下完东东的时候,都在磁盘上有个文件的。

这里下载好的资源已经保存在内存中了,我们只要取出来使用就好了:

[csharp]  view plain copy
  1. void Update ()
  2. {
  3. if(www == null) return;
  4. if(!isCompleted && www.isDone)
  5. {
  6. GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;
  7. isCompleted = true;
  8. }
  9. }

只需这样一句代码,你就在场景中可以看到这个令人兴奋的小东西了,哈哈,是不是很简单呢?

但是有没有发现什么问题呢?

我们当初导出的可不仅仅这点东西啊,我们回过头来看看:

起码还有这些个贴图怎么不见了?当初导出时可是明明放到Object[]一起导出了的。莫着急,其实它们也都一起下载过来了,只是我们还没有取来用罢了。

再看www对象或者www.assetBundle里面有一个方法返回了Object[]数组:www.assetBundle.LoadAll()。抓紧去试试:

[csharp]  view plain copy
  1. void Update ()
  2. {
  3. if(www == null) return;
  4. if(!isCompleted && www.isDone)
  5. {
  6. GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;
  7. isCompleted = true;
  8. // 取回打包在资源内部的数据 由于我们当初放进去的全是Texture2D类型的,所以使用LoadAll的
  9. // 带类型的重载方法,将Texture2D类型传入进去,表示我只取出Texture2D类型的数据
  10. Object[] opticals = www.assetBundle.LoadAll(typeof(Texture2D));
  11. foreach(Object o in opticals)
  12. {
  13. Texture2D tmp = o as Texture2D;
  14. print("name : " + tmp.name);
  15. }
  16. }
  17. }

这里打印除了 6 条记录我们当初打包到Object[]数组里的是 4 张贴图:black-dots、blue-dots、green-dots、yellow-dots。这里明显多出了red-dots和normal-dots,这不合适啊。细心的你也一定会发现,多出的那 2 张贴图,正是刚刚导出的模型上本身的一张漫反射贴图和一张法线贴图。原来,LoadAll()这个方法会将存在于下载过来的这个文件中符合类型的所有资源都取过来的,这也很简单处理,只要我们把不符合要求的剔除掉就好了。这里实现起来很简单,我就不说了,我这里想说的是另一种方法,这个是开发中比较常用的。

我们使用的时候,一般都是取确定的某个对象,可以通过Load(string name)方法来取得,这个方法返回的是一个AssetBundleRequest类型的值,我们可以通过它里面的asset属性取到需要的数据:

[csharp]  view plain copy
  1. AssetBundleRequest abr = www.assetBundle.LoadAsync("black-dots", typeof(Texture2D));

到现在,我们就下载过来了所有数据,并且可以取出需要的数据来使用了。接下来,我们完善一下这个小例子,把下载过来的资源充分的使用起来,就是给这个小东西换一个贴图。

这里完整的代码是这样子的:

[csharp]  view plain copy
  1. using UnityEngine;
  2. using System.Collections;
  3. /// <summary>
  4. /// author : qyxls
  5. /// </summary>
  6. public class Downloader : MonoBehaviour
  7. {
  8. private string url = "http://qd.baidupcs.com/file/5d06bd73f17afc5a5eb3e5497d0b6007?xcode=71a918e1fad4242470466f5cc4a869c8ec18245ec1f0d579&fid=604160625-250528-2002528139&time=1377831362&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-FsMzIz4cENbozwsto38a47bDc64%3D&to=qb&fm=Q,B,U&expires=8h&rt=pr&r=709102273&logid=1896966947&fn=dice.unity3d";
  9. private WWW www;
  10. private bool isCompleted = false;
  11. private GameObject dice;
  12. private Texture2D tex;
  13. void Start ()
  14. {
  15. this.www = new WWW(this.url);
  16. }
  17. void Update ()
  18. {
  19. if(www == null) return;
  20. if(!isCompleted && www.isDone)
  21. {
  22. dice = GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;
  23. isCompleted = true;
  24. /*
  25. // 取回打包在资源内部的数据 由于我们当初放进去的全是Texture2D类型的,所以使用LoadAll的//
  26. // 带类型的重载方法,将Texture2D类型传入进去,表示我只取出Texture2D类型的数据//
  27. Object[] opticals = www.assetBundle.LoadAll(typeof(Texture2D));
  28. foreach(Object o in opticals)
  29. {
  30. Texture2D tmp = o as Texture2D;
  31. print("name : " + tmp.name);
  32. }
  33. */
  34. AssetBundleRequest abr = www.assetBundle.LoadAsync("black-dots", typeof(Texture2D));
  35. tex = abr.asset as Texture2D;
  36. }
  37. }
  38. void OnGUI()
  39. {
  40. if(GUI.Button(new Rect(20, 20, 100, 40), "CHANGE"))
  41. {
  42. // 如果还没下载完,这时候是不能执行替换功能的 //
  43. if(dice == null || tex == null) return;
  44. dice.renderer.material.mainTexture = tex;
  45. }
  46. }

运行结果:

开始未替换之前:

替换之后:

到现在我们这个的流程以及要求就基本实现了,这里别忘了最后一步,清理使用完的无用资源,释放内存。

下载完的数据都保存在内存中,这时候它们都是一个AssetBundle的内存镜像,我们在使用数据时,只是从内存镜像里取出数据,通过Instance方法新实例化出来的一个对象,当我们有了这样一个对象,以后的操作都是针对这样的一个,而内存中保存的那块镜像已经没有用处了,我们可以释放掉:

AssetBundle.Unload(flase) : 是释放AssetBundle文件的内存镜像,不包含Load创建的Asset内存对象。
AssetBundle.Unload(true) : 是释放那个AssetBundle文件内存镜像和并销毁所有用Load创建的Asset内存对象。

这里我们使用

[csharp]  view plain copy
  1. www.assetBundle.Unload(false);

之所以不使用

[csharp]  view plain copy
  1. www.assetBundle.Unload(true);

是因为我们不能销毁掉实例化出来的Asset对象,我们还要继续操作它(下面的换贴图等)。否则,该对象会在场景里消失,彻底销毁掉了。

所有代码再给大家列一遍:

导出工具的代码:

[csharp]  view plain copy
  1. using System.IO;
  2. using UnityEditor;
  3. using UnityEngine;
  4. /// <summary>
  5. /// author : qyxls
  6. /// </summary>
  7. public class ExportTools : EditorWindow
  8. {
  9. [MenuItem("Tools/Export")]
  10. static void Execute ()
  11. {
  12. // 实例化一个Window窗口 //
  13. EditorWindow.GetWindow<ExportTools>(true, "Export Tools");
  14. }
  15. private string savePath;
  16. private GameObject exportObject;
  17. private int optionalCount = 0;
  18. private Texture2D[] optionalTexture = new Texture2D[0];
  19. void OnGUI()
  20. {
  21. /*
  22. * ObjectField:
  23. * 是这里的第一个控件,它可以允许用户拖拽将一个Object的对象赋给它。
  24. * 如果要限制可接收的对象类型,可以通过第三个参数来限制类型这里表示直接收GameObject类型
  25. * 第四个bool型的参数标志能否接受当前scene里的对象,true表示接受
  26. * 这个方法返回的是一个Object类型的值,最后要将它转化为需要的类型
  27. */
  28. exportObject = EditorGUILayout.ObjectField("Export Object", exportObject,
  29. typeof(GameObject), true)
  30. as GameObject;
  31. // 就相当于提供一个换行,用于格式化控件的 //
  32. EditorGUILayout.Space();
  33. // IntField:该控件只能输入 int 类型的值//
  34. optionalCount = EditorGUILayout.IntField("Optional Count", optionalCount);
  35. for(int i=0; i<optionalCount; i++)
  36. {
  37. if(optionalTexture.Length != optionalCount)
  38. {
  39. optionalTexture = new Texture2D[optionalCount];
  40. }
  41. EditorGUILayout.Space();
  42. // 这里将 ObjectField 限制只接受Texture2D类型的值 //
  43. optionalTexture[i] = EditorGUILayout.ObjectField("Optional Textures " + i, optionalTexture[i],
  44. typeof(Texture2D), false)
  45. as Texture2D;
  46. }
  47. EditorGUILayout.Space();
  48. EditorGUILayout.Space();
  49. EditorGUILayout.BeginHorizontal();
  50. EditorGUILayout.Space();
  51. // 导出按钮 //
  52. if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))
  53. {
  54. if(Validate())
  55. {
  56. ExportAndSave(exportObject);
  57. Clear();//成功导出数据后,清除导出信息//
  58. }
  59. else
  60. {
  61. //导出信息填写有误时,给出提示//
  62. EditorUtility.DisplayDialog("错误提示", "导出信息设置有误,请返回检查!", "确定");
  63. }
  64. }
  65. EditorGUILayout.EndHorizontal();
  66. }
  67. private void ExportAndSave(GameObject go)
  68. {
  69. //该方法将打开保存对话框,选择导出文件的保存位置。第二和第三个参数表示默认保存位置和默认文件名//
  70. savePath = EditorUtility.SaveFilePanel("Save", @"E:\", go.name, "unity3d");
  71. Export(go, savePath);
  72. }
  73. private void Export(GameObject go, string filePath)
  74. {
  75. // IsPersistent 判断传入的对象是磁盘文件还是场景文件(即是否是Project视图下的文件,是返回true)//
  76. if(!EditorUtility.IsPersistent(go))
  77. {
  78. GameObject tmp = GameObject.Instantiate(go) as GameObject;
  79. go = GetPrefab(tmp, go.name) as GameObject;
  80. }
  81. //Texture2D本身就是磁盘文件了,这里就没必要再转化了//
  82. Object[] asset = optionalTexture;
  83. if(File.Exists(filePath)) File.Delete(filePath);
  84. /*
  85. BuildPipeline.BuildAssetBundle():该方法是将提供的对象导出成Unity能识别的二进制文件
  86. 第一个参数是提供一个要导出的对象,第二个参数是一个Object[]类型,它可以将数据附加到第一个
  87. 参数定义的主数据一起整体导出.但是这两个参数要求必须是磁盘文件的格式,所以上面的if语句判断
  88. 是否是磁盘文件类型,如果不是,先将其转化为prefab,在Assets下临时保存一下。这个转化就是要
  89. 用到 PrefabUtility 类里的方法。
  90. */
  91. BuildPipeline.BuildAssetBundle(go, asset, filePath, BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);
  92. // 将暂时生成的prefab文件使用完后删除 //
  93. AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(go));
  94. }
  95. /// <summary>
  96. /// 该方法来产生临时prefab文件
  97. /// </param>
  98. private Object GetPrefab(GameObject go, string name)
  99. {
  100. Object result = PrefabUtility.CreateEmptyPrefab("Assets/" + name + ".prefab");
  101. result = PrefabUtility.ReplacePrefab(go, result);
  102. Object.DestroyImmediate(go);
  103. return result;
  104. }
  105. /// <summary>
  106. /// 数据验证,如果导出信息填写有误,将给用户错误提示
  107. /// </summary>
  108. private bool Validate()
  109. {
  110. bool b1 = (exportObject == null);
  111. bool b2 = false;
  112. foreach(Texture2D t in optionalTexture)
  113. {
  114. b2 = b2 || (t == null);
  115. }
  116. return !(b1 || b2);
  117. }
  118. /// <summary>
  119. /// 所有数据正确导出后,清除填写的导出信息,以便导出下一条数据
  120. /// </summary>
  121. private void Clear()
  122. {
  123. exportObject = null;
  124. optionalCount = 0;
  125. }
  126. }

下载实现代码:

[csharp]  view plain copy
  1. using UnityEngine;
  2. using System.Collections;
  3. /// <summary>
  4. /// author : qyxls
  5. /// </summary>
  6. public class Downloader : MonoBehaviour
  7. {
  8. private string url = "http://qd.baidupcs.com/file/5d06bd73f17afc5a5eb3e5497d0b6007?xcode=71a918e1fad4242470466f5cc4a869c8ec18245ec1f0d579&fid=604160625-250528-2002528139&time=1377831362&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-FsMzIz4cENbozwsto38a47bDc64%3D&to=qb&fm=Q,B,U&expires=8h&rt=pr&r=709102273&logid=1896966947&fn=dice.unity3d";
  9. private WWW www;
  10. private bool isCompleted = false;
  11. private GameObject dice;
  12. private Texture2D tex;
  13. void Start ()
  14. {
  15. this.www = new WWW(this.url);
  16. }
  17. void Update ()
  18. {
  19. if(www == null) return;
  20. if(!isCompleted && www.isDone)
  21. {
  22. dice = GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;
  23. isCompleted = true;
  24. /*
  25. // 取回打包在资源内部的数据 由于我们当初放进去的全是Texture2D类型的,所以使用LoadAll的//
  26. // 带类型的重载方法,将Texture2D类型传入进去,表示我只取出Texture2D类型的数据//
  27. Object[] opticals = www.assetBundle.LoadAll(typeof(Texture2D));
  28. foreach(Object o in opticals)
  29. {
  30. Texture2D tmp = o as Texture2D;
  31. print("name : " + tmp.name);
  32. }
  33. */
  34. AssetBundleRequest abr = www.assetBundle.LoadAsync("black-dots", typeof(Texture2D));
  35. tex = abr.asset as Texture2D;
  36. }
  37. }
  38. void OnGUI()
  39. {
  40. if(GUI.Button(new Rect(20, 20, 100, 40), "CHANGE"))
  41. {
  42. // 如果还没下载完,这时候是不能执行替换功能的 //
  43. if(dice == null || tex == null) return;
  44. dice.renderer.material.mainTexture = tex;
  45. }
  46. }
  47. /*
  48. private void WWWWithParameter(string url, string parameter)
  49. {
  50. WWWForm form = new WWWForm();
  51. form.AddField("Content", parameter);
  52. WWW www = new WWW(url, form);
  53. }
  54. */
  55. }

附上项目下载地址:http://download.csdn.net/detail/qyxls/6039269

(请原谅这里设置了下载需要积分,前段时间下载东东,把积分用完了。再有就是有积分限制,强迫你自己去敲代码嘛

游戏资源的制作和下载相关推荐

  1. Unity游戏运行资源的制作与下载

    我们的游戏制作完发布出去提供给玩家,为了给玩家带来更好的游戏体验,要做各种的优化以及设计,首先,游戏资源的加载就是一个非常重要的方面(尤其是网页游戏).由于我们的游戏资源比较大,不能一下全部加载出来, ...

  2. 海外开发者推荐:10个顶级2D游戏资源站

    Gamelook报道/随着手游市场的持续增长,HTML5的发展以及大型发行商的支持,2D游戏的数量变得越来越多,得益于Unity和在线2D游戏资源商店的出现,游戏研发变得更加容易.最近,海外开发者推荐 ...

  3. 【资源】Oculus meta Quest 2游戏下载VR一体机游戏资源合集下载教程Pro

    meta Oculus Quest2的VR一体机已经发布有一段时间了,虽然后起之秀的VR一体机设备陆续也发布,尤其是近期pico4的发布,但是不可否认,Quest2至今依然是VR一体机设备的生态之王, ...

  4. Pico 4一体机游戏下载教程(附游戏资源包)Pico4串流第三方VR游戏教程讲解

    我们看一下pico4的配置参数.他采用雪镜造型,pancake方案的加持,使他的前端头显和后端固垫都更加的轻薄.其单眼分辨率达到2160×2160,PP1提升到1200,视场角为105度.pro版本还 ...

  5. 3dm游戏运行包_动作游戏ACT 逃离丧尸镇 Shadows of Kurgansk 电脑游戏资源

    逃离丧尸镇 <逃离丧尸镇(Shadows of Kurgansk)>是由Yadon Studio制作,Gaijin inCubator发行的一款第一人称动作生存类游戏,玩家将被困在一块神秘 ...

  6. 通用型游戏资源提取工具介绍

    先感慨一下,这是篇2007年的帖子啊!!13年了! 游戏资源包括了游戏的图片.文字.音乐.动画和其他数据资源.虽然很多游戏的资源都是开放的或者采用通用格式压缩的,但也不少游戏是经特殊格式打包过了,要想 ...

  7. flash游戏教程集锦~~制作游戏有疑惑的先来这里看看~~

    http://www.flash8.net/bbs/dispbbs.asp?boardID=38&ID=183016&page=1 flash游戏教程集锦~~制作游戏有疑惑的先来这里看 ...

  8. 通用型游戏资源提取工具介绍收藏

    游戏资源包括了游戏的图片.文字.音乐.动画和其他数据资源.虽然很多游戏的资源都是开放的或者采用通用格式压缩的,但也不少游戏是经特殊格式打包过了,要想得到这些资源可以寻找专用的资源提取工具.但并非所有游 ...

  9. 【转贴】通用型游戏资源提取工具介绍 (推荐)

    游戏资源包括了游戏的图片.文字.音乐.动画和其他数据资源.虽然很多游戏的资源都是开放的或者采用通用格式压缩的,但也不少游戏是经特殊格式打包过了,要想得到这些资源可以寻找专用的资源提取工具.但并非所有游 ...

最新文章

  1. Nature灵魂拷问:微生物组数据一大堆,如何能改变人类健康?
  2. Android中removeCallbacks失效原因
  3. Hadoop的调度器总结
  4. centos6 rsync+inotify 数据同步
  5. Crawler:基于requests库+urllib3库+伪装浏览器实现爬取抖音账号的信息数据
  6. C语言 | 基于STM32的IIC代码实现(源代码)
  7. php mysql-mysqli
  8. Python-第三方库requests详解
  9. RabbitMq 安装
  10. 2018年7月份,python上传自己的包库到pypi官网的方法
  11. 别人在忙挖矿,京东架构师却悄悄用区块链搞了件大事
  12. 76Byte让你的JQuery更快
  13. 网络管理与维护作业10
  14. 配置好网络文件还是连不上外网
  15. iOS 多语言本地化 完美解决方案【自动+手动】
  16. php实现RSA加密解密
  17. React Hooks 分享
  18. 031_onetab
  19. 字幕滚动效果---非常酷哦
  20. 彻底卸载360画报教程

热门文章

  1. 雅思阅读话题词汇-arduous
  2. 8个免费高速文件传输工具,让你彻底告别网盘和数据线
  3. 将VOC2012转换为tfrecord
  4. 免费为网站配置ssl证书, 使网站变成https.半小时搞定
  5. ORA-12505: TNS: 监听程序当前无法识别连接描述符中所给出的SID等错误解决方法【连接本机】
  6. BeyondProd:云原生安全的一种新方法
  7. CSS3 greyscale 实现元素转换成黑白色(灰色、置灰)
  8. 使用记账本记录收支 如何将收支数据进行打印 使用记账软件轻松搞定
  9. Java实现身份证信息比较大小
  10. Dividing(HDU 1059)(多重背包_二进制优化)