1. 程式人生 > >Unity3D 一種開放世界物件序列化方案

Unity3D 一種開放世界物件序列化方案

開放世界遊戲,尤為生存遊戲,玩家自由建築系統是一個幾乎必不可少的功能。就算不是建築,那些迴圈生成的資源,也需要在儲存的時候序列化到檔案中去。

比如一堵牆,需要在執行時儲存他的位置,朝向和縮放等。

如果是一個物品箱,還需要儲存其保持的揹包資料資訊。所有者資訊等等。

同時這個系統還要滿足外掛式可擴充套件的需求。於是乎我設計了幾版序列化和 反序列化開放世界物件的方案。最終採納了下面的方案。


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);
            }
        }