当要加载一个较大的场景,长时间等待势必会影响用户体验,并且一个较大场景全部加载到场景中也会影响操作流畅度。不可避免的需要用一个技术就是在Unity中进行动态的加载场景中的资源。
当然本文的动态加载场景资源,是以玩家为中心,玩家的视野为半径进行加载。首先让美术将整个场景以一定的格式写入XML文件中,然后在程序开始运行时读取美术给我们提供的XML文件,然后遍历这个xml文件中的所有节点找到所有处于玩家周围一定范围内的节点并加载即可。
准备工作:
首先我们要定义一个对象GameObjectInfo,其中包括这个资源的名称(string)、位置(Vector3)、旋转(Vector3)、缩放(Vector3)、所有子节点(List<T>)、Prefab路径(string)、包围盒(Bounds)等。
1,将场景资源写入到XML文件中
当美术做好了我们要的模型之后,我们使用我们写的编辑器扩展程序将模型的属性写入到XML中。
首先这个方法对于美术制作场景有一定的要求:
1,场景中的节点分为两类,分组节点和模型节点
2,分组节点上不能挂有任何组件,其作用仅在于将其子节点作为一个整体分组。
3,模型节点是从资源里加载的节点,可以从Prefab中加载,也可以从网络上下载。
首先,我们在编辑器中添加菜单( [MenuItem("Helper/SaveToXml")]),弹出SaveToXmlEdit,将模型的根节点拖到ObjectField中,点击SaveToXml按钮将获取根节点并将其与其子节点的属性以及对应关系保存到xml文件中。而点击LoadFromXml则是会弹出一个选择要导入到场景中的模型信息的xml文件.
那下面呢就讲下他是怎么实现的吧
SaveToXml
在编辑器中获取选择对象,再选择要保存的路径。
将获取到的根节点下的所有子节点,实例化成GameObjectInfo对象,并将其序列化成xml文件。
LoadFromXml
选择文件,将获取到的xml文件进行反序列化GameObjectInfo对象。
首先创建一个空的Transform t,然后判断当前GameObjectInfo是否是Prefab,如果是则获取使用其PrefabPath加载其Prefab(”AssetDatabase.LoadAssetAtPath<Transform>(info.prefabPath)“),如果不是,则Tranform t=new GameObject().transform;
然后将GameObjectInfo中的属性(名称/位置/旋转/缩放/等)将t赋值。
最后获取他的子对象,并使用递归的方式将其子对象进行创建和赋值。
(注:如果当前对象是Prefab时,那么当前对象就不用再获取他的子对象了)
2,玩家移动时加载
在Start中加载xml文件,反序列化GameObjectInfo。
在Update中根据玩家当前的位置(P)遍历所XML节点:
1。如果P在当前节点的包围盒之内或和包围盒的距离小于viewDistance:
a 如果当前节点尚未加载,那么加载当前节点;如果当前节点是分组节点,那么递归遍历其子节点。
b 如果当前节点已经加载,那么将其显示出来。
2。否则如果当前节点已经加载那么将其隐藏。
在实现上,使用一个键值对字典用来判断某个节点是否加载,用一个枚举NodeBehaviour用来表示对当前节点作出什么处理(Load/Hide/None)
在Update中获取玩家的位置/GameObjectInfo/viewDistance根据GameObject中记录的包围盒进行计算对象是否需要被加载,在首次加载中如果GameObjectInfo需要被加载就将NodeBehaviour设置为Load,并将其添加到键值对中,以供下次加载时不用再次创建对象。然后既然前句判断玩家不在当前GameObjectInfo的包围盒内那么则将其NodeBehaviour设置为Hide。
最后,根据NodeBehaviour状态是否是将当前的GameObjectInfo是加载还是隐藏。
Load状态:
首先判断在保存GameObjectInfo和对象应创建的GameObject的键值对中是否存在,如果不存在,判断是否是Prefab,如果是prefab就获取其Prefab,如果不是则(new GameObject()).transform。然后将其名称/座标/旋转/缩放赋值,并将其保存于键值对中。
如果存在,将判断些对象的activeSelf是否为false,如果为false,则将其设置为true.
最后,判断其是否有子对象,如果有子对象,递归加载。
Hide状态:
判断在键值对中是否存在当前GameObjectInfo,如果存在就将此对象的状态设置为隐藏。
3,需特殊处理的物体
如果有一些物体需要特殊处理,比如灯光/地形等需要在场景中始终显示,并不需要根据与玩家的距离进行动态的加载。那么我们就需要将他作为一个特殊的物体进行处理了。
首先我们将新建一个脚本LoadFlag挂在我们需要特殊处理的物体上,这个脚本设置了一个枚举值Flag,分别是Dynamic/Allways/Ignore这三个状态,然后我们就可以根据自己的需要来选择其所需要的加载状态了。
我们还需要将GameObjectInfo中增加一个加载标识LoadFlag.Flag flag。
比如说灯光,我们需要他在场景运行时就一直加载到场景中无需隐藏,那么我们将这个脚本挂在这个灯光上,并设置其状态为Allways(一直显示),那么我们在给美术将模型写入xml的脚本中如果我们的模型中有LoadFlag这个脚本时,我们就将这个这个脚本中的枚举值赋值为GameObjectInfo中的flag属性中。
在程序使用的脚本中,当在场景运行时,我们在读取xml文档的时候实例化GameObjectInfo时,在解读加载到场景中时如我们首先判断GameObjectInfo中的flag属性,然后flag值为Allways那么我们就将NodeBehaviour设置为Load,如果flag值为Dynamic那么参考如上第二点玩家移动时加载,flag值为Ignore那么我们就将NodeBehaviour设置为None。