C# Unity-保存非顺序级别的数据(二进制序列化)
对于我的新游戏,我希望有一个系统,其中我有几个阶段,每个阶段都有一系列的级别 我想我可以在前一阶段的所有关卡都被清除后解锁下一阶段,但它并不真正适合游戏类型 问题是,我无法找到一种方法,让关卡在stage1_1、stage1_2内顺序解锁。。。不使用多个保存文件,因为在从另一个阶段解锁某个关卡后,列表将失去顺序。我目前正在将它们存储为列表。以下是当前的设置方式:C# Unity-保存非顺序级别的数据(二进制序列化),c#,unity3d,serialization,C#,Unity3d,Serialization,对于我的新游戏,我希望有一个系统,其中我有几个阶段,每个阶段都有一系列的级别 我想我可以在前一阶段的所有关卡都被清除后解锁下一阶段,但它并不真正适合游戏类型 问题是,我无法找到一种方法,让关卡在stage1_1、stage1_2内顺序解锁。。。不使用多个保存文件,因为在从另一个阶段解锁某个关卡后,列表将失去顺序。我目前正在将它们存储为列表。以下是当前的设置方式: public class SaveManager : MonoBehaviour { public static SaveManag
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
}
并从菜单中访问它们:
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…中创建不同的变量,但听起来效率不高。我已经重写了两次,在这一点上真的没有什么想法了
而且据我所知,字典不能进行二进制序列化,对吗
希望有人能给我指出正确的方向:
提前谢谢 您可能更愿意使用like,例如
private LevelData[,] levelData = new LevelData[X,Y];
在哪里
X:阶段数量
Y:每个阶段的级别数量
稍后,您可以使用访问特定条目,例如
LevelData[2,4].unlocked = true;
现在你解锁了第三阶段的第五关
不过,我已经填充了整个数组,而不仅仅是添加您已经通过的级别。与其保留空条目并进行比较,不如添加附加标志
public bool Completed;
并且已经使用有效的LevelData条目初始化了整个数组,请参见下面的示例。这避免了空引用,另外还从一开始就将必要的存储内存保持相同的大小,从而在您第一次序列化时就已经保留了所需的存储内存
SaveManager.manager.levelData.Add(nextLevelData);
宁愿成为
SaveManager.manager.levelData[stageID, levelID].unlocked = true;
不创建LevelData的新实例
及
将成为
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;
}
所以你仍然可以使用一个整体的平面指数-你只需要记住如何计算它
在内存中,这样的数组仍然是平面格式,因此可以简单地:
注意:对于以后添加更多级别和阶段的更新,您可能需要将存储的数据反序列化到临时数组中,然后将临时数组中的值复制到您现在实际更大的数组中
什么对字典不利
没有反序列化词典的标准方法。通常必须将键和值序列化为单独的列表。可以在两个不同的文件中或使用自定义类型执行此操作。然后,您可以决定是存储键值对列表,还是存储两个键值列表。无论哪种方式,这都有点麻烦。二进制数据是一个字节[]。字典的值可以是字节[]。在您的案例中,字典的键可以是一个字符串,它是级别和属性。您可能需要枚举级别A、枚举级别A=1、枚举级别B=2、枚举级别C=3、枚举级别D=4,它们可以有一个名称,并且可以将值转换为数字,以便您可以比较代码中是否大于某个级别。谢谢,我将尝试一下。我想知道为什么在许多关于相似主题的帖子中,他们总是反对连载词典。哇,非常感谢你的解释!我甚至不知道这是可能的。还有一个问题,因为您提到了在以后添加阶段和级别,我可能会在发布后这样做。这种解决方案是否能够处理具有不同级别的阶段?例如,如果我有一个特殊的阶段,与其他阶段不同,具有提供独特挑战的级别,它可能只有少数而不是40左右。您提到它们的长度相同,所以我想知道在这种情况下保留的(尽管未使用的)空间是否会太多。您可以在独立于数组的第二个列表中序列化它们,或者在当前数组中使用一个常用项,但不使用其余元素
SaveManager.manager.levelData[stageID, levelID].Completed
// 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);