2017-06-18 242 views
3

請閱讀整個問題,並在發佈答案前運行該示例。爲什麼在未加載的ScriptableObject資源中引用嵌套資源?


概述

我在Unity 5.6.1靜態編輯腳本加載嵌套資產(所以在標有[InitializeOnLoad]一類的靜態構造函數)時遇到了一些不一致的行爲。

我正在加載ScriptableObject資產Resources.Load,並且ScriptableObject具有對另一資產資源的公共引用,我們假設一個GameObject Prefab。從這一點來說,我將把ScriptableObject稱爲'包裝器',因爲在這個簡化的例子中,它是它唯一的目的。

雖然Resources.Load正確返回包裝,嵌套預製參考的往往不是第一次運行時沒有加載,但第二次運行後載入:

Screencap showing that the prefab is not loaded on the first run, but is on the second

我的理解是,這是一個數量級在靜態構造期間尚未加載有問題的Prefab資源的執行問題,並且在隨後的運行中它仍然被緩存。

我認爲在資產中加載帶有對另一資產的序列化引用的資產時,嵌套資產會默認自動加載,無論這是否在靜態初始化過程中。但是,這似乎並不是這種情況。

證明,包裝資產確實正確地引用預製在它的序列化數據(與Asset Serialization set to Force Text): Proof that prefab reference is correctly serialized

我也曾嘗試使用AssetDAtabase.LoadAssetAtPath(至少同時在編輯器),它並沒有作出一個區別。


示例項目

你可以下載一個UnityPackage here,它包含以下內容:

enter image description here

或者如下重現:

  • 腳本:

    ExampleWrapper.cs:

    using UnityEngine; 
    public class ExampleWrapper : ScriptableObject 
    { 
        public GameObject Value; 
    } 
    

    StaticLoader。CS:

    using UnityEngine; 
    #if UNITY_EDITOR 
    using UnityEditor; 
    [InitializeOnLoad] 
    #endif 
    public class Loader 
    { 
        static Loader() 
        { 
        var Wrapper = Resources.Load<ExampleWrapper>("Wrapper"); 
        Debug.Log(Wrapper);   // Prints the Wrapper ScriptableObject 
        Debug.Log(Wrapper.Value); // Prints the Wrapped GameObject 
        } 
    } 
    
  • 創建層次空 「ExampleObject」 遊戲物體,然後將其保存爲預製在Assets/Resources/ExampleObject.prefab

  • 創建ExampleWrapper,並在Assets/Resources/Wrapper.asset

    資產實例
    • 由於Unity 5不提供用於產生ScriptableObjects的UI,因此您可以創建您的own menu item或使用an automated solution。這個問題假定你對ScriptableObjects有足夠的熟悉,並擁有你自己的首選方法。
  • 包裝資產的Value字段設置爲ExampleObject預製 enter image description here

  • 注意,因爲有時候團結不正確緩存資產,


理由

示例這裏是故意簡化的,但它基於使用ScriptableObjects的實際項目來存儲/共享定製系統的配置數據。

不要回覆下列要求:

  • 「只是使用Object.Instantiate - 不改變結果,並修改由Resources.Load返回的原始對象在某些情況下是可取的。
  • 「跳過包裝,直接參考預製/手動加載」 - 雖然這繞過了加載問題,但它也忽略了問題的重點。增加抽象級別可以使系統之間的共享資源更易於維護。此外,這個問題不僅限於Prefabs(這裏僅用於簡單示例)。更現實的例子將包含多個嵌套的對象,如精靈,材料,其他ScriptableObjects等
  • 「靜施工期間不加載」 - 靜態系統通過統一的支持(?爲什麼別人提供[InitializeOnLoad]),並使用基於ScriptableObjects存儲這些系統的配置信息的資產是非常實際的用例。在完全重新設計系統之前,我想看看其他潛在的選擇。

尋找:

  • 是否有可能對我來說,強制統一預加載在包裝序列化的資產,當我在靜態情況下加載它像這樣,而無需通過路徑手動加載其內容?
  • 換句話說,我不想僅僅運行Resources.Load<GameObject>("ExampleObject"),因爲這會否定將它封裝在第一位的整個觀點。我很喜歡修改ExampleWrapper類,但任何可能的解決方案都需要足夠自動化,以便將預製件添加到檢查器的現場的工作流程將全部需要。

編輯:還應當指出的是古怪的是,當我關閉項目,打開它,我再次看到以下內容:

enter image description here

  • 在啓動過程中,靜態構造函數被調用一次,Wrapper被加載,並且嵌套預製實際加載正確。
  • 然後(仍然在初始啓動過程中,因爲它發生在我可以輸入任何動作之前),它再次靜態構造,並且這次當包裝被加載時,嵌套的預製件不是加載。

這個,我真的不明白。

+0

首先,你說'Resources.Load'不工作的第一次,那麼你說你不;噸甚至想要使用'Resources.Load' ...你想聽到什麼作爲答案?你想用什麼? – Programmer

+0

我想在包裝器上使用'Resource.Load',而不是在每個包裝的資源上手動調用'Resource.Load',因爲這會破壞將它們包裝在一個包裝器中的要點。請參閱Galandil的回答,尋找我正在尋找的有用信息。 – Johannes

回答

2

對您的問題存在誤解。

參考傳遞給Loader類,您可以在場景初始化完成後通過記錄Wrapper.Value來檢查它。

最有可能的,問題是在執行/串行化順序(正如你所指出),顯然它發生這樣的事情:

  • Loader構造函數被調用,並Wrapper引用傳遞正確。
  • Debug.Log(Wrapper.Value)回報null因爲腳本化的對象領域尚未連載
  • Wrapper的字段是序列化,現在登錄Wrapper.Value正確ExampleObject顯示。

所以,除非你打算什麼「特殊」做initalization在Wrapper領域,有真的不是在你的代碼中的問題:我想的ExampleWrapperOnEnable期間運行Debug.Log(Loader.Wrapper.Value),我得到了正確的價值。

關於你的編輯,顯然它發生「的設計」,在這個問題上明確提出:https://issuetracker.unity3d.com/issues/unityeditor-dot-initializeonload-calls-the-constructor-twice-when-the-editor-opens

+0

啊,這是有道理的。任何序列化值都尚未加載,因此即使添加「public int Blah」並將其更改爲靜態ctr中的默認值時也會導致默認值。 所以,這意味着就執行順序而言:'Static Init' - >'Apply Serialized Data' - >'Reset' - > etc ...? – Johannes

+0

是的確實我在'ExampleWrapper'中測試了一個'int',並且在開始時它總是記錄爲0。關於初始化期間的執行順序,您的猜測與我的一樣好,即我們無法確切知道發生了什麼,以及何時在Unity的幌子下。但是,可能'靜態'構造函數確實是第一個被執行的事情(Mono本地優先於Unity管理?沒有線索,真的)。 – Galandil

+0

有道理,謝謝您的反饋!另一個相關的資源,我的答案是:https://blogs.unity3d.com/2016/06/06/serialization-monobehaviour-constructors-and-unity-5-4/這確實表明,Unity - 真的 - 寧願不讓用戶在構造函數中加載資源。 – Johannes