zl程序教程

您现在的位置是:首页 >  其他

当前栏目

[unity3d]从服务器端获取资源动态加载到场景

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

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

(再注:本文涉及到的代码都是以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编辑器的菜单栏中,语法:


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

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


这里要注意两点:

1、 引入UnityEditor命名空间。

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

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

 

2、Editor模式下窗口制作

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


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

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

用例描述:

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

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

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


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

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

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


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

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


     * 如果要限制可接收的对象类型,可以通过第三个参数来限制类型这里表示直接收GameObject类型 
        optionalTexture[i] = EditorGUILayout.ObjectField("Optional Textures " + i, optionalTexture[i],   
到这里这个窗口就基本算是完成了。

3、导出功能的具体实现

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


        // IsPersistent 判断传入的对象是磁盘文件还是场景文件(即是否是Project视图下的文件,是返回true)//  
            BuildPipeline.BuildAssetBundle():该方法是将提供的对象导出成Unity能识别的二进制文件 
            第一个参数是提供一个要导出的对象,第二个参数是一个Object[]类型,它可以将数据附加到第一个 
            参数定义的主数据一起整体导出.但是这两个参数要求必须是磁盘文件的格式,所以上面的if语句判断 
            是否是磁盘文件类型,如果不是,先将其转化为prefab,在Assets下临时保存一下。这个转化就是要 
        BuildPipeline.BuildAssetBundle(go, asset, filePath, BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);  
        Object result = PrefabUtility.CreateEmptyPrefab("Assets/" + name + ".prefab");  
//该方法将打开保存对话框,选择导出文件的保存位置。第二和第三个参数表示默认保存位置和默认文件名//  
这个方法具体实现了导出二进制文件的功能。这里需要说明的是 BuildPipeline.BuildAssetBundle(): 该方法是将提供的对象导出成Unity能识别的二进制文件第一个参数是提供一个要导出的对象,第二个参数是一个Object[]类型,它可以将数据附加到第一个参数定义的主数据一起整体导出.但是这两个参数要求必须是磁盘文件的格式,所以上面的if语句判断是否是磁盘文件类型,如果不是,先将其转化为prefab,在Assets下临时保存一下。这个转化就是要用到 PrefabUtility 类里的方法。具体判断是否是磁盘文件,是通过 if(!EditorUtility.IsPersistent(go))这一句来判断的:如果go不是磁盘文件,是场景对象,则执行该语句里的代码来生成磁盘文件,具体的是下面这个方法来实现的。

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


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


                EditorUtility.DisplayDialog("错误提示", "导出信息设置有误,请返回检查!", "确定");  
这里可以看到我还添加了一个Clear()方法,该方法在用户导出完毕时,将导出工具面板的信息清除掉,以便开始导出其它资源:


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


         * 如果要限制可接收的对象类型,可以通过第三个参数来限制类型这里表示直接收GameObject类型 
            optionalTexture[i] = EditorGUILayout.ObjectField("Optional Textures " + i, optionalTexture[i],   
                EditorUtility.DisplayDialog("错误提示", "导出信息设置有误,请返回检查!", "确定");  
        //该方法将打开保存对话框,选择导出文件的保存位置。第二和第三个参数表示默认保存位置和默认文件名//  
        // IsPersistent 判断传入的对象是磁盘文件还是场景文件(即是否是Project视图下的文件,是返回true)//  
            BuildPipeline.BuildAssetBundle():该方法是将提供的对象导出成Unity能识别的二进制文件 
            第一个参数是提供一个要导出的对象,第二个参数是一个Object[]类型,它可以将数据附加到第一个 
            参数定义的主数据一起整体导出.但是这两个参数要求必须是磁盘文件的格式,所以上面的if语句判断 
            是否是磁盘文件类型,如果不是,先将其转化为prefab,在Assets下临时保存一下。这个转化就是要 
        BuildPipeline.BuildAssetBundle(go, asset, filePath, BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);  
        Object result = PrefabUtility.CreateEmptyPrefab("Assets/" + name + ".prefab");  
到这里我们通过这个小小的导出工具就可以制作出要需要的资源文件了,这些资源文件是存放在服务器上的,接下来我们一起看看关于这些资源文件的下载。

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)


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


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


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

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


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

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


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

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

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

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

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


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

 

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


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

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


    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";  
            // 取回打包在资源内部的数据 由于我们当初放进去的全是Texture2D类型的,所以使用LoadAll的// 
            // 带类型的重载方法,将Texture2D类型传入进去,表示我只取出Texture2D类型的数据// 
            AssetBundleRequest abr = www.assetBundle.LoadAsync("black-dots", typeof(Texture2D));  
到现在我们这个的流程以及要求就基本实现了,这里别忘了最后一步,清理使用完的无用资源,释放内存。

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

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

这里我们使用


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

导出工具的代码:


         * 如果要限制可接收的对象类型,可以通过第三个参数来限制类型这里表示直接收GameObject类型 
            optionalTexture[i] = EditorGUILayout.ObjectField("Optional Textures " + i, optionalTexture[i],   
                EditorUtility.DisplayDialog("错误提示", "导出信息设置有误,请返回检查!", "确定");  
        //该方法将打开保存对话框,选择导出文件的保存位置。第二和第三个参数表示默认保存位置和默认文件名//  
        // IsPersistent 判断传入的对象是磁盘文件还是场景文件(即是否是Project视图下的文件,是返回true)//  
            BuildPipeline.BuildAssetBundle():该方法是将提供的对象导出成Unity能识别的二进制文件 
            第一个参数是提供一个要导出的对象,第二个参数是一个Object[]类型,它可以将数据附加到第一个 
            参数定义的主数据一起整体导出.但是这两个参数要求必须是磁盘文件的格式,所以上面的if语句判断 
            是否是磁盘文件类型,如果不是,先将其转化为prefab,在Assets下临时保存一下。这个转化就是要 
        BuildPipeline.BuildAssetBundle(go, asset, filePath, BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);  
        Object result = PrefabUtility.CreateEmptyPrefab("Assets/" + name + ".prefab");  
    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";  
            // 取回打包在资源内部的数据 由于我们当初放进去的全是Texture2D类型的,所以使用LoadAll的// 
            // 带类型的重载方法,将Texture2D类型传入进去,表示我只取出Texture2D类型的数据// 
            AssetBundleRequest abr = www.assetBundle.LoadAsync("black-dots", typeof(Texture2D));  

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



自己测试的代码:从服务器上获取模型资源动态加载

using UnityEngine;

using System.Collections;

public class NewBehaviourScript : MonoBehaviour {

 private WWW www;

 private string url = @"http://www.baidupcs.com/file/02674ec2517acea386d30b31cc9920b1?xcode=842f4ac265ea0630cba454aa5bd13c58e4c1b64a40e54dd3 fid=1209145999-250528-2686658773 time=1382947909 sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-kZwHL122gdsgehOwysY9B84Myyc%3D to=wb fm=N,B,T,t expires=8h rt=sh r=631015269 logid=1425903517 sh=1 fn=is.unity3d

 private GameObject dice;

 // Use this for initialization

 void Start () {

 this.www = new WWW(this.url);

 private bool isCompleted = false;

 // Update is called once per frame

 void Update () {

 if (www == null) return;

 if (!isCompleted www.isDone)

 print("Download completed");

 isCompleted = true;

 dice =GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;

 dice.transform.position = new Vector3(0, 1, 20);

 //Instantiate(www.assetBundle.mainAsset);

 // print(dice);

}



5.0版本之后的AssetBundle资源的打包和解析加载(Unity3D) 这几天在研究AssetBundle资源打包盒解析加载,也踩过很多坑,参考过很多人的文章 发现很多人关于AssetBundle的文章不是API过时了不能用,就是有点乱 也不是有点乱,就是摸不着头脑,让人不能快速的get到这个东西如何使用 所以我特意在踩过坑之后把我这个学到的经验分享给大家。
Unity框架与资源打包 对象池一种通用型的技术,在其他语言中也会用到 池线程池、网络连接池,池是一个思想,将不用的东西暂时用池存起来,等到再次使用的时候再调出来用,节省CPU的调度 对象C#的任何一个类都可以实例化一个对象Object Unity中的游戏对象GameObject 思路最开始的时候,池中没有对象,需要生成。
unity 组件添加与访问 unity访问其他游戏对象的四种方式一、通过属性检查七指定参数进行访问其他游戏对象 using System.Collections;using System.Collections.Generic;using UnityEngine; public class Text : MonoBehavi...
蓬莱仙羽 麦子学院讲师,游戏蛮牛专栏作家,CSDN博客专家,热爱游戏开发,热爱Coding!