Unity3d游戏开发之使用AssetBundle和Xml实现场景的动态加载

简介: 在Unity3D游戏开发过程中,因为受到游戏容量、平台性能和热更新等诸多因素的限制,我们可能无法将所有的游戏场景打包到项目中然后相对”静态”地加载,那么这个时候就需要我们使用动态加载的方式来将游戏场景加载到场景中。

在Unity3D游戏开发过程中,因为受到游戏容量、平台性能和热更新等诸多因素的限制,我们可能无法将所有的游戏场景打包到项目中然后相对”静态”地加载,那么这个时候就需要我们使用动态加载的方式来将游戏场景加载到场景中。博主在研究了Unity3D动态加载的相关资料后发现,目前Unity3D中实现动态加载场景的方式主要有以下两种方式:

* 使用BuildStreamedSceneAssetBundle()方法将场景打包为AssetBundle:这种方法将生成一个流式的.unity3d文件,从而实现按需下载和加载,因此这种方式特别适合Web环境下游戏场景的加载,因为在Web环境下我们可以希望的是玩家可以在玩游戏的同时加载游戏。可是因为这种打包方式仅仅是保证了场景中的GameObject与本地资源的引用关系而非是将本地资源打包,因此从减少游戏容量的角度来说并不是十分实用,而且当我们使用WWW下载完AssetBundle后,需要使用Application.Load()方法来加载场景,我们知道在Unity3D中加载一个关卡(场景)是需要在BuildSetting中注册关卡的,因此在使用这种方式动态加载的时候请注意到这一点。

* 将场景内的所有物体打包为AssetBundle配合相关配置文件动态生成场景:这种方法的思路是使用一个配置文件来记录下当前场景中所有物体的位置、旋转和缩放信息,然后再根据配置文件使用Instantiate方法逐个生成即可。这种思路是考虑到需要在一个场景中动态替换GameObject或者是动态生成GameObject的情形,使用这种方法首先要满足一个条件,即:场景内所有的物体都是预制件(Prefab)。这是由Unity3D的机制决定的,因为Prefab是一个模板,当你需要动态生成一个物体的时候就需要为其提供一个模板(Prefab)。

如果你对这两种方式没有什么疑问的话,那么我觉得我们可以正式开始今天的内容了。既然今天的题目已然告诉大家是使用AssetBundle和Xml文件实现场景的动态加载,我相信大家已经明白我要使用那种方式了。好了,下面我们正式开始吧!

准备工作

在实现场景的动态加载前,我们首先要在本地准备好一个游戏场景,然后做两件事情:

* 将场景内的所有GameObject打包为AssetBundle

* 将场景内所有的GameObject的信息导出为Xml文件

做这两件事情的时候,相当于我们是在准备食材和菜谱,有了食材和菜谱我们就可以烹制出美味佳肴了。可是在做着两件事情前,我们还有一件更为重要的事情要做,那就是我们需要将场景中使用到的GameObject制作成预制体(Prefab)。因为在博主的印象中,Unity3D打包的最小粒度应该是Prefab,所以为了保险起见,我还是建议大家将场景中使用到的GameObject制作成预制体(Prefab)。那么问题来了,当我们将这些Prefab打包成AssetBundle后是否还需要本地的Prefab文件?这里博主一直迷惑,因为理论上当我们将这些Prefab打包成AssetBundle后,我们实例化一个物体的时候实际上是在使用AssetBundle的Load方法来获取该物体的一个模板,这个模板应该是存储在AssetBundle中的啊!因为我的笔记本使用的是免费版的Unity3D无法对此进行测试,所以如果想知道这个问题结果的朋友可以等我下周到公司以后测试了再做讨论(我不会告诉你公司无耻地使用了破解版),当然如果有知道这个问题的答案的朋友欢迎给我留言啊,哈哈!这里就是想告诉大家要准备好场景中物体的预设体(Prefab),重要的事情说三遍!!!

将场景内物体打包为AssetBundle

Unity3D打包的相关内容这里就不展开说了,因为在官方API文档中都能找到详细的说明,虽然说Unity5.0中AssetBundle打包

的方式发生了变化,不过考虑到大家都还在使用4.X的版本,所以等以后我用上了Unity5.0再说吧,哈哈!好了,下面直接给出代码:

view sourceprint?

01.    [MenuItem('Export/ExportTotal----对物体整体打包')]

02.staticvoidExportAll()

