Simple binary reader-writer code not working

796 views Asked by At

MS VS 2010, XNA 4.0
So, I have a class Planet & it has Save and Load functions.

public void SaveToFile(ContentManager content)
    {
        string path = content.RootDirectory + @"\Objects\Planets\" + this.name +@".planet"; 
        using (BinaryWriter bw = new BinaryWriter(File.Open(path, FileMode.Create)))
        {
            bw.Write(name);

            //sprite names
            bw.Write(planet.Sprite.Name); //"planet" is an animation.
            bw.Write(planet.HorFrames);   //and thats why it has a sprite
            bw.Write(planet.VerFrames);   //that has a name
            bw.Write((double)planet.FPS);

            bw.Write(asteroid1.Name);
            bw.Write(asteroid2.Name);
            bw.Write(asteroid3.Name);
            bw.Write(backgroundA.Name);

            //update related
            bw.Write((double)RSpeed);
            bw.Write((double)ASVariety);
            bw.Write((double)A1Chance);
            bw.Write((double)A2Chance);
            bw.Write((double)A3Chance);
            bw.Close();
        }
    }

public void LoadFromFile(ContentManager content, string name)
    {
        string path = content.RootDirectory + @"\Objects\Planets\" + name + @".planet";
        using (BinaryReader br = new BinaryReader(File.OpenRead(path)))
        {
            this.name = br.ReadString();
            this.planet = new Animation(Cont.Texture2D(br.ReadString()), br.ReadInt32(), br.ReadInt32(), (float)br.ReadDecimal(), true);
            this.asteroid1 = Cont.Texture2D(br.ReadString());
            this.asteroid2 = Cont.Texture2D(br.ReadString());
            this.asteroid3 = Cont.Texture2D(br.ReadString());
            this.backgroundA = Cont.Texture2D(br.ReadString());
            this.RSpeed = (float)br.ReadDouble();
            this.ASVariety = (float)br.ReadDouble();
            this.A1Chance = (float)br.ReadDouble();
            this.A2Chance = (float)br.ReadDouble();
            this.A3Chance = (float)br.ReadDouble();

            bposB = new Vector2(backgroundA.Width - 1, 0);
            bspd = new Vector2(-RSpeed / 8f, 0);

            pEngine1 = new ParticleEngine(MSTime, 40, new Vector2(20, -30), new Vector2(2, 1), asteroid1, new Color(100, 100, 100));
            pEngine1.Follow(new Vector2(-200, Game1.window_height / 2));

            pEngine2 = new ParticleEngine(MSTime * 3, 40, new Vector2(20, -30), new Vector2(2, 1), asteroid2, new Color(100, 100, 100));
            pEngine2.Follow(new Vector2(-200, Game1.window_height / 2));

            br.Close();
        }

Thats simple to understand, now isn't it?
Now, when I try to load sprite for planet animation, I get an error, that says:
"Value cannot be null.
Parameter name: assetName"
and this is happening at the line
this.planet = new Animation(Cont.Texture2D(br.ReadString()), br.ReadInt32(), br.ReadInt32(), (float)br.ReadDecimal(), true);
Cont.Texture2D is a static functions that returns a Texture2D witch name is equal to the path it was loaded from, and it looks like this:

public static Texture2D Texture2D(string path)
    {
        Texture2D sprite = content.Load<Texture2D>(path);
        sprite.Name = path;
        return sprite;
    }

So, when I'm saving it, it saves the right path. The test function looks like this:

private void testFunction()
    {
        /*
        p = new Planet("Mars", 
            new Animation(Cont.Texture2D("Sprites\\Planets\\mars"), 8, 2, 0.9f, true),
            Cont.Texture2D("Sprites\\Backgrounds\\space"),
            Cont.Texture2D("Sprites\\Asteroids\\small\\small 1"),
            Cont.Texture2D("Sprites\\Asteroids\\medium\\medium 1"),
            Cont.Texture2D("Sprites\\Asteroids\\big\\big 1"),
            100, 50, 40, 20, 40, 40);
        p.SaveToFile(Content);
        */
        p = new Planet(Content, "Mars");
        tests = p.ToString();
    }

"tests" is just a test string. Now, I first execute the commented code, so it could save the file, and then i execute the uncommented code. This planet constructor just calls the LoadFromFile function and nothing more. Also, I have the saved file in my content in my project. I did say for that file to treat it as content (dont compile it). So, the code "sees" the file, thats for shure, but it cant find the .png on the path that was read from the mars.planet. Is there maybe a misstake if i store 2 strings one after another, and then the reader cant see where is the end of the first one? Am I maybe saving it wrong?

My goal is to have binary files that will be loaded and saved, and in them would be planets, rockets, maps, etc, that are collections of names and values that are needed for constructors of these classes.

2

There are 2 answers

0
Monset On

Due to unnessesary complications with Binary writer-reader, I have decided to use Stream writer-reader. Big thanks to @Blas Soriano & @Peter Duniho.

Binary writers seem not to be able to write 2 strings one after another in such way that binary reader can read them, at least not in my case. I did try writing and reading 2 strings with binary w-r in another project, and they seem to work fine there. I hope nobody expiriences this kind of thing that happened to me. Thanks again to Blas and Peter.

EDIT:
It seems that my file that I included in Content of solution, "Mars.planet", in it's properties: Copy To Output, from 3 possible choices: Copy if newer, Never Copy, Always Copy, had selected the Copy Always, and that didnt allow changes to happen to that file, so, i DID change the file by saving it with my function, but, then I exit the game, so the file goes back to the old version (not really shure how), and that version is a realy old one, that didn't have all lines that I am in need now, so when I comment the saving code and uncomment the loading code, start the game, the function for loading would actually be loading the first version of the file (the version from the time when I included the file).

It works now, but not because I am using the Stream w-r, but because I changed the property of that file to:Do not copy. I will be going back to Binary w-r.

8
Blas Soriano On

In your testFunction() you create a new Planet and save it to a file without assigning the sprite name (and I guess it is not assigned in the Animation constructor), so when it is saved the sprite name will be null. To avoid that, you need to assign the planet.Sprite.Name before saving it to a file.

private void testFunction()
{
    Animation planetAnimation = new Animation(Cont.Texture2D("Sprites\\Planets\\mars"));
    planetAnimation.Sprite.Name = "Sprites\\Planets\\mars";
    p = new Planet("Mars", 
        planetAnimation , 8, 2, 0.9f, true),
        Cont.Texture2D("Sprites\\Backgrounds\\space"),
        Cont.Texture2D("Sprites\\Asteroids\\small\\small 1"),
        Cont.Texture2D("Sprites\\Asteroids\\medium\\medium 1"),
        Cont.Texture2D("Sprites\\Asteroids\\big\\big 1"),
        100, 50, 40, 20, 40, 40);
    p.SaveToFile(Content);
    /*
    p = new Planet(Content, "Mars");
    tests = p.ToString();
    */
}

