前言
而達到項目優化的效果,在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