03.{

04.//获取保存路径

05.string savePath=EditorUtility.SaveFilePanel('输出为AssetBundle','','New Resource','unity3d');

06.if(string.IsNullOrEmpty(savePath))return;

07.//获取选择的物体

08.Object[] objs=Selection.GetFiltered(typeof(Object),SelectionMode.DeepAssets);

09.if(objs.Length<0)return;

10.//打包

11.BuildPipeline.BuildAssetBundle(null,objs,savePath,BuildAssetBundleOptions.CollectDependencies|BuildAssetBundleOptions.CompleteAssets);

12.AssetDatabase.Refresh();

13.}

将场景内物体信息导出为Xml文件

导出场景内物体信息需要遍历场景中的每个游戏物体,因为我们在制作场景的时

候通常会用一个空的GameObject作为父物体来组织场景中的各种物体,因此我们在导出Xml文件的时候仅仅考虑导出这些父物体,因为如果考虑子物体

的话,可能会涉及到递归,整个问题将变得特别复杂。为了简化问题,我们这里仅仅考虑场景中的父物体。好了,开始写代码:

view sourceprint?

01.    [MenuItem('Export/ExportScene----将当前场景导出为Xml')]

02.staticvoidExportGameObjects()

03.{

04.//获取当前场景完整路径

05.string scenePath=EditorApplication.currentScene;

06.//获取当前场景名称

07.string sceneName=scenePath.Substring(scenePath.LastIndexOf('/')+1,scenePath.Length-scenePath.LastIndexOf('/')-1);

08.sceneName=sceneName.Substring(0,sceneName.LastIndexOf('.'));

09.//获取保存路径

10.string savePath=EditorUtility.SaveFilePanel('输出场景内物体','',sceneName,'xml');

11.//创建Xml文件

12.XmlDocument xmlDoc=newXmlDocument();

13.//创建根节点

14.XmlElement scene=xmlDoc.CreateElement('Scene');

15.scene.SetAttribute('Name',sceneName);

16.scene.SetAttribute('Asset',scenePath);

17.xmlDoc.AppendChild(scene);

18.//遍历场景中的所有物体

19.foreach(GameObject go in Object.FindObjectsOfType(typeof(GameObject)))

20.{

21.//仅导出场景中的父物体

22.if(go.transform.parent==null)

23.{

24.//创建每个物体

25.XmlElement gameObject=xmlDoc.CreateElement('GameObject');

26.gameObject.SetAttribute('Name',go.name);

27.gameObject.SetAttribute('Asset','Prefabs/'+ go.name +'.prefab');

28.//创建Transform

29.XmlElement transform=xmlDoc.CreateElement('Transform');

30.transform.SetAttribute('x',go.transform.position.x.ToString());

31.transform.SetAttribute('y',go.transform.position.y.ToString());

32.transform.SetAttribute('z',go.transform.position.z.ToString());

33.gameObject.AppendChild(transform);

34.//创建Rotation

35.XmlElement rotation=xmlDoc.CreateElement('Rotation');

36.rotation.SetAttribute('x',go.transform.eulerAngles.x.ToString());

37.rotation.SetAttribute('y',go.transform.eulerAngles.y.ToString());

38.rotation.SetAttribute('z',go.transform.eulerAngles.z.ToString());

39.gameObject.AppendChild(rotation);

40.//创建Scale

41.XmlElement scale=xmlDoc.CreateElement('Scale');

42.scale.SetAttribute('x',go.transform.localScale.x.ToString());

43.scale.SetAttribute('y',go.transform.localScale.y.ToString());

44.scale.SetAttribute('z',go.transform.localScale.z.ToString());

45.gameObject.AppendChild(scale);

46.//添加物体到根节点

47.scene.AppendChild(gameObject);

48.}

49.}

50.

51.xmlDoc.Save(savePath);

52.}

53.

好了,在这段代码中我们以Scene作为根节点,然后以每个GameObject作为Scene的子节点,重点在Xml文件中记录了每个GameObject的名称、Prefab、坐标、旋转和缩放等信息。下面是一个导出场景的Xml文件的部分内容:

view sourceprint?

01.

02.

03.

04.

05.

06.

07.

08.

09.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

21.

在这里我们假设所有的Prefab是放置在Resources/Prefabs目录中的,那么此时我们便有了两种动态加载场景的方式

* 通过每个GameObject的Asset属性,配合Resources.Load()方法实现动态加载

* 通过每个GameObject的Name属性,配合AssetBundle的Load()方法实现动态加载

这两种方法大同小异,区别仅仅在于是否需要从服务器下载相关资源。因此本文的主题是使用AssetBundle和Xml实现场景的动态加载,因此,接下来我们主要以第二种方式为主,第一种方式请大家自行实现吧!

