Unity3d多場景編輯功能簡介


Unity

5.3中新增加了多場景編輯功能,允許用戶將一個大場景以某種邏輯分割成多個小場景并方便的編輯和管理。這在某些情況下會比較有用,是對Unity編輯器

對場景編輯能力的一個重要提升。本文將由Unity官方工程師張為,為大家介紹一些多場景編輯的基本功能以及一些實例。

什么是多場景編輯

多場景編輯就是允許用戶在Unity編輯器中同時打開多個場景,并對它們進行編輯。Unity提供了一系列的UI和Scripting APIs來管理這些場景。以下就是一個在Unity編輯器中進行多場景編輯的一個實例。

什么是場景

在進一步了解多場景編輯之前,我們先了解下什么是場景(Scene)。簡而言之場景就是包含了游戲對象的一個文件,比如Game objects,Components等。不過有一些對象可能一些用戶并不會太在意,那就是Scene Game managers。

Unity中有兩類Game Managers:

一類是Global Game Managers。它們是全局的Game

managers,包括AudioManager、InputManager、PhysicsManager等。當你在Unity5.3中發布你的游戲的

時候,你會發現在輸出目錄或者發布包里面會有個globalgamemanagers文件,它包含了所有全局Game managers。

另一類是Scene game managers。如果你將Editor Settings中的Asset Serialization選擇為”Force Text”模式,并打開一個已經保存的*.unity場景文件,你會發現前保存在文件最前面的就是SceneSettings、RenderSettings、LightMapSettings和NavMeshSettings,它們就是每個場景都會有的Game managers。

為什么需要多場景編輯

首先將大場景分割成多個場景,可以更好的支持場景的流式加載(Scene streaming);

其次可以更好地支持協同合作,尤其是在有源代碼版本管理的時候可以允許多人同時編輯而不會產生沖突;

再者支持卸載場景(Scene unloading),在5.3之前用戶可以通過Application.LoadLevelAddtive()和Application.LoadLevelAdditiveAsync()動態加載場景,但沒有對應的Application.UnloadScene()。而5.3中提供了場景卸載,讓用戶可以更靈活的管理多個場景。

多場景編輯基本功能介紹

接下來我們看看Unity在5.3中具體提供了哪些多場景編輯的功能。

Scene結構

在5.3之前,Unity中只有概念上的場景,而到5.3中我們引入了真正的Scene結構。它包含了name、path、isLoaded等變量,同時也提供了IsValid()以及GetRootGameObjects()方法。

Active Scene

在5.3中,我們引入了Active Scene(當前場景)的概念。引入它的目的在于:

如果有多個場景同時打開,我們會選擇Active Scene的Scene game managers作為當前的Scene game managers。比如在bake lightmapping的時候,我們會使用Active scene的LightMapSettings來bake當前打開的所有場景。

在創建Game object的時候,會默認加入到Active scene。

SceneManager

SceneManager在UnityEngine.SceneManagement之下,它是Runtime中的Scene manager,提供了以下方法:

LoadScene() / LoadSceneAsync()

它們允許用戶通過name、build index來加載場景。用戶可以在Build Settings窗口查看name和build index。通過這兩個方法加載的場景,要么被加到了Build Settings,要么存在于AssetBundle之中。如果是從AssetBundle中加載場景,則只能通過名字加載。

要說明的是如果有多個場景同名但位于不同的目錄之下,可以使用完整的路徑(不帶.unity后綴名)來加載不同的場景。

用戶可以通過LoadSceneMode來指定不同的加載模式。LoadSceneMode.Single在加載之前會卸載其他所有的場景,LoadSceneMode.Additive則是加載的時候不關閉之前的場景。

還有一點很重要的是LoadScene()并不是完全同步的,它只能保證在下一幀開始之前加載完畢。所以在此推薦大家使用LoadSceneAsync()這個異步的加載方法。

UnLoadScene()

目前5.3中用戶只能通過name和build index來同步的卸載一個Scene。在后續的版本中我們會提供通過Scene結構來卸載一個Scene,并且提供異步卸載的方法。

GetActiveScene() / SetActiveScene()

獲取和設置Active scene。

GetSceneAt() / GetSceneByName() / GetSceneByPath()

我們也提供了一組方法來查詢Scene。

其它

EditorSceneManager

EditorSceneManager在UnityEditor.SceneManagement之下,它是Editor中的Scene manager,提供了以下方法:

OpenScene()

它是一個同步的方法,用戶只能通過path來打開場景。不同于LoadScene() / LoadSceneAsync(),它可以直接打開一個存在于Assets目錄下的場景,不管它是否被添加到Build Settings。

用戶可以通過OpenSceneMode來指定不同的打開模式,相比較LoadSceneMode,它多了一個AdditiveWithoutLoading模式,允許用戶增加一個場景但并不真正加載它。

CloseScene()

顧名思義,它可以關閉一個場景,同時它提供了一個bool參數來指定關閉的時候是否將場景從Scene manager中移除。

SaveScene() / SaveScenes()

通過它們可以保存一個或多個Scene。

MarkSceneDirty() / MarkAllScenesDirty()

通過它們可以將某個指定的場景或者所有場景標記為Dirty。大部分情況Unity內部通過Undo系統來實現場景的Dirty跟蹤,但是有些模塊并沒有完全支持Undo,比如Terrain在設置某些參數的時候就不支持Undo。所以我們提供了這兩個方法支持直接將場景設置為Dirty。

其它

API的使用限制

在Editor mode下,UnityEngine.SceneManagement.SceneManager的某些方法是不能使用的。

LoadScene()

LoadSceneAsync()

CreateScene()

UnloadScene()

同樣在Play mode下,UnityEditor.SceneManagement.EditorSceneManager的某些方法也不能使用。

OpenScene()

NewScene()

CloseScene()

SaveScene() / SaveScenes() / …

MarkSceneDirty() / MarkAllScenesDirty()

如果用戶在不同的模式下使用了錯誤的方法,我們會在Console輸出對應的錯誤信息引導用戶使用正確的方法。

DontDestroyOnLoad Scene

在Unity 5.3中,如果用戶通過Object.DontDestroyOnLoad()方法將某個Game object標記成DontDestroyOnLoad,在進入Play mode的時候會發現Hierarchy窗口中會多出一個DontDestroyOnLoad場景,它包含了之前標記成DontDestroyOnLoad的Game object。

為什么需要DontDestroyOnLoad Scene

在Unity 5.3中,所有的Game objects必須隸屬于某一個場景。這樣我們必須有一個特別的場景來管理這些被標記為DontDestroyOnLoad的Game objects,否則在Unload這些Game objects所屬的場景的時候,這些Game objects也會被刪除掉。這顯然不是我們想要的結果。

因此我們引入了DontDestroyOnLoad Scene,當進入Play mode時候,我們會把所有標記為DontDestroyOnLoad的Game objects從所屬的場景移入到這個特別的場景之中。

DontDestroyOnLoad Scene的特點

DontDestroyOnLoad Scene僅僅存在于Runtime,或者是Play mode。它不能從外部訪問,僅僅是Unity內部用于管理標記為DontDestroyOnLoad的Game objects。

事實上從Unity 5.3開始,我們并不推薦用戶使用DontDestroyOnLoad這一功能,它使得我們內部的代碼邏輯復雜度增加了不少。5.3之前因為沒有多場景的支持,所以并沒有很好地辦法繞開它并實現相同的功能。而從5.3開始我們推薦用戶創建一個Manager場景,由它負責加載/卸載其它所有的游戲場景。它從游戲開始便存在一直到游戲退出,這樣所有需要被標記為DontDestroyOnLoad的Game objects都應該屬于這個場景。

多場景編輯的進階

接下來我們介紹一些關于多場景編輯的進階以及一些小Tips。

Scene Manager Setup

Scene Manager Setup可以用來保存并恢復當前的Scene hierarchy。EditorSceneManager上提供了GetSceneManagerSetup() / RestoreSceneManagerSetup()來獲取和恢復Scene hierarchy。

我們可以通過ScriptableObject來保存Scene hierarchy,如下代碼所示:

[AppleScript]純文本查看復制代碼

?

1

2

3

4

5publicclassSceneSetupSerialization:ScriptableObject

{

[SerializeField]

public SceneSetup[] SceneSetups;

}

以下的代碼展示了如何保存以及讀取Scene manager setup。

