Unity3D 一種開放世界物件序列化方案
阿新 • • 發佈:2019-02-13
開放世界遊戲,尤為生存遊戲,玩家自由建築系統是一個幾乎必不可少的功能。就算不是建築,那些迴圈生成的資源,也需要在儲存的時候序列化到檔案中去。
比如一堵牆,需要在執行時儲存他的位置,朝向和縮放等。
如果是一個物品箱,還需要儲存其保持的揹包資料資訊。所有者資訊等等。
同時這個系統還要滿足外掛式可擴充套件的需求。於是乎我設計了幾版序列化和 反序列化開放世界物件的方案。最終採納了下面的方案。
UML圖早已還給學校老師,姑且領會精神吧。
【1】對於任意一個需要序列化的開放世界中的物件,可以是建築物件如牆,可以是儲存箱子,可以是自動防禦炮塔,等等。需要那種功能,就繫結那種物件的指令碼,其中WorldObject是必須的指令碼,其他的都是擴充套件指令碼。
【2】WorldObject處理WorldObjectProfile中的基礎資料,而對應的擴充套件指令碼,處理對應的擴充套件資料
基本資料處理程式碼:
擴充套件資料程式碼如下public void Load(WorldObjectProfile profile) { GUID = profile.GUID; cachedTransform.position = profile.position; cachedTransform.eulerAngles = profile.eularAngles; cachedTransform.localScale = profile.localScale; } public void Save(WorldObjectProfile profile) { profile.GUID = GUID; string prefab_name = gameObject.name; if (prefab_name.Contains("(")) { prefab_name = prefab_name.Remove(prefab_name.IndexOf("(")); } profile.prefab_name = prefab_name.Trim(); profile.position = cachedTransform.position; profile.eularAngles = cachedTransform.eulerAngles; profile.localScale = cachedTransform.localScale; }
這樣,任意一個物件,就是可以隨意組合擴充套件的,靈活性有保障,耦合也低。public class StorageObject : MonoBehaviour, iArchiveWorldObject { public string inventory_name; public void Load(WorldObjectProfile profile) { StorageProfile sp = profile.GetProfile<StorageProfile>(); if (sp != null) { inventory_name = sp.inventoryName; } } public void Save(WorldObjectProfile profile) { StorageProfile storage = new StorageProfile(); storage.inventoryName = inventory_name; profile.AppendProfile(storage); } }
其他人使用這個小系統,也不需要知道其他部件是怎麼運作的,只需要知道如何擴充套件xxxxProfile和xxxObject,並實現IArchiveWorldObject介面即可。
【3】WorldObjectProfile實現擴充套件功能:
//一般來講
public const int C_DEFAULT_PROFILE_LIST_CAPACITY = 4;
public List<ProfileExtension> profile_list = new List<ProfileExtension>(C_DEFAULT_PROFILE_LIST_CAPACITY);
public T GetProfile<T>() where T : ProfileExtension
{
foreach (var prof in profile_list)
{
if (prof.type == typeof(T).ToString())
{
return prof as T;
}
}
return default(T);
}
public void AppendProfile(ProfileExtension new_profile)
{
new_profile.type = new_profile.GetType().ToString();
profile_list.Add(new_profile);
}
【4】世界管理器,存取物件的程式碼
儲存
public void SaveWorld()
{
WorldObject [] world_objects = GetComponentsInChildren<WorldObject>();
WorldProfile wp = new WorldProfile();
foreach (var wo in world_objects)
{
WorldObjectProfile wop = wo.SaveToProfile();
if (wop != null )
{
wp.Append(wop);
}
}
wp.Save(this.gameObject.name);
}
儲存世界,直接遍歷子節點,並全部序列化。當前版本暫時不支援記錄父子關係的功能,但是由於有全域性唯一GUID,這個功能會非常好實現。
讀取:
WorldObject CreateWorldObjectFromProfile(WorldObjectProfile profile, Transform parent)
{
WorldObject world_object = null;
Object obj_to_init = null;
string prefab_name = profile.prefab_name;
cached_object.TryGetValue(prefab_name, out obj_to_init);
if (obj_to_init == null)
{
obj_to_init = Resources.Load(prefab_name);
cached_object[prefab_name] = obj_to_init;
}
if (obj_to_init != null)
{
GameObject gobj = GameObject.Instantiate(obj_to_init, parent) as GameObject;
world_object = gobj.GetComponent<WorldObject>();
world_object.LoadFromProfile(profile);
}
else
{
Debug.LogError(string.Format("object to init with path {0} is not exist", prefab_name));
}
return world_object;
}
public void LoadWorld()
{
WorldProfile w_profile = WorldProfile.Load(this.gameObject.name);
if (w_profile == null)
{
w_profile = new WorldProfile();
}
foreach (var wop in w_profile.saved_object_list)
{
if (wop != null )
{
CreateWorldObjectFromProfile(wop, cachedTransform);
}
}
}
【5】具體的WorldObject儲存和讀取:
public WorldObjectProfile SaveToProfile()
{
WorldObjectProfile profile = new WorldObjectProfile();
if (archivers == null)
{
archivers = GetComponents<iArchiveWorldObject>();
}
foreach (var iArc in archivers)
{
iArc.Save(profile);
}
return profile;
}
public void LoadFromProfile( WorldObjectProfile profie )
{
archivers = GetComponents<iArchiveWorldObject>();
foreach (var iArc in archivers)
{
iArc.Load(profie);
}
}