前言
而达到项目优化的效果,在3维场景中漫游的时候,同大多的的2维场景游戏一样,需要在不同的区域需要加载出不同的资源对象。然而2维场景游戏和伪3D的类型的游戏由于视角有限及资源相对较小,加载资源基本都是计算视角区域,进行计算并并加载相关的资源。在3维场景中大多数还是整体加载为主以细节资源加载为辅助,一是能在进入场景后就可眼观全局,二是在进入一个小区域或房间后可以看到具体的资源。为些本文将介绍一个用于快速加载一种状态下的资源的模块,这个模块将不仅仅用于场景漫游,而将用于所有使用状态模式的3维程序。
一、功能介绍
如图1.1所示,利用ScrptObject制作了一个可视的窗口,如按扭所示,提供了prefab和bundle两种资源加载的方式。在配制过程中,可以直接将prefab资源拖动到其中,在不同的scriptObject之间也可以相互拖动。一但将资源放置其中,相关的信息会自动进行填充,除非需要重置其坐标或旋转到指定的位置。由于状态有包含交叉等情况,所以这里也会也状态一和状态二都需要共用状态的情况,如果你传入到状态一那么状态一和共用状态的资源都会加载出来。
(图1.1)
二、基本状态
由于状态的相互引用,为防止状态一引用共用状态,同时共用状态又引用状态一的问题,所以做了一定的处理。一当填充一个状态下所需要的资源时,如果已经存在那么就不会重复加载了,脚本源码如下:
private StateItem[] LoadBundleListGroupsItems(string stateName, List<string> loadedKeys = null)
{
if (loadedKeys == null)
{
loadedKeys = new List<string>() { stateName };
}
else if (!loadedKeys.Contains(stateName))
{
loadedKeys.Add(stateName);
}
else
{
return null;
}
List<StateItem> items = new List<StateItem>();
var find = bundleList.FindAll(x => x.stateName == stateName);
if (find != null)
{
var groups = find .ToArray();
foreach (var bitem in groups)
{
foreach (var sitem in bitem.itemList)
{
if (!string.IsNullOrEmpty(sitem.assetName) && !string.IsNullOrEmpty(sitem.assetBundleName))
{
items.Add(sitem);
}
}
///subState
foreach (var item in bitem.subStateNames)
{
var subItems = LoadBundleListGroupsItems(item, loadedKeys);
if (subItems != null)
{
items.AddRange(subItems);
}
}
}
}
return items.ToArray();
}
}
一但记录了指定的资源,就不会重复记录了。当需要一个状态下加载同一个预制体到不同的位置,也不会有问题,因为ID中包含了坐标的信息的HashCode.但一次状态加载如果加载了两个完全相同的资源就会报错,当然这也是为了防止资源重复加载。
三、缓存功能
在状态进行切换的时候,如果加载的比较慢,而且用户需要来回切换,那么最后能有暂时不销毁指定对象的方式,则会自动进行隐藏操作。控制的脚本如下:
/// <summary>
/// 计算当前需要下载的资源
/// </summary>
/// <param name="state"></param>
private void ResetLoadingState()
{
//停止正在下载的资源
itemLoadCtrl.CansaleLoadAllLoadingObjs();
needDownLand.Clear();
var loadedKeys = new string[loadedDic.Count];
loadedDic.Keys.CopyTo(loadedKeys, 0);
///删除新状态下不再需要的对象
foreach (var item in loadedKeys)
{
var info = CurrentItems.Find(x => x.ID == item);
if (info == null)
{
if (loadedDic[item] != null)
{
if (catchStates.Contains(lastState))
{
loadedDic[item].gameObject.SetActive(false);
if (log) Debug.Log("隐藏1:" + item);
}
else
{
delyDestroyObjects.Add(loadedDic[item]);
loadedDic.Remove(item);
if (log) Debug.Log("销毁1:" + item);
}
}
}
else
{
loadedDic[item].gameObject.SetActive(true);
if (log) Debug.Log("保留:" + item);
}
}
///记录需要加载的资源
for (int i = 0; i < CurrentItems.Count; i++)
{
var info = CurrentItems[i];
if (!loadedDic.ContainsKey(info.ID))
{
needDownLand.Enqueue(info);
}
}
}
}
实现这种功能后的效果如图3.1所示
四、资源类型
由于一些工程资源比较少,加载的时候也都是本机,所以没有必要使得AssetBundle,但有些工程需要从AssetBundle资源加载,并实现动态更新,所以这个模块也将支持两种加载的方式,在使用的时候,如果没有AssetBundleTools这个库,就只能使用Prefab加载了,为防止报错可将宏定义AssetBundleTools删除。
其中数据模型上,资源包加载和预制体加载都使用 以下这个类为模板:
public abstract class StateItem
{
protected string _id;
public abstract string ID { get; }
public bool reset;
public Vector3 position;
public Vector3 rotation;
}
可以自行定义坐标和放旋转等信息,如果需要其他信息,可自行添加。不过要在对应的绘制脚本中修改相应的绘制方式。
五、引擎Bug
在开发以上这个模块的时候,遇到了非常难过的一天。最终发现了一个非同寻常的Bug,目前在unity5.3和unity5.6上都会出现,在编辑器完美运行而在打包出来后,不论是pc端还是webgl端都不能正常运行。主要是序列化的问题。从发现问题到找到问题后,提炼bug如下
一个可序列化的类:
public class Main : MonoBehaviour {
public Child item;
}
一个父级带宏定义的类:
using UnityEngine;
public class Parent
{
#if UNITY_EDITOR
public int a;
#endif
}
[System.Serializable]
public class Child:Parent
{
public string c;
}
在空场景中将Main脚本挂在一个对象上,将a输入任意的一个负数,然后打包运行,你将会发现和
类似的问题,反正就是资源加载失败了
六、源码说明
由于开发过程积累的小的非核心的模块都放置在github上面了,兴趣的同行可以参看源码:
https://github.com/zouhunter/StateLoader