1.9 将数据保存到文件,从文件读取数据
问题
你需要在游戏中实现保存功能。解决方案虽然保存功能通常是创建游戏的最后一步,但你肯定需要在游戏中实现这个功能。首先,XNA使用默认的.NET文件I/O 功能,这意味着创建/打开/删除文件是很容易的。接着,使用XmlSerializer类能非常容易地将数据保存到文件并在以后加载数据。
唯一的问题是找到在PC平台和Xbox360平台都能保存的位置,要解决这个问题,你可以使用StorageDevice,它必须首先被建立。
注意创建一个StorageDevice的原理可能很复杂,但别被吓倒,因为本教程的其余部分(从“将数据保存到磁盘”) 非常简单和有效,而且这个方法并不局限与在XNA上使用,因为它使用的是默认的.NET功能。
工作原理
在将数据保存到磁盘之前,你需要一个可用的位置进行写入。这可以通过创建一个StorageDevice实现,StorageDevice会询问Xbox360平台上的用户将数据保存到哪儿。
异步创建一个StorageDevice
在这个过程中会打开Xbox Guide并终止程序直到用户关闭Guide。要解决这个问题,可以让这个过程异步处理,原理在教程8-5中解释。
Guide需要将GamerServicesComponent添加到游戏中 (见教程8-1),所以要将下列代码添加到Game1构造函数中:
public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; Components.Add(new GamerServicesComponent(this)); }
下一步,当调用Update方法时,你会检查用户是否想保存数据,如果是,则调用Guide. BeginShowStorageDeviceSelector,它会打开一个对话框让用户选择在哪里保存数据。程序并不会在此终止,但会暂停程序直到用户关闭对话框。Guide. BeginShowStorageDeviceSelector方法要求你指定另一个FindStorageDevice方法作为第一个参数,一旦用户关闭对话框这个方法就会被调用:
KeyboardState keyState = Keyboard.GetState(); if (!Guide.IsVisible) if (keyState.IsKeyDown(Keys.S)) Guide.BeginShowStorageDeviceSelector(FindStorageDevice, null);
如果用户按下S键就会打开对话框,但代码并不会终止直到用户关闭对话框。
当对话框显示在屏幕上时你的程序仍在运行,一旦用户做出选择并关闭对话框,作为第一个参数的FindStorageDevice方法就会被调用,这意味着你应事先定义这个方法,否则编译器会报错:
private void FindStorageDevice(IAsyncResult result) { StorageDevice storageDevice = Guide.EndShowStorageDeviceSelector(result); if (storageDevice != null) SaveGame(storageDevice); }
BeginShowStorageDeviceSelector的输出结果作为这个方法的参数,如果你将结果传递到Guide. EndShowStorageDeviceSelector方法,就可以获取data storage。但是如果用户取消操作,结果将会是null,所以你应该事先检查。如果StorageDevice可用,你将它传递到SaveGame方法,这个方法一会儿后就会定义。
但如果当用户指定数据位置时允许用户进行第二种操作,例如加载数据,那么就需要定义第二个方法,比方说FindStorageDeviceForLoading。但清晰的方法是指定一个identifier,在FindStorageDevice方法中可以检查这个identifier。你的Update方法应该包含下列代码块:
KeyboardState keyState = Keyboard.GetState(); if (!Guide.IsVisible) { if (keyState.IsKeyDown(Keys.S)) Guide.BeginShowStorageDeviceSelector(FindStorageDevice, "saveRequest"); if (keyState.IsKeyDown(Keys.L)) Guide.BeginShowStorageDeviceSelector(FindStorageDevice, "loadRequest"); }
如你所见,在这两种情况下对话框都会显示,当对话框关闭后就会调用FindStorageDevice方法。但这次的区别是你指定了一个identifier并在FindStorageDevice方法中检查这个identifier:
private void FindStorageDevice(IAsyncResult result) { StorageDevice storageDevice = Guide.EndShowStorageDeviceSelector(result); if (storageDevice != null) { if (result.AsyncState == "saveRequest") SaveGame(storageDevice); else if (result.AsyncState == "loadRequest") LoadGame(storageDevice); } }
根据指定的identity,你将调用SaveGame或LoadGame方法。
将数据保存到磁盘
一旦有了一个正确的StorageDevice,你就可以很容易地为写入数据的文件指定一个名称:
private void SaveGame(StorageDevice storageDevice) { StorageContainer container = storageDevice.OpenContainer("BookCodeWin"); string fileName = Path.Combine(container.Path, "save0001.sav"); FileStream saveFile = File.Open(fileName, FileMode.Create); }
这将创建一个叫做save0001. Sav的文件,如果这个文件已经存在将会被覆盖。
注意在PC上这个文件将会创建在My Documents\SavedGames文件夹中。
一旦你有了正确的文件名并已经打开了这个文件,你就可以使用默认的.NET 方法保存文件,假设你将存储的数据结构如下:
public struct GameData { public int ActivePlayers; public float Time; }
你需要创建一个XmlSerializer,它能够将你的数据转换为XML并保存到磁盘:
XmlSerializer xmlSerializer = new XmlSerializer(typeof(GameData)); xmlSerializer.Serialize(saveFile, gameData); saveFile.Close();
XmlSerializer能够串行化GameData对象,之后使用一个命令就可以将GameData对象的数据流写到文件中。别忘了关闭文件流,否则程序会锁定文件流。
你需要添加System. IO和System.Xml. Serialization命名空间,只需在代码顶部添加下面的代码:
using System.IO; using System.Xml.Serialization;
最后一行代码需要你添加对System. Xml的引用,要添加这个引用,可以打开Project菜单并选择Add Reference,选择System.Xml,如图1-5所示,然后点击OK。
图1-5 添加对System. Xml的引用
从磁盘加载数据
加载数据的方法与保存数据的方法相同,顺序正好相反。你检查文件是否存在,如果存在就打开它。你仍要创建一个XmlSerializer,但这次你使用XmlSerializer从文件流中反串行化GameData对象,下面的代码加载从文件加载所有数据并把数据转换为GameData对象:
private void LoadGame(StorageDevice storageDevice) { StorageContainer container = storageDevice.OpenContainer("BookCodeWin"); string fileName = Path.Combine(container.Path, "save0001.sav"); if (File.Exists(fileName)) { FileStream saveFile = File.Open(fileName, FileMode.Open); XmlSerializer xmlSerializer = new XmlSerializer(typeof(GameData)); gameData = (GameData)xmlSerializer.Deserialize(saveFile); saveFile.Close(); } }
代码
Update方法检查用户是想保存还是加载文件并打开对话框:
protected override void Update(GameTime gameTime) { GamePadState gamePadState = GamePad.GetState(PlayerIndex.One); if (gamePadState.Buttons.Back == ButtonState.Pressed) this.Exit(); KeyboardState keyState = Keyboard.GetState(); if (!Guide.IsVisible) { if (keyState.IsKeyDown(Keys.S)) Guide.BeginShowStorageDeviceSelector(FindStorageDevice, "saveRequest"); if (keyState.IsKeyDown(Keys.L)) Guide.BeginShowStorageDeviceSelector(FindStorageDevice, "loadRequest"); } gameData.Time += (float)gameTime.ElapsedGameTime.TotalSeconds; base.Update(gameTime); }
当用户关闭Guide时就会调用FindStorageDevice方法,这个方法根据异步调用的identity调用SaveData或LoadData方法。你可以在前面的代码中看到整个FindStorageDevice方法,只漏了SaveGame 方法:
private void SaveGame(StorageDevice storageDevice) { StorageContainer container = storageDevice.OpenContainer("BookCodeWin"); string fileName = Path.Combine(container.Path, "save0001.sav"); FileStream saveFile = File.Open(fileName, FileMode.Create); XmlSerializer xmlSerializer = new XmlSerializer(typeof(GameData)); XmlSerializer.Serialize(saveFile, gameData); saveFile.Close(); log.Add("Game data saved!"); }