[C#]純文本查看復制代碼

string sceneSetupAssetPath = "Assets/SceneSetup.asset";

public static void SaveSceneSetups()

{

// Create SceneSetupSerialization

var sceneSetupSerialization = ScriptableObject.CreateInstance();

// Set SceneManagerSetup.

var sceneSetups = EditorSceneManager.GetSceneManagerSetup();

sceneSetupSerialization.SceneSetups = sceneSetups;

// Create and save the asset.

AssetDatabase.CreateAsset(sceneSetupSerialization, sceneSetupAssetPath);

AssetDatabase.SaveAssets();

}

public static void LoadSceneSetups()

{

// Restore SceneManagerSetup from the asset.

var sceneSetupSerialization = AssetDatabase.LoadMainAssetAtPath(sceneSetupAssetPath) as SceneSetupSerialization;

EditorSceneManager.RestoreSceneManagerSetup(sceneSetupSerialization.SceneSetups);

}

Lightmap & NavMesh Baking

在Unity 5.3中,Lightmap和NavMesh的烘焙都同時支持多個場景,它們之間的不同之處在于如何管理和劃分烘焙的結果。

對于Lightmap Baking,我們會根據Scenes劃分Lightmaps和Realtime GI數據。每個場景都只會加載和自己相關的那部分數據。

對于NavMesh Baking,因為它烘焙的結果很小,所以我們將NavMesh的數據保存在一個asset當中,每個場景都會引用到這個asset并能夠找到自己所關聯的那部分數據。

另外用戶也可以通過腳本進行Baking,Lightmapping.BakeMultipleScenes()和NavMeshBuilder.BuildNavMeshForMultipleScenes()都支持一次烘焙多個場景。

Scene Dirty Track

Unity內部大多通過Undo系統來實現Scene dirty追蹤。Unity 5.3為了支持多場景編輯,我們通過在Undo操作中保存Scene handles來擴展Undo系統。

另外我們增加了Undo.MoveGameObjectToScene()方法來支持場景之間Game object移動的Undo。同時Scene結構上面也有一個Scene.isDirty屬性用于查詢某個Scene是否被修改。

“Ctrl + S”的行為

在這里有一個問題想跟大家討論的是“Ctrl + S”的行為。在5.3以前,無論場景是否Dirty,只要用戶按下”Ctrl + S”,我們一定會保存該場景。而在5.3中,因為多場景編輯的引入,我們改變了這一行為。

從5.3開始,”Ctrl + S”只會保存Dirty的場景。試想如果用戶打開了上百個場景,只修改了其中某一個場景,如果“Ctrl + S”還是保存非Dirty的場景,保存速度會受到比較大的影響。

這個改動會影響到一些Editor的工具。比如某個Editor的工具創建了一個Game object,由于沒有使用Undo系統(Undo.RegisterCreatedObjectUndo),使得場景未標記成Dirty。這樣當在保存的時候,Unity并不會去真正保存這個場景。

在此推薦大家使用Undo系統來注冊Undo操作,從而能夠正確的將受影響的場景標記為Dirty。我們也樂于聽到大家的反饋,來看看我們是否有辦法更好的處理這個問題。

Scene加載的延遲Awaking

在多場景的使用中,一個比較有意思的地方就是Scene加載過程中的Delay awaking。在介紹它之前我們來看看Unity內部加載一個Scene所需的步驟。

Scene加載的兩個步驟

Unity內部場景的加載分為兩步:

Loading。是指從文件、內存(主要是Streamed scene

AssetBundle)中加載Scene的內容,創建并讀取所有相關的Game objects、Assets以及Scene game

managers。所有的IO操作都在這一步完成,所以它是比較耗時的過程。當這一步完成的時候,我們內部會將加載進度標記為90%。

Awaking。主要是一些輕量級的操作,比如在Transform的Awaking的時候,我們會將Game objects加入到它所屬于的Scene。

我們這里所說的Scene加載過程中的Delay awaking就是指第二步。

比如用戶有一個大場景劃分成了若干個子場景,在所有場景加載完畢我們才會開始Game play。這時我們就可以推遲所有子場景的Awaking。當所有的加載第一步完成了,我們才進行所有場景的Awaking。

用戶可以通過將AsyncOperation.allowSceneActivation設置成false來阻止Scene的Awaking,示例如下:

[C#]純文本查看復制代碼

?

1

2

3stringname = “TestScene”;

AsyncOperation operation = SceneManager.LoadSceneAsync(name, LoadSceneMode.Additive);

operation.allowSceneActivation =false;

當加載進度AsyncOperation.progress到達90%的時候,就可以將allowSceneActivation設置成true來允許Scene awaking。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容