using System.Collections.Generic; using System.IO; using Microsoft.Xna.Framework; namespace SomeFictionalGame { /// /// A hypothetical top score entry class. This will be used to demonstrate how to use /// BinaryReader and BinaryWriter with a collection (in our case a generic List) of a /// class you've written. It's otherwise devoid of the functionality you might want in /// such a class. And you might actually want this to be a struct, but that's up to you. /// public class TopScoreEntry { /// /// The player's score. /// public int Score; /// /// The name of the player who obtained this score. /// public string Name; /// /// A basic constructor that would be used in-game to create a new instance /// of this class. /// /// The score the player obtained. /// The name the player entered on the name entry screen. public TopScoreEntry(int scoreValue, string nameValue) { Score = scoreValue; Name = nameValue; } /// /// A constructor that is designed to read in a TopScoreEntry from an /// instance of BinaryReader, which would be created to read in a file that /// was written to isolated storage by our code that handles OnDeactivated. /// /// The BinaryReader instance to use. public TopScoreEntry(BinaryReader reader) { // Read in the score. Score = reader.ReadInt32(); // Read in the name. Name = reader.ReadString(); } /// /// A method to write out a TopScoreEntry instance using an existing BinaryWriter /// instance. This would happen during the tombstoning process (and any other /// time you wanted to save game data). Though as you'll see below, this /// is called indirectly by GameData's Write method. /// /// The BinaryWriter instance to use. public void Write(BinaryWriter writer) { // Write out the score. Note that it's crucial that things be read in in // the same order that they are written out in. writer.Write(Score); // Write out the name. writer.Write(Name); } } /// /// An artificial game data class. It exists to demonstrate BinaryReader /// and BinaryWriter usage. A real game data class would have methods for /// updating the top scores and some way of accessing the current score, /// current number of lives, etc. /// public class GameData { /// /// The current score. /// int currentScore; /// /// The current number of lives. /// int numberOfLives; /// /// The last name entered in the score entry screen. Caching this /// and reshowing it prevents the player from having to retype /// this every time. /// string playerName; /// /// The current lap time. This hypothetical game will be a /// hypothetical racing game of some sort. /// double currentLapTime; /// /// The player's current position on the course. BinaryReader and /// BinaryWriter don't know what a Vector2 is but that's fine as /// we will see. /// Vector2 currentCoursePosition; /// /// A list of lap times. /// List lapTimes; /// /// How many top scores to keep. /// const int maxTopScores = 10; /// /// The current list of top scores. /// List topScores; /// /// A constructor for creating game data for a new game. /// public GameData() { currentScore = 0; numberOfLives = 5; playerName = "Player 1"; currentLapTime = 0.0; currentCoursePosition = new Vector2(10f, 50f); lapTimes = new List(); topScores = new List(maxTopScores); } /// /// A constructor for reading game data for an ongoing game /// that was tombstoned in from isolated storage using /// a BinaryReader instance. /// /// public GameData(BinaryReader reader) { // Read in the current score. currentScore = reader.ReadInt32(); // Read in the current number of lives. numberOfLives = reader.ReadInt32(); // Read in the last name entered into the score entry screen. playerName = reader.ReadString(); // Read in the current lap time currentLapTime = reader.ReadDouble(); // Read in the current course position. Since BinaryReader doesn't know what // an XNA Vector2 struct is, we simply create a new Vector2 and read in the // underlying X and Y values for it. BinaryReader knows what floats are so // this works just fine. It's just a little bit of extra code to get the result // that we want. currentCoursePosition = new Vector2(reader.ReadSingle(), reader.ReadSingle()); // When reading and writing lists (or any collection or array for that matter), // you first want to write out the count of elements. This way // you know how many of the things you need to read in. You'll see in the Write // method later that we write out the count first then write out the list // elements. int count = 0; // Read in the count of lapTimes. count = reader.ReadInt32(); // Create the lapTimes list and initialize its capacity to the count. This saves // on garbage generation by allocating the internal storage once rather than // going through multiple new allocations and copies. It would still reallocate // as soon as you added a new element to it though, so it's often better (where // possible) to predefine the number of elements that can possibly exist in a // particular List and initialize its capacity to that pre-determined amount. lapTimes = new List(count); // Read in the lap times that were written out. If there weren't any yet, then count // would have been zero and so nothing will be read in (which is exactly what we // would want). for (int i = 0; i < count; i++) { lapTimes.Add(reader.ReadDouble()); } // Read in the count of topScores. count = reader.ReadInt32(); // As discussed for lapTimes, in this case we determined a maximum number of // entries we would want for the top scores list so we set the capacity to // that. Note that this doesn't enforce that capacity. It is up to you to // remove an element before adding one when the count of elements is equal // to the capacity in order to prevent any new allocation from occuring. topScores = new List(maxTopScores); // Read in the topScores entries. Note how we use the TopScoreEntry constructor // that takes a BinaryReader as an argument and just pass it the current reader. // GameData doesn't need to know anything about TopScoreEntry; it just needs to know // that TopScoreEntry can create itself by reading data in from a BinaryReader // instance when we've reached the point in the underlying stream where a // TopScoreEntry had been written out. for (int i = 0; i < count; i++) { topScores.Add(new TopScoreEntry(reader)); } } /// /// A method to write out a GameData instance using an existing BinaryWriter /// instance. This would happen during the tombstoning process (and any other /// time you wanted to save game data). This would, in our hypothetical game, /// be called directly from your game method that saves game data, unlike /// TopScoreEntry's Write method, which would only be called from here. /// /// The BinaryWriter instance to use. public void Write(BinaryWriter writer) { // Write out the current score. Remember that the order must match // exactly between writing and reading otherwise BinaryReader will // get angry with you and you'll either get an exception somewhere // or else get junk data. writer.Write(currentScore); // Write out the current number of lives. writer.Write(numberOfLives); // Write out the last name entered at the score entry screen. writer.Write(playerName); // Write out the current lap time. writer.Write(currentLapTime); // BinaryWriter doesn't know what a Microsoft.Xna.Framework.Vector2 // is. But it does know what floats are. Since a Vector2's data is // just two floats, X and Y, we write those out and then use the // Vector2 constructor that takes an X and a Y to restore it when // we read it in later. writer.Write(currentCoursePosition.X); writer.Write(currentCoursePosition.Y); // Write out the count of lap times. Remember that we have to do this // so that we know how many to read in when we load the data later. writer.Write(lapTimes.Count); // Write out each of the lap times. for (int i = 0; i < count; i++) { writer.Write(lapTimes[i]); } // Write out the count of topScores. writer.Write(topScores.Count); // Write out each of the top score entries using TopScoreEntry's Write // method. If you wanted to add a little more structure to things, you // could create an interface that contains a definition for a Write // method that takes a BinaryWriter instance as its method parameter. // It's likely a good idea (and is one I use in my own code) but which // I left out here to keep this as simple as possible. for (int i = 0; i < count; i++) { topScores[i].Write(writer); } } } }