首頁 > 科技週邊 > IT業界 > 掌握Unity中的保存和加載功能5

掌握Unity中的保存和加載功能5

Joseph Gordon-Levitt
發布: 2025-02-19 09:21:10
原創
391 人瀏覽過

掌握Unity中的保存和加載功能5

感謝Vincent Quarle的友好訪問本文。 在本教程中,我們將完成遊戲中保存和加載功能的實現。在上一個關於“團結”中保存和加載玩家遊戲數據的教程中,我們成功保存了與玩家相關的數據,例如統計和庫存,但是現在我們將解決最困難的部分 - 世界對象。最終系統應該讓人聯想到上古捲軸遊戲 - 每個物體準確地保存在哪裡,無限期的時間。

> 如果您需要一個項目才能練習,請以下是我們在上一個教程中完成的項目的版本。它已經使用了一對源自產生的物品的遊戲內相互作用的對象進行升級 - 一把藥水和一把劍。它們可以產生並

拾取

>( despawned

),我們需要正確保存和加載其狀態。該項目的完成版本(已完全實施了保存系統)可以在本文的底部找到。

下載項目啟動文件 >項目github頁面 項目zip下載

鑰匙要點


>利用委託事件系統來通知對象何時保存其狀態,增強保存/加載系統的模塊化和靈活性。

>實現一個級別的主對象來管理對象的產卵和絕望,以確保世界對像在遊戲過程中保持其狀態。

>使用可序列化類來保存對象屬性(例如位置),可以輕鬆地擴展或修改針對不同類型的對象。 >

