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);
}
}
}
}