如果丟失格式、圖片或視頻,請查看原文:https://mp.weixin.qq.com/s/RDVMg6l41uc2IHBsscc0cQ
很多童鞋沒有系統的Unity3D游戲開發基礎,也不知道從何開始學。為此我們精選了一套國外優秀的Unity3D游戲開發教程,翻譯整理后放送給大家,教您從零開始一步一步掌握Unity3D游戲開發。 本文不是廣告,不是推廣,是免費的純干貨!本文全名:喵的Unity游戲開發之路 - 對象管理 - 多場景:場景加載
在播放模式下創建場景。
在場景之間移動對象。
處理多個場景。
支持游戲關卡。
這是有關對象管理的系列教程中的第四篇。這是關於將對象放置在自己的場景中,一次處理多個場景,以及加載和卸載場景。
本教程使用Unity 2017.4.4f1制作。
緩存場景
在播放模式下實例化許多形狀時,場景會迅速充滿對象,並且層次結構窗口可能會變得很混亂。這可能使查找特定對象變得困難,並且還可能降低編輯器的速度。
可以通過折疊場景層次結構或刪除層次結構窗口來防止潛在的編輯器變慢,但是之后我們就看不到對象了。理想情況下,我們可以將所有形狀實例折疊到層次結構窗口中的單個條目中,而其他所有內容保持可見。有兩種方法可以做到這一點。
第一種選擇是創建一個根對象,並使該對象的所有形狀成為子對象。然后,我們可以折疊根對象。不幸的是,這會在形狀改變時對我們的游戲性能產生負面影響。每當對象的活動狀態或變換狀態發生更改時,都會將該更改通知其所有父對象。因此,在並非絕對必要時,最好避免使對象成為另一個對象的子對象。
第二種選擇是將所有形狀放置在單獨的場景中。它們仍然是沒有父級的根對象,但成為額外場景的一部分,可以在層次結構窗口中折疊。場景不在乎對象的狀態,因此不會降低游戲的速度。這是我們將要使用的選項。
播放時創建場景
我們想要一個包含形狀實例的專用場景。由於形狀實例僅在播放模式下存在,因此場景也僅在我們處於播放模式時才需要存在。因此,我們將通過代碼而不是通過編輯器來創建一個。
ShapeFactory負責創建,銷毀和回收形狀,因此還應負責保存形狀的場景。要直接處理場景,它需要訪問UnityEngine.SceneManagement名稱空間中的代碼,因此請在ShapeFactory類文件中使用它。
using System.Collections.Generic;using UnityEngine;using UnityEngine.SceneManagement;[CreateAssetMenu]public class ShapeFactory : ScriptableObject { …}
我們將創建一個池場景以包含所有可以回收的形狀實例。所有工廠的形狀都進入該池,並且絕對不能從池中刪除。我們可以使用一個Scene字段來跟蹤此池場景。
Scene poolScene;
啟用回收后,我們只需要一個場景。當不回收資源時,可以由任何人來管理實例。因此,僅在需要池時才需要創建場景。因此,在CreatePools調用結束時SceneManager.CreateScene創建一個新場景並對其進行跟蹤。場景需要一個名稱,為此我們可以簡單地使用工廠名稱。如果您使用多個工廠,它們都會有自己的場景,因此請確保為每個工廠賦予唯一的名稱。
void CreatePools () {
…
poolScene = SceneManager.CreateScene(name);
}
現在,雖然我們尚未將形狀工廠放進去,但是在播放模式下我們第一次創建形狀時,會出現形狀工廠場景。當我們停止播放時,場景消失了。
將對象放入緩存場景
實例化游戲對象時,會將其添加到活動場景。在我們的例子中,活動場景是Scene,這是項目中唯一的持久場景。可以更改活動場景,但是我們不希望工廠弄亂場景。相反,我們可以在創建形狀后通過SceneManager.MoveGameObjectToScene將游戲對象和場景作為參數調用來將形狀遷移到緩存場景。
public Shape Get (int shapeId = 0, int materialId = 0) {
Shape instance;
if (recycle) {
…
if (lastIndex >= 0) {
…
}
else {
instance = Instantiate(prefabs[shapeId]);
instance.ShapeId = shapeId;
SceneManager.MoveGameObjectToScene(
instance.gameObject, poolScene
);
}
}
…
}
從現在開始,將形狀整齊地放置在“ 形狀工廠”場景中,您可以在層次結構窗口中將其折疊,也可以在要查看時保持打開狀態。
從重新編譯中恢復
至少在構建中或只要我們保持游戲模式,工廠就可以正常工作。不幸的是,在游戲模式下重新編譯會弄亂我們的回收和池場景。
雖然Unity MonoBehaviour在編譯時會序列化類型的私有字段,但不會對ScriptableObject類型執行此操作。這意味着重新編譯后池列表將丟失。這樣的結果是CreatePools重新編譯后將再次被調用。
我們不能只是標記pools為Serializable?
這將使Unity將池保存為資產的一部分,在編輯器播放會話之間將其持久保存,並將其包含在構建中。那不是我們想要的。
第一個明顯的問題是,我們嘗試再次創建池場景,該場景將失敗,因為具有該名稱的場景已經存在。我們可以通過Scene.isLoaded屬性檢查池場景是否已加載,以防發生這種情況。如果是這樣,我們將在創建場景之前放棄。
void CreatePools () {
…
if (poolScene.isLoaded) {
return;
}
poolScene = SceneManager.CreateScene(name);
}
這似乎不起作用。那是因為它Scene是一個結構,而不是對實際場景的直接引用。由於它不可序列化,因此重新編譯會將結構重置為其默認值,這表明場景已卸載。我們必須通過該SceneManager.GetSceneByName方法請求重新建立連接。
poolScene = SceneManager.GetSceneByName(name);
if (poolScene.isLoaded) {
return;
}
poolScene = SceneManager.CreateScene(name);
這可行,但是我們只需要在Unity編輯器(而不是構建)中進行操作即可。我們可以通過Application.isEditor屬性檢查是否在編輯器中。
if (Application.isEditor) {
poolScene = SceneManager.GetSceneByName(name);
if (poolScene.isLoaded) {
return;
}
}
poolScene = SceneManager.CreateScene(name);
第二個不太明顯的問題是,在重新編譯之前處於非活動狀態的形狀實例永遠不會被重用。那是因為我們丟失了跟蹤它們的列表。我們可以通過重新填充列表來解決此問題。首先,通過該Scene.GetRootGameObjects方法檢索包含池場景的所有根游戲對象的數組。
if (Application.isEditor) {
poolScene = SceneManager.GetSceneByName(name);
if (poolScene.isLoaded) {
GameObject[] rootObjects = poolScene.GetRootGameObjects();
return;
}
}
這不是創建一個臨時數組嗎?
是。還有一個使用list參數的變體,可用於避免使用臨時數組。但是我們在重新編譯后就進入了編輯器,因此,這里真的不需要擔心內存效率。
接下來,遍歷所有對象並獲取其形狀組件。由於這是工廠場景,因此只應包含形狀,因此我們總是得到一個組件。在此之后,如果引用錯誤為空,則表明其他地方存在問題。
if (poolScene.isLoaded) {
GameObject[] rootObjects = poolScene.GetRootGameObjects();
for (int i = 0; i < rootObjects.Length; i++) {
Shape pooledShape = rootObjects[i].GetComponent<Shape>();
}
return;
}
通過activeSelf其游戲對象的屬性檢查形狀是否處於活動狀態。如果它不處於活動狀態,則我們有一個形狀等待重用,必須將其添加到適當的池列表中。
Shape pooledShape = rootObjects[i].GetComponent<Shape>();
if (!pooledShape.gameObject.activeSelf) {
pools[pooledShape.ShapeId].Add(pooledShape);
}
我們不應該使用activeInHierarchy嗎?
不需要,因為我們正在處理根對象。
現在,我們的池可以通過在需要時進行自我重建來生存。
關卡1
場景不僅對在播放模式下對對象進行分組有用。通常,項目會分為多個場景。最明顯的配置是每個游戲關卡一個場景。但是游戲通常具有的對象不屬於單個關卡,而是屬於整個游戲。除了將這些對象的副本放置在每個場景中之外,還可以將它們放置在自己的場景中。這使您可以將項目分解為多個場景,但是需要在編輯時同時打開多個場景。
多場景編輯
我們將把游戲分為兩個場景。我們當前的場景是主場景,因此將其重命名為Main Scene。然后通過File / New Scene創建另一個名為Level 1的場景。這個新場景代表了我們游戲的第一層次。
現在打開關卡1場景,同時也保持主場景打開。這是通過將場景從項目窗口拖動到層次結構窗口中來完成的。在關卡1的場景將被添加以下主場景,就像我們的緩存一幕出現在播放模式。主場景以粗體顯示,因為它仍然是活動場景。如果現在進入播放模式,則最終會出現三個場景:主場景,關卡和工廠場景。
這個想法是,無論我們在哪個關卡玩游戲,主場景都包含運行游戲所需的一切。在我們的案例中,這就是主攝像機,游戲對象,存儲,畫布和事件系統。但是,我們將使照明取決於關卡水平。因此,請從“ 主要場景”中刪除燈光,並從“ 關卡1”中刪除相機。
場景照明
我們唯一更改的是將燈光放在一個單獨的場景中,該場景也是開放的。游戲應該像以前一樣運行。但是,有區別。事實證明,環境照明已經變得非常暗。
除了作為游戲對象的集合外,場景還具有自己的照明設置。環境照明發生了變化,因為主場景中不再有燈光,因此其環境照明變暗了。由於使用了活動場景的照明設置,因此我們得到了此結果。
關卡場景中有一個燈光,並帶有匹配的環境照明。因此,要固定照明,我們必須使“ 關卡1”成為活動場景。這可以通過層次結構窗口中每個場景的下拉菜單中的“ 設置活動場景”選項來完成。
在構建中包括多個場景
在關卡1作為活動場景的情況下,我們的游戲至少在編輯器中可以按預期工作。為了使它在構建中也能正常工作,我們必須確保兩個場景都包括在內。轉到“ 文件/構建設置”,並通過單擊“ 添加打開的場景”或將它們拖到“ 構建中的場景”列表中,確保添加了兩個場景。確保主場景的索引為0,關卡1的索引為1。
從現在開始,兩個場景都被添加到構建中,即使它們在構建時未打開也是如此。您可以通過其下拉菜單中的“ 卸載場景”選項來卸載場景。這會將其保留在層次結構窗口中,但被禁用。
您也可以使用“ 刪除場景”選項。它將卸載並將其從層次結構窗口中刪除。它不會將其從項目中刪除。
加載場景
即使兩個場景都包含在構建中,運行游戲構建時也只會加載第一個場景(索引為0)。這與進入播放模式時僅在編輯器中打開主場景相同。為了確保同時加載兩個關卡,我們必須手動加載Level 1。
向Game添加LoadLevel方法。在其中,以關卡名稱作為參數進行調用SceneManager.LoadScene。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Game : PersistableObject {
…
void LoadLevel () {
SceneManager.LoadScene("Level 1");
}
…
}
我們的游戲沒有啟動畫面,徽標簡介或主菜單,因此在喚醒時立即加載關卡。
void Awake () {
shapes = new List<Shape>();
LoadLevel();
}
這沒有理想的效果。Unity卸載所有當前打開的場景,然后加載請求的場景。結果是我們最終只剩下了輕的物體。這等效於在編輯器中雙擊場景。
我們想要的是除了已經加載的場景外,還加載關卡場景,就像我們之前在編輯器中所做的一樣。可以通過提供SceneManager.LoadScene額外的參數LoadSceneMode.Additive來完成此操作。
void LoadLevel () {
SceneManager.LoadScene("Level 1", LoadSceneMode.Additive);
}
在沒有加載關卡1的情況下,在編輯器中嘗試一下。它可以工作,但是不幸的是,環境照明仍然不正確,盡管這次很難發現。有點太黑了。
再次,我們必須通過代碼確保關卡1是活動場景。通過調用帶有Scene參數的SceneManager.SetActiveScene來完成。我們可以通過SceneManager.GetSceneByName獲取所需的場景數據。
void LoadLevel () {
SceneManager.LoadScene("Level 1", LoadSceneMode.Additive);
SceneManager.SetActiveScene(SceneManager.GetSceneByName("Level 1"));
}
不幸的是,這會導致錯誤。SceneManager.SetActiveScene僅適用於已加載的場景,即使我們剛剛調用LoadScene,它顯然也不適用。那是因為加載場景需要一些時間。場景僅在下一幀上完全加載。
等待幀
由於加載的場景不會立即完全加載,因此我們必須等到下一幀才能將其設為活動場景。最簡單的方法是LoadLevel變成協程。然后,我們可以在調用LoadScene和SetActiveScene之間產生一次,並增加一個幀的延遲。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Game : PersistableObject {
…
void Awake () {
shapes = new List<Shape>();
StartCoroutine(LoadLevel());
}
…
IEnumeratorLoadLevel () {
SceneManager.LoadScene("Level 1", LoadSceneMode.Additive);
yield return null;
SceneManager.SetActiveScene(SceneManager.GetSceneByName("Level 1"));
}
…
}
烘烤燈
盡管關卡1現在可以正確地變為活動場景,但我們仍然無法獲得正確的環境照明。至少不是在編輯器中。構造很好,因為可以正確包含所有照明。但是在編輯器中,以播放模式加載場景時,自動生成的照明數據無法正常工作。為了確保編輯器中的照明正確,我們必須關閉自動設置選項,該選項位於照明設置的底部,根據Unity版本,通過窗口/照明/設置或窗口/渲染/照明設置打開。
打開關卡1場景,確保它是活動場景,然后單擊生成照明。Unity將烘焙照明數據並將其保存在場景資產旁邊的文件夾中。
這些設置是針對每個場景的。您只需要手動烘焙關卡1即可。我們不使用主場景的照明數據,因此您可以將其保留為自動生成模式。
異步加載
加載場景需要多長時間取決於它包含多少內容。在我們的例子中,它是單個光源,因此加載非常快。但總的來說,加載可能需要一段時間,這會凍結游戲直到完成。為避免這種情況,可以通過SceneManager.LoadSceneAsync異步加載場景。這將開始加載場景的過程,並返回AsyncOperation對象引用,該對象引用可用於檢查場景是否已完成加載。或者,它可用於產生協程。讓我們這樣做,而不是只產生一幀。
IEnumerator LoadLevel () {
//SceneManager.LoadScene("Level 1", LoadSceneMode.Additive);
//yield return null;
yield return SceneManager.LoadSceneAsync(
"Level 1", LoadSceneMode.Additive
);
SceneManager.SetActiveScene(SceneManager.GetSceneByName("Level 1"));
}
現在我們的游戲在加載關卡時不會凍結。這意味着Update在加載關卡並成為活動場景之前,我們的游戲方法有可能被任意調用了幾次。這是一個問題,因為它使玩家可以在加載關卡之前發出命令。為避免這種情況,Game組件必須在開始加載過程之前禁用自身,並在加載完成后再次啟用自身。
IEnumerator LoadLevel () {
enabled = false;
yield return SceneManager.LoadSceneAsync(
"Level 1", LoadSceneMode.Additive
);
SceneManager.SetActiveScene(SceneManager.GetSceneByName("Level 1"));
enabled = true;
}
在更復雜的游戲中,您還將在這些位置顯示和隱藏加載屏幕。
防止雙重裝載
游戲開始時加載關卡工作正常,但是如果我們已經在編輯器中打開關卡場景,則在進入游戲模式時最終會再次加載關卡場景。
因為我們的關卡場景包含一盞燈,所以我們最終只能使用兩盞燈,從而導致亮度過高。
同樣,這只是在編輯器中工作時的問題。但這是我們應該處理的事情,因為您通常使用開放關卡場景並進入播放模式進行測試。您不希望第二次加載關卡。
為防止雙重加載場景,請在Awake調用LoadLevel中檢查它是否已加載。如果已加載,請確保它是活動場景,然后中止。
void Awake () {
shapes = new List<Shape>();
Scene loadedLevel = SceneManager.GetSceneByName("Level 1");
if (loadedLevel.isLoaded) {
SceneManager.SetActiveScene(loadedLevel);
return;
}
StartCoroutine(LoadLevel());
}
再次,這是行不通的,因為尚未將場景標記為已加載。嘗試尚為時過早,但是如果我們稍稍延遲一下,而是改用一種方法Start,它會奏效。對於場景中的所有游戲對象都是如此。場景加載后Awake立即被調用,但尚未算作加載。Starth和Update在場景正式加載后調用。
//void Awake () {
void Start () {
shapes = new List<Shape>();
…
}
僅當我們在編輯器中時,所有這些都是必需的。
void Start () {
shapes = new List<Shape>();
if (Application.isEditor) {
Scene loadedLevel = SceneManager.GetSceneByName("Level 1");
if (loadedLevel.isLoaded) {
SceneManager.SetActiveScene(loadedLevel);
return;
}
}
StartCoroutine(LoadLevel());
}
更多關卡
有些游戲只有一個關卡,但通常有多個關卡。因此,讓我們添加另一個關卡,並使其能夠在它們之間進行切換。
關卡2
要創建關卡2,您可以復制關卡1場景並將其命名為關卡2。為了使它們在視覺上可區分,請打開新場景並調整其光線。例如,將其X旋轉設置為1而不是50,表示太陽正好位於地平線上方。然后烘烤關卡2照明。
還將關卡2添加到構建中,為其分配構建索引2。
檢測場景加載
雖然可以同時打開兩個關卡場景,但只使用一個關卡是很有意義的。打開多個關卡以復制或移動內容可能很方便,但這應該是暫時的。進入播放模式時,除了主要場景外,我們希望沒有或只有一個關卡打開。當關卡1打開時,此方法工作正常,但如果關卡2打開,則當我們進入播放模式時,關卡1也將加載。
為了防止這種情況的發生,我們必須在中調整電平檢測Game.Start。無需顯式檢查關卡1,我們必須檢查任何關卡。當前,我們有兩個關卡,但是我們應該至少支持幾個關卡。
我們可以做的是要求所有關卡場景的名稱都包含單詞Level,后跟一個空格。然后,我們可以遍歷所有當前加載的場景,檢查場景名稱是否包含所需的字符串,如果是,則將其設為活動場景。
要遍歷所有加載的場景,我們可以使用SceneManager.sceneCount屬性獲取計數,並使用SceneManager.GetSceneAt方法獲取特定索引處的場景。
void Start () {
shapes = new List<Shape>();
if (Application.isEditor) {
//Scene loadedLevel = SceneManager.GetSceneByName("Level 1");
//if (loadedLevel.isLoaded) {
// SceneManager.SetActiveScene(loadedLevel);
// return;
//}
for (int i = 0; i < SceneManager.sceneCount; i++) {
Scene loadedScene = SceneManager.GetSceneAt(i);
if (loadedScene.name.Contains("Level ")) {
SceneManager.SetActiveScene(loadedScene);
return;
}
}
}
StartCoroutine(LoadLevel());
}
現在,如果碰巧在編輯器中將其打開,則游戲將保持關卡2不變。這使得直接玩任何關卡成為可能,而無需進行游戲內關卡選擇。
加載特定關卡
為了在游戲中加載特定關卡,我們必須進行調整LoadLevel。因為我們只有幾個關卡,所以我們可以手動將它們分配給構建,從而賦予關卡1構建索引1、2 關卡索引2,依此類推。要加載這些關卡之一,我們必須添加關卡的構建索引作為參數。然后,我們在加載場景時使用該索引,並使用GetSceneByBuildIndex代替GetSceneByName。
IEnumerator LoadLevel (int levelBuildIndex) {
enabled = false;
yield return SceneManager.LoadSceneAsync(
levelBuildIndex, LoadSceneMode.Additive
);
SceneManager.SetActiveScene(
SceneManager.GetSceneByBuildIndex(levelBuildIndex)
);
enabled = true;
}
默認情況下,我們在中加載Start具有構建索引1 的第一關卡。
void Start () {
…
StartCoroutine(LoadLevel(1));
}
我們該如何應對許多層次?
如果游戲具有多個關卡,那么將它們放在單獨的資產捆綁包中(可能可以按需下載)更加實用。這也使得以后可以更新或添加游戲關卡。資產捆綁包不在本教程中。
選擇關卡
對於簡單的小型游戲,我們將使用最直接的方法來選擇一個關卡。只需按數字鍵即可加載相應的電平。這最多可用於九個關卡。為了輕松調整我們支持的關卡,請在Game上添加一個關卡計數字段,然后通過檢查器將其設置為2。
public int levelCount;
現在我們必須檢查玩家是否按下數字鍵之一來加載關卡。我們可以通過遍歷所有有效的構建索引來做到這一點。相應的鍵代碼是KeyCode.Alpha0加索引。如果按下該鍵,則開始加載該關卡,然后跳過該Update方法的其余部分。
void Update () {
…
else if (Input.GetKeyDown(loadKey)) {
BeginNewGame();
storage.Load(this);
}
else {
for (int i = 1; i <= levelCount; i++) {
if (Input.GetKeyDown(KeyCode.Alpha0 + i)) {
StartCoroutine(LoadLevel(i));
return;
}
}
}
…
}
我們不能以這種方式支持十個關卡嗎?
是的,如果對第十項進行特殊檢查,則綁定到零鍵。
卸載關卡
現在,我們可以在播放模式下在兩個關卡之間切換,但是每次加載關卡時,我們都會獲得一個開放的場景,添加到當前打開的關卡中,而不是替換它們。發生這種情況是因為我們加了場景。
我們可以通過跟蹤當前已加載關卡的構建索引來防止這種情況。因此,為其添加一個字段。
int loadedLevelBuildIndex;
如果我們以已加載的關卡場景開始,則Start初始化中的索引。否則,它將保持其默認值零。
if (loadedScene.name.Contains("Level ")) {
SceneManager.SetActiveScene(loadedScene);
loadedLevelBuildIndex = loadedScene.buildIndex;
return;
}
完成加載關卡后,還更新此索引。
IEnumerator LoadLevel (int levelBuildIndex) {
…
loadedLevelBuildIndex = levelBuildIndex;
enabled = true;
}
現在,我們可以在開始加載關卡之前檢查索引是否為非零。如果是這樣,則已經存在一個關卡場景。我們必須首先卸載該場景,這是通過調用SceneManager.UnloadSceneAsync舊索引來異步完成的。在繼續加載下一個關卡之前,要降低卸載的產量。
IEnumerator LoadLevel (int levelBuildIndex) {
enabled = false;
if (loadedLevelBuildIndex > 0) {
yield return SceneManager.UnloadSceneAsync(loadedLevelBuildIndex);
}
yield return SceneManager.LoadSceneAsync(
levelBuildIndex, LoadSceneMode.Additive
);
SceneManager.SetActiveScene(
SceneManager.GetSceneByBuildIndex(levelBuildIndex)
);
loadedLevelBuildIndex = levelBuildIndex;
enabled = true;
}
最后,將關卡的加載視為新游戲的開始是有道理的,它擺脫了所有已生成的對象。因此,在加載另一個關卡之前調用BeginNewGame。
if (Input.GetKeyDown(KeyCode.Alpha0 + i)) {
BeginNewGame();
StartCoroutine(LoadLevel(i));
return;
}
如果我們要再次加載同一關卡,是否可以跳過加載關卡?
假設當前已加載關卡2,並且玩家按下2按鈕。然后,一個新的游戲開始,關卡2被卸載,然后關卡2被加載。我們可以只開始一個新游戲,而不必跳過同一場景的卸載和加載嗎?
就我們而言,目前的答案是肯定的。場景僅包含一個燈光。比賽過程中什么都沒有改變。通常,答案通常是“否”,因為場景的狀態可能會發生很大變化。可以將關卡重置為初始狀態,而不是重新加載它,但是值得一試的是值得懷疑的。由於我們的關卡加載非常快,因此我們只需重新加載即可。
記住關卡
此時,我們可以在游戲過程中在關卡之間切換,但是保存和加載游戲仍然會忽略關卡。結果,我們可以將形狀保存在一個關卡中,然后將其加載到另一個關卡中。我們必須確保游戲記住已保存的關卡。
保存關卡
保存關卡要求我們將其他數據添加到保存文件中,從而使其與我們的游戲的舊版本不兼容。因此,將保存版本從1增加到2。
const int saveVersion =2;
當游戲保存自身時,現在還要編寫當前打開關卡的構建索引。讓我們在形狀計數之后但在編寫形狀之前執行此操作。
public override void Save (GameDataWriter writer) {
writer.Write(shapes.Count);
writer.Write(loadedLevelBuildIndex);
for (int i = 0; i < shapes.Count; i++) {
…
}
}
這種方法依賴於我們關卡的特定構建索引,因此在此之后我們不能在不破壞向后兼容性的情況下更改它們,就像我們無法更改工廠預制件的順序一樣。
加載關卡
加載時,我們像往常一樣首先處理形狀計數的特殊情況。然后讀取關卡構建索引,除非我們有一個較舊的保存文件,在這種情況下,我們總是加載關卡1。然后立即開始加載該關卡。
public override void Load (GameDataReader reader) {
…
int count = version <= 0 ? -version : reader.ReadInt();
StartCoroutine(LoadLevel(version < 2 ? 1 : reader.ReadInt()));
for (int i = 0; i < count; i++) {
…
}
}
通過這種方法,我們開始加載關卡,同時仍然需要讀取和創建所有形狀。由於關卡加載是異步的,因此當我們讀取和創建形狀時,當前關卡場景仍然是加載的場景。直到稍后,正確的關卡才會被加載並激活場景。這不是問題,因為我們將所有形狀放置在單獨的工廠場景中,並且它們不依賴於關卡中的任何內容。將來這可能會改變,從而使此過程更加復雜。我們將在需要時處理該問題。
下一個教程是 生成區:各色場景。
資源庫(Repository)
https://bitbucket.org/catlikecodingunitytutorials/object-management-04-multiple-scenes
往期精選
Unity3D游戲開發中100+效果的實現和源碼大全 - 收藏起來肯定用得着
Shader學習應該如何切入?
喵的Unity游戲開發之路 - 從入門到精通的學習線路和全教程
聲明:發布此文是出於傳遞更多知識以供交流學習之目的。若有來源標注錯誤或侵犯了您的合法權益,請作者持權屬證明與我們聯系,我們將及時更正、刪除,謝謝。
原作者:Jasper Flick
原文:
https://catlikecoding.com/unity/tutorials/object-management/multiple-scenes/
翻譯、編輯、整理:MarsZhou
More:【微信公眾號】 u3dnotes