动态加载物体到场景中

首先我们来定义一个根据配置文件动态加载AssetBundle中场景的方法LoadDynamicScene

view sourceprint?

01.///

02./// 根据配置文件动态加载AssetBundle中的场景

03.///

04./// 从服务器上下载的AssetBundle文件

05./// AssetBundle文件对应的场景配置文件

06.publicstaticvoidLoadDynamicScene(AssetBundle bundle,string xmlFile)

07.{

08.//加载本地配置文件

09.XmlDocument xmlDoc=newXmlDocument();

10.xmlDoc.LoadXml(((TextAsset)Resources.Load(xmlFile)).text);

11.//读取根节点

12.XmlElement root=xmlDoc.DocumentElement;

13.if(root.Name=='Scene')

14.{

15.XmlNodeList nodes=root.SelectNodes('/Scene/GameObject');

16.//定义物体位置、旋转和缩放

17.Vector3 position=Vector3.zero;

18.Vector3 rotation=Vector3.zero;

19.Vector3 scale=Vector3.zero;

20.//遍历每一个物体

21.foreach(XmlElement xe1 in nodes)

22.{

23.//遍历每一个物体的属性节点

24.foreach(XmlElement xe2 in xe1.ChildNodes)

25.{

26.//根据节点名称为相应的变量赋值

27.if(xe2.Name=='Transform')

28.{

29.position=newVector3(float.Parse(xe2.GetAttribute('x')),float.Parse(xe2.GetAttribute('y')),float.Parse(xe2.GetAttribute('z')));

30.}elseif(xe2.Name=='Rotation')

31.{

32.rotation=newVector3(float.Parse(xe2.GetAttribute('x')),float.Parse(xe2.GetAttribute('y')),float.Parse(xe2.GetAttribute('z')));

33.}else{

34.scale=newVector3(float.Parse(xe2.GetAttribute('x')),float.Parse(xe2.GetAttribute('y')),float.Parse(xe2.GetAttribute('z')));

35.}

36.}

37.//生成物体

38.GameObject go=(GameObject)GameObject.Instantiate(bundle.Load(xe1.GetAttribute('Name')),position,Quaternion.Euler(rotation));

39.go.transform.localScale=scale;

40.}

41.}

42.}

因为该方法中的AssetBundle是需要从服务器下载下来的,因此我们需要使用协程来下载AssetBundle:

view sourceprint?

01.    IEnumerator Download()

02.{

03.WWW _www =newWWW ('http://localhost/DoneStealth.unity3d');

04.yieldreturn_www;

05.//检查是否发生错误

06.if(string.IsNullOrEmpty (_www.error))

07.{

08.//检查AssetBundle是否为空

09.if(_www.assetBundle!=null)

10.{

11.LoadDynamicScene(_www.assetBundle,'DoneStealth.xml');

12.}

13.}

14.}

好了,现在运行程序,可以发现场景将被动态地加载到当前场景中:),哈哈

img_db5cb955bfdbab70e5fd901fb893064a.png

小结

使

用这种方式来加载场景主要是为了提高游戏的性能,如果存在大量重复性的场景的时候,可以使用这种方式来减小游戏的体积,可是这种方式本质上是一种用时间换

效率的方式,因为在使用这种方法前,我们首先要做好游戏场景,然后再导出相关的配置文件和AssetBundle,从根本上来讲,工作量其实没有减少。

当场景导出的Xml文件中的内容较多时,建议使用内存池来管理物体的生成和销毁,因为频繁的生成和销毁是会带来较大的内存消耗的。说到这里的时候,我不得

不吐槽下公司最近的项目,在将近300个场景中只有30个场景是最终发布游戏时需要打包的场景,然后剩余场景将被用来动态地加载到场景中,因为领导希望可

以实现动态改变场景的目的,更为郁闷的是整个场景要高度DIY,模型要能够随用户拖拽移动、旋转,模型和材质要能够让用户自由替换。从整体上来讲,频繁地

销毁和生成物体会耗费大量资源,因此如果遇到这种情况建议还是使用内存池进行管理吧!

