我和一个小组一起做游戏项目已经有一段时间了,我们遇到了障碍。这款游戏的功能之一是用户可以通过使用游戏内编辑器生成自己的关卡。编辑器创建一个Level对象,该对象存储Level的长度和宽度以及Tile对象的二维数组。我们已经成功地实现了相机系统,可以在没有太多困难的情况下编辑一个简单的概念关卡,但成功保存关卡并稍后加载的过程是一个很困难的概念,我希望你们中的一个人能提供一些指导,以降低预期的功能。
在当前状态下,当用户按下"S"键时,我们的LevelManager类运行下面的SaveLevel方法:
public static void SaveLevel()
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create("example.xml", settings))
{
IntermediateSerializer.Serialize(writer, CurrentLevel, null);
}
}
它将我们的级别(CurrentLevel)序列化为项目中的XML文件(在我们完成基本设置后,我们会担心保存到不同的文件。)
<?xml version="1.0" encoding="utf-8"?>
<XnaContent>
<Asset Type="LevelEditorPrototype.Level">
<TileGrid>
<Item>
<Item Type="LevelEditorPrototype.TileFloor">
<X>0</X>
<Y>0</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FFFFFFFF</Tint>
</Item>
<Item Type="LevelEditorPrototype.TileBlock">
<X>0</X>
<Y>32</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FF0000FF</Tint>
</Item>
<Item Type="LevelEditorPrototype.TileVoid">
<X>0</X>
<Y>64</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FFFF0000</Tint>
</Item>
<Item Type="LevelEditorPrototype.TileFloor">
<X>0</X>
<Y>96</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FFFFFFFF</Tint>
</Item>
</Item>
<Item>
<Item Type="LevelEditorPrototype.TileVoid">
<X>32</X>
<Y>0</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FFFF0000</Tint>
</Item>
<Item Type="LevelEditorPrototype.TileFloor">
<X>32</X>
<Y>32</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FFFFFFFF</Tint>
</Item>
<Item Type="LevelEditorPrototype.TileBlock">
<X>32</X>
<Y>64</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FF0000FF</Tint>
</Item>
<Item Type="LevelEditorPrototype.TileVoid">
<X>32</X>
<Y>96</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FFFF0000</Tint>
</Item>
</Item>
<Item>
<Item Type="LevelEditorPrototype.TileBlock">
<X>64</X>
<Y>0</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FF0000FF</Tint>
</Item>
<Item Type="LevelEditorPrototype.TileVoid">
<X>64</X>
<Y>32</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FFFF0000</Tint>
</Item>
<Item Type="LevelEditorPrototype.TileFloor">
<X>64</X>
<Y>64</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FFFFFFFF</Tint>
</Item>
<Item Type="LevelEditorPrototype.TileBlock">
<X>64</X>
<Y>96</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FF0000FF</Tint>
</Item>
</Item>
<Item>
<Item Type="LevelEditorPrototype.TileFloor">
<X>96</X>
<Y>0</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FFFFFFFF</Tint>
</Item>
<Item Type="LevelEditorPrototype.TileBlock">
<X>96</X>
<Y>32</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FF0000FF</Tint>
</Item>
<Item Type="LevelEditorPrototype.TileVoid">
<X>96</X>
<Y>64</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FFFF0000</Tint>
</Item>
<Item Type="LevelEditorPrototype.TileFloor">
<X>96</X>
<Y>96</Y>
<Width>32</Width>
<Height>32</Height>
<Origin>0 0</Origin>
<Depth>0</Depth>
<Tint>FFFFFFFF</Tint>
</Item>
</Item>
</TileGrid>
</Asset>
</XnaContent>
因此,至少我们确实有关于该级别中生成的瓦片的数据信息,所以这是有意义的。我们希望我们的用户也能在运行时加载保存的级别,所以我们映射了"L"键来加载保存的XML文件,这就是问题所在。我们的读数如下:
public static void LoadLevel()
{
using (FileStream stream = new FileStream("example.xml", FileMode.Open))
{
using (XmlReader reader = XmlReader.Create(stream))
{
currentLevel = IntermediateSerializer.Deserialize<Level>(reader, null);
}
}
}
当我们尝试测试该功能时,我们会得到以下错误:
System.MethodAccessException was unhandled
HResult=-2146233072
Message=Attempt by method 'DynamicClass.ReflectionEmitUtils(System.Object, System.Object)' to access method 'DynamicClass.ReflectionEmitUtils(System.Object, System.Object)' failed.
我偷偷怀疑IntermediateSerializer并没有按照我们希望的方式工作,但我不确定如何有效地解析和存储数据。我应该在这里使用不同的设置吗?
根据我的经验,要100%确保保存和加载基于tile的级别,最有效的方法是使用BinaryWriter和BinaryReader。
不过,我们的级别结构与您的有很大不同;我们有很多层。每个层都使用一个平铺集,并由平铺的实例组成。平铺保持其位置(Vector2D)和平铺ID(平铺集纹理中的平铺索引)。
我们在级别中放置对象的方式是使用波浪形集,这些波浪形集在加载时被真实对象替换。
无论如何,保存和加载数据的一种合适的通用方法是让类具有一个可以将BinaryReader作为参数的构造函数,以及一个将自身写入BinaryWriter的方法。
像这样:
public Tile(BinaryReader reader)
{
Position.X = reader.ReadFloat();
Position.Y = reader.ReadFloat();
TileId = reader.ReadInt32();
}
public void WriteToStream(BinaryWriter writer)
{
writer.Write(Position.X);
writer.Write(Position.Y);
writer.Write(TileId);
}
如果你只有一个类要加载,那么你可以简单地:
用于加载:
var tiles = new List<Tile>();
var reader = new BinaryReader(File.Open("level.bin"));
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
var tile = new Tile(reader);
tiles.Add(tile);
}
reader.Close();
保存:
var tiles; //lets pretend this is the level
var writer = new BinaryWriter(File.Create("level.bin"));
foreach (var tile in tiles)
{
tile.WriteToStream(writer);
}
writer.Flush(); //IMPORTANT!!!
writer.Close();
但是,如果您的列表包含不同类型的项目,则还需要存储该类型。一种非常通用的方法是插入:
writer.Write(tile.GetType().FullName);
平铺之前。WriteToStream(写入程序);
然后在加载时,您需要:
var tileType = Type.GetType(reader.ReadString()); //Read the type from the stream
var constructor = tileType.GetConstructor(new [] { typeof(BinaryReader)}); //get a constructor that can use a binaryreader
var tile = constructor.Invoke(new [] { reader }); //use said constructor to create an instance
希望这能有所帮助。请注意,我写这篇文章是出于记忆,所以语法错误很可能。。。。