確保正確訂閱和取消訂閱以將事件保存在對像腳本中以避免錯誤並有效地管理遊戲狀態。
    開發一個可靠的系統,用於使用二進制格式來保存和加載,以處理複雜的數據結構並確保數據完整性。
  • 測試並擴展系統以包括新的對像類型和功能,證明了系統的適應性和針對不同遊戲開發需求的可伸縮性。
  • 實施理論
  • >我們需要在實現該對象之前分解保存和加載對象的系統。首先,我們需要某種將產生和絕望對象的對象
  • 級別的主體。它需要在級別中保存對象(如果我們正在加載級別而不是重新啟動),Despawn拾取對象,通知對象需要保存自己並管理對象列表。聽起來很多,所以讓我們將其放入流程圖中:
  • >基本上,整個邏輯都將對象列表保存到硬盤驅動器中 - 下次加載級別時,將在我們開始播放時產生的所有對象。聽起來很容易,但是魔鬼在細節中:我們如何知道要保存哪些對象,以及我們如何將它們產生回去?

    >

    >委託和事件

    >在上一篇文章中,我提到我們將使用委託事件系統通知對象需要保存自己的對象。讓我們首先解釋什麼是代表和事件。

    >

    >您可以閱讀官方代表文檔和官方事件文檔。但請放心:即使我也不了解官方文檔中的很多技術,所以我會用簡單的英語說:>

    委託

    >您可以將委託人視為函數藍圖。它描述了函數應該是什麼樣的:它的返回類型是什麼以及它接受的參數。例如:

    public delegate void SaveDelegate(object sender, EventArgs args);
    登入後複製
    登入後複製
    登入後複製
    登入後複製
    登入後複製
    這個委託描述了一個函數,該函數什麼都沒有返回(void),並接受.NET/MONO框架的兩個標準參數:代表事件發送者的通用對象,以及最終可以使用的參數,您可以使用這些數據傳遞各種數據。您真的不必擔心這一點,我們可以將(Null,Null)作為爭論,但必須在那裡。

    >這與事件如何結合?

    >

    >事件

    >您可以將事件視為。它僅接受與委託(藍圖)匹配的函數,並且您可以在運行時刪除並刪除函數。 然後,您可以隨時觸發事件,這意味著運行當前框中的所有功能 - 立即運行。考慮以下事件聲明: 此語法說:聲明公共事件(任何人都可以訂閱它 - 我們稍後會訂閱),該事件接受了Savedelegate委託所描述的功能(請參見上文),並稱為SaveEvent。

    >訂閱並取消訂閱

    public event SaveDelegate SaveEvent;
    登入後複製
    登入後複製
    登入後複製
    登入後複製
    訂閱事件基本上意味著“將功能放在框中”。語法很簡單。讓我們假設我們的事件在我們著名的全球視頻類別中聲明,並且我們有一些名為

    > potiondroppobable

    的藥水對像類,需要訂閱事件:>

    >讓我們在這裡解釋語法。我們首先需要具有符合描述的代表標準的函數。在對象的腳本中,有一個名為SaveFunction的函數。我們稍後會寫自己的書,但是現在我們假設它是將對象保存到硬盤驅動器中的工作功能,以便以後加載。

    >

    >當我們擁有它時,我們只是在腳本開始或清醒時將該功能放在框中,然後在被銷毀時將其刪除。 (取消訂閱非常重要:如果您嘗試調用被破壞對象的函數,則在運行時會得到null引用異常)。我們通過訪問已聲明的事件對象,然後使用添加操作員然後使用功能名稱來做到這一點。

    >注意:我們沒有使用括號或參數調用函數;我們只是使用該函數的名稱,別無其他。

    >

    因此,讓我們解釋一下這一切最終在遊戲的某個示例流中的作用。 >

    >邏輯流

    >假設,通過遊戲的流動,玩家的動作催生了世界上的兩把劍和兩個藥水(例如,玩家用戰利品打開了一個箱子)。

    >

    這四個對像在保存事件中註冊其功能:

    掌握Unity中的保存和加載功能5

    現在,假設玩家從世界上撿起一把劍和一把藥水。由於對像被“拾取”,因此它們有效地觸發了玩家庫存的變化,然後破壞了自己(有點毀了魔術,我知道):>

    掌握Unity中的保存和加載功能5>然後,假設玩家決定保存遊戲 - 也許他們確實需要回答現在響了三遍的手機(嘿,您做了一個很棒的遊戲):

    >

    掌握Unity中的保存和加載功能5基本上,發生的事情是框中的功能一個一個一個一個一個一個一個觸發的功能,並且在所有功能完成之前,遊戲才能運行到任何地方。每個“保存自身”的對象基本上都在列表中寫下自己 - 在下一個遊戲負載上,將由級別的主對象進行檢查,列表中的所有對像都將進行實例化(產生)。就是這樣:如果您到目前為止遵循了這篇文章,那麼您基本上就可以立即開始實施它。但是,我們將在這裡進行一些具體的代碼示例,一如既往,如果您想看看整個事物應該如何看,就會有一個完成的項目在文章結束時等待您。

    >代碼

    >讓我們首先介紹現有的項目,並熟悉內部的內容。

    >

    如您所見,我們擁有這些設計精美的盒子,它們充當我們已經提到的兩個對象的產卵器。這兩個場景中的每個場景都有一對。您可以立即看到問題:如果您過渡場景或使用

    f5/f9掌握Unity中的保存和加載功能5來保存/加載,則產生的對象將消失。

    盒子產卵器和產卵的對象本身使用了一個簡單的可交互接口機械師,該機制使我們能夠識別到這些對象,在屏幕上寫入文本,然後使用[e]鍵與它們進行交互。

    沒有更多的存在。我們在這裡的任務是:

    • 列出藥水
    • 的列表
    • 列出劍對象的列表
    • 實施全局保存事件
    • >使用保存功能訂閱事件
    • 實現級別主對象
    • 如果我們加載了遊戲,則
    • 使級別主產生所有保存的對象。
    • >
    如您所見,這並不像人們希望這樣的基本功能那樣微不足道。實際上,沒有現有的遊戲引擎(Cryengine,UDK,Unreal Engine 4,其他)確實可以使用簡單的保存/加載功能實現。這是因為,正如您可能想像的那樣,節省機制確實是每個遊戲的特定特定的。還有更多通常需要保存的對像類;它是世界各州,例如完成/積極的任務,派系友善/敵意,甚至在某些遊戲中的當前天氣狀況。它變得非常複雜,但是有了正確的節省機制的基礎,可以簡單地使用更多功能升級它。

    >

    >對像類列表

    >讓我們首先開始使用簡單的內容 - 對象列表。通過簡單表示序列化類中的數據來保存和加載我們的播放器數據。

    掌握Unity中的保存和加載功能5>以類似的方式,我們需要一些代表我們對象的可序列化類。要編寫它們,我們需要知道我們需要保存的屬性 - 對於我們的玩家,我們有很多東西要保存。幸運的是,對於對象,您很少需要比他們的世界地位更多。在我們的示例中,我們只需要保存對象的位置,而無需保存。

    為了很好地構建我們的代碼,我們將從一個簡單的類開始,從我們的序列化末尾開始:

    >

    >您可能想知道為什麼我們不簡單地使用基類。答案是我們可以,但是您永遠不知道何時需要添加或更改需要保存的特定項目屬性。此外,對於代碼可讀性而言,這要容易得多。
    public delegate void SaveDelegate(object sender, EventArgs args);
    登入後複製
    登入後複製
    登入後複製
    登入後複製
    登入後複製
    >

    到目前為止,您可能已經註意到我使用了很多詞> droppable

    。這是因為我們需要區分可輟學的(產生)對象和可放置的對象,這些對象遵循不同的保存和產卵規則。我們稍後再解決。

    > 現在,與玩家的數據不同,我們知道在任何給定時間實際上只有一個玩家,我們可以擁有多個諸如藥水之類的對象。我們需要列出一個動態列表,並表示此列表所屬的場景:我們不能在Level1中spawn Level2的對象。這很容易做到,再次在序列化中。在我們的最後一堂課下面寫這篇文章:

    public delegate void SaveDelegate(object sender, EventArgs args);
    登入後複製
    登入後複製
    登入後複製
    登入後複製
    登入後複製

    列出這些列表實例的最佳場所將是我們的全球控制類別:

    public event SaveDelegate SaveEvent;
    登入後複製
    登入後複製
    登入後複製
    登入後複製

    >現在我們的列表非常好:稍後我們將在需要產卵項目時從LevelMaster對象訪問它們,並從GlobalControl內部的硬盤驅動器中保存/加載,就像我們已經使用Player Data一樣。

    >委託和事件

    啊,終於。讓我們實施著名的活動。

    在GlobalControl中

    >

    //In PotionDroppable's Start or Awake function:
    GlobalObject.Instance.SaveEvent += SaveFunction;
    
    //In PotionDroppable's OnDestroy() function:
    GlobalObject.Instance.SaveEvent -= SaveFunction;
    
    //[...]
    public void SaveFunction (object sender, EventArgs args)
    {
     //Here is code that saves this instance of an object.
    }
    
    登入後複製
    登入後複製
    如您所見,我們正在使活動成為靜態參考,因此以後更邏輯且更易於工作。

    >

    >有關事件實施的最後一個說明:只有包含事件聲明的類才能解僱事件。任何人都可以通過訪問globalcontrol.saveevent = ...來訂閱它,但是只有GlobalControl類才能使用SaveEvent(null,null)觸發它。嘗試使用globalcontrol.saveevent(null,null);從其他地方導致編譯器錯誤!

    >

    就是事件實施!讓我們訂閱一些東西!

    >

    >事件訂閱

    >現在我們已經有事件,我們的對象需要訂閱它,換句話說,

    開始聆聽在觸發事件時進行反應。 >

    我們需要一個在事件觸發時運行的函數 - 對於每個對象。讓我們在

    pickups>文件夾中轉到PotionDroppoppable腳本。注意:Sword還沒有設置其腳本;我們將稍後完成! >

    在PotionDroppable中,添加以下內容:

    我們正確地進行了訂閱並取消訂閱活動。現在,問題仍然存在,
    [Serializable]
    public class SavedDroppablePotion
    {
        public float PositionX, PositionY, PositionZ;
    }
    
    [Serializable]
    public class SavedDroppableSword
    {
        public float PositionX, PositionY, PositionZ;
    }
    登入後複製
    登入後複製
    如何將此對象保存在列表中? > 我們首先需要確保我們有針對當前場景初始化的對象列表。 在GlobalControl.cs中: 此功能需要每個級別觸發一次。問題是,我們的全球控制範圍及其開始和清醒功能僅發射一次。我們將通過簡單地從我們的級別主對象調用此函數來解決這個問題,我們將在稍後創建。 我們將需要一個小的輔助功能來返回當前的活動場景列表。在GlobalControl.cs中:
    [Serializable]
    public class SavedDroppableList
    {
        public int SceneID;
        public List<SavedDroppablePotion> SavedPotions;
        public List<SavedDroppableSword> SavedSword;
    
        public SavedDroppableList(int newSceneID)
        {
            this.SceneID = newSceneID;
            this.SavedPotions = new List<SavedDroppablePotion>();
            this.SavedSword = new List<SavedDroppableSword>();
        }
    }
    登入後複製
    現在,我們確定我們始終有一個列表可以保存我們的物品。讓我們回到我們的藥水腳本:
        public List<SavedDroppableList> SavedLists = new List<SavedDroppableList>();
    
    登入後複製
        public delegate void SaveDelegate(object sender, EventArgs args);
        public static event SaveDelegate SaveEvent;
    
    登入後複製
    這是我們所有句法糖塗層真正發光的地方。當您需要時,這是非常可讀,易於理解且易於改變的需求!簡而言之,我們創建了一個新的“藥水”表示,並將其保存在實際列表中。

    創建一個級別的主對象

    首先,一小部分準備工作。在我們現有的項目中,我們有一個全局變量,可以告訴我們是否正在加載場景。但是,我們沒有這樣的變量來告訴我們是否使用門過渡。我們希望當我們返回上一個房間時,所有掉落的對象仍然存在,即使我們沒有在兩者之間的任何位置保存/加載遊戲。 為此

    在TransitionScript中

    >

    public delegate void SaveDelegate(object sender, EventArgs args);
    登入後複製
    登入後複製
    登入後複製
    登入後複製
    登入後複製

    我們已經準備好製作一個正常工作的levelmaster對象。

    >
    public event SaveDelegate SaveEvent;
    登入後複製
    登入後複製
    登入後複製
    登入後複製
    >現在,我們只需要讀取列表並在加載遊戲時從它們中產生對象。這就是級別主體對象所做的。讓我們創建一個新腳本,並將其稱為

    levelmaster

    >

    這是很多代碼,所以讓我們將其分解。 >

    >代碼僅在開始時運行,如果需要,我們用來在GlobalControl中初始化保存的列表。然後,我們詢問GlobalControl是否正在加載或過渡場景。如果我們重新啟動場景(例如新遊戲或類似的遊戲),那沒關係 - 我們沒有產生任何物體。
    //In PotionDroppable's Start or Awake function:
    GlobalObject.Instance.SaveEvent += SaveFunction;
    
    //In PotionDroppable's OnDestroy() function:
    GlobalObject.Instance.SaveEvent -= SaveFunction;
    
    //[...]
    public void SaveFunction (object sender, EventArgs args)
    {
     //Here is code that saves this instance of an object.
    }
    
    登入後複製
    登入後複製
    >

    如果我們

    加載場景,我們需要獲取我們的本地副本保存的對象列表(只是為了在重複訪問GlobalControl,>和的重複訪問時節省一些性能使語法更可讀)。

    接下來,我們只需遍歷列表,並在其中催生所有藥水對象。產卵的確切語法基本上是實例化方法過載之一。我們必須將實例化方法的結果投入到> gameObject(由於某種原因,默認返回類型是簡單的對象),以便我們可以訪問其轉換並更改其位置。 >這是對象產生的地方:如果您需要在產卵時間更改任何其他值,那麼這是這樣做的地方。 >

    我們需要將我們的級別主體放在每個場景中,並為其分配有效的預製:>

    >現在我們只缺少一個關鍵作品:我們需要實際啟動活動,將列表序列化為硬盤驅動器並從中讀取。我們將在GlobalControl中的現有保存和加載功能中簡單地做到這一點:

    這似乎也有很多代碼,但是其中大多數已經存在。 (如果您遵循我以前的教程,您將識別二進制序列化命令;這裡唯一的新事物是FiresaveEvent函數,還有一個保存我們列表的附加文件。就是這樣!

    >

    初始測試掌握Unity中的保存和加載功能5

    如果您現在運行項目,則每次點擊

    > f5

    and f9
    [Serializable]
    public class SavedDroppablePotion
    {
        public float PositionX, PositionY, PositionZ;
    }
    
    [Serializable]
    public class SavedDroppableSword
    {
        public float PositionX, PositionY, PositionZ;
    }
    登入後複製
    登入後複製
    >>>>>>>>>>>>>>>>>>>>> >>>>>>>>>>>這樣)。

    但是,還有一個問題要解決:我們沒有保存劍。

    這僅僅是為了演示如何在建立類似基礎的基礎後向項目中添加新的可保存的對象。

    擴展系統

    >因此,假設您已經有了一個新的對象覆蓋系統,就像我們已經對劍對象所做的一樣。它們目前的交互不多(基本物理學之外),因此我們需要編寫一個類似於藥水的腳本,這將使我們能夠“撿起”劍並使其正確保存。

    >當前要產生的劍的預製可以在資產> prefabs文件夾中找到。

    >讓它起作用。轉到資產>腳本>拾音器,在那裡您會看到potiondroppable腳本。旁邊,創建一個新的SwordDroppoppable腳本:

    public delegate void SaveDelegate(object sender, EventArgs args);
    登入後複製
    登入後複製
    登入後複製
    登入後複製
    登入後複製

    不要忘記“可相互作用”的接口實現。非常重要的是:如果沒有它,您的劍將不會被攝像機射線廣播所識別,並且將保持不可分割效果。另外,請仔細檢查劍前是否屬於項目層。否則,Raycast將再次忽略它。現在,將此腳本添加到劍前的第一個孩子(實際上具有網狀渲染器和其他組件):

    >

    掌握Unity中的保存和加載功能5

    現在,我們需要產生它們。在級別的大師中,在我們的循環下產生了藥水:

    public event SaveDelegate SaveEvent;
    登入後複製
    登入後複製
    登入後複製
    登入後複製

    …就是這樣。每當您需要保存的新項目類型時:

    • 添加Serializables類
    • 創建訂閱以保存事件
    • 的項目腳本
    • 在級別主持人中添加實例化邏輯。
    • >

    結論

    >目前,系統很粗糙:硬盤上有多個保存文件,對象的旋轉未保存(劍,玩家等),並且在場景過渡期間定位播放器的邏輯(但沒有)加載)有點古怪。

    一旦實心系統到位,這些現在都是要解決的小問題,我邀請您嘗試修改本教程和完成的項目,以查看您是否可以改善系統。

    ,但即使如此,它已經是一種在遊戲中進行保存/加載機制的可靠且堅實的方法 - 但是,它可能與這些

    >示例項目有很大不同。 如所承諾的,這是完成的項目,如果您需要參考,或者是因為您被卡在某個地方。根據本教程的說明和相同的命名計劃,將實現保存系統。

    下載完成的項目:

    Project GitHub頁面
    項目zip下載

    >在Unity中掌握保存和加載功能的常見問題(常見問題解答)5

    >在Unity 5中實現保存系統的最佳方法是什麼? PlayerPrefs是一種在遊戲會話之間存儲和檢索數據的簡單方法。它使您可以以整數,浮點和字符串的形式保存和加載數據。但是,重要的是要注意,PlayerPrefs不安全,不應用於敏感數據。對於更複雜或安全的數據,您可能需要考慮使用二進制格式或JSON Serialializer。在Unity 5中,可以使用PlayerPrefs,JSON序列化或二進制格式來實現。 PlayerPrefs是最簡單的方法,允許您保存和加載整數,浮點和字符串。 JSON序列化更為複雜,但可以提高靈活性和安全性。二進制格式是最安全的方法,但也是最複雜的方法。

    >執行順序如何影響Unity 5? >

    >如何在Unity 5?

    中保護保存數據5可以通過使用二進制格式或加密來實現。二進制格式將您的數據轉換為不容易讀取的二進制格式。加密通過以一種只能用特定鍵來解碼的方式來編碼您的數據來添加額外的安全性。

    >

    >在Unity 5中使用PlayerPrefs用於保存和加載功能的局限性是什麼? 🎜>雖然PlayerPrefs是在Unity 5中實現和加載功能的一種簡單便捷的方法,但它具有多個限制。首先,它僅支持整數,浮子和字符串。其次,它不安全,很容易被操縱。最後,playerPrefs具有尺寸限制,這對於具有大量數據的遊戲可能是一個問題。

    >我如何保存和加載Unity 5?

    保存和加載中的複雜數據結構使用JSON序列化或二進制格式可以實現Unity 5中的複雜數據結構。 JSON序列化使您可以將復雜的數據結構轉換為可以輕鬆保存和加載的字符串格式。二進制格式是一種更安全的方法,可將您的數據轉換為二進制格式。

    >我可以在Unity 5中的不同平台上保存和加載數據嗎?跨平台兼容性。例如,playerpRefs是跨平台兼容的,但是二進制格式可能並非在所有平台上兼容。

    >

    我如何在Unity 5?

    中加載問題的問題對問題進行故障排除。可以通過檢查腳本的執行順序,確保您的數據正確序列化或格式化並進行測試,從而在Unity 5中保存和加載功能。您打算在平台上發布的遊戲。可以通過最大程度地減少保存和加載的數據量,使用有效的數據結構,並確保您的腳本被良好地實現。

    如何實現Unity中的AutoSave功能5?

    可以通過創建一個腳本來自動保存遊戲的腳本,以定期或在特定事件中自動保存您的遊戲,可以在Unity 5中實現AutoSave功能。該腳本應使用相同的方法來保存數據與您的手動保存系統。

    >

以上是掌握Unity中的保存和加載功能5的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板