相关文章
|
3月前
|
图形学
Unity 不同Scene场景转换(简)
本文提供了Unity中实现场景转换的基本方法,包括编写传送脚本、创建传送门和玩家对象,并通过触发器实现玩家触碰传送门时切换到另一个场景的功能。
Unity 不同Scene场景转换(简)
|
3月前
|
图形学
小功能⭐️Unity获取场景中所有物体
小功能⭐️Unity获取场景中所有物体
小功能⭐️Unity获取场景中所有物体
|
2月前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
189 3
|
2月前
|
图形学 开发工具 git
Unity与版本控制:游戏开发团队如何利用Git打造高效协作流程,实现代码管理的最佳实践指南
【8月更文挑战第31天】版本控制在软件开发中至关重要,尤其在Unity游戏开发中,能提升团队协作效率并避免错误。本文介绍如何在Unity项目中应用版本控制的最佳实践,包括选择Git、配置项目以排除不必要的文件、组织项目结构、避免冲突、规范提交信息以及使用分支管理开发流程,从而提高代码质量和团队协作效率。
234 1
|
3月前
|
图形学 机器学习/深度学习 人工智能
颠覆传统游戏开发,解锁未来娱乐新纪元:深度解析如何运用Unity引擎结合机器学习技术,打造具备自我进化能力的智能游戏角色,彻底改变你的游戏体验——从基础设置到高级应用全面指南
【8月更文挑战第31天】本文探讨了如何在Unity中利用机器学习增强游戏智能。作为领先的游戏开发引擎,Unity通过ML-Agents Toolkit等工具支持AI代理的强化学习训练,使游戏角色能自主学习完成任务。文章提供了一个迷宫游戏示例及其C#脚本,展示了环境观察、动作响应及奖励机制的设计,并介绍了如何设置训练流程。此外,还提到了Unity与其他机器学习框架(如TensorFlow和PyTorch)的集成,以实现更复杂的游戏玩法。通过这些技术,游戏的智能化程度得以显著提升,为玩家带来更丰富的体验。
64 1
|
2月前
|
图形学 开发者
透视与正交之外的奇妙视界:深入解析Unity游戏开发中的相机与视角控制艺术,探索打造沉浸式玩家体验的奥秘与技巧
【8月更文挑战第31天】在Unity中,相机不仅是玩家观察游戏世界的窗口,更是塑造氛围和引导注意力的关键工具。通过灵活运用相机系统,开发者能大幅提升游戏的艺术表现力和沉浸感。本文将探讨如何实现多种相机控制,包括第三人称跟随和第一人称视角,并提供实用代码示例。
142 0
|
2月前
|
图形学 开发者
【独家揭秘】Unity游戏开发秘籍:从基础到进阶,掌握材质与纹理的艺术,打造超现实游戏视效的全过程剖析——案例教你如何让每一面墙都会“说话”
【8月更文挑战第31天】Unity 是全球领先的跨平台游戏开发引擎,以其高效性能和丰富的工具集著称,尤其在提升游戏视觉效果方面表现突出。本文通过具体案例分析,介绍如何利用 Unity 中的材质与纹理技术打造逼真且具艺术感的游戏世界。材质定义物体表面属性,如颜色、光滑度等;纹理则用于模拟真实细节。结合使用两者可显著增强场景真实感。以 FPS 游戏为例,通过调整材质参数和编写脚本动态改变属性,可实现自然视觉效果。此外,Unity 还提供了多种高级技术和优化方法供开发者探索。
54 0
|
2月前
|
图形学 开发者 UED
Unity游戏开发必备技巧:深度解析事件系统运用之道,从生命周期回调到自定义事件,打造高效逻辑与流畅交互的全方位指南
【8月更文挑战第31天】在游戏开发中,事件系统是连接游戏逻辑与用户交互的关键。Unity提供了多种机制处理事件,如MonoBehaviour生命周期回调、事件系统组件及自定义事件。本文介绍如何有效利用这些机制,包括创建自定义事件和使用Unity内置事件系统提升游戏体验。通过合理安排代码执行时机,如在Awake、Start等方法中初始化组件,以及使用委托和事件处理复杂逻辑,可以使游戏更加高效且逻辑清晰。掌握这些技巧有助于开发者更好地应对游戏开发挑战。
129 0
|
2月前
|
图形学 开发者 搜索推荐
Unity Asset Store资源大解密:自制与现成素材的优劣对比分析,教你如何巧用海量资产加速游戏开发进度
【8月更文挑战第31天】游戏开发充满挑战,尤其对独立开发者或小团队而言。Unity Asset Store 提供了丰富的资源库,涵盖美术、模板、音频和脚本等,能显著加快开发进度。自制资源虽具个性化,但耗时长且需专业技能;而 Asset Store 的资源经官方审核,质量可靠,可大幅缩短开发周期,使开发者更专注于核心玩法。然而,使用第三方资源需注意版权问题,且可能需调整以适应特定需求。总体而言,合理利用 Asset Store 能显著提升开发效率和项目质量。
73 0
|
3月前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
134 0