[英]Unity - Saving non-sequential level data (Binary Serialization)
對於我的新游戲,我想要一個系統,其中我有幾個階段,每個階段都有一堆關卡。
我以為我可以在前一階段的所有關卡都通關后解鎖下一階段,但它並不真正適合游戲類型。
問題是我無法找到一種方法來在不使用多個保存文件的情況下在階段(stage1_1、stage1_2...)中順序解鎖關卡,因為在從另一個階段解鎖關卡后,列表會出現亂序。 我目前將它們存儲為列表。 這是它目前的設置方式:
public class SaveManager : MonoBehaviour
{
public static SaveManager manager;
public List<LevelData> levelData;
public GameStats gameStats;
private void Awake()
{
if(manager == null)
{
DontDestroyOnLoad(gameObject);
manager = this;
} else if(manager != this)
{
Destroy(gameObject);
}
}
private void OnEnable()
{
levelData = GetLevelData();
gameStats = GetGameStats();
}
public List<LevelData> GetLevelData()
{
string saveFilePath = Application.persistentDataPath + "/levels.dat";
if (File.Exists(saveFilePath))
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(saveFilePath, FileMode.Open);
List<LevelData> data = bf.Deserialize(file) as List<LevelData>;
file.Close();
Debug.Log("loaded!");
return data;
//Debug.Log(Application.persistentDataPath + "/save.dat");
} else
{
Debug.Log("set level defaults!");
return SetLevelDefaults();
}
}
public GameStats GetGameStats()
{
string saveFilePath = Application.persistentDataPath + "/gamestats.dat";
if (File.Exists(saveFilePath))
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(saveFilePath, FileMode.Open);
GameStats data = bf.Deserialize(file) as GameStats;
file.Close();
Debug.Log("loaded!");
return data;
//Debug.Log(Application.persistentDataPath + "/save.dat");
}
else
{
Debug.Log("set stats defaults!");
return SetStartingGameStats();
}
}
public void SaveLevelData()
{
string saveFilePath = Application.persistentDataPath + "/levels.dat";
Save(saveFilePath, "levels");
}
public void SaveGameStats()
{
string saveFilePath = Application.persistentDataPath + "/gamestats.dat";
Save(saveFilePath, "stats");
}
public List<LevelData> SetLevelDefaults()
{
// unlock level 1 and create the file
List<LevelData> ld = new List<LevelData>();
LevelData levelOne = new LevelData()
{
time = 0,
stars = 0,
unlocked = true
};
ld.Add(levelOne);
return ld;
}
public GameStats SetStartingGameStats()
{
return new GameStats()
{
money = 0
};
}
public void Save(string saveFilePath, string type)
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(saveFilePath, FileMode.Create);
switch (type)
{
case "levels":
bf.Serialize(file, levelData);
break;
case "stats":
bf.Serialize(file, gameStats);
break;
default:
break;
}
file.Close();
Debug.Log("saved!");
}
}
[Serializable]
public class LevelData
{
public int time;
public int stars;
public bool unlocked;
}
[Serializable]
public class GameStats
{
public int money;
// todo add powerups
}
然后,當一個級別完成時:
// only unlock the next level if it's not been unlocked yet
if (SaveManager.manager.levelData.Count - 1 == id)
SaveManager.manager.levelData.Add(nextLevelData);
SaveManager.manager.SaveLevelData();
並從菜單訪問它們:
if(SaveManager.manager.levelData.Count > levelID &&
SaveManager.manager.levelData[levelID] != null)
{
LevelData levelData = SaveManager.manager.levelData[levelID];
SetLevelTime(levelData.time);
SetLevelStars(levelData.stars);
// todo display best time
if (levelData.unlocked)
Unlock();
}
這就是我的情況。 這有點(太)混亂,我沒有意識到非順序解鎖級別的問題,所以此時我唯一的解決方案是在 SaveManager (stage1_levelData, stage2_levelData ...) 中創建不同的變量,但它沒有聲音高效。 我已經重寫了兩次,此時真的沒有想法了。
另外據我所知,字典不能被二進制序列化,我對嗎?
希望有人能指出我正確的方向:) 提前致謝!
您可能更願意使用二維數組,例如
private LevelData[,] levelData = new LevelData[X,Y];
在哪里
X
: 階段數Y
: 每個階段的級別數量稍后您可以使用例如訪問特定條目
LevelData[2,4].unlocked = true;
現在您已解鎖第 3 階段的第 5 級。
然而,我已經填充了整個數組,而不僅僅是添加您已經通過的級別。 與其留下null
條目並進行比較,不如添加額外的標志
public bool Completed;
並且已經使用有效的LevelData
條目初始化了整個數組(參見下面的示例)。 這避免了 NullReferences,另外還從一開始就將必要的存儲內存保持在相同的大小,從而在您第一次序列化的那一刻就已經保留了所需的存儲內存。
SaveManager.manager.levelData.Add(nextLevelData);
寧願成為
SaveManager.manager.levelData[stageID, levelID].unlocked = true;
無需創建LevelData
的新實例。
和
SaveManager.manager.levelData[levelID] != null
會成為
SaveManager.manager.levelData[stageID, levelID].Completed
優點:
您可以輕松地遍歷此數組並檢查值
在我建議使用兩個輸入StageID
和LevelID
。 由於在多維數組中,所有條目都是具有相同長度的數組,因此您可以使用單個平面索引輕松計算兩個值:
// EXAMPLE FOR INITIALLY FILLING THE ARRAY for(var i = 0; i < StageAmount * LevelsPerStageAmount; i++) { // using integer division var stageID = i / LevelsPerStageAmount; // using modulo // (starts over from 0 after exceeding LevelsPerStageAmount - 1) var levelID = i % LevelsPerStageAmount; var newLevelEntry = new LevelData; newLevelEntry.Completed = false; newLevelEntry.stars = -1; newLevelEntry.time = -1; newLevelEntry.unlocked = false; SaveManager.manager.levelData[stageID, levelID] = newLevelEntry; }
或者在另一個方向
// For example if you want to keep a continues level name var overallFlatIndex = stageID * LevelsPerStageAmount + levelID;
所以你仍然可以使用整體平坦指數——你只需要記住如何計算它。
在內存中,這樣的數組仍然是平面格式,因此可以簡單地序列化和反序列化:
using System.Runtime.Serialization.Formatters.Binary; ... LevelData[,] levelData = new LevelData[X,Y]{ .... }; BinaryFormatter bf = new BinaryFormatter(); MemoryStream ms = new MemoryStream(); bf.Serialize(ms, levelData);
注意:對於以后的更新(添加更多級別和階段),您可能不得不將存儲的數據反序列化為一個時間數組,然后將時間數組中的值復制到您的實際(現在更大)數組中;)
什么反對Dictionary
?
沒有用於(反)序列化Dictionary
標准方法。 通常,您必須將鍵和值序列化為分隔列表。 您可以在兩個不同的文件中或使用自定義類型執行此操作。 您可以決定是要存儲鍵值對列表,還是要存儲鍵和值的兩個列表。 無論哪種方式,這都有些麻煩。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.