Instead of that, you could modify your Load and Save methods to avoid saving twice that string. First delete or comment the line bw.Write(planet.Sprite.Name) in SaveToFile():

public void SaveToFile(ContentManager content)
{
    string path = content.RootDirectory + @"\Objects\Planets\" + this.name +@".planet"; 
    using (BinaryWriter bw = new BinaryWriter(File.Open(path, FileMode.Create)))
    {
        bw.Write(name);

        //sprite names
        //bw.Write(planet.Sprite.Name); //"planet" is an animation.
        bw.Write(planet.HorFrames);   //and thats why it has a sprite
        bw.Write(planet.VerFrames);   //that has a name
        bw.Write((double)planet.FPS);
...

Then change the LoadFromFile() to reuse the string:

public void LoadFromFile(ContentManager content, string name)
{
    string path = content.RootDirectory + @"\Objects\Planets\" + name + @".planet";
    using (BinaryReader br = new BinaryReader(File.OpenRead(path)))
    {
        this.name = br.ReadString();
        this.planet = new Animation(Cont.Texture2D(this.name), br.ReadInt32(), br.ReadInt32(), br.ReadDouble(), true);
        this.asteroid1 = Cont.Texture2D(br.ReadString());
        this.asteroid2 = Cont.Texture2D(br.ReadString());

Note that following cubrr suggestion, I replaced decimal to double while reading planet.FPS.


EDIT: From your comment it seems you think the null string should be assigned by using the static method, but there are 2 different things here.
First take a look at that name inside Cont.Texture2D():

public static Texture2D Texture2D(string path)
{
    Texture2D sprite = content.Load<Texture2D>(path);
    sprite.Name = path;
    return sprite;
}

This name is Texture2D.Name
Now take a look inside SaveToFile():

bw.Write(name);

//sprite names
bw.Write(planet.Sprite.Name); //"planet" is an animation.

Here we can see 2 different names, Planet.Name and Planet.Sprite.Name. Where are you assigning that second name? That's why I used planetAnimation.Sprite.Name = "Sprites\\Planets\\mars";.