Unity
5.3中新增加了多場(chǎng)景編輯功能刑桑,允許用戶將一個(gè)大場(chǎng)景以某種邏輯分割成多個(gè)小場(chǎng)景并方便的編輯和管理鳖目。這在某些情況下會(huì)比較有用洲敢,是對(duì)Unity編輯器
對(duì)場(chǎng)景編輯能力的一個(gè)重要提升败玉。本文將由Unity官方工程師張為蛀恩,為大家介紹一些多場(chǎng)景編輯的基本功能以及一些實(shí)例疫铜。
什么是多場(chǎng)景編輯
多場(chǎng)景編輯就是允許用戶在Unity編輯器中同時(shí)打開(kāi)多個(gè)場(chǎng)景,并對(duì)它們進(jìn)行編輯双谆。Unity提供了一系列的UI和Scripting APIs來(lái)管理這些場(chǎng)景壳咕。以下就是一個(gè)在Unity編輯器中進(jìn)行多場(chǎng)景編輯的一個(gè)實(shí)例。
什么是場(chǎng)景
在進(jìn)一步了解多場(chǎng)景編輯之前顽馋,我們先了解下什么是場(chǎng)景(Scene)谓厘。簡(jiǎn)而言之場(chǎng)景就是包含了游戲?qū)ο蟮囊粋€(gè)文件,比如Game objects寸谜,Components等竟稳。不過(guò)有一些對(duì)象可能一些用戶并不會(huì)太在意,那就是Scene Game managers熊痴。
Unity中有兩類(lèi)Game Managers:
一類(lèi)是Global Game Managers他爸。它們是全局的Game
managers,包括AudioManager果善、InputManager诊笤、PhysicsManager等。當(dāng)你在Unity5.3中發(fā)布你的游戲的
時(shí)候岭埠,你會(huì)發(fā)現(xiàn)在輸出目錄或者發(fā)布包里面會(huì)有個(gè)globalgamemanagers文件盏混,它包含了所有全局Game managers蔚鸥。
另一類(lèi)是Scene game managers。如果你將Editor Settings中的Asset Serialization選擇為”Force Text”模式许赃,并打開(kāi)一個(gè)已經(jīng)保存的*.unity場(chǎng)景文件止喷,你會(huì)發(fā)現(xiàn)前保存在文件最前面的就是SceneSettings、RenderSettings混聊、LightMapSettings和NavMeshSettings弹谁,它們就是每個(gè)場(chǎng)景都會(huì)有的Game managers。
為什么需要多場(chǎng)景編輯
首先將大場(chǎng)景分割成多個(gè)場(chǎng)景句喜,可以更好的支持場(chǎng)景的流式加載(Scene streaming)预愤;
其次可以更好地支持協(xié)同合作,尤其是在有源代碼版本管理的時(shí)候可以允許多人同時(shí)編輯而不會(huì)產(chǎn)生沖突咳胃;
再者支持卸載場(chǎng)景(Scene unloading)植康,在5.3之前用戶可以通過(guò)Application.LoadLevelAddtive()和Application.LoadLevelAdditiveAsync()動(dòng)態(tài)加載場(chǎng)景,但沒(méi)有對(duì)應(yīng)的Application.UnloadScene()展懈。而5.3中提供了場(chǎng)景卸載销睁,讓用戶可以更靈活的管理多個(gè)場(chǎng)景。
多場(chǎng)景編輯基本功能介紹
接下來(lái)我們看看Unity在5.3中具體提供了哪些多場(chǎng)景編輯的功能存崖。
Scene結(jié)構(gòu)
在5.3之前冻记,Unity中只有概念上的場(chǎng)景,而到5.3中我們引入了真正的Scene結(jié)構(gòu)来惧。它包含了name冗栗、path、isLoaded等變量供搀,同時(shí)也提供了IsValid()以及GetRootGameObjects()方法隅居。
Active Scene
在5.3中,我們引入了Active Scene(當(dāng)前場(chǎng)景)的概念趁曼。引入它的目的在于:
如果有多個(gè)場(chǎng)景同時(shí)打開(kāi)军浆,我們會(huì)選擇Active Scene的Scene game managers作為當(dāng)前的Scene game managers。比如在bake lightmapping的時(shí)候挡闰,我們會(huì)使用Active scene的LightMapSettings來(lái)bake當(dāng)前打開(kāi)的所有場(chǎng)景乒融。
在創(chuàng)建Game object的時(shí)候,會(huì)默認(rèn)加入到Active scene摄悯。
SceneManager
SceneManager在UnityEngine.SceneManagement之下赞季,它是Runtime中的Scene manager,提供了以下方法:
LoadScene() / LoadSceneAsync()
它們?cè)试S用戶通過(guò)name奢驯、build index來(lái)加載場(chǎng)景申钩。用戶可以在Build Settings窗口查看name和build index。通過(guò)這兩個(gè)方法加載的場(chǎng)景瘪阁,要么被加到了Build Settings撒遣,要么存在于AssetBundle之中邮偎。如果是從AssetBundle中加載場(chǎng)景,則只能通過(guò)名字加載义黎。
要說(shuō)明的是如果有多個(gè)場(chǎng)景同名但位于不同的目錄之下禾进,可以使用完整的路徑(不帶.unity后綴名)來(lái)加載不同的場(chǎng)景。
用戶可以通過(guò)LoadSceneMode來(lái)指定不同的加載模式廉涕。LoadSceneMode.Single在加載之前會(huì)卸載其他所有的場(chǎng)景泻云,LoadSceneMode.Additive則是加載的時(shí)候不關(guān)閉之前的場(chǎng)景。
還有一點(diǎn)很重要的是LoadScene()并不是完全同步的狐蜕,它只能保證在下一幀開(kāi)始之前加載完畢宠纯。所以在此推薦大家使用LoadSceneAsync()這個(gè)異步的加載方法。
UnLoadScene()
目前5.3中用戶只能通過(guò)name和build index來(lái)同步的卸載一個(gè)Scene层释。在后續(xù)的版本中我們會(huì)提供通過(guò)Scene結(jié)構(gòu)來(lái)卸載一個(gè)Scene婆瓜,并且提供異步卸載的方法。
GetActiveScene() / SetActiveScene()
獲取和設(shè)置Active scene湃累。
GetSceneAt() / GetSceneByName() / GetSceneByPath()
我們也提供了一組方法來(lái)查詢Scene勃救。
其它
EditorSceneManager
EditorSceneManager在UnityEditor.SceneManagement之下,它是Editor中的Scene manager治力,提供了以下方法:
OpenScene()
它是一個(gè)同步的方法,用戶只能通過(guò)path來(lái)打開(kāi)場(chǎng)景勃黍。不同于LoadScene() / LoadSceneAsync()宵统,它可以直接打開(kāi)一個(gè)存在于Assets目錄下的場(chǎng)景,不管它是否被添加到Build Settings覆获。
用戶可以通過(guò)OpenSceneMode來(lái)指定不同的打開(kāi)模式马澈,相比較LoadSceneMode,它多了一個(gè)AdditiveWithoutLoading模式弄息,允許用戶增加一個(gè)場(chǎng)景但并不真正加載它痊班。
CloseScene()
顧名思義,它可以關(guān)閉一個(gè)場(chǎng)景摹量,同時(shí)它提供了一個(gè)bool參數(shù)來(lái)指定關(guān)閉的時(shí)候是否將場(chǎng)景從Scene manager中移除涤伐。
SaveScene() / SaveScenes()
通過(guò)它們可以保存一個(gè)或多個(gè)Scene。
MarkSceneDirty() / MarkAllScenesDirty()
通過(guò)它們可以將某個(gè)指定的場(chǎng)景或者所有場(chǎng)景標(biāo)記為Dirty缨称。大部分情況Unity內(nèi)部通過(guò)Undo系統(tǒng)來(lái)實(shí)現(xiàn)場(chǎng)景的Dirty跟蹤凝果,但是有些模塊并沒(méi)有完全支持Undo,比如Terrain在設(shè)置某些參數(shù)的時(shí)候就不支持Undo睦尽。所以我們提供了這兩個(gè)方法支持直接將場(chǎng)景設(shè)置為Dirty器净。
其它
API的使用限制
在Editor mode下,UnityEngine.SceneManagement.SceneManager的某些方法是不能使用的当凡。
LoadScene()
LoadSceneAsync()
CreateScene()
UnloadScene()
同樣在Play mode下山害,UnityEditor.SceneManagement.EditorSceneManager的某些方法也不能使用纠俭。
OpenScene()
NewScene()
CloseScene()
SaveScene() / SaveScenes() / …
MarkSceneDirty() / MarkAllScenesDirty()
…
如果用戶在不同的模式下使用了錯(cuò)誤的方法,我們會(huì)在Console輸出對(duì)應(yīng)的錯(cuò)誤信息引導(dǎo)用戶使用正確的方法浪慌。
DontDestroyOnLoad Scene
在Unity 5.3中冤荆,如果用戶通過(guò)Object.DontDestroyOnLoad()方法將某個(gè)Game object標(biāo)記成DontDestroyOnLoad,在進(jìn)入Play mode的時(shí)候會(huì)發(fā)現(xiàn)Hierarchy窗口中會(huì)多出一個(gè)DontDestroyOnLoad場(chǎng)景眷射,它包含了之前標(biāo)記成DontDestroyOnLoad的Game object匙赞。
為什么需要DontDestroyOnLoad Scene
在Unity 5.3中,所有的Game objects必須隸屬于某一個(gè)場(chǎng)景妖碉。這樣我們必須有一個(gè)特別的場(chǎng)景來(lái)管理這些被標(biāo)記為DontDestroyOnLoad的Game objects涌庭,否則在Unload這些Game objects所屬的場(chǎng)景的時(shí)候,這些Game objects也會(huì)被刪除掉欧宜。這顯然不是我們想要的結(jié)果坐榆。
因此我們引入了DontDestroyOnLoad Scene,當(dāng)進(jìn)入Play mode時(shí)候冗茸,我們會(huì)把所有標(biāo)記為DontDestroyOnLoad的Game objects從所屬的場(chǎng)景移入到這個(gè)特別的場(chǎng)景之中席镀。
DontDestroyOnLoad Scene的特點(diǎn)
DontDestroyOnLoad Scene僅僅存在于Runtime,或者是Play mode夏漱。它不能從外部訪問(wèn)豪诲,僅僅是Unity內(nèi)部用于管理標(biāo)記為DontDestroyOnLoad的Game objects。
事實(shí)上從Unity 5.3開(kāi)始挂绰,我們并不推薦用戶使用DontDestroyOnLoad這一功能屎篱,它使得我們內(nèi)部的代碼邏輯復(fù)雜度增加了不少。5.3之前因?yàn)闆](méi)有多場(chǎng)景的支持葵蒂,所以并沒(méi)有很好地辦法繞開(kāi)它并實(shí)現(xiàn)相同的功能交播。而從5.3開(kāi)始我們推薦用戶創(chuàng)建一個(gè)Manager場(chǎng)景,由它負(fù)責(zé)加載/卸載其它所有的游戲場(chǎng)景践付。它從游戲開(kāi)始便存在一直到游戲退出秦士,這樣所有需要被標(biāo)記為DontDestroyOnLoad的Game objects都應(yīng)該屬于這個(gè)場(chǎng)景。
多場(chǎng)景編輯的進(jìn)階
接下來(lái)我們介紹一些關(guān)于多場(chǎng)景編輯的進(jìn)階以及一些小Tips永高。
Scene Manager Setup
Scene Manager Setup可以用來(lái)保存并恢復(fù)當(dāng)前的Scene hierarchy隧土。EditorSceneManager上提供了GetSceneManagerSetup() / RestoreSceneManagerSetup()來(lái)獲取和恢復(fù)Scene hierarchy。
我們可以通過(guò)ScriptableObject來(lái)保存Scene hierarchy乏梁,如下代碼所示:
[AppleScript]純文本查看復(fù)制代碼
1
2
3
4
5publicclassSceneSetupSerialization:ScriptableObject
{
[SerializeField]
public SceneSetup[] SceneSetups;
}
以下的代碼展示了如何保存以及讀取Scene manager setup次洼。
[C#]純文本查看復(fù)制代碼
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的烘焙都同時(shí)支持多個(gè)場(chǎng)景遇骑,它們之間的不同之處在于如何管理和劃分烘焙的結(jié)果卖毁。
對(duì)于Lightmap Baking,我們會(huì)根據(jù)Scenes劃分Lightmaps和Realtime GI數(shù)據(jù)。每個(gè)場(chǎng)景都只會(huì)加載和自己相關(guān)的那部分?jǐn)?shù)據(jù)亥啦。
對(duì)于NavMesh Baking炭剪,因?yàn)樗姹旱慕Y(jié)果很小,所以我們將NavMesh的數(shù)據(jù)保存在一個(gè)asset當(dāng)中翔脱,每個(gè)場(chǎng)景都會(huì)引用到這個(gè)asset并能夠找到自己所關(guān)聯(lián)的那部分?jǐn)?shù)據(jù)奴拦。
另外用戶也可以通過(guò)腳本進(jìn)行Baking,Lightmapping.BakeMultipleScenes()和NavMeshBuilder.BuildNavMeshForMultipleScenes()都支持一次烘焙多個(gè)場(chǎng)景届吁。
Scene Dirty Track
Unity內(nèi)部大多通過(guò)Undo系統(tǒng)來(lái)實(shí)現(xiàn)Scene dirty追蹤错妖。Unity 5.3為了支持多場(chǎng)景編輯,我們通過(guò)在Undo操作中保存Scene handles來(lái)擴(kuò)展Undo系統(tǒng)疚沐。
另外我們?cè)黾恿薝ndo.MoveGameObjectToScene()方法來(lái)支持場(chǎng)景之間Game object移動(dòng)的Undo暂氯。同時(shí)Scene結(jié)構(gòu)上面也有一個(gè)Scene.isDirty屬性用于查詢某個(gè)Scene是否被修改。
“Ctrl + S”的行為
在這里有一個(gè)問(wèn)題想跟大家討論的是“Ctrl + S”的行為亮蛔。在5.3以前痴施,無(wú)論場(chǎng)景是否Dirty,只要用戶按下”Ctrl + S”究流,我們一定會(huì)保存該場(chǎng)景辣吃。而在5.3中,因?yàn)槎鄨?chǎng)景編輯的引入芬探,我們改變了這一行為神得。
從5.3開(kāi)始,”Ctrl + S”只會(huì)保存Dirty的場(chǎng)景偷仿。試想如果用戶打開(kāi)了上百個(gè)場(chǎng)景循头,只修改了其中某一個(gè)場(chǎng)景,如果“Ctrl + S”還是保存非Dirty的場(chǎng)景炎疆,保存速度會(huì)受到比較大的影響。
這個(gè)改動(dòng)會(huì)影響到一些Editor的工具国裳。比如某個(gè)Editor的工具創(chuàng)建了一個(gè)Game object形入,由于沒(méi)有使用Undo系統(tǒng)(Undo.RegisterCreatedObjectUndo),使得場(chǎng)景未標(biāo)記成Dirty缝左。這樣當(dāng)在保存的時(shí)候亿遂,Unity并不會(huì)去真正保存這個(gè)場(chǎng)景。
在此推薦大家使用Undo系統(tǒng)來(lái)注冊(cè)Undo操作渺杉,從而能夠正確的將受影響的場(chǎng)景標(biāo)記為Dirty蛇数。我們也樂(lè)于聽(tīng)到大家的反饋,來(lái)看看我們是否有辦法更好的處理這個(gè)問(wèn)題是越。
Scene加載的延遲Awaking
在多場(chǎng)景的使用中耳舅,一個(gè)比較有意思的地方就是Scene加載過(guò)程中的Delay awaking。在介紹它之前我們來(lái)看看Unity內(nèi)部加載一個(gè)Scene所需的步驟。
Scene加載的兩個(gè)步驟
Unity內(nèi)部場(chǎng)景的加載分為兩步:
Loading浦徊。是指從文件馏予、內(nèi)存(主要是Streamed scene
AssetBundle)中加載Scene的內(nèi)容,創(chuàng)建并讀取所有相關(guān)的Game objects盔性、Assets以及Scene game
managers霞丧。所有的IO操作都在這一步完成,所以它是比較耗時(shí)的過(guò)程冕香。當(dāng)這一步完成的時(shí)候蛹尝,我們內(nèi)部會(huì)將加載進(jìn)度標(biāo)記為90%。
Awaking悉尾。主要是一些輕量級(jí)的操作突那,比如在Transform的Awaking的時(shí)候,我們會(huì)將Game objects加入到它所屬于的Scene焕襟。
我們這里所說(shuō)的Scene加載過(guò)程中的Delay awaking就是指第二步陨收。
比如用戶有一個(gè)大場(chǎng)景劃分成了若干個(gè)子場(chǎng)景,在所有場(chǎng)景加載完畢我們才會(huì)開(kāi)始Game play鸵赖。這時(shí)我們就可以推遲所有子場(chǎng)景的Awaking务漩。當(dāng)所有的加載第一步完成了,我們才進(jìn)行所有場(chǎng)景的Awaking它褪。
用戶可以通過(guò)將AsyncOperation.allowSceneActivation設(shè)置成false來(lái)阻止Scene的Awaking饵骨,示例如下:
[C#]純文本查看復(fù)制代碼
1
2
3stringname = “TestScene”;
AsyncOperation operation = SceneManager.LoadSceneAsync(name, LoadSceneMode.Additive);
operation.allowSceneActivation =false;
當(dāng)加載進(jìn)度AsyncOperation.progress到達(dá)90%的時(shí)候,就可以將allowSceneActivation設(shè)置成true來(lái)允許Scene awaking茫打。